ActivityChooserModel.java revision e290ed32f85ff6307a53922a78684b31d30b8dc5
1e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes/* 2e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Copyright (C) 2013 The Android Open Source Project 3e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 4e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 5e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * you may not use this file except in compliance with the License. 6e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * You may obtain a copy of the License at 7e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 8e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * http://www.apache.org/licenses/LICENSE-2.0 9e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 10e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Unless required by applicable law or agreed to in writing, software 11e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * distributed under the License is distributed on an "AS IS" BASIS, 12e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * See the License for the specific language governing permissions and 14e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * limitations under the License. 15e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 16e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 17e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banespackage android.support.v7.internal.widget; 18e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 19e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport android.content.ComponentName; 20e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport android.content.Context; 21e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport android.content.Intent; 22e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport android.content.pm.ResolveInfo; 23e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport android.database.DataSetObservable; 24e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport android.os.AsyncTask; 25e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport android.os.Build; 26e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport android.text.TextUtils; 27e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport android.util.Log; 28e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport android.util.Xml; 29e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 30e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport org.xmlpull.v1.XmlPullParser; 31e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport org.xmlpull.v1.XmlPullParserException; 32e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport org.xmlpull.v1.XmlSerializer; 33e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 34e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport java.io.FileInputStream; 35e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport java.io.FileNotFoundException; 36e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport java.io.FileOutputStream; 37e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport java.io.IOException; 38e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport java.math.BigDecimal; 39e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport java.util.ArrayList; 40e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport java.util.Collections; 41e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport java.util.HashMap; 42e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport java.util.List; 43e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banesimport java.util.Map; 44e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 45e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes/** 46e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 47e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * This class represents a data model for choosing a component for handing a 48e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * given {@link Intent}. The model is responsible for querying the system for 49e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * activities that can handle the given intent and order found activities 50e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * based on historical data of previous choices. The historical data is stored 51e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * in an application private file. If a client does not want to have persistent 52e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * choice history the file can be omitted, thus the activities will be ordered 53e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * based on historical usage for the current session. 54e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 55e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * </p> 56e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * For each backing history file there is a singleton instance of this class. Thus, 57e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * several clients that specify the same history file will share the same model. Note 58e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * that if multiple clients are sharing the same model they should implement semantically 59e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * equivalent functionality since setting the model intent will change the found 60e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * activities and they may be inconsistent with the functionality of some of the clients. 61e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * For example, choosing a share activity can be implemented by a single backing 62e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * model and two different views for performing the selection. If however, one of the 63e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * views is used for sharing but the other for importing, for example, then each 64e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * view should be backed by a separate model. 65e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * </p> 66e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 67e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The way clients interact with this class is as follows: 68e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * </p> 69e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 70e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <pre> 71e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <code> 72e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * // Get a model and set it to a couple of clients with semantically similar function. 73e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * ActivityChooserModel dataModel = 74e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * ActivityChooserModel.get(context, "task_specific_history_file_name.xml"); 75e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 76e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * ActivityChooserModelClient modelClient1 = getActivityChooserModelClient1(); 77e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * modelClient1.setActivityChooserModel(dataModel); 78e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 79e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * ActivityChooserModelClient modelClient2 = getActivityChooserModelClient2(); 80e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * modelClient2.setActivityChooserModel(dataModel); 81e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 82e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * // Set an intent to choose a an activity for. 83e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * dataModel.setIntent(intent); 84e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <pre> 85e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <code> 86e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * </p> 87e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 88e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <strong>Note:</strong> This class is thread safe. 89e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * </p> 90e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 91e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @hide 92e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 93e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banespublic class ActivityChooserModel extends DataSetObservable { 94e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 95e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 96e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Client that utilizes an {@link ActivityChooserModel}. 97e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 98e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public interface ActivityChooserModelClient { 99e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 100e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 101e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Sets the {@link ActivityChooserModel}. 102e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 103e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param dataModel The model. 104e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 105e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public void setActivityChooserModel(ActivityChooserModel dataModel); 106e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 107e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 108e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 109e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Defines a sorter that is responsible for sorting the activities 110e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * based on the provided historical choices and an intent. 111e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 112e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public interface ActivitySorter { 113e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 114e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 115e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Sorts the <code>activities</code> in descending order of relevance 116e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * based on previous history and an intent. 117e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 118e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param intent The {@link Intent}. 119e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param activities Activities to be sorted. 120e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param historicalRecords Historical records. 121e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 122e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes // This cannot be done by a simple comparator since an Activity weight 123e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes // is computed from history. Note that Activity implements Comparable. 124e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public void sort(Intent intent, List<ActivityResolveInfo> activities, 125e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes List<HistoricalRecord> historicalRecords); 126e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 127e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 128e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 129e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Listener for choosing an activity. 130e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 131e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public interface OnChooseActivityListener { 132e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 133e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 134e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Called when an activity has been chosen. The client can decide whether 135e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * an activity can be chosen and if so the caller of 136e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * {@link ActivityChooserModel#chooseActivity(int)} will receive and {@link Intent} 137e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * for launching it. 138e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 139e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <strong>Note:</strong> Modifying the intent is not permitted and 140e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * any changes to the latter will be ignored. 141e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * </p> 142e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 143e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param host The listener's host model. 144e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param intent The intent for launching the chosen activity. 145e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return Whether the intent is handled and should not be delivered to clients. 146e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 147e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @see ActivityChooserModel#chooseActivity(int) 148e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 149e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public boolean onChooseActivity(ActivityChooserModel host, Intent intent); 150e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 151e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 152e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 153e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Flag for selecting debug mode. 154e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 155e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final boolean DEBUG = false; 156e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 157e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 158e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Tag used for logging. 159e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 160e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final String LOG_TAG = ActivityChooserModel.class.getSimpleName(); 161e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 162e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 163e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The root tag in the history file. 164e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 165e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final String TAG_HISTORICAL_RECORDS = "historical-records"; 166e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 167e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 168e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The tag for a record in the history file. 169e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 170e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final String TAG_HISTORICAL_RECORD = "historical-record"; 171e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 172e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 173e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Attribute for the activity. 174e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 175e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final String ATTRIBUTE_ACTIVITY = "activity"; 176e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 177e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 178e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Attribute for the choice time. 179e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 180e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final String ATTRIBUTE_TIME = "time"; 181e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 182e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 183e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Attribute for the choice weight. 184e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 185e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final String ATTRIBUTE_WEIGHT = "weight"; 186e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 187e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 188e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The default name of the choice history file. 189e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 190e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public static final String DEFAULT_HISTORY_FILE_NAME = 191e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes "activity_choser_model_history.xml"; 192e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 193e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 194e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The default maximal length of the choice history. 195e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 196e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public static final int DEFAULT_HISTORY_MAX_LENGTH = 50; 197e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 198e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 199e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The amount with which to inflate a chosen activity when set as default. 200e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 201e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final int DEFAULT_ACTIVITY_INFLATION = 5; 202e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 203e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 204e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Default weight for a choice record. 205e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 206e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final float DEFAULT_HISTORICAL_RECORD_WEIGHT = 1.0f; 207e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 208e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 209e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The extension of the history file. 210e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 211e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final String HISTORY_FILE_EXTENSION = ".xml"; 212e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 213e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 214e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * An invalid item index. 215e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 216e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final int INVALID_INDEX = -1; 217e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 218e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 219e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Lock to guard the model registry. 220e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 221e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final Object sRegistryLock = new Object(); 222e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 223e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 224e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * This the registry for data models. 225e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 226e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final Map<String, ActivityChooserModel> sDataModelRegistry = 227e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes new HashMap<String, ActivityChooserModel>(); 228e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 229e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 230e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Lock for synchronizing on this instance. 231e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 232e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private final Object mInstanceLock = new Object(); 233e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 234e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 235e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * List of activities that can handle the current intent. 236e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 237e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private final List<ActivityResolveInfo> mActivities = new ArrayList<ActivityResolveInfo>(); 238e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 239e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 240e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * List with historical choice records. 241e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 242e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private final List<HistoricalRecord> mHistoricalRecords = new ArrayList<HistoricalRecord>(); 243e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 244e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 245e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Context for accessing resources. 246e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 247e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private final Context mContext; 248e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 249e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 250e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The name of the history file that backs this model. 251e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 252e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private final String mHistoryFileName; 253e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 254e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 255e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The intent for which a activity is being chosen. 256e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 257e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private Intent mIntent; 258e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 259e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 260e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The sorter for ordering activities based on intent and past choices. 261e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 262e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private ActivitySorter mActivitySorter = new DefaultSorter(); 263e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 264e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 265e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The maximal length of the choice history. 266e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 267e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private int mHistoryMaxSize = DEFAULT_HISTORY_MAX_LENGTH; 268e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 269e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 270e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Flag whether choice history can be read. In general many clients can 271e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * share the same data model and {@link #readHistoricalDataIfNeeded()} may be called 272e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * by arbitrary of them any number of times. Therefore, this class guarantees 273e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * that the very first read succeeds and subsequent reads can be performed 274e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * only after a call to {@link #persistHistoricalDataIfNeeded()} followed by change 275e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * of the share records. 276e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 277e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private boolean mCanReadHistoricalData = true; 278e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 279e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 280e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Flag whether the choice history was read. This is used to enforce that 281e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * before calling {@link #persistHistoricalDataIfNeeded()} a call to 282e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * {@link #persistHistoricalDataIfNeeded()} has been made. This aims to avoid a 283e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * scenario in which a choice history file exits, it is not read yet and 284e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * it is overwritten. Note that always all historical records are read in 285e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * full and the file is rewritten. This is necessary since we need to 286e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * purge old records that are outside of the sliding window of past choices. 287e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 288e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private boolean mReadShareHistoryCalled = false; 289e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 290e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 291e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Flag whether the choice records have changed. In general many clients can 292e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * share the same data model and {@link #persistHistoricalDataIfNeeded()} may be called 293e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * by arbitrary of them any number of times. Therefore, this class guarantees 294e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * that choice history will be persisted only if it has changed. 295e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 296e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private boolean mHistoricalRecordsChanged = true; 297e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 298e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 299e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Flag whether to reload the activities for the current intent. 300e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 301e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private boolean mReloadActivities = false; 302e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 303e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 304e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Policy for controlling how the model handles chosen activities. 305e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 306e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private OnChooseActivityListener mActivityChoserModelPolicy; 307e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 308e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 309e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Gets the data model backed by the contents of the provided file with historical data. 310e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Note that only one data model is backed by a given file, thus multiple calls with 311e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * the same file name will return the same model instance. If no such instance is present 312e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * it is created. 313e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 314e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <strong>Note:</strong> To use the default historical data file clients should explicitly 315e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * pass as file name {@link #DEFAULT_HISTORY_FILE_NAME}. If no persistence of the choice 316e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * history is desired clients should pass <code>null</code> for the file name. In such 317e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * case a new model is returned for each invocation. 318e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * </p> 319e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 320e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 321e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <strong>Always use difference historical data files for semantically different actions. 322e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * For example, sharing is different from importing.</strong> 323e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * </p> 324e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 325e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param context Context for loading resources. 326e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param historyFileName File name with choice history, <code>null</code> 327e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * if the model should not be backed by a file. In this case the activities 328e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * will be ordered only by data from the current session. 329e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 330e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return The model. 331e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 332e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public static ActivityChooserModel get(Context context, String historyFileName) { 333e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (sRegistryLock) { 334e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ActivityChooserModel dataModel = sDataModelRegistry.get(historyFileName); 335e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (dataModel == null) { 336e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes dataModel = new ActivityChooserModel(context, historyFileName); 337e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes sDataModelRegistry.put(historyFileName, dataModel); 338e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 339e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return dataModel; 340e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 341e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 342e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 343e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 344e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Creates a new instance. 345e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 346e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param context Context for loading resources. 347e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param historyFileName The history XML file. 348e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 349e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private ActivityChooserModel(Context context, String historyFileName) { 350e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mContext = context.getApplicationContext(); 351e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (!TextUtils.isEmpty(historyFileName) 352e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes && !historyFileName.endsWith(HISTORY_FILE_EXTENSION)) { 353e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mHistoryFileName = historyFileName + HISTORY_FILE_EXTENSION; 354e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } else { 355e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mHistoryFileName = historyFileName; 356e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 357e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 358e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 359e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 360e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Sets an intent for which to choose a activity. 361e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 362e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <strong>Note:</strong> Clients must set only semantically similar 363e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * intents for each data model. 364e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 365e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 366e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param intent The intent. 367e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 368e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public void setIntent(Intent intent) { 369e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 370e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (mIntent == intent) { 371e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return; 372e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 373e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mIntent = intent; 374e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mReloadActivities = true; 375e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ensureConsistentState(); 376e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 377e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 378e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 379e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 380e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Gets the intent for which a activity is being chosen. 381e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 382e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return The intent. 383e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 384e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public Intent getIntent() { 385e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 386e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return mIntent; 387e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 388e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 389e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 390e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 391e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Gets the number of activities that can handle the intent. 392e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 393e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return The activity count. 394e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 395e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @see #setIntent(Intent) 396e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 397e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public int getActivityCount() { 398e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 399e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ensureConsistentState(); 400e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return mActivities.size(); 401e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 402e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 403e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 404e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 405e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Gets an activity at a given index. 406e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 407e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return The activity. 408e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 409e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @see ActivityResolveInfo 410e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @see #setIntent(Intent) 411e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 412e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public ResolveInfo getActivity(int index) { 413e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 414e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ensureConsistentState(); 415e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return mActivities.get(index).resolveInfo; 416e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 417e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 418e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 419e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 420e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Gets the index of a the given activity. 421e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 422e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param activity The activity index. 423e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 424e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return The index if found, -1 otherwise. 425e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 426e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public int getActivityIndex(ResolveInfo activity) { 427e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 428e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ensureConsistentState(); 429e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes List<ActivityResolveInfo> activities = mActivities; 430e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final int activityCount = activities.size(); 431e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes for (int i = 0; i < activityCount; i++) { 432e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ActivityResolveInfo currentActivity = activities.get(i); 433e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (currentActivity.resolveInfo == activity) { 434e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return i; 435e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 436e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 437e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return INVALID_INDEX; 438e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 439e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 440e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 441e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 442e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Chooses a activity to handle the current intent. This will result in 443e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * adding a historical record for that action and construct intent with 444e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * its component name set such that it can be immediately started by the 445e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * client. 446e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 447e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <strong>Note:</strong> By calling this method the client guarantees 448e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * that the returned intent will be started. This intent is returned to 449e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * the client solely to let additional customization before the start. 450e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * </p> 451e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 452e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return An {@link Intent} for launching the activity or null if the 453e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * policy has consumed the intent or there is not current intent 454e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * set via {@link #setIntent(Intent)}. 455e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 456e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @see HistoricalRecord 457e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @see OnChooseActivityListener 458e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 459e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public Intent chooseActivity(int index) { 460e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 461e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (mIntent == null) { 462e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return null; 463e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 464e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 465e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ensureConsistentState(); 466e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 467e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ActivityResolveInfo chosenActivity = mActivities.get(index); 468e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 469e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ComponentName chosenName = new ComponentName( 470e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes chosenActivity.resolveInfo.activityInfo.packageName, 471e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes chosenActivity.resolveInfo.activityInfo.name); 472e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 473e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Intent choiceIntent = new Intent(mIntent); 474e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes choiceIntent.setComponent(chosenName); 475e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 476e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (mActivityChoserModelPolicy != null) { 477e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes // Do not allow the policy to change the intent. 478e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Intent choiceIntentCopy = new Intent(choiceIntent); 479e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final boolean handled = mActivityChoserModelPolicy.onChooseActivity(this, 480e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes choiceIntentCopy); 481e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (handled) { 482e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return null; 483e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 484e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 485e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 486e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes HistoricalRecord historicalRecord = new HistoricalRecord(chosenName, 487e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes System.currentTimeMillis(), DEFAULT_HISTORICAL_RECORD_WEIGHT); 488e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes addHisoricalRecord(historicalRecord); 489e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 490e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return choiceIntent; 491e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 492e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 493e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 494e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 495e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Sets the listener for choosing an activity. 496e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 497e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param listener The listener. 498e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 499e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public void setOnChooseActivityListener(OnChooseActivityListener listener) { 500e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 501e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mActivityChoserModelPolicy = listener; 502e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 503e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 504e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 505e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 506e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Gets the default activity, The default activity is defined as the one 507e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * with highest rank i.e. the first one in the list of activities that can 508e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * handle the intent. 509e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 510e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return The default activity, <code>null</code> id not activities. 511e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 512e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @see #getActivity(int) 513e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 514e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public ResolveInfo getDefaultActivity() { 515e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 516e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ensureConsistentState(); 517e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (!mActivities.isEmpty()) { 518e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return mActivities.get(0).resolveInfo; 519e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 520e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 521e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return null; 522e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 523e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 524e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 525e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Sets the default activity. The default activity is set by adding a 526e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * historical record with weight high enough that this activity will 527e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * become the highest ranked. Such a strategy guarantees that the default 528e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * will eventually change if not used. Also the weight of the record for 529e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * setting a default is inflated with a constant amount to guarantee that 530e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * it will stay as default for awhile. 531e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 532e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param index The index of the activity to set as default. 533e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 534e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public void setDefaultActivity(int index) { 535e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 536e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ensureConsistentState(); 537e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 538e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ActivityResolveInfo newDefaultActivity = mActivities.get(index); 539e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ActivityResolveInfo oldDefaultActivity = mActivities.get(0); 540e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 541e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final float weight; 542e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (oldDefaultActivity != null) { 543e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes // Add a record with weight enough to boost the chosen at the top. 544e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes weight = oldDefaultActivity.weight - newDefaultActivity.weight 545e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes + DEFAULT_ACTIVITY_INFLATION; 546e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } else { 547e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes weight = DEFAULT_HISTORICAL_RECORD_WEIGHT; 548e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 549e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 550e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ComponentName defaultName = new ComponentName( 551e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes newDefaultActivity.resolveInfo.activityInfo.packageName, 552e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes newDefaultActivity.resolveInfo.activityInfo.name); 553e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes HistoricalRecord historicalRecord = new HistoricalRecord(defaultName, 554e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes System.currentTimeMillis(), weight); 555e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes addHisoricalRecord(historicalRecord); 556e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 557e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 558e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 559e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 560e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Persists the history data to the backing file if the latter 561e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * was provided. Calling this method before a call to {@link #readHistoricalDataIfNeeded()} 562e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * throws an exception. Calling this method more than one without choosing an 563e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * activity has not effect. 564e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 565e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @throws IllegalStateException If this method is called before a call to 566e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * {@link #readHistoricalDataIfNeeded()}. 567e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 568e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private void persistHistoricalDataIfNeeded() { 569e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (!mReadShareHistoryCalled) { 570e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes throw new IllegalStateException("No preceding call to #readHistoricalData"); 571e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 572e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (!mHistoricalRecordsChanged) { 573e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return; 574e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 575e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mHistoricalRecordsChanged = false; 576e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (!TextUtils.isEmpty(mHistoryFileName)) { 577e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 578e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes executePersistHistoryAsyncTaskSDK11(); 579e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } else { 580e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes executePersistHistoryAsyncTaskBase(); 581e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 582e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 583e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 584e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 585e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private void executePersistHistoryAsyncTaskBase() { 586e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes new PersistHistoryAsyncTask().execute(new ArrayList<HistoricalRecord>(mHistoricalRecords), 587e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mHistoryFileName); 588e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 589e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 590e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private void executePersistHistoryAsyncTaskSDK11() { 591e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes new PersistHistoryAsyncTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, 592e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes new ArrayList<HistoricalRecord>(mHistoricalRecords), mHistoryFileName); 593e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 594e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 595e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 596e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Sets the sorter for ordering activities based on historical data and an intent. 597e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 598e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param activitySorter The sorter. 599e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 600e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @see ActivitySorter 601e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 602e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public void setActivitySorter(ActivitySorter activitySorter) { 603e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 604e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (mActivitySorter == activitySorter) { 605e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return; 606e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 607e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mActivitySorter = activitySorter; 608e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (sortActivitiesIfNeeded()) { 609e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes notifyChanged(); 610e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 611e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 612e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 613e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 614e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 615e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Sets the maximal size of the historical data. Defaults to 616e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * {@link #DEFAULT_HISTORY_MAX_LENGTH} 617e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <p> 618e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * <strong>Note:</strong> Setting this property will immediately 619e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * enforce the specified max history size by dropping enough old 620e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * historical records to enforce the desired size. Thus, any 621e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * records that exceed the history size will be discarded and 622e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * irreversibly lost. 623e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * </p> 624e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 625e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param historyMaxSize The max history size. 626e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 627e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public void setHistoryMaxSize(int historyMaxSize) { 628e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 629e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (mHistoryMaxSize == historyMaxSize) { 630e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return; 631e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 632e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mHistoryMaxSize = historyMaxSize; 633e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes pruneExcessiveHistoricalRecordsIfNeeded(); 634e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (sortActivitiesIfNeeded()) { 635e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes notifyChanged(); 636e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 637e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 638e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 639e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 640e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 641e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Gets the history max size. 642e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 643e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return The history max size. 644e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 645e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public int getHistoryMaxSize() { 646e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 647e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return mHistoryMaxSize; 648e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 649e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 650e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 651e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 652e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Gets the history size. 653e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 654e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return The history size. 655e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 656e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public int getHistorySize() { 657e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes synchronized (mInstanceLock) { 658e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ensureConsistentState(); 659e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return mHistoricalRecords.size(); 660e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 661e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 662e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 663e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 664e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Ensures the model is in a consistent state which is the 665e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * activities for the current intent have been loaded, the 666e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * most recent history has been read, and the activities 667e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * are sorted. 668e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 669e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private void ensureConsistentState() { 670e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes boolean stateChanged = loadActivitiesIfNeeded(); 671e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes stateChanged |= readHistoricalDataIfNeeded(); 672e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes pruneExcessiveHistoricalRecordsIfNeeded(); 673e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (stateChanged) { 674e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes sortActivitiesIfNeeded(); 675e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes notifyChanged(); 676e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 677e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 678e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 679e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 680e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Sorts the activities if necessary which is if there is a 681e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * sorter, there are some activities to sort, and there is some 682e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * historical data. 683e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 684e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return Whether sorting was performed. 685e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 686e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private boolean sortActivitiesIfNeeded() { 687e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (mActivitySorter != null && mIntent != null 688e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes && !mActivities.isEmpty() && !mHistoricalRecords.isEmpty()) { 689e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mActivitySorter.sort(mIntent, mActivities, 690e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Collections.unmodifiableList(mHistoricalRecords)); 691e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return true; 692e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 693e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 694e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 695e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 696e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 697e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Loads the activities for the current intent if needed which is 698e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * if they are not already loaded for the current intent. 699e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 700e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return Whether loading was performed. 701e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 702e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private boolean loadActivitiesIfNeeded() { 703e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (mReloadActivities && mIntent != null) { 704e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mReloadActivities = false; 705e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mActivities.clear(); 706e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes List<ResolveInfo> resolveInfos = mContext.getPackageManager() 707e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes .queryIntentActivities(mIntent, 0); 708e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final int resolveInfoCount = resolveInfos.size(); 709e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes for (int i = 0; i < resolveInfoCount; i++) { 710e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ResolveInfo resolveInfo = resolveInfos.get(i); 711e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mActivities.add(new ActivityResolveInfo(resolveInfo)); 712e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 713e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return true; 714e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 715e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 716e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 717e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 718e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 719e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Reads the historical data if necessary which is it has 720e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * changed, there is a history file, and there is not persist 721e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * in progress. 722e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 723e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return Whether reading was performed. 724e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 725e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private boolean readHistoricalDataIfNeeded() { 726e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (mCanReadHistoricalData && mHistoricalRecordsChanged && 727e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes !TextUtils.isEmpty(mHistoryFileName)) { 728e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mCanReadHistoricalData = false; 729e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mReadShareHistoryCalled = true; 730e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes readHistoricalDataImpl(); 731e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return true; 732e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 733e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 734e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 735e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 736e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 737e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Adds a historical record. 738e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 739e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param historicalRecord The record to add. 740e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @return True if the record was added. 741e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 742e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private boolean addHisoricalRecord(HistoricalRecord historicalRecord) { 743e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final boolean added = mHistoricalRecords.add(historicalRecord); 744e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (added) { 745e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mHistoricalRecordsChanged = true; 746e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes pruneExcessiveHistoricalRecordsIfNeeded(); 747e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes persistHistoricalDataIfNeeded(); 748e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes sortActivitiesIfNeeded(); 749e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes notifyChanged(); 750e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 751e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return added; 752e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 753e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 754e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 755e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Prunes older excessive records to guarantee maxHistorySize. 756e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 757e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private void pruneExcessiveHistoricalRecordsIfNeeded() { 758e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final int pruneCount = mHistoricalRecords.size() - mHistoryMaxSize; 759e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (pruneCount <= 0) { 760e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return; 761e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 762e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mHistoricalRecordsChanged = true; 763e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes for (int i = 0; i < pruneCount; i++) { 764e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes HistoricalRecord prunedRecord = mHistoricalRecords.remove(0); 765e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (DEBUG) { 766e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.i(LOG_TAG, "Pruned: " + prunedRecord); 767e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 768e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 769e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 770e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 771e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 772e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Represents a record in the history. 773e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 774e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public final static class HistoricalRecord { 775e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 776e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 777e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The activity name. 778e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 779e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public final ComponentName activity; 780e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 781e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 782e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The choice time. 783e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 784e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public final long time; 785e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 786e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 787e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The record weight. 788e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 789e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public final float weight; 790e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 791e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 792e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Creates a new instance. 793e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 794e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param activityName The activity component name flattened to string. 795e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param time The time the activity was chosen. 796e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param weight The weight of the record. 797e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 798e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public HistoricalRecord(String activityName, long time, float weight) { 799e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes this(ComponentName.unflattenFromString(activityName), time, weight); 800e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 801e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 802e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 803e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Creates a new instance. 804e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 805e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param activityName The activity name. 806e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param time The time the activity was chosen. 807e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param weight The weight of the record. 808e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 809e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public HistoricalRecord(ComponentName activityName, long time, float weight) { 810e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes this.activity = activityName; 811e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes this.time = time; 812e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes this.weight = weight; 813e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 814e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 815e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes @Override 816e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public int hashCode() { 817e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final int prime = 31; 818e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes int result = 1; 819e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes result = prime * result + ((activity == null) ? 0 : activity.hashCode()); 820e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes result = prime * result + (int) (time ^ (time >>> 32)); 821e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes result = prime * result + Float.floatToIntBits(weight); 822e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return result; 823e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 824e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 825e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes @Override 826e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public boolean equals(Object obj) { 827e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (this == obj) { 828e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return true; 829e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 830e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (obj == null) { 831e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 832e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 833e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (getClass() != obj.getClass()) { 834e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 835e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 836e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes HistoricalRecord other = (HistoricalRecord) obj; 837e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (activity == null) { 838e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (other.activity != null) { 839e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 840e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 841e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } else if (!activity.equals(other.activity)) { 842e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 843e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 844e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (time != other.time) { 845e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 846e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 847e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (Float.floatToIntBits(weight) != Float.floatToIntBits(other.weight)) { 848e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 849e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 850e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return true; 851e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 852e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 853e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes @Override 854e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public String toString() { 855e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes StringBuilder builder = new StringBuilder(); 856e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes builder.append("["); 857e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes builder.append("; activity:").append(activity); 858e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes builder.append("; time:").append(time); 859e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes builder.append("; weight:").append(new BigDecimal(weight)); 860e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes builder.append("]"); 861e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return builder.toString(); 862e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 863e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 864e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 865e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 866e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Represents an activity. 867e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 868e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public final class ActivityResolveInfo implements Comparable<ActivityResolveInfo> { 869e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 870e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 871e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * The {@link ResolveInfo} of the activity. 872e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 873e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public final ResolveInfo resolveInfo; 874e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 875e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 876e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Weight of the activity. Useful for sorting. 877e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 878e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public float weight; 879e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 880e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 881e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Creates a new instance. 882e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * 883e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * @param resolveInfo activity {@link ResolveInfo}. 884e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 885e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public ActivityResolveInfo(ResolveInfo resolveInfo) { 886e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes this.resolveInfo = resolveInfo; 887e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 888e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 889e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes @Override 890e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public int hashCode() { 891e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return 31 + Float.floatToIntBits(weight); 892e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 893e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 894e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes @Override 895e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public boolean equals(Object obj) { 896e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (this == obj) { 897e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return true; 898e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 899e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (obj == null) { 900e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 901e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 902e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (getClass() != obj.getClass()) { 903e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 904e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 905e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ActivityResolveInfo other = (ActivityResolveInfo) obj; 906e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (Float.floatToIntBits(weight) != Float.floatToIntBits(other.weight)) { 907e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return false; 908e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 909e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return true; 910e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 911e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 912e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public int compareTo(ActivityResolveInfo another) { 913e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight); 914e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 915e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 916e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes @Override 917e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public String toString() { 918e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes StringBuilder builder = new StringBuilder(); 919e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes builder.append("["); 920e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes builder.append("resolveInfo:").append(resolveInfo.toString()); 921e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes builder.append("; weight:").append(new BigDecimal(weight)); 922e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes builder.append("]"); 923e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return builder.toString(); 924e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 925e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 926e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 927e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 928e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Default activity sorter implementation. 929e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 930e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private final class DefaultSorter implements ActivitySorter { 931e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private static final float WEIGHT_DECAY_COEFFICIENT = 0.95f; 932e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 933e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private final Map<String, ActivityResolveInfo> mPackageNameToActivityMap = 934e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes new HashMap<String, ActivityResolveInfo>(); 935e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 936e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public void sort(Intent intent, List<ActivityResolveInfo> activities, 937e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes List<HistoricalRecord> historicalRecords) { 938e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Map<String, ActivityResolveInfo> packageNameToActivityMap = 939e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mPackageNameToActivityMap; 940e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes packageNameToActivityMap.clear(); 941e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 942e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final int activityCount = activities.size(); 943e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes for (int i = 0; i < activityCount; i++) { 944e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ActivityResolveInfo activity = activities.get(i); 945e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes activity.weight = 0.0f; 946e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes String packageName = activity.resolveInfo.activityInfo.packageName; 947e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes packageNameToActivityMap.put(packageName, activity); 948e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 949e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 950e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final int lastShareIndex = historicalRecords.size() - 1; 951e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes float nextRecordWeight = 1; 952e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes for (int i = lastShareIndex; i >= 0; i--) { 953e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes HistoricalRecord historicalRecord = historicalRecords.get(i); 954e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes String packageName = historicalRecord.activity.getPackageName(); 955e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes ActivityResolveInfo activity = packageNameToActivityMap.get(packageName); 956e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (activity != null) { 957e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes activity.weight += historicalRecord.weight * nextRecordWeight; 958e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes nextRecordWeight = nextRecordWeight * WEIGHT_DECAY_COEFFICIENT; 959e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 960e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 961e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 962e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Collections.sort(activities); 963e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 964e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (DEBUG) { 965e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes for (int i = 0; i < activityCount; i++) { 966e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.i(LOG_TAG, "Sorted: " + activities.get(i)); 967e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 968e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 969e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 970e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 971e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 972e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 973e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Command for reading the historical records from a file off the UI thread. 974e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 975e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private void readHistoricalDataImpl() { 976e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes FileInputStream fis = null; 977e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes try { 978e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes fis = mContext.openFileInput(mHistoryFileName); 979e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } catch (FileNotFoundException fnfe) { 980e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (DEBUG) { 981e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.i(LOG_TAG, "Could not open historical records file: " + mHistoryFileName); 982e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 983e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return; 984e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 985e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes try { 986e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes XmlPullParser parser = Xml.newPullParser(); 987e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes parser.setInput(fis, null); 988e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 989e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes int type = XmlPullParser.START_DOCUMENT; 990e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { 991e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes type = parser.next(); 992e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 993e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 994e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (!TAG_HISTORICAL_RECORDS.equals(parser.getName())) { 995e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes throw new XmlPullParserException("Share records file does not start with " 996e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes + TAG_HISTORICAL_RECORDS + " tag."); 997e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 998e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 999e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes List<HistoricalRecord> historicalRecords = mHistoricalRecords; 1000e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes historicalRecords.clear(); 1001e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1002e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes while (true) { 1003e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes type = parser.next(); 1004e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (type == XmlPullParser.END_DOCUMENT) { 1005e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes break; 1006e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1007e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 1008e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes continue; 1009e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1010e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes String nodeName = parser.getName(); 1011e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (!TAG_HISTORICAL_RECORD.equals(nodeName)) { 1012e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes throw new XmlPullParserException("Share records file not well-formed."); 1013e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1014e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1015e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes String activity = parser.getAttributeValue(null, ATTRIBUTE_ACTIVITY); 1016e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final long time = 1017e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Long.parseLong(parser.getAttributeValue(null, ATTRIBUTE_TIME)); 1018e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final float weight = 1019e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Float.parseFloat(parser.getAttributeValue(null, ATTRIBUTE_WEIGHT)); 1020e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes HistoricalRecord readRecord = new HistoricalRecord(activity, time, weight); 1021e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes historicalRecords.add(readRecord); 1022e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1023e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (DEBUG) { 1024e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.i(LOG_TAG, "Read " + readRecord.toString()); 1025e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1026e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1027e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1028e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (DEBUG) { 1029e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.i(LOG_TAG, "Read " + historicalRecords.size() + " historical records."); 1030e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1031e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } catch (XmlPullParserException xppe) { 1032e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, xppe); 1033e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } catch (IOException ioe) { 1034e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, ioe); 1035e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } finally { 1036e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (fis != null) { 1037e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes try { 1038e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes fis.close(); 1039e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } catch (IOException ioe) { 1040e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /* ignore */ 1041e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1042e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1043e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1044e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1045e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1046e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /** 1047e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes * Command for persisting the historical records to a file off the UI thread. 1048e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes */ 1049e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes private final class PersistHistoryAsyncTask extends AsyncTask<Object, Void, Void> { 1050e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1051e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes @Override 1052e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes @SuppressWarnings("unchecked") 1053e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes public Void doInBackground(Object... args) { 1054e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes List<HistoricalRecord> historicalRecords = (List<HistoricalRecord>) args[0]; 1055e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes String hostoryFileName = (String) args[1]; 1056e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1057e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes FileOutputStream fos = null; 1058e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1059e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes try { 1060e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes fos = mContext.openFileOutput(hostoryFileName, Context.MODE_PRIVATE); 1061e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } catch (FileNotFoundException fnfe) { 1062e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.e(LOG_TAG, "Error writing historical recrod file: " + hostoryFileName, fnfe); 1063e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return null; 1064e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1065e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1066e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes XmlSerializer serializer = Xml.newSerializer(); 1067e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1068e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes try { 1069e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes serializer.setOutput(fos, null); 1070e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes serializer.startDocument("UTF-8", true); 1071e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes serializer.startTag(null, TAG_HISTORICAL_RECORDS); 1072e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1073e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes final int recordCount = historicalRecords.size(); 1074e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes for (int i = 0; i < recordCount; i++) { 1075e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes HistoricalRecord record = historicalRecords.remove(0); 1076e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes serializer.startTag(null, TAG_HISTORICAL_RECORD); 1077e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes serializer.attribute(null, ATTRIBUTE_ACTIVITY, 1078e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes record.activity.flattenToString()); 1079e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes serializer.attribute(null, ATTRIBUTE_TIME, String.valueOf(record.time)); 1080e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes serializer.attribute(null, ATTRIBUTE_WEIGHT, String.valueOf(record.weight)); 1081e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes serializer.endTag(null, TAG_HISTORICAL_RECORD); 1082e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (DEBUG) { 1083e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.i(LOG_TAG, "Wrote " + record.toString()); 1084e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1085e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1086e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1087e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes serializer.endTag(null, TAG_HISTORICAL_RECORDS); 1088e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes serializer.endDocument(); 1089e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes 1090e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (DEBUG) { 1091e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.i(LOG_TAG, "Wrote " + recordCount + " historical records."); 1092e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1093e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } catch (IllegalArgumentException iae) { 1094e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, iae); 1095e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } catch (IllegalStateException ise) { 1096e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, ise); 1097e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } catch (IOException ioe) { 1098e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, ioe); 1099e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } finally { 1100e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes mCanReadHistoricalData = true; 1101e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes if (fos != null) { 1102e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes try { 1103e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes fos.close(); 1104e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } catch (IOException e) { 1105e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes /* ignore */ 1106e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1107e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1108e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1109e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes return null; 1110e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1111e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes } 1112e290ed32f85ff6307a53922a78684b31d30b8dc5Chris Banes}