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}