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