aboutsummaryrefslogtreecommitdiffstats
path: root/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/ActionBarSherlock/src/com/actionbarsherlock/widget')
-rw-r--r--libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserModel.java1104
-rw-r--r--libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserView.java839
-rw-r--r--libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java1811
-rw-r--r--libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ShareActionProvider.java316
-rw-r--r--libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SuggestionsAdapter.java758
5 files changed, 0 insertions, 4828 deletions
diff --git a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserModel.java b/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserModel.java
deleted file mode 100644
index d7f110fc6..000000000
--- a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserModel.java
+++ /dev/null
@@ -1,1104 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.actionbarsherlock.widget;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.database.DataSetObservable;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Xml;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/**
- * <p>
- * This class represents a data model for choosing a component for handing a
- * given {@link Intent}. The model is responsible for querying the system for
- * activities that can handle the given intent and order found activities
- * based on historical data of previous choices. The historical data is stored
- * in an application private file. If a client does not want to have persistent
- * choice history the file can be omitted, thus the activities will be ordered
- * based on historical usage for the current session.
- * <p>
- * </p>
- * For each backing history file there is a singleton instance of this class. Thus,
- * several clients that specify the same history file will share the same model. Note
- * that if multiple clients are sharing the same model they should implement semantically
- * equivalent functionality since setting the model intent will change the found
- * activities and they may be inconsistent with the functionality of some of the clients.
- * For example, choosing a share activity can be implemented by a single backing
- * model and two different views for performing the selection. If however, one of the
- * views is used for sharing but the other for importing, for example, then each
- * view should be backed by a separate model.
- * </p>
- * <p>
- * The way clients interact with this class is as follows:
- * </p>
- * <p>
- * <pre>
- * <code>
- * // Get a model and set it to a couple of clients with semantically similar function.
- * ActivityChooserModel dataModel =
- * ActivityChooserModel.get(context, "task_specific_history_file_name.xml");
- *
- * ActivityChooserModelClient modelClient1 = getActivityChooserModelClient1();
- * modelClient1.setActivityChooserModel(dataModel);
- *
- * ActivityChooserModelClient modelClient2 = getActivityChooserModelClient2();
- * modelClient2.setActivityChooserModel(dataModel);
- *
- * // Set an intent to choose a an activity for.
- * dataModel.setIntent(intent);
- * <pre>
- * <code>
- * </p>
- * <p>
- * <strong>Note:</strong> This class is thread safe.
- * </p>
- *
- * @hide
- */
-class ActivityChooserModel extends DataSetObservable {
-
- /**
- * Client that utilizes an {@link ActivityChooserModel}.
- */
- public interface ActivityChooserModelClient {
-
- /**
- * Sets the {@link ActivityChooserModel}.
- *
- * @param dataModel The model.
- */
- public void setActivityChooserModel(ActivityChooserModel dataModel);
- }
-
- /**
- * Defines a sorter that is responsible for sorting the activities
- * based on the provided historical choices and an intent.
- */
- public interface ActivitySorter {
-
- /**
- * Sorts the <code>activities</code> in descending order of relevance
- * based on previous history and an intent.
- *
- * @param intent The {@link Intent}.
- * @param activities Activities to be sorted.
- * @param historicalRecords Historical records.
- */
- // This cannot be done by a simple comparator since an Activity weight
- // is computed from history. Note that Activity implements Comparable.
- public void sort(Intent intent, List<ActivityResolveInfo> activities,
- List<HistoricalRecord> historicalRecords);
- }
-
- /**
- * Listener for choosing an activity.
- */
- public interface OnChooseActivityListener {
-
- /**
- * Called when an activity has been chosen. The client can decide whether
- * an activity can be chosen and if so the caller of
- * {@link ActivityChooserModel#chooseActivity(int)} will receive and {@link Intent}
- * for launching it.
- * <p>
- * <strong>Note:</strong> Modifying the intent is not permitted and
- * any changes to the latter will be ignored.
- * </p>
- *
- * @param host The listener's host model.
- * @param intent The intent for launching the chosen activity.
- * @return Whether the intent is handled and should not be delivered to clients.
- *
- * @see ActivityChooserModel#chooseActivity(int)
- */
- public boolean onChooseActivity(ActivityChooserModel host, Intent intent);
- }
-
- /**
- * Flag for selecting debug mode.
- */
- private static final boolean DEBUG = false;
-
- /**
- * Tag used for logging.
- */
- private static final String LOG_TAG = ActivityChooserModel.class.getSimpleName();
-
- /**
- * The root tag in the history file.
- */
- private static final String TAG_HISTORICAL_RECORDS = "historical-records";
-
- /**
- * The tag for a record in the history file.
- */
- private static final String TAG_HISTORICAL_RECORD = "historical-record";
-
- /**
- * Attribute for the activity.
- */
- private static final String ATTRIBUTE_ACTIVITY = "activity";
-
- /**
- * Attribute for the choice time.
- */
- private static final String ATTRIBUTE_TIME = "time";
-
- /**
- * Attribute for the choice weight.
- */
- private static final String ATTRIBUTE_WEIGHT = "weight";
-
- /**
- * The default name of the choice history file.
- */
- public static final String DEFAULT_HISTORY_FILE_NAME =
- "activity_choser_model_history.xml";
-
- /**
- * The default maximal length of the choice history.
- */
- public static final int DEFAULT_HISTORY_MAX_LENGTH = 50;
-
- /**
- * The amount with which to inflate a chosen activity when set as default.
- */
- private static final int DEFAULT_ACTIVITY_INFLATION = 5;
-
- /**
- * Default weight for a choice record.
- */
- private static final float DEFAULT_HISTORICAL_RECORD_WEIGHT = 1.0f;
-
- /**
- * The extension of the history file.
- */
- private static final String HISTORY_FILE_EXTENSION = ".xml";
-
- /**
- * An invalid item index.
- */
- private static final int INVALID_INDEX = -1;
-
- /**
- * Lock to guard the model registry.
- */
- private static final Object sRegistryLock = new Object();
-
- /**
- * This the registry for data models.
- */
- private static final Map<String, ActivityChooserModel> sDataModelRegistry =
- new HashMap<String, ActivityChooserModel>();
-
- /**
- * Lock for synchronizing on this instance.
- */
- private final Object mInstanceLock = new Object();
-
- /**
- * List of activities that can handle the current intent.
- */
- private final List<ActivityResolveInfo> mActivites = new ArrayList<ActivityResolveInfo>();
-
- /**
- * List with historical choice records.
- */
- private final List<HistoricalRecord> mHistoricalRecords = new ArrayList<HistoricalRecord>();
-
- /**
- * Context for accessing resources.
- */
- private final Context mContext;
-
- /**
- * The name of the history file that backs this model.
- */
- private final String mHistoryFileName;
-
- /**
- * The intent for which a activity is being chosen.
- */
- private Intent mIntent;
-
- /**
- * The sorter for ordering activities based on intent and past choices.
- */
- private ActivitySorter mActivitySorter = new DefaultSorter();
-
- /**
- * The maximal length of the choice history.
- */
- private int mHistoryMaxSize = DEFAULT_HISTORY_MAX_LENGTH;
-
- /**
- * Flag whether choice history can be read. In general many clients can
- * share the same data model and {@link #readHistoricalData()} may be called
- * by arbitrary of them any number of times. Therefore, this class guarantees
- * that the very first read succeeds and subsequent reads can be performed
- * only after a call to {@link #persistHistoricalData()} followed by change
- * of the share records.
- */
- private boolean mCanReadHistoricalData = true;
-
- /**
- * Flag whether the choice history was read. This is used to enforce that
- * before calling {@link #persistHistoricalData()} a call to
- * {@link #persistHistoricalData()} has been made. This aims to avoid a
- * scenario in which a choice history file exits, it is not read yet and
- * it is overwritten. Note that always all historical records are read in
- * full and the file is rewritten. This is necessary since we need to
- * purge old records that are outside of the sliding window of past choices.
- */
- private boolean mReadShareHistoryCalled = false;
-
- /**
- * Flag whether the choice records have changed. In general many clients can
- * share the same data model and {@link #persistHistoricalData()} may be called
- * by arbitrary of them any number of times. Therefore, this class guarantees
- * that choice history will be persisted only if it has changed.
- */
- private boolean mHistoricalRecordsChanged = true;
-
- /**
- * Hander for scheduling work on client tread.
- */
- private final Handler mHandler = new Handler();
-
- /**
- * Policy for controlling how the model handles chosen activities.
- */
- private OnChooseActivityListener mActivityChoserModelPolicy;
-
- /**
- * Gets the data model backed by the contents of the provided file with historical data.
- * Note that only one data model is backed by a given file, thus multiple calls with
- * the same file name will return the same model instance. If no such instance is present
- * it is created.
- * <p>
- * <strong>Note:</strong> To use the default historical data file clients should explicitly
- * pass as file name {@link #DEFAULT_HISTORY_FILE_NAME}. If no persistence of the choice
- * history is desired clients should pass <code>null</code> for the file name. In such
- * case a new model is returned for each invocation.
- * </p>
- *
- * <p>
- * <strong>Always use difference historical data files for semantically different actions.
- * For example, sharing is different from importing.</strong>
- * </p>
- *
- * @param context Context for loading resources.
- * @param historyFileName File name with choice history, <code>null</code>
- * if the model should not be backed by a file. In this case the activities
- * will be ordered only by data from the current session.
- *
- * @return The model.
- */
- public static ActivityChooserModel get(Context context, String historyFileName) {
- synchronized (sRegistryLock) {
- ActivityChooserModel dataModel = sDataModelRegistry.get(historyFileName);
- if (dataModel == null) {
- dataModel = new ActivityChooserModel(context, historyFileName);
- sDataModelRegistry.put(historyFileName, dataModel);
- }
- dataModel.readHistoricalData();
- return dataModel;
- }
- }
-
- /**
- * Creates a new instance.
- *
- * @param context Context for loading resources.
- * @param historyFileName The history XML file.
- */
- private ActivityChooserModel(Context context, String historyFileName) {
- mContext = context.getApplicationContext();
- if (!TextUtils.isEmpty(historyFileName)
- && !historyFileName.endsWith(HISTORY_FILE_EXTENSION)) {
- mHistoryFileName = historyFileName + HISTORY_FILE_EXTENSION;
- } else {
- mHistoryFileName = historyFileName;
- }
- }
-
- /**
- * Sets an intent for which to choose a activity.
- * <p>
- * <strong>Note:</strong> Clients must set only semantically similar
- * intents for each data model.
- * <p>
- *
- * @param intent The intent.
- */
- public void setIntent(Intent intent) {
- synchronized (mInstanceLock) {
- if (mIntent == intent) {
- return;
- }
- mIntent = intent;
- loadActivitiesLocked();
- }
- }
-
- /**
- * Gets the intent for which a activity is being chosen.
- *
- * @return The intent.
- */
- public Intent getIntent() {
- synchronized (mInstanceLock) {
- return mIntent;
- }
- }
-
- /**
- * Gets the number of activities that can handle the intent.
- *
- * @return The activity count.
- *
- * @see #setIntent(Intent)
- */
- public int getActivityCount() {
- synchronized (mInstanceLock) {
- return mActivites.size();
- }
- }
-
- /**
- * Gets an activity at a given index.
- *
- * @return The activity.
- *
- * @see ActivityResolveInfo
- * @see #setIntent(Intent)
- */
- public ResolveInfo getActivity(int index) {
- synchronized (mInstanceLock) {
- return mActivites.get(index).resolveInfo;
- }
- }
-
- /**
- * Gets the index of a the given activity.
- *
- * @param activity The activity index.
- *
- * @return The index if found, -1 otherwise.
- */
- public int getActivityIndex(ResolveInfo activity) {
- List<ActivityResolveInfo> activities = mActivites;
- final int activityCount = activities.size();
- for (int i = 0; i < activityCount; i++) {
- ActivityResolveInfo currentActivity = activities.get(i);
- if (currentActivity.resolveInfo == activity) {
- return i;
- }
- }
- return INVALID_INDEX;
- }
-
- /**
- * Chooses a activity to handle the current intent. This will result in
- * adding a historical record for that action and construct intent with
- * its component name set such that it can be immediately started by the
- * client.
- * <p>
- * <strong>Note:</strong> By calling this method the client guarantees
- * that the returned intent will be started. This intent is returned to
- * the client solely to let additional customization before the start.
- * </p>
- *
- * @return An {@link Intent} for launching the activity or null if the
- * policy has consumed the intent.
- *
- * @see HistoricalRecord
- * @see OnChooseActivityListener
- */
- public Intent chooseActivity(int index) {
- ActivityResolveInfo chosenActivity = mActivites.get(index);
-
- ComponentName chosenName = new ComponentName(
- chosenActivity.resolveInfo.activityInfo.packageName,
- chosenActivity.resolveInfo.activityInfo.name);
-
- Intent choiceIntent = new Intent(mIntent);
- choiceIntent.setComponent(chosenName);
-
- if (mActivityChoserModelPolicy != null) {
- // Do not allow the policy to change the intent.
- Intent choiceIntentCopy = new Intent(choiceIntent);
- final boolean handled = mActivityChoserModelPolicy.onChooseActivity(this,
- choiceIntentCopy);
- if (handled) {
- return null;
- }
- }
-
- HistoricalRecord historicalRecord = new HistoricalRecord(chosenName,
- System.currentTimeMillis(), DEFAULT_HISTORICAL_RECORD_WEIGHT);
- addHisoricalRecord(historicalRecord);
-
- return choiceIntent;
- }
-
- /**
- * Sets the listener for choosing an activity.
- *
- * @param listener The listener.
- */
- public void setOnChooseActivityListener(OnChooseActivityListener listener) {
- mActivityChoserModelPolicy = listener;
- }
-
- /**
- * Gets the default activity, The default activity is defined as the one
- * with highest rank i.e. the first one in the list of activities that can
- * handle the intent.
- *
- * @return The default activity, <code>null</code> id not activities.
- *
- * @see #getActivity(int)
- */
- public ResolveInfo getDefaultActivity() {
- synchronized (mInstanceLock) {
- if (!mActivites.isEmpty()) {
- return mActivites.get(0).resolveInfo;
- }
- }
- return null;
- }
-
- /**
- * Sets the default activity. The default activity is set by adding a
- * historical record with weight high enough that this activity will
- * become the highest ranked. Such a strategy guarantees that the default
- * will eventually change if not used. Also the weight of the record for
- * setting a default is inflated with a constant amount to guarantee that
- * it will stay as default for awhile.
- *
- * @param index The index of the activity to set as default.
- */
- public void setDefaultActivity(int index) {
- ActivityResolveInfo newDefaultActivity = mActivites.get(index);
- ActivityResolveInfo oldDefaultActivity = mActivites.get(0);
-
- final float weight;
- if (oldDefaultActivity != null) {
- // Add a record with weight enough to boost the chosen at the top.
- weight = oldDefaultActivity.weight - newDefaultActivity.weight
- + DEFAULT_ACTIVITY_INFLATION;
- } else {
- weight = DEFAULT_HISTORICAL_RECORD_WEIGHT;
- }
-
- ComponentName defaultName = new ComponentName(
- newDefaultActivity.resolveInfo.activityInfo.packageName,
- newDefaultActivity.resolveInfo.activityInfo.name);
- HistoricalRecord historicalRecord = new HistoricalRecord(defaultName,
- System.currentTimeMillis(), weight);
- addHisoricalRecord(historicalRecord);
- }
-
- /**
- * Reads the history data from the backing file if the latter
- * was provided. Calling this method more than once before a call
- * to {@link #persistHistoricalData()} has been made has no effect.
- * <p>
- * <strong>Note:</strong> Historical data is read asynchronously and
- * as soon as the reading is completed any registered
- * {@link DataSetObserver}s will be notified. Also no historical
- * data is read until this method is invoked.
- * <p>
- */
- private void readHistoricalData() {
- synchronized (mInstanceLock) {
- if (!mCanReadHistoricalData || !mHistoricalRecordsChanged) {
- return;
- }
- mCanReadHistoricalData = false;
- mReadShareHistoryCalled = true;
- if (!TextUtils.isEmpty(mHistoryFileName)) {
- /*AsyncTask.*/SERIAL_EXECUTOR.execute(new HistoryLoader());
- }
- }
- }
-
- private static final Executor SERIAL_EXECUTOR = Executors.newSingleThreadExecutor();
-
- /**
- * Persists the history data to the backing file if the latter
- * was provided. Calling this method before a call to {@link #readHistoricalData()}
- * throws an exception. Calling this method more than one without choosing an
- * activity has not effect.
- *
- * @throws IllegalStateException If this method is called before a call to
- * {@link #readHistoricalData()}.
- */
- private void persistHistoricalData() {
- synchronized (mInstanceLock) {
- if (!mReadShareHistoryCalled) {
- throw new IllegalStateException("No preceding call to #readHistoricalData");
- }
- if (!mHistoricalRecordsChanged) {
- return;
- }
- mHistoricalRecordsChanged = false;
- mCanReadHistoricalData = true;
- if (!TextUtils.isEmpty(mHistoryFileName)) {
- /*AsyncTask.*/SERIAL_EXECUTOR.execute(new HistoryPersister());
- }
- }
- }
-
- /**
- * Sets the sorter for ordering activities based on historical data and an intent.
- *
- * @param activitySorter The sorter.
- *
- * @see ActivitySorter
- */
- public void setActivitySorter(ActivitySorter activitySorter) {
- synchronized (mInstanceLock) {
- if (mActivitySorter == activitySorter) {
- return;
- }
- mActivitySorter = activitySorter;
- sortActivities();
- }
- }
-
- /**
- * Sorts the activities based on history and an intent. If
- * a sorter is not specified this a default implementation is used.
- *
- * @see #setActivitySorter(ActivitySorter)
- */
- private void sortActivities() {
- synchronized (mInstanceLock) {
- if (mActivitySorter != null && !mActivites.isEmpty()) {
- mActivitySorter.sort(mIntent, mActivites,
- Collections.unmodifiableList(mHistoricalRecords));
- notifyChanged();
- }
- }
- }
-
- /**
- * Sets the maximal size of the historical data. Defaults to
- * {@link #DEFAULT_HISTORY_MAX_LENGTH}
- * <p>
- * <strong>Note:</strong> Setting this property will immediately
- * enforce the specified max history size by dropping enough old
- * historical records to enforce the desired size. Thus, any
- * records that exceed the history size will be discarded and
- * irreversibly lost.
- * </p>
- *
- * @param historyMaxSize The max history size.
- */
- public void setHistoryMaxSize(int historyMaxSize) {
- synchronized (mInstanceLock) {
- if (mHistoryMaxSize == historyMaxSize) {
- return;
- }
- mHistoryMaxSize = historyMaxSize;
- pruneExcessiveHistoricalRecordsLocked();
- sortActivities();
- }
- }
-
- /**
- * Gets the history max size.
- *
- * @return The history max size.
- */
- public int getHistoryMaxSize() {
- synchronized (mInstanceLock) {
- return mHistoryMaxSize;
- }
- }
-
- /**
- * Gets the history size.
- *
- * @return The history size.
- */
- public int getHistorySize() {
- synchronized (mInstanceLock) {
- return mHistoricalRecords.size();
- }
- }
-
- /**
- * Adds a historical record.
- *
- * @param historicalRecord The record to add.
- * @return True if the record was added.
- */
- private boolean addHisoricalRecord(HistoricalRecord historicalRecord) {
- synchronized (mInstanceLock) {
- final boolean added = mHistoricalRecords.add(historicalRecord);
- if (added) {
- mHistoricalRecordsChanged = true;
- pruneExcessiveHistoricalRecordsLocked();
- persistHistoricalData();
- sortActivities();
- }
- return added;
- }
- }
-
- /**
- * Prunes older excessive records to guarantee {@link #mHistoryMaxSize}.
- */
- private void pruneExcessiveHistoricalRecordsLocked() {
- List<HistoricalRecord> choiceRecords = mHistoricalRecords;
- final int pruneCount = choiceRecords.size() - mHistoryMaxSize;
- if (pruneCount <= 0) {
- return;
- }
- mHistoricalRecordsChanged = true;
- for (int i = 0; i < pruneCount; i++) {
- HistoricalRecord prunedRecord = choiceRecords.remove(0);
- if (DEBUG) {
- Log.i(LOG_TAG, "Pruned: " + prunedRecord);
- }
- }
- }
-
- /**
- * Loads the activities.
- */
- private void loadActivitiesLocked() {
- mActivites.clear();
- if (mIntent != null) {
- List<ResolveInfo> resolveInfos =
- mContext.getPackageManager().queryIntentActivities(mIntent, 0);
- final int resolveInfoCount = resolveInfos.size();
- for (int i = 0; i < resolveInfoCount; i++) {
- ResolveInfo resolveInfo = resolveInfos.get(i);
- mActivites.add(new ActivityResolveInfo(resolveInfo));
- }
- sortActivities();
- } else {
- notifyChanged();
- }
- }
-
- /**
- * Represents a record in the history.
- */
- public final static class HistoricalRecord {
-
- /**
- * The activity name.
- */
- public final ComponentName activity;
-
- /**
- * The choice time.
- */
- public final long time;
-
- /**
- * The record weight.
- */
- public final float weight;
-
- /**
- * Creates a new instance.
- *
- * @param activityName The activity component name flattened to string.
- * @param time The time the activity was chosen.
- * @param weight The weight of the record.
- */
- public HistoricalRecord(String activityName, long time, float weight) {
- this(ComponentName.unflattenFromString(activityName), time, weight);
- }
-
- /**
- * Creates a new instance.
- *
- * @param activityName The activity name.
- * @param time The time the activity was chosen.
- * @param weight The weight of the record.
- */
- public HistoricalRecord(ComponentName activityName, long time, float weight) {
- this.activity = activityName;
- this.time = time;
- this.weight = weight;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((activity == null) ? 0 : activity.hashCode());
- result = prime * result + (int) (time ^ (time >>> 32));
- result = prime * result + Float.floatToIntBits(weight);
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- HistoricalRecord other = (HistoricalRecord) obj;
- if (activity == null) {
- if (other.activity != null) {
- return false;
- }
- } else if (!activity.equals(other.activity)) {
- return false;
- }
- if (time != other.time) {
- return false;
- }
- if (Float.floatToIntBits(weight) != Float.floatToIntBits(other.weight)) {
- return false;
- }
- return true;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("[");
- builder.append("; activity:").append(activity);
- builder.append("; time:").append(time);
- builder.append("; weight:").append(new BigDecimal(weight));
- builder.append("]");
- return builder.toString();
- }
- }
-
- /**
- * Represents an activity.
- */
- public final class ActivityResolveInfo implements Comparable<ActivityResolveInfo> {
-
- /**
- * The {@link ResolveInfo} of the activity.
- */
- public final ResolveInfo resolveInfo;
-
- /**
- * Weight of the activity. Useful for sorting.
- */
- public float weight;
-
- /**
- * Creates a new instance.
- *
- * @param resolveInfo activity {@link ResolveInfo}.
- */
- public ActivityResolveInfo(ResolveInfo resolveInfo) {
- this.resolveInfo = resolveInfo;
- }
-
- @Override
- public int hashCode() {
- return 31 + Float.floatToIntBits(weight);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- ActivityResolveInfo other = (ActivityResolveInfo) obj;
- if (Float.floatToIntBits(weight) != Float.floatToIntBits(other.weight)) {
- return false;
- }
- return true;
- }
-
- public int compareTo(ActivityResolveInfo another) {
- return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("[");
- builder.append("resolveInfo:").append(resolveInfo.toString());
- builder.append("; weight:").append(new BigDecimal(weight));
- builder.append("]");
- return builder.toString();
- }
- }
-
- /**
- * Default activity sorter implementation.
- */
- private final class DefaultSorter implements ActivitySorter {
- private static final float WEIGHT_DECAY_COEFFICIENT = 0.95f;
-
- private final Map<String, ActivityResolveInfo> mPackageNameToActivityMap =
- new HashMap<String, ActivityResolveInfo>();
-
- public void sort(Intent intent, List<ActivityResolveInfo> activities,
- List<HistoricalRecord> historicalRecords) {
- Map<String, ActivityResolveInfo> packageNameToActivityMap =
- mPackageNameToActivityMap;
- packageNameToActivityMap.clear();
-
- final int activityCount = activities.size();
- for (int i = 0; i < activityCount; i++) {
- ActivityResolveInfo activity = activities.get(i);
- activity.weight = 0.0f;
- String packageName = activity.resolveInfo.activityInfo.packageName;
- packageNameToActivityMap.put(packageName, activity);
- }
-
- final int lastShareIndex = historicalRecords.size() - 1;
- float nextRecordWeight = 1;
- for (int i = lastShareIndex; i >= 0; i--) {
- HistoricalRecord historicalRecord = historicalRecords.get(i);
- String packageName = historicalRecord.activity.getPackageName();
- ActivityResolveInfo activity = packageNameToActivityMap.get(packageName);
- if (activity != null) {
- activity.weight += historicalRecord.weight * nextRecordWeight;
- nextRecordWeight = nextRecordWeight * WEIGHT_DECAY_COEFFICIENT;
- }
- }
-
- Collections.sort(activities);
-
- if (DEBUG) {
- for (int i = 0; i < activityCount; i++) {
- Log.i(LOG_TAG, "Sorted: " + activities.get(i));
- }
- }
- }
- }
-
- /**
- * Command for reading the historical records from a file off the UI thread.
- */
- private final class HistoryLoader implements Runnable {
-
- public void run() {
- FileInputStream fis = null;
- try {
- fis = mContext.openFileInput(mHistoryFileName);
- } catch (FileNotFoundException fnfe) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Could not open historical records file: " + mHistoryFileName);
- }
- return;
- }
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(fis, null);
-
- int type = XmlPullParser.START_DOCUMENT;
- while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
- type = parser.next();
- }
-
- if (!TAG_HISTORICAL_RECORDS.equals(parser.getName())) {
- throw new XmlPullParserException("Share records file does not start with "
- + TAG_HISTORICAL_RECORDS + " tag.");
- }
-
- List<HistoricalRecord> readRecords = new ArrayList<HistoricalRecord>();
-
- while (true) {
- type = parser.next();
- if (type == XmlPullParser.END_DOCUMENT) {
- break;
- }
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String nodeName = parser.getName();
- if (!TAG_HISTORICAL_RECORD.equals(nodeName)) {
- throw new XmlPullParserException("Share records file not well-formed.");
- }
-
- String activity = parser.getAttributeValue(null, ATTRIBUTE_ACTIVITY);
- final long time =
- Long.parseLong(parser.getAttributeValue(null, ATTRIBUTE_TIME));
- final float weight =
- Float.parseFloat(parser.getAttributeValue(null, ATTRIBUTE_WEIGHT));
-
- HistoricalRecord readRecord = new HistoricalRecord(activity, time,
- weight);
- readRecords.add(readRecord);
-
- if (DEBUG) {
- Log.i(LOG_TAG, "Read " + readRecord.toString());
- }
- }
-
- if (DEBUG) {
- Log.i(LOG_TAG, "Read " + readRecords.size() + " historical records.");
- }
-
- synchronized (mInstanceLock) {
- Set<HistoricalRecord> uniqueShareRecords =
- new LinkedHashSet<HistoricalRecord>(readRecords);
-
- // Make sure no duplicates. Example: Read a file with
- // one record, add one record, persist the two records,
- // add a record, read the persisted records - the
- // read two records should not be added again.
- List<HistoricalRecord> historicalRecords = mHistoricalRecords;
- final int historicalRecordsCount = historicalRecords.size();
- for (int i = historicalRecordsCount - 1; i >= 0; i--) {
- HistoricalRecord historicalRecord = historicalRecords.get(i);
- uniqueShareRecords.add(historicalRecord);
- }
-
- if (historicalRecords.size() == uniqueShareRecords.size()) {
- return;
- }
-
- // Make sure the oldest records go to the end.
- historicalRecords.clear();
- historicalRecords.addAll(uniqueShareRecords);
-
- mHistoricalRecordsChanged = true;
-
- // Do this on the client thread since the client may be on the UI
- // thread, wait for data changes which happen during sorting, and
- // perform UI modification based on the data change.
- mHandler.post(new Runnable() {
- public void run() {
- pruneExcessiveHistoricalRecordsLocked();
- sortActivities();
- }
- });
- }
- } catch (XmlPullParserException xppe) {
- Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, xppe);
- } catch (IOException ioe) {
- Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, ioe);
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException ioe) {
- /* ignore */
- }
- }
- }
- }
- }
-
- /**
- * Command for persisting the historical records to a file off the UI thread.
- */
- private final class HistoryPersister implements Runnable {
-
- public void run() {
- FileOutputStream fos = null;
- List<HistoricalRecord> records = null;
-
- synchronized (mInstanceLock) {
- records = new ArrayList<HistoricalRecord>(mHistoricalRecords);
- }
-
- try {
- fos = mContext.openFileOutput(mHistoryFileName, Context.MODE_PRIVATE);
- } catch (FileNotFoundException fnfe) {
- Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, fnfe);
- return;
- }
-
- XmlSerializer serializer = Xml.newSerializer();
-
- try {
- serializer.setOutput(fos, null);
- serializer.startDocument("UTF-8", true);
- serializer.startTag(null, TAG_HISTORICAL_RECORDS);
-
- final int recordCount = records.size();
- for (int i = 0; i < recordCount; i++) {
- HistoricalRecord record = records.remove(0);
- serializer.startTag(null, TAG_HISTORICAL_RECORD);
- serializer.attribute(null, ATTRIBUTE_ACTIVITY, record.activity.flattenToString());
- serializer.attribute(null, ATTRIBUTE_TIME, String.valueOf(record.time));
- serializer.attribute(null, ATTRIBUTE_WEIGHT, String.valueOf(record.weight));
- serializer.endTag(null, TAG_HISTORICAL_RECORD);
- if (DEBUG) {
- Log.i(LOG_TAG, "Wrote " + record.toString());
- }
- }
-
- serializer.endTag(null, TAG_HISTORICAL_RECORDS);
- serializer.endDocument();
-
- if (DEBUG) {
- Log.i(LOG_TAG, "Wrote " + recordCount + " historical records.");
- }
- } catch (IllegalArgumentException iae) {
- Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, iae);
- } catch (IllegalStateException ise) {
- Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, ise);
- } catch (IOException ioe) {
- Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, ioe);
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- /* ignore */
- }
- }
- }
- }
- }
-}
diff --git a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserView.java b/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserView.java
deleted file mode 100644
index 7eb7330e4..000000000
--- a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserView.java
+++ /dev/null
@@ -1,839 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.actionbarsherlock.widget;
-
-import android.os.Build;
-import com.actionbarsherlock.R;
-import com.actionbarsherlock.internal.widget.IcsLinearLayout;
-import com.actionbarsherlock.internal.widget.IcsListPopupWindow;
-import com.actionbarsherlock.view.ActionProvider;
-import com.actionbarsherlock.widget.ActivityChooserModel.ActivityChooserModelClient;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.database.DataSetObserver;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.PopupWindow;
-import android.widget.TextView;
-
-/**
- * This class is a view for choosing an activity for handling a given {@link Intent}.
- * <p>
- * The view is composed of two adjacent buttons:
- * <ul>
- * <li>
- * The left button is an immediate action and allows one click activity choosing.
- * Tapping this button immediately executes the intent without requiring any further
- * user input. Long press on this button shows a popup for changing the default
- * activity.
- * </li>
- * <li>
- * The right button is an overflow action and provides an optimized menu
- * of additional activities. Tapping this button shows a popup anchored to this
- * view, listing the most frequently used activities. This list is initially
- * limited to a small number of items in frequency used order. The last item,
- * "Show all..." serves as an affordance to display all available activities.
- * </li>
- * </ul>
- * </p>
- *
- * @hide
- */
-class ActivityChooserView extends ViewGroup implements ActivityChooserModelClient {
-
- /**
- * An adapter for displaying the activities in an {@link AdapterView}.
- */
- private final ActivityChooserViewAdapter mAdapter;
-
- /**
- * Implementation of various interfaces to avoid publishing them in the APIs.
- */
- private final Callbacks mCallbacks;
-
- /**
- * The content of this view.
- */
- private final IcsLinearLayout mActivityChooserContent;
-
- /**
- * Stores the background drawable to allow hiding and latter showing.
- */
- private final Drawable mActivityChooserContentBackground;
-
- /**
- * The expand activities action button;
- */
- private final FrameLayout mExpandActivityOverflowButton;
-
- /**
- * The image for the expand activities action button;
- */
- private final ImageView mExpandActivityOverflowButtonImage;
-
- /**
- * The default activities action button;
- */
- private final FrameLayout mDefaultActivityButton;
-
- /**
- * The image for the default activities action button;
- */
- private final ImageView mDefaultActivityButtonImage;
-
- /**
- * The maximal width of the list popup.
- */
- private final int mListPopupMaxWidth;
-
- /**
- * The ActionProvider hosting this view, if applicable.
- */
- ActionProvider mProvider;
-
- /**
- * Observer for the model data.
- */
- private final DataSetObserver mModelDataSetOberver = new DataSetObserver() {
-
- @Override
- public void onChanged() {
- super.onChanged();
- mAdapter.notifyDataSetChanged();
- }
- @Override
- public void onInvalidated() {
- super.onInvalidated();
- mAdapter.notifyDataSetInvalidated();
- }
- };
-
- private final OnGlobalLayoutListener mOnGlobalLayoutListener = new OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- if (isShowingPopup()) {
- if (!isShown()) {
- getListPopupWindow().dismiss();
- } else {
- getListPopupWindow().show();
- if (mProvider != null) {
- mProvider.subUiVisibilityChanged(true);
- }
- }
- }
- }
- };
-
- /**
- * Popup window for showing the activity overflow list.
- */
- private IcsListPopupWindow mListPopupWindow;
-
- /**
- * Listener for the dismissal of the popup/alert.
- */
- private PopupWindow.OnDismissListener mOnDismissListener;
-
- /**
- * Flag whether a default activity currently being selected.
- */
- private boolean mIsSelectingDefaultActivity;
-
- /**
- * The count of activities in the popup.
- */
- private int mInitialActivityCount = ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT;
-
- /**
- * Flag whether this view is attached to a window.
- */
- private boolean mIsAttachedToWindow;
-
- /**
- * String resource for formatting content description of the default target.
- */
- private int mDefaultActionButtonContentDescription;
-
- private final Context mContext;
-
- /**
- * Create a new instance.
- *
- * @param context The application environment.
- */
- public ActivityChooserView(Context context) {
- this(context, null);
- }
-
- /**
- * Create a new instance.
- *
- * @param context The application environment.
- * @param attrs A collection of attributes.
- */
- public ActivityChooserView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- /**
- * Create a new instance.
- *
- * @param context The application environment.
- * @param attrs A collection of attributes.
- * @param defStyle The default style to apply to this view.
- */
- public ActivityChooserView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mContext = context;
-
- TypedArray attributesArray = context.obtainStyledAttributes(attrs,
- R.styleable.SherlockActivityChooserView, defStyle, 0);
-
- mInitialActivityCount = attributesArray.getInt(
- R.styleable.SherlockActivityChooserView_initialActivityCount,
- ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT);
-
- Drawable expandActivityOverflowButtonDrawable = attributesArray.getDrawable(
- R.styleable.SherlockActivityChooserView_expandActivityOverflowButtonDrawable);
-
- attributesArray.recycle();
-
- LayoutInflater inflater = LayoutInflater.from(mContext);
- inflater.inflate(R.layout.abs__activity_chooser_view, this, true);
-
- mCallbacks = new Callbacks();
-
- mActivityChooserContent = (IcsLinearLayout) findViewById(R.id.abs__activity_chooser_view_content);
- mActivityChooserContentBackground = mActivityChooserContent.getBackground();
-
- mDefaultActivityButton = (FrameLayout) findViewById(R.id.abs__default_activity_button);
- mDefaultActivityButton.setOnClickListener(mCallbacks);
- mDefaultActivityButton.setOnLongClickListener(mCallbacks);
- mDefaultActivityButtonImage = (ImageView) mDefaultActivityButton.findViewById(R.id.abs__image);
-
- mExpandActivityOverflowButton = (FrameLayout) findViewById(R.id.abs__expand_activities_button);
- mExpandActivityOverflowButton.setOnClickListener(mCallbacks);
- mExpandActivityOverflowButtonImage =
- (ImageView) mExpandActivityOverflowButton.findViewById(R.id.abs__image);
- mExpandActivityOverflowButtonImage.setImageDrawable(expandActivityOverflowButtonDrawable);
-
- mAdapter = new ActivityChooserViewAdapter();
- mAdapter.registerDataSetObserver(new DataSetObserver() {
- @Override
- public void onChanged() {
- super.onChanged();
- updateAppearance();
- }
- });
-
- Resources resources = context.getResources();
- mListPopupMaxWidth = Math.max(resources.getDisplayMetrics().widthPixels / 2,
- resources.getDimensionPixelSize(R.dimen.abs__config_prefDialogWidth));
- }
-
- /**
- * {@inheritDoc}
- */
- public void setActivityChooserModel(ActivityChooserModel dataModel) {
- mAdapter.setDataModel(dataModel);
- if (isShowingPopup()) {
- dismissPopup();
- showPopup();
- }
- }
-
- /**
- * Sets the background for the button that expands the activity
- * overflow list.
- *
- * <strong>Note:</strong> Clients would like to set this drawable
- * as a clue about the action the chosen activity will perform. For
- * example, if a share activity is to be chosen the drawable should
- * give a clue that sharing is to be performed.
- *
- * @param drawable The drawable.
- */
- public void setExpandActivityOverflowButtonDrawable(Drawable drawable) {
- mExpandActivityOverflowButtonImage.setImageDrawable(drawable);
- }
-
- /**
- * Sets the content description for the button that expands the activity
- * overflow list.
- *
- * description as a clue about the action performed by the button.
- * For example, if a share activity is to be chosen the content
- * description should be something like "Share with".
- *
- * @param resourceId The content description resource id.
- */
- public void setExpandActivityOverflowButtonContentDescription(int resourceId) {
- CharSequence contentDescription = mContext.getString(resourceId);
- mExpandActivityOverflowButtonImage.setContentDescription(contentDescription);
- }
-
- /**
- * Set the provider hosting this view, if applicable.
- * @hide Internal use only
- */
- public void setProvider(ActionProvider provider) {
- mProvider = provider;
- }
-
- /**
- * Shows the popup window with activities.
- *
- * @return True if the popup was shown, false if already showing.
- */
- public boolean showPopup() {
- if (isShowingPopup() || !mIsAttachedToWindow) {
- return false;
- }
- mIsSelectingDefaultActivity = false;
- showPopupUnchecked(mInitialActivityCount);
- return true;
- }
-
- /**
- * Shows the popup no matter if it was already showing.
- *
- * @param maxActivityCount The max number of activities to display.
- */
- private void showPopupUnchecked(int maxActivityCount) {
- if (mAdapter.getDataModel() == null) {
- throw new IllegalStateException("No data model. Did you call #setDataModel?");
- }
-
- getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
-
- final boolean defaultActivityButtonShown =
- mDefaultActivityButton.getVisibility() == VISIBLE;
-
- final int activityCount = mAdapter.getActivityCount();
- final int maxActivityCountOffset = defaultActivityButtonShown ? 1 : 0;
- if (maxActivityCount != ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED
- && activityCount > maxActivityCount + maxActivityCountOffset) {
- mAdapter.setShowFooterView(true);
- mAdapter.setMaxActivityCount(maxActivityCount - 1);
- } else {
- mAdapter.setShowFooterView(false);
- mAdapter.setMaxActivityCount(maxActivityCount);
- }
-
- IcsListPopupWindow popupWindow = getListPopupWindow();
- if (!popupWindow.isShowing()) {
- if (mIsSelectingDefaultActivity || !defaultActivityButtonShown) {
- mAdapter.setShowDefaultActivity(true, defaultActivityButtonShown);
- } else {
- mAdapter.setShowDefaultActivity(false, false);
- }
- final int contentWidth = Math.min(mAdapter.measureContentWidth(), mListPopupMaxWidth);
- popupWindow.setContentWidth(contentWidth);
- popupWindow.show();
- if (mProvider != null) {
- mProvider.subUiVisibilityChanged(true);
- }
- popupWindow.getListView().setContentDescription(mContext.getString(
- R.string.abs__activitychooserview_choose_application));
- }
- }
-
- /**
- * Dismisses the popup window with activities.
- *
- * @return True if dismissed, false if already dismissed.
- */
- public boolean dismissPopup() {
- if (isShowingPopup()) {
- getListPopupWindow().dismiss();
- ViewTreeObserver viewTreeObserver = getViewTreeObserver();
- if (viewTreeObserver.isAlive()) {
- viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
- }
- }
- return true;
- }
-
- /**
- * Gets whether the popup window with activities is shown.
- *
- * @return True if the popup is shown.
- */
- public boolean isShowingPopup() {
- return getListPopupWindow().isShowing();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- ActivityChooserModel dataModel = mAdapter.getDataModel();
- if (dataModel != null) {
- try {
- dataModel.registerObserver(mModelDataSetOberver);
- } catch (IllegalStateException e) {
- // Related to #557.
- }
- }
- mIsAttachedToWindow = true;
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- ActivityChooserModel dataModel = mAdapter.getDataModel();
- if (dataModel != null) {
- try {
- dataModel.unregisterObserver(mModelDataSetOberver);
- } catch (IllegalStateException e) {
- //Oh, well... fixes issue #557
- }
- }
- ViewTreeObserver viewTreeObserver = getViewTreeObserver();
- if (viewTreeObserver.isAlive()) {
- viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
- }
- mIsAttachedToWindow = false;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- View child = mActivityChooserContent;
- // If the default action is not visible we want to be as tall as the
- // ActionBar so if this widget is used in the latter it will look as
- // a normal action button.
- if (mDefaultActivityButton.getVisibility() != VISIBLE) {
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
- MeasureSpec.EXACTLY);
- }
- measureChild(child, widthMeasureSpec, heightMeasureSpec);
- setMeasuredDimension(child.getMeasuredWidth(), child.getMeasuredHeight());
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- mActivityChooserContent.layout(0, 0, right - left, bottom - top);
- if (getListPopupWindow().isShowing()) {
- showPopupUnchecked(mAdapter.getMaxActivityCount());
- } else {
- dismissPopup();
- }
- }
-
- public ActivityChooserModel getDataModel() {
- return mAdapter.getDataModel();
- }
-
- /**
- * Sets a listener to receive a callback when the popup is dismissed.
- *
- * @param listener The listener to be notified.
- */
- public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
- mOnDismissListener = listener;
- }
-
- /**
- * Sets the initial count of items shown in the activities popup
- * i.e. the items before the popup is expanded. This is an upper
- * bound since it is not guaranteed that such number of intent
- * handlers exist.
- *
- * @param itemCount The initial popup item count.
- */
- public void setInitialActivityCount(int itemCount) {
- mInitialActivityCount = itemCount;
- }
-
- /**
- * Sets a content description of the default action button. This
- * resource should be a string taking one formatting argument and
- * will be used for formatting the content description of the button
- * dynamically as the default target changes. For example, a resource
- * pointing to the string "share with %1$s" will result in a content
- * description "share with Bluetooth" for the Bluetooth activity.
- *
- * @param resourceId The resource id.
- */
- public void setDefaultActionButtonContentDescription(int resourceId) {
- mDefaultActionButtonContentDescription = resourceId;
- }
-
- /**
- * Gets the list popup window which is lazily initialized.
- *
- * @return The popup.
- */
- private IcsListPopupWindow getListPopupWindow() {
- if (mListPopupWindow == null) {
- mListPopupWindow = new IcsListPopupWindow(getContext());
- mListPopupWindow.setAdapter(mAdapter);
- mListPopupWindow.setAnchorView(ActivityChooserView.this);
- mListPopupWindow.setModal(true);
- mListPopupWindow.setOnItemClickListener(mCallbacks);
- mListPopupWindow.setOnDismissListener(mCallbacks);
- }
- return mListPopupWindow;
- }
-
- /**
- * Updates the buttons state.
- */
- private void updateAppearance() {
- // Expand overflow button.
- if (mAdapter.getCount() > 0) {
- mExpandActivityOverflowButton.setEnabled(true);
- } else {
- mExpandActivityOverflowButton.setEnabled(false);
- }
- // Default activity button.
- final int activityCount = mAdapter.getActivityCount();
- final int historySize = mAdapter.getHistorySize();
- if (activityCount > 0 && historySize > 0) {
- mDefaultActivityButton.setVisibility(VISIBLE);
- ResolveInfo activity = mAdapter.getDefaultActivity();
- PackageManager packageManager = mContext.getPackageManager();
- mDefaultActivityButtonImage.setImageDrawable(activity.loadIcon(packageManager));
- if (mDefaultActionButtonContentDescription != 0) {
- CharSequence label = activity.loadLabel(packageManager);
- String contentDescription = mContext.getString(
- mDefaultActionButtonContentDescription, label);
- mDefaultActivityButton.setContentDescription(contentDescription);
- }
-
- // Work-around for #415.
- mAdapter.setShowDefaultActivity(false, false);
- } else {
- mDefaultActivityButton.setVisibility(View.GONE);
- }
- // Activity chooser content.
- if (mDefaultActivityButton.getVisibility() == VISIBLE) {
- mActivityChooserContent.setBackgroundDrawable(mActivityChooserContentBackground);
- } else {
- mActivityChooserContent.setBackgroundDrawable(null);
- mActivityChooserContent.setPadding(0, 0, 0, 0);
- }
- }
-
- /**
- * Interface implementation to avoid publishing them in the APIs.
- */
- private class Callbacks implements AdapterView.OnItemClickListener,
- View.OnClickListener, View.OnLongClickListener, PopupWindow.OnDismissListener {
-
- // AdapterView#OnItemClickListener
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- ActivityChooserViewAdapter adapter = (ActivityChooserViewAdapter) parent.getAdapter();
- final int itemViewType = adapter.getItemViewType(position);
- switch (itemViewType) {
- case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_FOOTER: {
- showPopupUnchecked(ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED);
- } break;
- case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_ACTIVITY: {
- dismissPopup();
- if (mIsSelectingDefaultActivity) {
- // The item at position zero is the default already.
- if (position > 0) {
- mAdapter.getDataModel().setDefaultActivity(position);
- }
- } else {
- // If the default target is not shown in the list, the first
- // item in the model is default action => adjust index
- position = mAdapter.getShowDefaultActivity() ? position : position + 1;
- Intent launchIntent = mAdapter.getDataModel().chooseActivity(position);
- if (launchIntent != null) {
- mContext.startActivity(launchIntent);
- }
- }
- } break;
- default:
- throw new IllegalArgumentException();
- }
- }
-
- // View.OnClickListener
- public void onClick(View view) {
- if (view == mDefaultActivityButton) {
- dismissPopup();
- ResolveInfo defaultActivity = mAdapter.getDefaultActivity();
- final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity);
- Intent launchIntent = mAdapter.getDataModel().chooseActivity(index);
- if (launchIntent != null) {
- mContext.startActivity(launchIntent);
- }
- } else if (view == mExpandActivityOverflowButton) {
- mIsSelectingDefaultActivity = false;
- showPopupUnchecked(mInitialActivityCount);
- } else {
- throw new IllegalArgumentException();
- }
- }
-
- // OnLongClickListener#onLongClick
- @Override
- public boolean onLongClick(View view) {
- if (view == mDefaultActivityButton) {
- if (mAdapter.getCount() > 0) {
- mIsSelectingDefaultActivity = true;
- showPopupUnchecked(mInitialActivityCount);
- }
- } else {
- throw new IllegalArgumentException();
- }
- return true;
- }
-
- // PopUpWindow.OnDismissListener#onDismiss
- public void onDismiss() {
- notifyOnDismissListener();
- if (mProvider != null) {
- mProvider.subUiVisibilityChanged(false);
- }
- }
-
- private void notifyOnDismissListener() {
- if (mOnDismissListener != null) {
- mOnDismissListener.onDismiss();
- }
- }
- }
-
- private static class SetActivated {
- public static void invoke(View view, boolean activated) {
- view.setActivated(activated);
- }
- }
-
- private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
-
- /**
- * Adapter for backing the list of activities shown in the popup.
- */
- private class ActivityChooserViewAdapter extends BaseAdapter {
-
- public static final int MAX_ACTIVITY_COUNT_UNLIMITED = Integer.MAX_VALUE;
-
- public static final int MAX_ACTIVITY_COUNT_DEFAULT = 4;
-
- private static final int ITEM_VIEW_TYPE_ACTIVITY = 0;
-
- private static final int ITEM_VIEW_TYPE_FOOTER = 1;
-
- private static final int ITEM_VIEW_TYPE_COUNT = 3;
-
- private ActivityChooserModel mDataModel;
-
- private int mMaxActivityCount = MAX_ACTIVITY_COUNT_DEFAULT;
-
- // Work-around for #415.
- private boolean mShowDefaultActivity = true;
-
- private boolean mHighlightDefaultActivity;
-
- private boolean mShowFooterView;
-
- public void setDataModel(ActivityChooserModel dataModel) {
- ActivityChooserModel oldDataModel = mAdapter.getDataModel();
- if (oldDataModel != null && isShown()) {
- try {
- oldDataModel.unregisterObserver(mModelDataSetOberver);
- } catch (IllegalStateException e) {
- //Oh, well... fixes issue #557
- }
- }
- mDataModel = dataModel;
- if (dataModel != null && isShown()) {
- try {
- dataModel.registerObserver(mModelDataSetOberver);
- } catch (IllegalStateException e) {
- // Related to #557.
- }
- }
- notifyDataSetChanged();
- }
-
- @Override
- public int getItemViewType(int position) {
- if (mShowFooterView && position == getCount() - 1) {
- return ITEM_VIEW_TYPE_FOOTER;
- } else {
- return ITEM_VIEW_TYPE_ACTIVITY;
- }
- }
-
- @Override
- public int getViewTypeCount() {
- return ITEM_VIEW_TYPE_COUNT;
- }
-
- public int getCount() {
- int count = 0;
- int activityCount = mDataModel.getActivityCount();
- if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
- activityCount--;
- }
- count = Math.min(activityCount, mMaxActivityCount);
- if (mShowFooterView) {
- count++;
- }
- return count;
- }
-
- public Object getItem(int position) {
- final int itemViewType = getItemViewType(position);
- switch (itemViewType) {
- case ITEM_VIEW_TYPE_FOOTER:
- return null;
- case ITEM_VIEW_TYPE_ACTIVITY:
- if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
- position++;
- }
- return mDataModel.getActivity(position);
- default:
- throw new IllegalArgumentException();
- }
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- final int itemViewType = getItemViewType(position);
- switch (itemViewType) {
- case ITEM_VIEW_TYPE_FOOTER:
- if (convertView == null || convertView.getId() != ITEM_VIEW_TYPE_FOOTER) {
- convertView = LayoutInflater.from(getContext()).inflate(
- R.layout.abs__activity_chooser_view_list_item, parent, false);
- convertView.setId(ITEM_VIEW_TYPE_FOOTER);
- TextView titleView = (TextView) convertView.findViewById(R.id.abs__title);
- titleView.setText(mContext.getString(
- R.string.abs__activity_chooser_view_see_all));
- }
- return convertView;
- case ITEM_VIEW_TYPE_ACTIVITY:
- if (convertView == null || convertView.getId() != R.id.abs__list_item) {
- convertView = LayoutInflater.from(getContext()).inflate(
- R.layout.abs__activity_chooser_view_list_item, parent, false);
- }
- PackageManager packageManager = mContext.getPackageManager();
- // Set the icon
- ImageView iconView = (ImageView) convertView.findViewById(R.id.abs__icon);
- ResolveInfo activity = (ResolveInfo) getItem(position);
- iconView.setImageDrawable(activity.loadIcon(packageManager));
- // Set the title.
- TextView titleView = (TextView) convertView.findViewById(R.id.abs__title);
- titleView.setText(activity.loadLabel(packageManager));
- if (IS_HONEYCOMB) {
- // Highlight the default.
- if (mShowDefaultActivity && position == 0 && mHighlightDefaultActivity) {
- SetActivated.invoke(convertView, true);
- } else {
- SetActivated.invoke(convertView, false);
- }
- }
- return convertView;
- default:
- throw new IllegalArgumentException();
- }
- }
-
- public int measureContentWidth() {
- // The user may have specified some of the target not to be shown but we
- // want to measure all of them since after expansion they should fit.
- final int oldMaxActivityCount = mMaxActivityCount;
- mMaxActivityCount = MAX_ACTIVITY_COUNT_UNLIMITED;
-
- int contentWidth = 0;
- View itemView = null;
-
- final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final int count = getCount();
-
- for (int i = 0; i < count; i++) {
- itemView = getView(i, itemView, null);
- itemView.measure(widthMeasureSpec, heightMeasureSpec);
- contentWidth = Math.max(contentWidth, itemView.getMeasuredWidth());
- }
-
- mMaxActivityCount = oldMaxActivityCount;
-
- return contentWidth;
- }
-
- public void setMaxActivityCount(int maxActivityCount) {
- if (mMaxActivityCount != maxActivityCount) {
- mMaxActivityCount = maxActivityCount;
- notifyDataSetChanged();
- }
- }
-
- public ResolveInfo getDefaultActivity() {
- return mDataModel.getDefaultActivity();
- }
-
- public void setShowFooterView(boolean showFooterView) {
- if (mShowFooterView != showFooterView) {
- mShowFooterView = showFooterView;
- notifyDataSetChanged();
- }
- }
-
- public int getActivityCount() {
- return mDataModel.getActivityCount();
- }
-
- public int getHistorySize() {
- return mDataModel.getHistorySize();
- }
-
- public int getMaxActivityCount() {
- return mMaxActivityCount;
- }
-
- public ActivityChooserModel getDataModel() {
- return mDataModel;
- }
-
- public void setShowDefaultActivity(boolean showDefaultActivity,
- boolean highlightDefaultActivity) {
- if (mShowDefaultActivity != showDefaultActivity
- || mHighlightDefaultActivity != highlightDefaultActivity) {
- mShowDefaultActivity = showDefaultActivity;
- mHighlightDefaultActivity = highlightDefaultActivity;
- notifyDataSetChanged();
- }
- }
-
- public boolean getShowDefaultActivity() {
- return mShowDefaultActivity;
- }
- }
-}
diff --git a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java b/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java
deleted file mode 100644
index fb8319645..000000000
--- a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java
+++ /dev/null
@@ -1,1811 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.actionbarsherlock.widget;
-
-import android.app.PendingIntent;
-import android.app.SearchManager;
-import android.app.SearchableInfo;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.speech.RecognizerIntent;
-import android.support.v4.view.KeyEventCompat;
-import android.support.v4.widget.CursorAdapter;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.text.style.ImageSpan;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.AutoCompleteTextView;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
-import com.actionbarsherlock.R;
-import com.actionbarsherlock.view.CollapsibleActionView;
-
-import java.lang.reflect.Method;
-import java.util.WeakHashMap;
-
-import static com.actionbarsherlock.widget.SuggestionsAdapter.getColumnString;
-
-/**
- * A widget that provides a user interface for the user to enter a search query and submit a request
- * to a search provider. Shows a list of query suggestions or results, if available, and allows the
- * user to pick a suggestion or result to launch into.
- *
- * <p>
- * When the SearchView is used in an ActionBar as an action view for a collapsible menu item, it
- * needs to be set to iconified by default using {@link #setIconifiedByDefault(boolean)
- * setIconifiedByDefault(true)}. This is the default, so nothing needs to be done.
- * </p>
- * <p>
- * If you want the search field to always be visible, then call setIconifiedByDefault(false).
- * </p>
- *
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For information about using {@code SearchView}, read the
- * <a href="{@docRoot}guide/topics/search/index.html">Search</a> developer guide.</p>
- * </div>
- *
- * @see android.view.MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
- * @attr ref android.R.styleable#SearchView_iconifiedByDefault
- * @attr ref android.R.styleable#SearchView_imeOptions
- * @attr ref android.R.styleable#SearchView_inputType
- * @attr ref android.R.styleable#SearchView_maxWidth
- * @attr ref android.R.styleable#SearchView_queryHint
- */
-public class SearchView extends LinearLayout implements CollapsibleActionView {
-
- private static final boolean DBG = false;
- private static final String LOG_TAG = "SearchView";
-
- /**
- * Private constant for removing the microphone in the keyboard.
- */
- private static final String IME_OPTION_NO_MICROPHONE = "nm";
-
- private OnQueryTextListener mOnQueryChangeListener;
- private OnCloseListener mOnCloseListener;
- private OnFocusChangeListener mOnQueryTextFocusChangeListener;
- private OnSuggestionListener mOnSuggestionListener;
- private OnClickListener mOnSearchClickListener;
-
- private boolean mIconifiedByDefault;
- private boolean mIconified;
- private CursorAdapter mSuggestionsAdapter;
- private View mSearchButton;
- private View mSubmitButton;
- private View mSearchPlate;
- private View mSubmitArea;
- private ImageView mCloseButton;
- private View mSearchEditFrame;
- private View mVoiceButton;
- private SearchAutoComplete mQueryTextView;
- private View mDropDownAnchor;
- private ImageView mSearchHintIcon;
- private boolean mSubmitButtonEnabled;
- private CharSequence mQueryHint;
- private boolean mQueryRefinement;
- private boolean mClearingFocus;
- private int mMaxWidth;
- private boolean mVoiceButtonEnabled;
- private CharSequence mOldQueryText;
- private CharSequence mUserQuery;
- private boolean mExpandedInActionView;
- private int mCollapsedImeOptions;
-
- private SearchableInfo mSearchable;
- private Bundle mAppSearchData;
-
- /*
- * SearchView can be set expanded before the IME is ready to be shown during
- * initial UI setup. The show operation is asynchronous to account for this.
- */
- private Runnable mShowImeRunnable = new Runnable() {
- public void run() {
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-
- if (imm != null) {
- showSoftInputUnchecked(SearchView.this, imm, 0);
- }
- }
- };
-
- private Runnable mUpdateDrawableStateRunnable = new Runnable() {
- public void run() {
- updateFocusedState();
- }
- };
-
- private Runnable mReleaseCursorRunnable = new Runnable() {
- public void run() {
- if (mSuggestionsAdapter != null && mSuggestionsAdapter instanceof SuggestionsAdapter) {
- mSuggestionsAdapter.changeCursor(null);
- }
- }
- };
-
- // For voice searching
- private final Intent mVoiceWebSearchIntent;
- private final Intent mVoiceAppSearchIntent;
-
- // A weak map of drawables we've gotten from other packages, so we don't load them
- // more than once.
- private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache =
- new WeakHashMap<String, Drawable.ConstantState>();
-
- /**
- * Callbacks for changes to the query text.
- */
- public interface OnQueryTextListener {
-
- /**
- * Called when the user submits the query. This could be due to a key press on the
- * keyboard or due to pressing a submit button.
- * The listener can override the standard behavior by returning true
- * to indicate that it has handled the submit request. Otherwise return false to
- * let the SearchView handle the submission by launching any associated intent.
- *
- * @param query the query text that is to be submitted
- *
- * @return true if the query has been handled by the listener, false to let the
- * SearchView perform the default action.
- */
- boolean onQueryTextSubmit(String query);
-
- /**
- * Called when the query text is changed by the user.
- *
- * @param newText the new content of the query text field.
- *
- * @return false if the SearchView should perform the default action of showing any
- * suggestions if available, true if the action was handled by the listener.
- */
- boolean onQueryTextChange(String newText);
- }
-
- public interface OnCloseListener {
-
- /**
- * The user is attempting to close the SearchView.
- *
- * @return true if the listener wants to override the default behavior of clearing the
- * text field and dismissing it, false otherwise.
- */
- boolean onClose();
- }
-
- /**
- * Callback interface for selection events on suggestions. These callbacks
- * are only relevant when a SearchableInfo has been specified by {@link #setSearchableInfo}.
- */
- public interface OnSuggestionListener {
-
- /**
- * Called when a suggestion was selected by navigating to it.
- * @param position the absolute position in the list of suggestions.
- *
- * @return true if the listener handles the event and wants to override the default
- * behavior of possibly rewriting the query based on the selected item, false otherwise.
- */
- boolean onSuggestionSelect(int position);
-
- /**
- * Called when a suggestion was clicked.
- * @param position the absolute position of the clicked item in the list of suggestions.
- *
- * @return true if the listener handles the event and wants to override the default
- * behavior of launching any intent or submitting a search query specified on that item.
- * Return false otherwise.
- */
- boolean onSuggestionClick(int position);
- }
-
- public SearchView(Context context) {
- this(context, null);
- }
-
- public SearchView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
- throw new IllegalStateException("SearchView is API 8+ only.");
- }
-
- LayoutInflater inflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.abs__search_view, this, true);
-
- mSearchButton = findViewById(R.id.abs__search_button);
- mQueryTextView = (SearchAutoComplete) findViewById(R.id.abs__search_src_text);
- mQueryTextView.setSearchView(this);
-
- mSearchEditFrame = findViewById(R.id.abs__search_edit_frame);
- mSearchPlate = findViewById(R.id.abs__search_plate);
- mSubmitArea = findViewById(R.id.abs__submit_area);
- mSubmitButton = findViewById(R.id.abs__search_go_btn);
- mCloseButton = (ImageView) findViewById(R.id.abs__search_close_btn);
- mVoiceButton = findViewById(R.id.abs__search_voice_btn);
- mSearchHintIcon = (ImageView) findViewById(R.id.abs__search_mag_icon);
-
- mSearchButton.setOnClickListener(mOnClickListener);
- mCloseButton.setOnClickListener(mOnClickListener);
- mSubmitButton.setOnClickListener(mOnClickListener);
- mVoiceButton.setOnClickListener(mOnClickListener);
- mQueryTextView.setOnClickListener(mOnClickListener);
-
- mQueryTextView.addTextChangedListener(mTextWatcher);
- mQueryTextView.setOnEditorActionListener(mOnEditorActionListener);
- mQueryTextView.setOnItemClickListener(mOnItemClickListener);
- mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener);
- mQueryTextView.setOnKeyListener(mTextKeyListener);
- // Inform any listener of focus changes
- mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
-
- public void onFocusChange(View v, boolean hasFocus) {
- if (mOnQueryTextFocusChangeListener != null) {
- mOnQueryTextFocusChangeListener.onFocusChange(SearchView.this, hasFocus);
- }
- }
- });
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SherlockSearchView, 0, 0);
- setIconifiedByDefault(a.getBoolean(R.styleable.SherlockSearchView_iconifiedByDefault, true));
- int maxWidth = a.getDimensionPixelSize(R.styleable.SherlockSearchView_android_maxWidth, -1);
- if (maxWidth != -1) {
- setMaxWidth(maxWidth);
- }
- CharSequence queryHint = a.getText(R.styleable.SherlockSearchView_queryHint);
- if (!TextUtils.isEmpty(queryHint)) {
- setQueryHint(queryHint);
- }
- int imeOptions = a.getInt(R.styleable.SherlockSearchView_android_imeOptions, -1);
- if (imeOptions != -1) {
- setImeOptions(imeOptions);
- }
- int inputType = a.getInt(R.styleable.SherlockSearchView_android_inputType, -1);
- if (inputType != -1) {
- setInputType(inputType);
- }
-
- a.recycle();
-
- boolean focusable = true;
-
- a = context.obtainStyledAttributes(attrs, R.styleable.SherlockView, 0, 0);
- focusable = a.getBoolean(R.styleable.SherlockView_android_focusable, focusable);
- a.recycle();
- setFocusable(focusable);
-
- // Save voice intent for later queries/launching
- mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
- mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
- RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
-
- mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
- mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- mDropDownAnchor = findViewById(mQueryTextView.getDropDownAnchor());
- if (mDropDownAnchor != null) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- mDropDownAnchor.addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- adjustDropDownSizeAndPosition();
- }
- });
- } else {
- mDropDownAnchor.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override public void onGlobalLayout() {
- adjustDropDownSizeAndPosition();
- }
- });
- }
- }
-
- updateViewsVisibility(mIconifiedByDefault);
- updateQueryHint();
- }
-
- /**
- * Sets the SearchableInfo for this SearchView. Properties in the SearchableInfo are used
- * to display labels, hints, suggestions, create intents for launching search results screens
- * and controlling other affordances such as a voice button.
- *
- * @param searchable a SearchableInfo can be retrieved from the SearchManager, for a specific
- * activity or a global search provider.
- */
- public void setSearchableInfo(SearchableInfo searchable) {
- mSearchable = searchable;
- if (mSearchable != null) {
- updateSearchAutoComplete();
- updateQueryHint();
- }
- // Cache the voice search capability
- mVoiceButtonEnabled = hasVoiceSearch();
-
- if (mVoiceButtonEnabled) {
- // Disable the microphone on the keyboard, as a mic is displayed near the text box
- // TODO: use imeOptions to disable voice input when the new API will be available
- mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
- }
- updateViewsVisibility(isIconified());
- }
-
- /**
- * Sets the APP_DATA for legacy SearchDialog use.
- * @param appSearchData bundle provided by the app when launching the search dialog
- * @hide
- */
- public void setAppSearchData(Bundle appSearchData) {
- mAppSearchData = appSearchData;
- }
-
- /**
- * Sets the IME options on the query text field.
- *
- * @see TextView#setImeOptions(int)
- * @param imeOptions the options to set on the query text field
- *
- * @attr ref android.R.styleable#SearchView_imeOptions
- */
- public void setImeOptions(int imeOptions) {
- mQueryTextView.setImeOptions(imeOptions);
- }
-
- /**
- * Returns the IME options set on the query text field.
- * @return the ime options
- * @see TextView#setImeOptions(int)
- *
- * @attr ref android.R.styleable#SearchView_imeOptions
- */
- public int getImeOptions() {
- return mQueryTextView.getImeOptions();
- }
-
- /**
- * Sets the input type on the query text field.
- *
- * @see TextView#setInputType(int)
- * @param inputType the input type to set on the query text field
- *
- * @attr ref android.R.styleable#SearchView_inputType
- */
- public void setInputType(int inputType) {
- mQueryTextView.setInputType(inputType);
- }
-
- /**
- * Returns the input type set on the query text field.
- * @return the input type
- *
- * @attr ref android.R.styleable#SearchView_inputType
- */
- public int getInputType() {
- return mQueryTextView.getInputType();
- }
-
- /** @hide */
- @Override
- public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
- // Don't accept focus if in the middle of clearing focus
- if (mClearingFocus) return false;
- // Check if SearchView is focusable.
- if (!isFocusable()) return false;
- // If it is not iconified, then give the focus to the text field
- if (!isIconified()) {
- boolean result = mQueryTextView.requestFocus(direction, previouslyFocusedRect);
- if (result) {
- updateViewsVisibility(false);
- }
- return result;
- } else {
- return super.requestFocus(direction, previouslyFocusedRect);
- }
- }
-
- /** @hide */
- @Override
- public void clearFocus() {
- mClearingFocus = true;
- setImeVisibility(false);
- super.clearFocus();
- mQueryTextView.clearFocus();
- mClearingFocus = false;
- }
-
- /**
- * Sets a listener for user actions within the SearchView.
- *
- * @param listener the listener object that receives callbacks when the user performs
- * actions in the SearchView such as clicking on buttons or typing a query.
- */
- public void setOnQueryTextListener(OnQueryTextListener listener) {
- mOnQueryChangeListener = listener;
- }
-
- /**
- * Sets a listener to inform when the user closes the SearchView.
- *
- * @param listener the listener to call when the user closes the SearchView.
- */
- public void setOnCloseListener(OnCloseListener listener) {
- mOnCloseListener = listener;
- }
-
- /**
- * Sets a listener to inform when the focus of the query text field changes.
- *
- * @param listener the listener to inform of focus changes.
- */
- public void setOnQueryTextFocusChangeListener(OnFocusChangeListener listener) {
- mOnQueryTextFocusChangeListener = listener;
- }
-
- /**
- * Sets a listener to inform when a suggestion is focused or clicked.
- *
- * @param listener the listener to inform of suggestion selection events.
- */
- public void setOnSuggestionListener(OnSuggestionListener listener) {
- mOnSuggestionListener = listener;
- }
-
- /**
- * Sets a listener to inform when the search button is pressed. This is only
- * relevant when the text field is not visible by default. Calling {@link #setIconified
- * setIconified(false)} can also cause this listener to be informed.
- *
- * @param listener the listener to inform when the search button is clicked or
- * the text field is programmatically de-iconified.
- */
- public void setOnSearchClickListener(OnClickListener listener) {
- mOnSearchClickListener = listener;
- }
-
- /**
- * Returns the query string currently in the text field.
- *
- * @return the query string
- */
- public CharSequence getQuery() {
- return mQueryTextView.getText();
- }
-
- /**
- * Sets a query string in the text field and optionally submits the query as well.
- *
- * @param query the query string. This replaces any query text already present in the
- * text field.
- * @param submit whether to submit the query right now or only update the contents of
- * text field.
- */
- public void setQuery(CharSequence query, boolean submit) {
- mQueryTextView.setText(query);
- if (query != null) {
- mQueryTextView.setSelection(mQueryTextView.length());
- mUserQuery = query;
- }
-
- // If the query is not empty and submit is requested, submit the query
- if (submit && !TextUtils.isEmpty(query)) {
- onSubmitQuery();
- }
- }
-
- /**
- * Sets the hint text to display in the query text field. This overrides any hint specified
- * in the SearchableInfo.
- *
- * @param hint the hint text to display
- *
- * @attr ref android.R.styleable#SearchView_queryHint
- */
- public void setQueryHint(CharSequence hint) {
- mQueryHint = hint;
- updateQueryHint();
- }
-
- /**
- * Gets the hint text to display in the query text field.
- * @return the query hint text, if specified, null otherwise.
- *
- * @attr ref android.R.styleable#SearchView_queryHint
- */
- public CharSequence getQueryHint() {
- if (mQueryHint != null) {
- return mQueryHint;
- } else if (mSearchable != null) {
- CharSequence hint = null;
- int hintId = mSearchable.getHintId();
- if (hintId != 0) {
- hint = getContext().getString(hintId);
- }
- return hint;
- }
- return null;
- }
-
- /**
- * Sets the default or resting state of the search field. If true, a single search icon is
- * shown by default and expands to show the text field and other buttons when pressed. Also,
- * if the default state is iconified, then it collapses to that state when the close button
- * is pressed. Changes to this property will take effect immediately.
- *
- * <p>The default value is true.</p>
- *
- * @param iconified whether the search field should be iconified by default
- *
- * @attr ref android.R.styleable#SearchView_iconifiedByDefault
- */
- public void setIconifiedByDefault(boolean iconified) {
- if (mIconifiedByDefault == iconified) return;
- mIconifiedByDefault = iconified;
- updateViewsVisibility(iconified);
- updateQueryHint();
- }
-
- /**
- * Returns the default iconified state of the search field.
- * @return
- *
- * @attr ref android.R.styleable#SearchView_iconifiedByDefault
- */
- public boolean isIconfiedByDefault() {
- return mIconifiedByDefault;
- }
-
- /**
- * Iconifies or expands the SearchView. Any query text is cleared when iconified. This is
- * a temporary state and does not override the default iconified state set by
- * {@link #setIconifiedByDefault(boolean)}. If the default state is iconified, then
- * a false here will only be valid until the user closes the field. And if the default
- * state is expanded, then a true here will only clear the text field and not close it.
- *
- * @param iconify a true value will collapse the SearchView to an icon, while a false will
- * expand it.
- */
- public void setIconified(boolean iconify) {
- if (iconify) {
- onCloseClicked();
- } else {
- onSearchClicked();
- }
- }
-
- /**
- * Returns the current iconified state of the SearchView.
- *
- * @return true if the SearchView is currently iconified, false if the search field is
- * fully visible.
- */
- public boolean isIconified() {
- return mIconified;
- }
-
- /**
- * Enables showing a submit button when the query is non-empty. In cases where the SearchView
- * is being used to filter the contents of the current activity and doesn't launch a separate
- * results activity, then the submit button should be disabled.
- *
- * @param enabled true to show a submit button for submitting queries, false if a submit
- * button is not required.
- */
- public void setSubmitButtonEnabled(boolean enabled) {
- mSubmitButtonEnabled = enabled;
- updateViewsVisibility(isIconified());
- }
-
- /**
- * Returns whether the submit button is enabled when necessary or never displayed.
- *
- * @return whether the submit button is enabled automatically when necessary
- */
- public boolean isSubmitButtonEnabled() {
- return mSubmitButtonEnabled;
- }
-
- /**
- * Specifies if a query refinement button should be displayed alongside each suggestion
- * or if it should depend on the flags set in the individual items retrieved from the
- * suggestions provider. Clicking on the query refinement button will replace the text
- * in the query text field with the text from the suggestion. This flag only takes effect
- * if a SearchableInfo has been specified with {@link #setSearchableInfo(SearchableInfo)}
- * and not when using a custom adapter.
- *
- * @param enable true if all items should have a query refinement button, false if only
- * those items that have a query refinement flag set should have the button.
- *
- * @see SearchManager#SUGGEST_COLUMN_FLAGS
- * @see SearchManager#FLAG_QUERY_REFINEMENT
- */
- public void setQueryRefinementEnabled(boolean enable) {
- mQueryRefinement = enable;
- if (mSuggestionsAdapter instanceof SuggestionsAdapter) {
- ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
- enable ? SuggestionsAdapter.REFINE_ALL : SuggestionsAdapter.REFINE_BY_ENTRY);
- }
- }
-
- /**
- * Returns whether query refinement is enabled for all items or only specific ones.
- * @return true if enabled for all items, false otherwise.
- */
- public boolean isQueryRefinementEnabled() {
- return mQueryRefinement;
- }
-
- /**
- * You can set a custom adapter if you wish. Otherwise the default adapter is used to
- * display the suggestions from the suggestions provider associated with the SearchableInfo.
- *
- * @see #setSearchableInfo(SearchableInfo)
- */
- public void setSuggestionsAdapter(CursorAdapter adapter) {
- mSuggestionsAdapter = adapter;
-
- mQueryTextView.setAdapter(mSuggestionsAdapter);
- }
-
- /**
- * Returns the adapter used for suggestions, if any.
- * @return the suggestions adapter
- */
- public CursorAdapter getSuggestionsAdapter() {
- return mSuggestionsAdapter;
- }
-
- /**
- * Makes the view at most this many pixels wide
- *
- * @attr ref android.R.styleable#SearchView_maxWidth
- */
- public void setMaxWidth(int maxpixels) {
- mMaxWidth = maxpixels;
-
- requestLayout();
- }
-
- /**
- * Gets the specified maximum width in pixels, if set. Returns zero if
- * no maximum width was specified.
- * @return the maximum width of the view
- *
- * @attr ref android.R.styleable#SearchView_maxWidth
- */
- public int getMaxWidth() {
- return mMaxWidth;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Let the standard measurements take effect in iconified state.
- if (isIconified()) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- return;
- }
-
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int width = MeasureSpec.getSize(widthMeasureSpec);
-
- switch (widthMode) {
- case MeasureSpec.AT_MOST:
- // If there is an upper limit, don't exceed maximum width (explicit or implicit)
- if (mMaxWidth > 0) {
- width = Math.min(mMaxWidth, width);
- } else {
- width = Math.min(getPreferredWidth(), width);
- }
- break;
- case MeasureSpec.EXACTLY:
- // If an exact width is specified, still don't exceed any specified maximum width
- if (mMaxWidth > 0) {
- width = Math.min(mMaxWidth, width);
- }
- break;
- case MeasureSpec.UNSPECIFIED:
- // Use maximum width, if specified, else preferred width
- width = mMaxWidth > 0 ? mMaxWidth : getPreferredWidth();
- break;
- }
- widthMode = MeasureSpec.EXACTLY;
- super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode), heightMeasureSpec);
- }
-
- private int getPreferredWidth() {
- return getContext().getResources()
- .getDimensionPixelSize(R.dimen.abs__search_view_preferred_width);
- }
-
- private void updateViewsVisibility(final boolean collapsed) {
- mIconified = collapsed;
- // Visibility of views that are visible when collapsed
- final int visCollapsed = collapsed ? VISIBLE : GONE;
- // Is there text in the query
- final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
-
- mSearchButton.setVisibility(visCollapsed);
- updateSubmitButton(hasText);
- mSearchEditFrame.setVisibility(collapsed ? GONE : VISIBLE);
- mSearchHintIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE);
- updateCloseButton();
- updateVoiceButton(!hasText);
- updateSubmitArea();
- }
-
- private boolean hasVoiceSearch() {
- if (mSearchable != null && mSearchable.getVoiceSearchEnabled()) {
- Intent testIntent = null;
- if (mSearchable.getVoiceSearchLaunchWebSearch()) {
- testIntent = mVoiceWebSearchIntent;
- } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
- testIntent = mVoiceAppSearchIntent;
- }
- if (testIntent != null) {
- ResolveInfo ri = getContext().getPackageManager().resolveActivity(testIntent,
- PackageManager.MATCH_DEFAULT_ONLY);
- return ri != null;
- }
- }
- return false;
- }
-
- private boolean isSubmitAreaEnabled() {
- return (mSubmitButtonEnabled || mVoiceButtonEnabled) && !isIconified();
- }
-
- private void updateSubmitButton(boolean hasText) {
- int visibility = GONE;
- if (mSubmitButtonEnabled && isSubmitAreaEnabled() && hasFocus()
- && (hasText || !mVoiceButtonEnabled)) {
- visibility = VISIBLE;
- }
- mSubmitButton.setVisibility(visibility);
- }
-
- private void updateSubmitArea() {
- int visibility = GONE;
- if (isSubmitAreaEnabled()
- && (mSubmitButton.getVisibility() == VISIBLE
- || mVoiceButton.getVisibility() == VISIBLE)) {
- visibility = VISIBLE;
- }
- mSubmitArea.setVisibility(visibility);
- }
-
- private void updateCloseButton() {
- final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
- // Should we show the close button? It is not shown if there's no focus,
- // field is not iconified by default and there is no text in it.
- final boolean showClose = hasText || (mIconifiedByDefault && !mExpandedInActionView);
- mCloseButton.setVisibility(showClose ? VISIBLE : GONE);
- mCloseButton.getDrawable().setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET);
- }
-
- private void postUpdateFocusedState() {
- post(mUpdateDrawableStateRunnable);
- }
-
- private void updateFocusedState() {
- boolean focused = mQueryTextView.hasFocus();
- mSearchPlate.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
- mSubmitArea.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
- invalidate();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- removeCallbacks(mUpdateDrawableStateRunnable);
- post(mReleaseCursorRunnable);
- super.onDetachedFromWindow();
- }
-
- private void setImeVisibility(final boolean visible) {
- if (visible) {
- post(mShowImeRunnable);
- } else {
- removeCallbacks(mShowImeRunnable);
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-
- if (imm != null) {
- imm.hideSoftInputFromWindow(getWindowToken(), 0);
- }
- }
- }
-
- /**
- * Called by the SuggestionsAdapter
- * @hide
- */
- /* package */void onQueryRefine(CharSequence queryText) {
- setQuery(queryText);
- }
-
- private final OnClickListener mOnClickListener = new OnClickListener() {
-
- public void onClick(View v) {
- if (v == mSearchButton) {
- onSearchClicked();
- } else if (v == mCloseButton) {
- onCloseClicked();
- } else if (v == mSubmitButton) {
- onSubmitQuery();
- } else if (v == mVoiceButton) {
- onVoiceClicked();
- } else if (v == mQueryTextView) {
- forceSuggestionQuery();
- }
- }
- };
-
- /**
- * Handles the key down event for dealing with action keys.
- *
- * @param keyCode This is the keycode of the typed key, and is the same value as
- * found in the KeyEvent parameter.
- * @param event The complete event record for the typed key
- *
- * @return true if the event was handled here, or false if not.
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (mSearchable == null) {
- return false;
- }
-
- // if it's an action specified by the searchable activity, launch the
- // entered query with the action key
- // TODO SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
- // TODO if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
- // TODO launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView.getText()
- // TODO .toString());
- // TODO return true;
- // TODO }
-
- return super.onKeyDown(keyCode, event);
- }
-
- /**
- * React to the user typing "enter" or other hardwired keys while typing in
- * the search box. This handles these special keys while the edit box has
- * focus.
- */
- View.OnKeyListener mTextKeyListener = new View.OnKeyListener() {
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- // guard against possible race conditions
- if (mSearchable == null) {
- return false;
- }
-
- if (DBG) {
- Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event + "), selection: "
- + mQueryTextView.getListSelection());
- }
-
- // If a suggestion is selected, handle enter, search key, and action keys
- // as presses on the selected suggestion
- if (mQueryTextView.isPopupShowing()
- && mQueryTextView.getListSelection() != ListView.INVALID_POSITION) {
- return onSuggestionsKey(v, keyCode, event);
- }
-
- // If there is text in the query box, handle enter, and action keys
- // The search key is handled by the dialog's onKeyDown().
- if (!mQueryTextView.isEmpty() && KeyEventCompat.hasNoModifiers(event)) {
- if (event.getAction() == KeyEvent.ACTION_UP) {
- if (keyCode == KeyEvent.KEYCODE_ENTER) {
- v.cancelLongPress();
-
- // Launch as a regular search.
- launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mQueryTextView.getText()
- .toString());
- return true;
- }
- }
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- // TODO SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
- // TODO if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
- // TODO launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView
- // TODO .getText().toString());
- // TODO return true;
- // TODO }
- }
- }
- return false;
- }
- };
-
- /**
- * React to the user typing while in the suggestions list. First, check for
- * action keys. If not handled, try refocusing regular characters into the
- * EditText.
- */
- private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) {
- // guard against possible race conditions (late arrival after dismiss)
- if (mSearchable == null) {
- return false;
- }
- if (mSuggestionsAdapter == null) {
- return false;
- }
- if (event.getAction() == KeyEvent.ACTION_DOWN && KeyEventCompat.hasNoModifiers(event)) {
- // First, check for enter or search (both of which we'll treat as a
- // "click")
- if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH
- || keyCode == KeyEvent.KEYCODE_TAB) {
- int position = mQueryTextView.getListSelection();
- return onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
- }
-
- // Next, check for left/right moves, which we use to "return" the
- // user to the edit view
- if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
- // give "focus" to text editor, with cursor at the beginning if
- // left key, at end if right key
- // TODO: Reverse left/right for right-to-left languages, e.g.
- // Arabic
- int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mQueryTextView
- .length();
- mQueryTextView.setSelection(selPoint);
- mQueryTextView.setListSelection(0);
- mQueryTextView.clearListSelection();
- ensureImeVisible(mQueryTextView, true);
-
- return true;
- }
-
- // Next, check for an "up and out" move
- if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mQueryTextView.getListSelection()) {
- // TODO: restoreUserQuery();
- // let ACTV complete the move
- return false;
- }
-
- // Next, check for an "action key"
- // TODO SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
- // TODO if ((actionKey != null)
- // TODO && ((actionKey.getSuggestActionMsg() != null) || (actionKey
- // TODO .getSuggestActionMsgColumn() != null))) {
- // TODO // launch suggestion using action key column
- // TODO int position = mQueryTextView.getListSelection();
- // TODO if (position != ListView.INVALID_POSITION) {
- // TODO Cursor c = mSuggestionsAdapter.getCursor();
- // TODO if (c.moveToPosition(position)) {
- // TODO final String actionMsg = getActionKeyMessage(c, actionKey);
- // TODO if (actionMsg != null && (actionMsg.length() > 0)) {
- // TODO return onItemClicked(position, keyCode, actionMsg);
- // TODO }
- // TODO }
- // TODO }
- // TODO }
- }
- return false;
- }
-
- /**
- * For a given suggestion and a given cursor row, get the action message. If
- * not provided by the specific row/column, also check for a single
- * definition (for the action key).
- *
- * @param c The cursor providing suggestions
- * @param actionKey The actionkey record being examined
- *
- * @return Returns a string, or null if no action key message for this
- * suggestion
- */
- // TODO private static String getActionKeyMessage(Cursor c, SearchableInfo.ActionKeyInfo actionKey) {
- // TODO String result = null;
- // TODO // check first in the cursor data, for a suggestion-specific message
- // TODO final String column = actionKey.getSuggestActionMsgColumn();
- // TODO if (column != null) {
- // TODO result = SuggestionsAdapter.getColumnString(c, column);
- // TODO }
- // TODO // If the cursor didn't give us a message, see if there's a single
- // TODO // message defined
- // TODO // for the actionkey (for all suggestions)
- // TODO if (result == null) {
- // TODO result = actionKey.getSuggestActionMsg();
- // TODO }
- // TODO return result;
- // TODO }
-
- private int getSearchIconId() {
- TypedValue outValue = new TypedValue();
- getContext().getTheme().resolveAttribute(R.attr.searchViewSearchIcon,
- outValue, true);
- return outValue.resourceId;
- }
-
- private CharSequence getDecoratedHint(CharSequence hintText) {
- // If the field is always expanded, then don't add the search icon to the hint
- if (!mIconifiedByDefault) return hintText;
-
- SpannableStringBuilder ssb = new SpannableStringBuilder(" "); // for the icon
- ssb.append(hintText);
- Drawable searchIcon = getContext().getResources().getDrawable(getSearchIconId());
- int textSize = (int) (mQueryTextView.getTextSize() * 1.25);
- searchIcon.setBounds(0, 0, textSize, textSize);
- ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- return ssb;
- }
-
- private void updateQueryHint() {
- if (mQueryHint != null) {
- mQueryTextView.setHint(getDecoratedHint(mQueryHint));
- } else if (mSearchable != null) {
- CharSequence hint = null;
- int hintId = mSearchable.getHintId();
- if (hintId != 0) {
- hint = getContext().getString(hintId);
- }
- if (hint != null) {
- mQueryTextView.setHint(getDecoratedHint(hint));
- }
- } else {
- mQueryTextView.setHint(getDecoratedHint(""));
- }
- }
-
- /**
- * Updates the auto-complete text view.
- */
- private void updateSearchAutoComplete() {
- // TODO mQueryTextView.setDropDownAnimationStyle(0); // no animation
- mQueryTextView.setThreshold(mSearchable.getSuggestThreshold());
- mQueryTextView.setImeOptions(mSearchable.getImeOptions());
- int inputType = mSearchable.getInputType();
- // We only touch this if the input type is set up for text (which it almost certainly
- // should be, in the case of search!)
- if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
- // The existence of a suggestions authority is the proxy for "suggestions
- // are available here"
- inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
- if (mSearchable.getSuggestAuthority() != null) {
- inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
- // TYPE_TEXT_FLAG_AUTO_COMPLETE means that the text editor is performing
- // auto-completion based on its own semantics, which it will present to the user
- // as they type. This generally means that the input method should not show its
- // own candidates, and the spell checker should not be in action. The text editor
- // supplies its candidates by calling InputMethodManager.displayCompletions(),
- // which in turn will call InputMethodSession.displayCompletions().
- inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
- }
- }
- mQueryTextView.setInputType(inputType);
- if (mSuggestionsAdapter != null) {
- mSuggestionsAdapter.changeCursor(null);
- }
- // attach the suggestions adapter, if suggestions are available
- // The existence of a suggestions authority is the proxy for "suggestions available here"
- if (mSearchable.getSuggestAuthority() != null) {
- mSuggestionsAdapter = new SuggestionsAdapter(getContext(),
- this, mSearchable, mOutsideDrawablesCache);
- mQueryTextView.setAdapter(mSuggestionsAdapter);
- ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
- mQueryRefinement ? SuggestionsAdapter.REFINE_ALL
- : SuggestionsAdapter.REFINE_BY_ENTRY);
- }
- }
-
- /**
- * Update the visibility of the voice button. There are actually two voice search modes,
- * either of which will activate the button.
- * @param empty whether the search query text field is empty. If it is, then the other
- * criteria apply to make the voice button visible.
- */
- private void updateVoiceButton(boolean empty) {
- int visibility = GONE;
- if (mVoiceButtonEnabled && !isIconified() && empty) {
- visibility = VISIBLE;
- mSubmitButton.setVisibility(GONE);
- }
- mVoiceButton.setVisibility(visibility);
- }
-
- private final OnEditorActionListener mOnEditorActionListener = new OnEditorActionListener() {
-
- /**
- * Called when the input method default action key is pressed.
- */
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- onSubmitQuery();
- return true;
- }
- };
-
- private void onTextChanged(CharSequence newText) {
- CharSequence text = mQueryTextView.getText();
- mUserQuery = text;
- boolean hasText = !TextUtils.isEmpty(text);
- updateSubmitButton(hasText);
- updateVoiceButton(!hasText);
- updateCloseButton();
- updateSubmitArea();
- if (mOnQueryChangeListener != null && !TextUtils.equals(newText, mOldQueryText)) {
- mOnQueryChangeListener.onQueryTextChange(newText.toString());
- }
- mOldQueryText = newText.toString();
- }
-
- private void onSubmitQuery() {
- CharSequence query = mQueryTextView.getText();
- if (query != null && TextUtils.getTrimmedLength(query) > 0) {
- if (mOnQueryChangeListener == null
- || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) {
- if (mSearchable != null) {
- launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString());
- setImeVisibility(false);
- }
- dismissSuggestions();
- }
- }
- }
-
- private void dismissSuggestions() {
- mQueryTextView.dismissDropDown();
- }
-
- private void onCloseClicked() {
- CharSequence text = mQueryTextView.getText();
- if (TextUtils.isEmpty(text)) {
- if (mIconifiedByDefault) {
- // If the app doesn't override the close behavior
- if (mOnCloseListener == null || !mOnCloseListener.onClose()) {
- // hide the keyboard and remove focus
- clearFocus();
- // collapse the search field
- updateViewsVisibility(true);
- }
- }
- } else {
- mQueryTextView.setText("");
- mQueryTextView.requestFocus();
- setImeVisibility(true);
- }
-
- }
-
- private void onSearchClicked() {
- updateViewsVisibility(false);
- mQueryTextView.requestFocus();
- setImeVisibility(true);
- if (mOnSearchClickListener != null) {
- mOnSearchClickListener.onClick(this);
- }
- }
-
- private void onVoiceClicked() {
- // guard against possible race conditions
- if (mSearchable == null) {
- return;
- }
- SearchableInfo searchable = mSearchable;
- try {
- if (searchable.getVoiceSearchLaunchWebSearch()) {
- Intent webSearchIntent = createVoiceWebSearchIntent(mVoiceWebSearchIntent,
- searchable);
- getContext().startActivity(webSearchIntent);
- } else if (searchable.getVoiceSearchLaunchRecognizer()) {
- Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent,
- searchable);
- getContext().startActivity(appSearchIntent);
- }
- } catch (ActivityNotFoundException e) {
- // Should not happen, since we check the availability of
- // voice search before showing the button. But just in case...
- Log.w(LOG_TAG, "Could not find voice search activity");
- }
- }
-
- void onTextFocusChanged() {
- updateViewsVisibility(isIconified());
- // Delayed update to make sure that the focus has settled down and window focus changes
- // don't affect it. A synchronous update was not working.
- postUpdateFocusedState();
- if (mQueryTextView.hasFocus()) {
- forceSuggestionQuery();
- }
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
-
- postUpdateFocusedState();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onActionViewCollapsed() {
- clearFocus();
- updateViewsVisibility(true);
- mQueryTextView.setImeOptions(mCollapsedImeOptions);
- mExpandedInActionView = false;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onActionViewExpanded() {
- if (mExpandedInActionView) return;
-
- mExpandedInActionView = true;
- mCollapsedImeOptions = mQueryTextView.getImeOptions();
- mQueryTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN);
- mQueryTextView.setText("");
- setIconified(false);
- }
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(SearchView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(SearchView.class.getName());
- }
-
- private void adjustDropDownSizeAndPosition() {
- if (mDropDownAnchor.getWidth() > 1) {
- Resources res = getContext().getResources();
- int anchorPadding = mSearchPlate.getPaddingLeft();
- Rect dropDownPadding = new Rect();
- int iconOffset = mIconifiedByDefault
- ? res.getDimensionPixelSize(R.dimen.abs__dropdownitem_icon_width)
- + res.getDimensionPixelSize(R.dimen.abs__dropdownitem_text_padding_left)
- : 0;
- mQueryTextView.getDropDownBackground().getPadding(dropDownPadding);
- mQueryTextView.setDropDownHorizontalOffset(-(dropDownPadding.left + iconOffset)
- + anchorPadding);
- mQueryTextView.setDropDownWidth(mDropDownAnchor.getWidth() + dropDownPadding.left
- + dropDownPadding.right + iconOffset - (anchorPadding));
- }
- }
-
- private boolean onItemClicked(int position, int actionKey, String actionMsg) {
- if (mOnSuggestionListener == null
- || !mOnSuggestionListener.onSuggestionClick(position)) {
- launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
- setImeVisibility(false);
- dismissSuggestions();
- return true;
- }
- return false;
- }
-
- private boolean onItemSelected(int position) {
- if (mOnSuggestionListener == null
- || !mOnSuggestionListener.onSuggestionSelect(position)) {
- rewriteQueryFromSuggestion(position);
- return true;
- }
- return false;
- }
-
- private final OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
-
- /**
- * Implements OnItemClickListener
- */
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (DBG) Log.d(LOG_TAG, "onItemClick() position " + position);
- onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
- }
- };
-
- private final OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() {
-
- /**
- * Implements OnItemSelectedListener
- */
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- if (DBG) Log.d(LOG_TAG, "onItemSelected() position " + position);
- SearchView.this.onItemSelected(position);
- }
-
- /**
- * Implements OnItemSelectedListener
- */
- public void onNothingSelected(AdapterView<?> parent) {
- if (DBG)
- Log.d(LOG_TAG, "onNothingSelected()");
- }
- };
-
- /**
- * Query rewriting.
- */
- private void rewriteQueryFromSuggestion(int position) {
- CharSequence oldQuery = mQueryTextView.getText();
- Cursor c = mSuggestionsAdapter.getCursor();
- if (c == null) {
- return;
- }
- if (c.moveToPosition(position)) {
- // Get the new query from the suggestion.
- CharSequence newQuery = mSuggestionsAdapter.convertToString(c);
- if (newQuery != null) {
- // The suggestion rewrites the query.
- // Update the text field, without getting new suggestions.
- setQuery(newQuery);
- } else {
- // The suggestion does not rewrite the query, restore the user's query.
- setQuery(oldQuery);
- }
- } else {
- // We got a bad position, restore the user's query.
- setQuery(oldQuery);
- }
- }
-
- /**
- * Launches an intent based on a suggestion.
- *
- * @param position The index of the suggestion to create the intent from.
- * @param actionKey The key code of the action key that was pressed,
- * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
- * @param actionMsg The message for the action key that was pressed,
- * or <code>null</code> if none.
- * @return true if a successful launch, false if could not (e.g. bad position).
- */
- private boolean launchSuggestion(int position, int actionKey, String actionMsg) {
- Cursor c = mSuggestionsAdapter.getCursor();
- if ((c != null) && c.moveToPosition(position)) {
-
- Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
-
- // launch the intent
- launchIntent(intent);
-
- return true;
- }
- return false;
- }
-
- /**
- * Launches an intent, including any special intent handling.
- */
- private void launchIntent(Intent intent) {
- if (intent == null) {
- return;
- }
- try {
- // If the intent was created from a suggestion, it will always have an explicit
- // component here.
- getContext().startActivity(intent);
- } catch (RuntimeException ex) {
- Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
- }
- }
-
- /**
- * Sets the text in the query box, without updating the suggestions.
- */
- private void setQuery(CharSequence query) {
- setText(mQueryTextView, query, true);
- // Move the cursor to the end
- mQueryTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length());
- }
-
- private void launchQuerySearch(int actionKey, String actionMsg, String query) {
- String action = Intent.ACTION_SEARCH;
- Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
- getContext().startActivity(intent);
- }
-
- /**
- * Constructs an intent from the given information and the search dialog state.
- *
- * @param action Intent action.
- * @param data Intent data, or <code>null</code>.
- * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
- * @param query Intent query, or <code>null</code>.
- * @param actionKey The key code of the action key that was pressed,
- * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
- * @param actionMsg The message for the action key that was pressed,
- * or <code>null</code> if none.
- * @return The intent.
- */
- private Intent createIntent(String action, Uri data, String extraData, String query,
- int actionKey, String actionMsg) {
- // Now build the Intent
- Intent intent = new Intent(action);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // We need CLEAR_TOP to avoid reusing an old task that has other activities
- // on top of the one we want. We don't want to do this in in-app search though,
- // as it can be destructive to the activity stack.
- if (data != null) {
- intent.setData(data);
- }
- intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
- if (query != null) {
- intent.putExtra(SearchManager.QUERY, query);
- }
- if (extraData != null) {
- intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
- }
- if (mAppSearchData != null) {
- intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
- }
- if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
- intent.putExtra(SearchManager.ACTION_KEY, actionKey);
- intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
- }
- intent.setComponent(mSearchable.getSearchActivity());
- return intent;
- }
-
- /**
- * Create and return an Intent that can launch the voice search activity for web search.
- */
- private Intent createVoiceWebSearchIntent(Intent baseIntent, SearchableInfo searchable) {
- Intent voiceIntent = new Intent(baseIntent);
- ComponentName searchActivity = searchable.getSearchActivity();
- voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null
- : searchActivity.flattenToShortString());
- return voiceIntent;
- }
-
- /**
- * Create and return an Intent that can launch the voice search activity, perform a specific
- * voice transcription, and forward the results to the searchable activity.
- *
- * @param baseIntent The voice app search intent to start from
- * @return A completely-configured intent ready to send to the voice search activity
- */
- private Intent createVoiceAppSearchIntent(Intent baseIntent, SearchableInfo searchable) {
- ComponentName searchActivity = searchable.getSearchActivity();
-
- // create the necessary intent to set up a search-and-forward operation
- // in the voice search system. We have to keep the bundle separate,
- // because it becomes immutable once it enters the PendingIntent
- Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
- queryIntent.setComponent(searchActivity);
- PendingIntent pending = PendingIntent.getActivity(getContext(), 0, queryIntent,
- PendingIntent.FLAG_ONE_SHOT);
-
- // Now set up the bundle that will be inserted into the pending intent
- // when it's time to do the search. We always build it here (even if empty)
- // because the voice search activity will always need to insert "QUERY" into
- // it anyway.
- Bundle queryExtras = new Bundle();
-
- // Now build the intent to launch the voice search. Add all necessary
- // extras to launch the voice recognizer, and then all the necessary extras
- // to forward the results to the searchable activity
- Intent voiceIntent = new Intent(baseIntent);
-
- // Add all of the configuration options supplied by the searchable's metadata
- String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
- String prompt = null;
- String language = null;
- int maxResults = 1;
-
- Resources resources = getResources();
- if (searchable.getVoiceLanguageModeId() != 0) {
- languageModel = resources.getString(searchable.getVoiceLanguageModeId());
- }
- if (searchable.getVoicePromptTextId() != 0) {
- prompt = resources.getString(searchable.getVoicePromptTextId());
- }
- if (searchable.getVoiceLanguageId() != 0) {
- language = resources.getString(searchable.getVoiceLanguageId());
- }
- if (searchable.getVoiceMaxResults() != 0) {
- maxResults = searchable.getVoiceMaxResults();
- }
- voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null
- : searchActivity.flattenToShortString());
-
- // Add the values that configure forwarding the results
- voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
-
- return voiceIntent;
- }
-
- /**
- * When a particular suggestion has been selected, perform the various lookups required
- * to use the suggestion. This includes checking the cursor for suggestion-specific data,
- * and/or falling back to the XML for defaults; It also creates REST style Uri data when
- * the suggestion includes a data id.
- *
- * @param c The suggestions cursor, moved to the row of the user's selection
- * @param actionKey The key code of the action key that was pressed,
- * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
- * @param actionMsg The message for the action key that was pressed,
- * or <code>null</code> if none.
- * @return An intent for the suggestion at the cursor's position.
- */
- private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) {
- try {
- // use specific action if supplied, or default action if supplied, or fixed default
- String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
-
- if (action == null) {
- action = mSearchable.getSuggestIntentAction();
- }
- if (action == null) {
- action = Intent.ACTION_SEARCH;
- }
-
- // use specific data if supplied, or default data if supplied
- String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
- if (data == null) {
- data = mSearchable.getSuggestIntentData();
- }
- // then, if an ID was provided, append it.
- if (data != null) {
- String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
- if (id != null) {
- data = data + "/" + Uri.encode(id);
- }
- }
- Uri dataUri = (data == null) ? null : Uri.parse(data);
-
- String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
- String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
-
- return createIntent(action, dataUri, extraData, query, actionKey, actionMsg);
- } catch (RuntimeException e ) {
- int rowNum;
- try { // be really paranoid now
- rowNum = c.getPosition();
- } catch (RuntimeException e2 ) {
- rowNum = -1;
- }
- Log.w(LOG_TAG, "Search suggestions cursor at row " + rowNum +
- " returned exception.", e);
- return null;
- }
- }
-
- private void forceSuggestionQuery() {
- try {
- Method before = AutoCompleteTextView.class.getDeclaredMethod("doBeforeTextChanged");
- Method after = AutoCompleteTextView.class.getDeclaredMethod("doAfterTextChanged");
- before.setAccessible(true);
- after.setAccessible(true);
- before.invoke(mQueryTextView);
- after.invoke(mQueryTextView);
- } catch (Exception e) {
- // Oh well...
- }
- }
-
- static boolean isLandscapeMode(Context context) {
- return context.getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
- }
-
- /**
- * Callback to watch the text field for empty/non-empty
- */
- private TextWatcher mTextWatcher = new TextWatcher() {
-
- public void beforeTextChanged(CharSequence s, int start, int before, int after) { }
-
- public void onTextChanged(CharSequence s, int start,
- int before, int after) {
- SearchView.this.onTextChanged(s);
- }
-
- public void afterTextChanged(Editable s) {
- }
- };
-
- /**
- * Local subclass for AutoCompleteTextView.
- * @hide
- */
- public static class SearchAutoComplete extends AutoCompleteTextView {
-
- private int mThreshold;
- private SearchView mSearchView;
-
- public SearchAutoComplete(Context context) {
- super(context);
- mThreshold = getThreshold();
- }
-
- public SearchAutoComplete(Context context, AttributeSet attrs) {
- super(context, attrs);
- mThreshold = getThreshold();
- }
-
- public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mThreshold = getThreshold();
- }
-
- void setSearchView(SearchView searchView) {
- mSearchView = searchView;
- }
-
- @Override
- public void setThreshold(int threshold) {
- super.setThreshold(threshold);
- mThreshold = threshold;
- }
-
- /**
- * Returns true if the text field is empty, or contains only whitespace.
- */
- private boolean isEmpty() {
- return TextUtils.getTrimmedLength(getText()) == 0;
- }
-
- /**
- * We override this method to avoid replacing the query box text when a
- * suggestion is clicked.
- */
- @Override
- protected void replaceText(CharSequence text) {
- }
-
- /**
- * We override this method to avoid an extra onItemClick being called on
- * the drop-down's OnItemClickListener by
- * {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)} when an item is
- * clicked with the trackball.
- */
- @Override
- public void performCompletion() {
- }
-
- /**
- * We override this method to be sure and show the soft keyboard if
- * appropriate when the TextView has focus.
- */
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
-
- if (hasWindowFocus && mSearchView.hasFocus() && getVisibility() == VISIBLE) {
- InputMethodManager inputManager = (InputMethodManager) getContext()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
- inputManager.showSoftInput(this, 0);
- // If in landscape mode, then make sure that
- // the ime is in front of the dropdown.
- if (isLandscapeMode(getContext())) {
- ensureImeVisible(this, true);
- }
- }
- }
-
- @Override
- protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(focused, direction, previouslyFocusedRect);
- mSearchView.onTextFocusChanged();
- }
-
- /**
- * We override this method so that we can allow a threshold of zero,
- * which ACTV does not.
- */
- @Override
- public boolean enoughToFilter() {
- return mThreshold <= 0 || super.enoughToFilter();
- }
-
- @Override
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- // special case for the back key, we do not even try to send it
- // to the drop down list but instead, consume it immediately
- if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
- if (state != null) {
- state.startTracking(event, this);
- }
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
- if (state != null) {
- state.handleUpEvent(event);
- }
- if (event.isTracking() && !event.isCanceled()) {
- mSearchView.clearFocus();
- mSearchView.setImeVisibility(false);
- return true;
- }
- }
- }
- return super.onKeyPreIme(keyCode, event);
- }
-
- }
-
- private static void ensureImeVisible(AutoCompleteTextView view, boolean visible) {
- try {
- Method method = AutoCompleteTextView.class.getMethod("ensureImeVisible", boolean.class);
- method.setAccessible(true);
- method.invoke(view, visible);
- } catch (Exception e) {
- //Oh well...
- }
- }
-
- private static void showSoftInputUnchecked(View view, InputMethodManager imm, int flags) {
- try {
- Method method = imm.getClass().getMethod("showSoftInputUnchecked", int.class, ResultReceiver.class);
- method.setAccessible(true);
- method.invoke(imm, flags, null);
- } catch (Exception e) {
- //Fallback to public API which hopefully does mostly the same thing
- imm.showSoftInput(view, flags);
- }
- }
-
- private static void setText(AutoCompleteTextView view, CharSequence text, boolean filter) {
- try {
- Method method = AutoCompleteTextView.class.getMethod("setText", CharSequence.class, boolean.class);
- method.setAccessible(true);
- method.invoke(view, text, filter);
- } catch (Exception e) {
- //Fallback to public API which hopefully does mostly the same thing
- view.setText(text);
- }
- }
-}
diff --git a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ShareActionProvider.java b/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ShareActionProvider.java
deleted file mode 100644
index 83e9f0ca9..000000000
--- a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/ShareActionProvider.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.actionbarsherlock.widget;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-import android.util.TypedValue;
-import android.view.View;
-
-import com.actionbarsherlock.R;
-import com.actionbarsherlock.view.ActionProvider;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-import com.actionbarsherlock.view.MenuItem.OnMenuItemClickListener;
-import com.actionbarsherlock.view.SubMenu;
-import com.actionbarsherlock.widget.ActivityChooserModel.OnChooseActivityListener;
-
-/**
- * This is a provider for a share action. It is responsible for creating views
- * that enable data sharing and also to show a sub menu with sharing activities
- * if the hosting item is placed on the overflow menu.
- * <p>
- * Here is how to use the action provider with custom backing file in a {@link MenuItem}:
- * </p>
- * <p>
- * <pre>
- * <code>
- * // In Activity#onCreateOptionsMenu
- * public boolean onCreateOptionsMenu(Menu menu) {
- * // Get the menu item.
- * MenuItem menuItem = menu.findItem(R.id.my_menu_item);
- * // Get the provider and hold onto it to set/change the share intent.
- * mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider();
- * // Set history different from the default before getting the action
- * // view since a call to {@link MenuItem#getActionView() MenuItem.getActionView()} calls
- * // {@link ActionProvider#onCreateActionView()} which uses the backing file name. Omit this
- * // line if using the default share history file is desired.
- * mShareActionProvider.setShareHistoryFileName("custom_share_history.xml");
- * . . .
- * }
- *
- * // Somewhere in the application.
- * public void doShare(Intent shareIntent) {
- * // When you want to share set the share intent.
- * mShareActionProvider.setShareIntent(shareIntent);
- * }
- * </pre>
- * </code>
- * </p>
- * <p>
- * <strong>Note:</strong> While the sample snippet demonstrates how to use this provider
- * in the context of a menu item, the use of the provider is not limited to menu items.
- * </p>
- *
- * @see ActionProvider
- */
-public class ShareActionProvider extends ActionProvider {
-
- /**
- * Listener for the event of selecting a share target.
- */
- public interface OnShareTargetSelectedListener {
-
- /**
- * Called when a share target has been selected. The client can
- * decide whether to handle the intent or rely on the default
- * behavior which is launching it.
- * <p>
- * <strong>Note:</strong> Modifying the intent is not permitted and
- * any changes to the latter will be ignored.
- * </p>
- *
- * @param source The source of the notification.
- * @param intent The intent for launching the chosen share target.
- * @return Whether the client has handled the intent.
- */
- public boolean onShareTargetSelected(ShareActionProvider source, Intent intent);
- }
-
- /**
- * The default for the maximal number of activities shown in the sub-menu.
- */
- private static final int DEFAULT_INITIAL_ACTIVITY_COUNT = 4;
-
- /**
- * The the maximum number activities shown in the sub-menu.
- */
- private int mMaxShownActivityCount = DEFAULT_INITIAL_ACTIVITY_COUNT;
-
- /**
- * Listener for handling menu item clicks.
- */
- private final ShareMenuItemOnMenuItemClickListener mOnMenuItemClickListener =
- new ShareMenuItemOnMenuItemClickListener();
-
- /**
- * The default name for storing share history.
- */
- public static final String DEFAULT_SHARE_HISTORY_FILE_NAME = "share_history.xml";
-
- /**
- * Context for accessing resources.
- */
- private final Context mContext;
-
- /**
- * The name of the file with share history data.
- */
- private String mShareHistoryFileName = DEFAULT_SHARE_HISTORY_FILE_NAME;
-
- private OnShareTargetSelectedListener mOnShareTargetSelectedListener;
-
- private OnChooseActivityListener mOnChooseActivityListener;
-
- /**
- * Creates a new instance.
- *
- * @param context Context for accessing resources.
- */
- public ShareActionProvider(Context context) {
- super(context);
- mContext = context;
- }
-
- /**
- * Sets a listener to be notified when a share target has been selected.
- * The listener can optionally decide to handle the selection and
- * not rely on the default behavior which is to launch the activity.
- * <p>
- * <strong>Note:</strong> If you choose the backing share history file
- * you will still be notified in this callback.
- * </p>
- * @param listener The listener.
- */
- public void setOnShareTargetSelectedListener(OnShareTargetSelectedListener listener) {
- mOnShareTargetSelectedListener = listener;
- setActivityChooserPolicyIfNeeded();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public View onCreateActionView() {
- // Create the view and set its data model.
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
- ActivityChooserView activityChooserView = new ActivityChooserView(mContext);
- activityChooserView.setActivityChooserModel(dataModel);
-
- // Lookup and set the expand action icon.
- TypedValue outTypedValue = new TypedValue();
- mContext.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true);
- Drawable drawable = mContext.getResources().getDrawable(outTypedValue.resourceId);
- activityChooserView.setExpandActivityOverflowButtonDrawable(drawable);
- activityChooserView.setProvider(this);
-
- // Set content description.
- activityChooserView.setDefaultActionButtonContentDescription(
- R.string.abs__shareactionprovider_share_with_application);
- activityChooserView.setExpandActivityOverflowButtonContentDescription(
- R.string.abs__shareactionprovider_share_with);
-
- return activityChooserView;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean hasSubMenu() {
- return true;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onPrepareSubMenu(SubMenu subMenu) {
- // Clear since the order of items may change.
- subMenu.clear();
-
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
- PackageManager packageManager = mContext.getPackageManager();
-
- final int expandedActivityCount = dataModel.getActivityCount();
- final int collapsedActivityCount = Math.min(expandedActivityCount, mMaxShownActivityCount);
-
- // Populate the sub-menu with a sub set of the activities.
- for (int i = 0; i < collapsedActivityCount; i++) {
- ResolveInfo activity = dataModel.getActivity(i);
- subMenu.add(0, i, i, activity.loadLabel(packageManager))
- .setIcon(activity.loadIcon(packageManager))
- .setOnMenuItemClickListener(mOnMenuItemClickListener);
- }
-
- if (collapsedActivityCount < expandedActivityCount) {
- // Add a sub-menu for showing all activities as a list item.
- SubMenu expandedSubMenu = subMenu.addSubMenu(Menu.NONE, collapsedActivityCount,
- collapsedActivityCount,
- mContext.getString(R.string.abs__activity_chooser_view_see_all));
- for (int i = 0; i < expandedActivityCount; i++) {
- ResolveInfo activity = dataModel.getActivity(i);
- expandedSubMenu.add(0, i, i, activity.loadLabel(packageManager))
- .setIcon(activity.loadIcon(packageManager))
- .setOnMenuItemClickListener(mOnMenuItemClickListener);
- }
- }
- }
-
- /**
- * Sets the file name of a file for persisting the share history which
- * history will be used for ordering share targets. This file will be used
- * for all view created by {@link #onCreateActionView()}. Defaults to
- * {@link #DEFAULT_SHARE_HISTORY_FILE_NAME}. Set to <code>null</code>
- * if share history should not be persisted between sessions.
- * <p>
- * <strong>Note:</strong> The history file name can be set any time, however
- * only the action views created by {@link #onCreateActionView()} after setting
- * the file name will be backed by the provided file.
- * <p>
- *
- * @param shareHistoryFile The share history file name.
- */
- public void setShareHistoryFileName(String shareHistoryFile) {
- mShareHistoryFileName = shareHistoryFile;
- setActivityChooserPolicyIfNeeded();
- }
-
- /**
- * Sets an intent with information about the share action. Here is a
- * sample for constructing a share intent:
- * <p>
- * <pre>
- * <code>
- * Intent shareIntent = new Intent(Intent.ACTION_SEND);
- * shareIntent.setType("image/*");
- * Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg"));
- * shareIntent.putExtra(Intent.EXTRA_STREAM, uri.toString());
- * </pre>
- * </code>
- * </p>
- *
- * @param shareIntent The share intent.
- *
- * @see Intent#ACTION_SEND
- * @see Intent#ACTION_SEND_MULTIPLE
- */
- public void setShareIntent(Intent shareIntent) {
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
- mShareHistoryFileName);
- dataModel.setIntent(shareIntent);
- }
-
- /**
- * Reusable listener for handling share item clicks.
- */
- private class ShareMenuItemOnMenuItemClickListener implements OnMenuItemClickListener {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
- mShareHistoryFileName);
- final int itemId = item.getItemId();
- Intent launchIntent = dataModel.chooseActivity(itemId);
- if (launchIntent != null) {
- mContext.startActivity(launchIntent);
- }
- return true;
- }
- }
-
- /**
- * Set the activity chooser policy of the model backed by the current
- * share history file if needed which is if there is a registered callback.
- */
- private void setActivityChooserPolicyIfNeeded() {
- if (mOnShareTargetSelectedListener == null) {
- return;
- }
- if (mOnChooseActivityListener == null) {
- mOnChooseActivityListener = new ShareAcitivityChooserModelPolicy();
- }
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
- dataModel.setOnChooseActivityListener(mOnChooseActivityListener);
- }
-
- /**
- * Policy that delegates to the {@link OnShareTargetSelectedListener}, if such.
- */
- private class ShareAcitivityChooserModelPolicy implements OnChooseActivityListener {
- @Override
- public boolean onChooseActivity(ActivityChooserModel host, Intent intent) {
- if (mOnShareTargetSelectedListener != null) {
- return mOnShareTargetSelectedListener.onShareTargetSelected(
- ShareActionProvider.this, intent);
- }
- return false;
- }
- }
-}
diff --git a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SuggestionsAdapter.java b/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SuggestionsAdapter.java
deleted file mode 100644
index 82d4f0c42..000000000
--- a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SuggestionsAdapter.java
+++ /dev/null
@@ -1,758 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.actionbarsherlock.widget;
-
-import android.app.SearchManager;
-import android.app.SearchableInfo;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.widget.ResourceCursorAdapter;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.style.TextAppearanceSpan;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-import com.actionbarsherlock.R;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import java.util.WeakHashMap;
-
-/**
- * Provides the contents for the suggestion drop-down list.
- *
- * @hide
- */
-class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListener {
-
- private static final boolean DBG = false;
- private static final String LOG_TAG = "SuggestionsAdapter";
- private static final int QUERY_LIMIT = 50;
-
- static final int REFINE_NONE = 0;
- static final int REFINE_BY_ENTRY = 1;
- static final int REFINE_ALL = 2;
-
- private SearchManager mSearchManager;
- private SearchableInfo mSearchable;
- private SearchView mSearchView;
- private Context mProviderContext;
- private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache;
- private boolean mClosed = false;
- private int mQueryRefinement = REFINE_BY_ENTRY;
-
- // URL color
- private ColorStateList mUrlColor;
-
- static final int INVALID_INDEX = -1;
-
- // Cached column indexes, updated when the cursor changes.
- private int mText1Col = INVALID_INDEX;
- private int mText2Col = INVALID_INDEX;
- private int mText2UrlCol = INVALID_INDEX;
- private int mIconName1Col = INVALID_INDEX;
- private int mIconName2Col = INVALID_INDEX;
- private int mFlagsCol = INVALID_INDEX;
-
- // private final Runnable mStartSpinnerRunnable;
- // private final Runnable mStopSpinnerRunnable;
-
- /**
- * The amount of time we delay in the filter when the user presses the delete key.
- */
- //private static final long DELETE_KEY_POST_DELAY = 500L;
-
- public SuggestionsAdapter(Context context, SearchView searchView,
- SearchableInfo searchable, WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache) {
- super(context,
- R.layout.abs__search_dropdown_item_icons_2line,
- null, // no initial cursor
- true); // auto-requery
- mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
- mSearchable = searchable;
- mProviderContext = mContext;
- mSearchView = searchView;
-
- mOutsideDrawablesCache = outsideDrawablesCache;
-
- // mStartSpinnerRunnable = new Runnable() {
- // public void run() {
- // // mSearchView.setWorking(true); // TODO:
- // }
- // };
- //
- // mStopSpinnerRunnable = new Runnable() {
- // public void run() {
- // // mSearchView.setWorking(false); // TODO:
- // }
- // };
-
- // delay 500ms when deleting
-// TODO getFilter().setDelayer(new Filter.Delayer() {
-//
-// private int mPreviousLength = 0;
-//
-// public long getPostingDelay(CharSequence constraint) {
-// if (constraint == null) return 0;
-//
-// long delay = constraint.length() < mPreviousLength ? DELETE_KEY_POST_DELAY : 0;
-// mPreviousLength = constraint.length();
-// return delay;
-// }
-// });
- }
-
- /**
- * Enables query refinement for all suggestions. This means that an additional icon
- * will be shown for each entry. When clicked, the suggested text on that line will be
- * copied to the query text field.
- * <p>
- *
- * @param refineWhat which queries to refine. Possible values are {@link #REFINE_NONE},
- * {@link #REFINE_BY_ENTRY}, and {@link #REFINE_ALL}.
- */
- public void setQueryRefinement(int refineWhat) {
- mQueryRefinement = refineWhat;
- }
-
- /**
- * Returns the current query refinement preference.
- * @return value of query refinement preference
- */
- public int getQueryRefinement() {
- return mQueryRefinement;
- }
-
- /**
- * Overridden to always return <code>false</code>, since we cannot be sure that
- * suggestion sources return stable IDs.
- */
- @Override
- public boolean hasStableIds() {
- return false;
- }
-
- /**
- * Use the search suggestions provider to obtain a live cursor. This will be called
- * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
- * The results will be processed in the UI thread and changeCursor() will be called.
- */
- @Override
- public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
- if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")");
- String query = (constraint == null) ? "" : constraint.toString();
- /**
- * for in app search we show the progress spinner until the cursor is returned with
- * the results.
- */
- Cursor cursor = null;
- if (mSearchView.getVisibility() != View.VISIBLE
- || mSearchView.getWindowVisibility() != View.VISIBLE) {
- return null;
- }
- //mSearchView.getWindow().getDecorView().post(mStartSpinnerRunnable); // TODO:
- try {
- cursor = getSuggestions(query, QUERY_LIMIT);
- // trigger fill window so the spinner stays up until the results are copied over and
- // closer to being ready
- if (cursor != null) {
- cursor.getCount();
- return cursor;
- }
- } catch (RuntimeException e) {
- Log.w(LOG_TAG, "Search suggestions query threw an exception.", e);
- }
- // If cursor is null or an exception was thrown, stop the spinner and return null.
- // changeCursor doesn't get called if cursor is null
- // mSearchView.getWindow().getDecorView().post(mStopSpinnerRunnable); // TODO:
- return null;
- }
-
- public Cursor getSuggestions(String query, int limit) {
- if (mSearchable == null) {
- return null;
- }
-
- String authority = mSearchable.getSuggestAuthority();
- if (authority == null) {
- return null;
- }
-
- Uri.Builder uriBuilder = new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority(authority)
- .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
- .fragment(""); // TODO: Remove, workaround for a bug in Uri.writeToParcel()
-
- // if content path provided, insert it now
- final String contentPath = mSearchable.getSuggestPath();
- if (contentPath != null) {
- uriBuilder.appendEncodedPath(contentPath);
- }
-
- // append standard suggestion query path
- uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY);
-
- // get the query selection, may be null
- String selection = mSearchable.getSuggestSelection();
- // inject query, either as selection args or inline
- String[] selArgs = null;
- if (selection != null) { // use selection if provided
- selArgs = new String[] { query };
- } else { // no selection, use REST pattern
- uriBuilder.appendPath(query);
- }
-
- if (limit > 0) {
- uriBuilder.appendQueryParameter("limit", String.valueOf(limit));
- }
-
- Uri uri = uriBuilder.build();
-
- // finally, make the query
- return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
- }
-
- public void close() {
- if (DBG) Log.d(LOG_TAG, "close()");
- changeCursor(null);
- mClosed = true;
- }
-
- @Override
- public void notifyDataSetChanged() {
- if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged");
- super.notifyDataSetChanged();
-
- // mSearchView.onDataSetChanged(); // TODO:
-
- updateSpinnerState(getCursor());
- }
-
- @Override
- public void notifyDataSetInvalidated() {
- if (DBG) Log.d(LOG_TAG, "notifyDataSetInvalidated");
- super.notifyDataSetInvalidated();
-
- updateSpinnerState(getCursor());
- }
-
- private void updateSpinnerState(Cursor cursor) {
- Bundle extras = cursor != null ? cursor.getExtras() : null;
- if (DBG) {
- Log.d(LOG_TAG, "updateSpinnerState - extra = "
- + (extras != null
- ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)
- : null));
- }
- // Check if the Cursor indicates that the query is not complete and show the spinner
- if (extras != null
- && extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)) {
- // mSearchView.getWindow().getDecorView().post(mStartSpinnerRunnable); // TODO:
- return;
- }
- // If cursor is null or is done, stop the spinner
- // mSearchView.getWindow().getDecorView().post(mStopSpinnerRunnable); // TODO:
- }
-
- /**
- * Cache columns.
- */
- @Override
- public void changeCursor(Cursor c) {
- if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")");
-
- if (mClosed) {
- Log.w(LOG_TAG, "Tried to change cursor after adapter was closed.");
- if (c != null) c.close();
- return;
- }
-
- try {
- super.changeCursor(c);
-
- if (c != null) {
- mText1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
- mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
- mText2UrlCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
- mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
- mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
- mFlagsCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FLAGS);
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "error changing cursor and caching columns", e);
- }
- }
-
- /**
- * Tags the view with cached child view look-ups.
- */
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- View v = super.newView(context, cursor, parent);
- v.setTag(new ChildViewCache(v));
- return v;
- }
-
- /**
- * Cache of the child views of drop-drown list items, to avoid looking up the children
- * each time the contents of a list item are changed.
- */
- private final static class ChildViewCache {
- public final TextView mText1;
- public final TextView mText2;
- public final ImageView mIcon1;
- public final ImageView mIcon2;
- public final ImageView mIconRefine;
-
- public ChildViewCache(View v) {
- mText1 = (TextView) v.findViewById(android.R.id.text1);
- mText2 = (TextView) v.findViewById(android.R.id.text2);
- mIcon1 = (ImageView) v.findViewById(android.R.id.icon1);
- mIcon2 = (ImageView) v.findViewById(android.R.id.icon2);
- mIconRefine = (ImageView) v.findViewById(R.id.edit_query);
- }
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- ChildViewCache views = (ChildViewCache) view.getTag();
-
- int flags = 0;
- if (mFlagsCol != INVALID_INDEX) {
- flags = cursor.getInt(mFlagsCol);
- }
- if (views.mText1 != null) {
- String text1 = getStringOrNull(cursor, mText1Col);
- setViewText(views.mText1, text1);
- }
- if (views.mText2 != null) {
- // First check TEXT_2_URL
- CharSequence text2 = getStringOrNull(cursor, mText2UrlCol);
- if (text2 != null) {
- text2 = formatUrl(text2);
- } else {
- text2 = getStringOrNull(cursor, mText2Col);
- }
-
- // If no second line of text is indicated, allow the first line of text
- // to be up to two lines if it wants to be.
- if (TextUtils.isEmpty(text2)) {
- if (views.mText1 != null) {
- views.mText1.setSingleLine(false);
- views.mText1.setMaxLines(2);
- }
- } else {
- if (views.mText1 != null) {
- views.mText1.setSingleLine(true);
- views.mText1.setMaxLines(1);
- }
- }
- setViewText(views.mText2, text2);
- }
-
- if (views.mIcon1 != null) {
- setViewDrawable(views.mIcon1, getIcon1(cursor), View.INVISIBLE);
- }
- if (views.mIcon2 != null) {
- setViewDrawable(views.mIcon2, getIcon2(cursor), View.GONE);
- }
- if (mQueryRefinement == REFINE_ALL
- || (mQueryRefinement == REFINE_BY_ENTRY
- && (flags & SearchManager.FLAG_QUERY_REFINEMENT) != 0)) {
- views.mIconRefine.setVisibility(View.VISIBLE);
- views.mIconRefine.setTag(views.mText1.getText());
- views.mIconRefine.setOnClickListener(this);
- } else {
- views.mIconRefine.setVisibility(View.GONE);
- }
- }
-
- public void onClick(View v) {
- Object tag = v.getTag();
- if (tag instanceof CharSequence) {
- mSearchView.onQueryRefine((CharSequence) tag);
- }
- }
-
- private CharSequence formatUrl(CharSequence url) {
- if (mUrlColor == null) {
- // Lazily get the URL color from the current theme.
- TypedValue colorValue = new TypedValue();
- mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true);
- mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId);
- }
-
- SpannableString text = new SpannableString(url);
- text.setSpan(new TextAppearanceSpan(null, 0, 0, mUrlColor, null),
- 0, url.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- return text;
- }
-
- private void setViewText(TextView v, CharSequence text) {
- // Set the text even if it's null, since we need to clear any previous text.
- v.setText(text);
-
- if (TextUtils.isEmpty(text)) {
- v.setVisibility(View.GONE);
- } else {
- v.setVisibility(View.VISIBLE);
- }
- }
-
- private Drawable getIcon1(Cursor cursor) {
- if (mIconName1Col == INVALID_INDEX) {
- return null;
- }
- String value = cursor.getString(mIconName1Col);
- Drawable drawable = getDrawableFromResourceValue(value);
- if (drawable != null) {
- return drawable;
- }
- return getDefaultIcon1(cursor);
- }
-
- private Drawable getIcon2(Cursor cursor) {
- if (mIconName2Col == INVALID_INDEX) {
- return null;
- }
- String value = cursor.getString(mIconName2Col);
- return getDrawableFromResourceValue(value);
- }
-
- /**
- * Sets the drawable in an image view, makes sure the view is only visible if there
- * is a drawable.
- */
- private void setViewDrawable(ImageView v, Drawable drawable, int nullVisibility) {
- // Set the icon even if the drawable is null, since we need to clear any
- // previous icon.
- v.setImageDrawable(drawable);
-
- if (drawable == null) {
- v.setVisibility(nullVisibility);
- } else {
- v.setVisibility(View.VISIBLE);
-
- // This is a hack to get any animated drawables (like a 'working' spinner)
- // to animate. You have to setVisible true on an AnimationDrawable to get
- // it to start animating, but it must first have been false or else the
- // call to setVisible will be ineffective. We need to clear up the story
- // about animated drawables in the future, see http://b/1878430.
- drawable.setVisible(false, false);
- drawable.setVisible(true, false);
- }
- }
-
- /**
- * Gets the text to show in the query field when a suggestion is selected.
- *
- * @param cursor The Cursor to read the suggestion data from. The Cursor should already
- * be moved to the suggestion that is to be read from.
- * @return The text to show, or <code>null</code> if the query should not be
- * changed when selecting this suggestion.
- */
- @Override
- public CharSequence convertToString(Cursor cursor) {
- if (cursor == null) {
- return null;
- }
-
- String query = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_QUERY);
- if (query != null) {
- return query;
- }
-
- return null;
- }
-
- /**
- * This method is overridden purely to provide a bit of protection against
- * flaky content providers.
- *
- * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
- */
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- try {
- return super.getView(position, convertView, parent);
- } catch (RuntimeException e) {
- Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
- // Put exception string in item title
- View v = newView(mContext, mCursor, parent);
- if (v != null) {
- ChildViewCache views = (ChildViewCache) v.getTag();
- TextView tv = views.mText1;
- tv.setText(e.toString());
- }
- return v;
- }
- }
-
- /**
- * Gets a drawable given a value provided by a suggestion provider.
- *
- * This value could be just the string value of a resource id
- * (e.g., "2130837524"), in which case we will try to retrieve a drawable from
- * the provider's resources. If the value is not an integer, it is
- * treated as a Uri and opened with
- * {@link ContentResolver#openOutputStream(android.net.Uri, String)}.
- *
- * All resources and URIs are read using the suggestion provider's context.
- *
- * If the string is not formatted as expected, or no drawable can be found for
- * the provided value, this method returns null.
- *
- * @param drawableId a string like "2130837524",
- * "android.resource://com.android.alarmclock/2130837524",
- * or "content://contacts/photos/253".
- * @return a Drawable, or null if none found
- */
- private Drawable getDrawableFromResourceValue(String drawableId) {
- if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) {
- return null;
- }
- try {
- // First, see if it's just an integer
- int resourceId = Integer.parseInt(drawableId);
- // It's an int, look for it in the cache
- String drawableUri = ContentResolver.SCHEME_ANDROID_RESOURCE
- + "://" + mProviderContext.getPackageName() + "/" + resourceId;
- // Must use URI as cache key, since ints are app-specific
- Drawable drawable = checkIconCache(drawableUri);
- if (drawable != null) {
- return drawable;
- }
- // Not cached, find it by resource ID
- drawable = mProviderContext.getResources().getDrawable(resourceId);
- // Stick it in the cache, using the URI as key
- storeInIconCache(drawableUri, drawable);
- return drawable;
- } catch (NumberFormatException nfe) {
- // It's not an integer, use it as a URI
- Drawable drawable = checkIconCache(drawableId);
- if (drawable != null) {
- return drawable;
- }
- Uri uri = Uri.parse(drawableId);
- drawable = getDrawable(uri);
- storeInIconCache(drawableId, drawable);
- return drawable;
- } catch (Resources.NotFoundException nfe) {
- // It was an integer, but it couldn't be found, bail out
- Log.w(LOG_TAG, "Icon resource not found: " + drawableId);
- return null;
- }
- }
-
- /**
- * Gets a drawable by URI, without using the cache.
- *
- * @return A drawable, or {@code null} if the drawable could not be loaded.
- */
- private Drawable getDrawable(Uri uri) {
- try {
- String scheme = uri.getScheme();
- if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
- // Load drawables through Resources, to get the source density information
- try {
- return getTheDrawable(uri);
- } catch (Resources.NotFoundException ex) {
- throw new FileNotFoundException("Resource does not exist: " + uri);
- }
- } else {
- // Let the ContentResolver handle content and file URIs.
- InputStream stream = mProviderContext.getContentResolver().openInputStream(uri);
- if (stream == null) {
- throw new FileNotFoundException("Failed to open " + uri);
- }
- try {
- return Drawable.createFromStream(stream, null);
- } finally {
- try {
- stream.close();
- } catch (IOException ex) {
- Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex);
- }
- }
- }
- } catch (FileNotFoundException fnfe) {
- Log.w(LOG_TAG, "Icon not found: " + uri + ", " + fnfe.getMessage());
- return null;
- }
- }
-
- public Drawable getTheDrawable(Uri uri) throws FileNotFoundException {
- String authority = uri.getAuthority();
- Resources r;
- if (TextUtils.isEmpty(authority)) {
- throw new FileNotFoundException("No authority: " + uri);
- } else {
- try {
- r = mContext.getPackageManager().getResourcesForApplication(authority);
- } catch (NameNotFoundException ex) {
- throw new FileNotFoundException("No package found for authority: " + uri);
- }
- }
- List<String> path = uri.getPathSegments();
- if (path == null) {
- throw new FileNotFoundException("No path: " + uri);
- }
- int len = path.size();
- int id;
- if (len == 1) {
- try {
- id = Integer.parseInt(path.get(0));
- } catch (NumberFormatException e) {
- throw new FileNotFoundException("Single path segment is not a resource ID: " + uri);
- }
- } else if (len == 2) {
- id = r.getIdentifier(path.get(1), path.get(0), authority);
- } else {
- throw new FileNotFoundException("More than two path segments: " + uri);
- }
- if (id == 0) {
- throw new FileNotFoundException("No resource found for: " + uri);
- }
- return r.getDrawable(id);
- }
-
- private Drawable checkIconCache(String resourceUri) {
- Drawable.ConstantState cached = mOutsideDrawablesCache.get(resourceUri);
- if (cached == null) {
- return null;
- }
- if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + resourceUri);
- return cached.newDrawable();
- }
-
- private void storeInIconCache(String resourceUri, Drawable drawable) {
- if (drawable != null) {
- mOutsideDrawablesCache.put(resourceUri, drawable.getConstantState());
- }
- }
-
- /**
- * Gets the left-hand side icon that will be used for the current suggestion
- * if the suggestion contains an icon column but no icon or a broken icon.
- *
- * @param cursor A cursor positioned at the current suggestion.
- * @return A non-null drawable.
- */
- private Drawable getDefaultIcon1(Cursor cursor) {
- // Fall back to a default icon
- return mContext.getPackageManager().getDefaultActivityIcon();
- }
-
- /**
- * Gets the activity or application icon for an activity.
- * Uses the local icon cache for fast repeated lookups.
- *
- * @param component Name of an activity.
- * @return A drawable, or {@code null} if neither the activity nor the application
- * has an icon set.
- */
- private Drawable getActivityIconWithCache(ComponentName component) {
- // First check the icon cache
- String componentIconKey = component.flattenToShortString();
- // Using containsKey() since we also store null values.
- if (mOutsideDrawablesCache.containsKey(componentIconKey)) {
- Drawable.ConstantState cached = mOutsideDrawablesCache.get(componentIconKey);
- return cached == null ? null : cached.newDrawable(mProviderContext.getResources());
- }
- // Then try the activity or application icon
- Drawable drawable = getActivityIcon(component);
- // Stick it in the cache so we don't do this lookup again.
- Drawable.ConstantState toCache = drawable == null ? null : drawable.getConstantState();
- mOutsideDrawablesCache.put(componentIconKey, toCache);
- return drawable;
- }
-
- /**
- * Gets the activity or application icon for an activity.
- *
- * @param component Name of an activity.
- * @return A drawable, or {@code null} if neither the acitivy or the application
- * have an icon set.
- */
- private Drawable getActivityIcon(ComponentName component) {
- PackageManager pm = mContext.getPackageManager();
- final ActivityInfo activityInfo;
- try {
- activityInfo = pm.getActivityInfo(component, PackageManager.GET_META_DATA);
- } catch (NameNotFoundException ex) {
- Log.w(LOG_TAG, ex.toString());
- return null;
- }
- int iconId = activityInfo.getIconResource();
- if (iconId == 0) return null;
- String pkg = component.getPackageName();
- Drawable drawable = pm.getDrawable(pkg, iconId, activityInfo.applicationInfo);
- if (drawable == null) {
- Log.w(LOG_TAG, "Invalid icon resource " + iconId + " for "
- + component.flattenToShortString());
- return null;
- }
- return drawable;
- }
-
- /**
- * Gets the value of a string column by name.
- *
- * @param cursor Cursor to read the value from.
- * @param columnName The name of the column to read.
- * @return The value of the given column, or <code>null</null>
- * if the cursor does not contain the given column.
- */
- public static String getColumnString(Cursor cursor, String columnName) {
- int col = cursor.getColumnIndex(columnName);
- return getStringOrNull(cursor, col);
- }
-
- private static String getStringOrNull(Cursor cursor, int col) {
- if (col == INVALID_INDEX) {
- return null;
- }
- try {
- return cursor.getString(col);
- } catch (Exception e) {
- Log.e(LOG_TAG,
- "unexpected error retrieving valid column from cursor, "
- + "did the remote process die?", e);
- return null;
- }
- }
-}