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