Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b1ae25cb authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Android (Google) Code Review
Browse files

Merge "ActivityChooserModel does not handle package changes on the thread that...

Merge "ActivityChooserModel does not handle package changes on the thread that created it." into jb-dev
parents b2ee0d57 ca858797
Loading
Loading
Loading
Loading
+244 −303
Original line number Diff line number Diff line
@@ -21,9 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.os.AsyncTask;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
@@ -42,10 +40,8 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * <p>
@@ -239,7 +235,7 @@ public class ActivityChooserModel extends DataSetObservable {
    /**
     * List of activities that can handle the current intent.
     */
    private final List<ActivityResolveInfo> mActivites = new ArrayList<ActivityResolveInfo>();
    private final List<ActivityResolveInfo> mActivities = new ArrayList<ActivityResolveInfo>();

    /**
     * List with historical choice records.
@@ -278,18 +274,18 @@ public class ActivityChooserModel extends DataSetObservable {

    /**
     * Flag whether choice history can be read. In general many clients can
     * share the same data model and {@link #readHistoricalData()} may be called
     * share the same data model and {@link #readHistoricalDataIfNeeded()} may be called
     * by arbitrary of them any number of times. Therefore, this class guarantees
     * that the very first read succeeds and subsequent reads can be performed
     * only after a call to {@link #persistHistoricalData()} followed by change
     * only after a call to {@link #persistHistoricalDataIfNeeded()} followed by change
     * of the share records.
     */
    private boolean mCanReadHistoricalData = true;

    /**
     * Flag whether the choice history was read. This is used to enforce that
     * before calling {@link #persistHistoricalData()} a call to
     * {@link #persistHistoricalData()} has been made. This aims to avoid a
     * before calling {@link #persistHistoricalDataIfNeeded()} a call to
     * {@link #persistHistoricalDataIfNeeded()} has been made. This aims to avoid a
     * scenario in which a choice history file exits, it is not read yet and
     * it is overwritten. Note that always all historical records are read in
     * full and the file is rewritten. This is necessary since we need to
@@ -299,16 +295,16 @@ public class ActivityChooserModel extends DataSetObservable {

    /**
     * Flag whether the choice records have changed. In general many clients can
     * share the same data model and {@link #persistHistoricalData()} may be called
     * share the same data model and {@link #persistHistoricalDataIfNeeded()} may be called
     * by arbitrary of them any number of times. Therefore, this class guarantees
     * that choice history will be persisted only if it has changed.
     */
    private boolean mHistoricalRecordsChanged = true;

    /**
     * Hander for scheduling work on client tread.
     * Flag whether to reload the activities for the current intent.
     */
    private final Handler mHandler = new Handler();
    private boolean mReloadActivities = false;

    /**
     * Policy for controlling how the model handles chosen activities.
@@ -346,7 +342,6 @@ public class ActivityChooserModel extends DataSetObservable {
                dataModel = new ActivityChooserModel(context, historyFileName);
                sDataModelRegistry.put(historyFileName, dataModel);
            }
            dataModel.readHistoricalData();
            return dataModel;
        }
    }
@@ -383,7 +378,8 @@ public class ActivityChooserModel extends DataSetObservable {
                return;
            }
            mIntent = intent;
            loadActivitiesLocked();
            mReloadActivities = true;
            ensureConsistentState();
        }
    }

@@ -407,7 +403,8 @@ public class ActivityChooserModel extends DataSetObservable {
     */
    public int getActivityCount() {
        synchronized (mInstanceLock) {
            return mActivites.size();
            ensureConsistentState();
            return mActivities.size();
        }
    }

@@ -421,7 +418,8 @@ public class ActivityChooserModel extends DataSetObservable {
     */
    public ResolveInfo getActivity(int index) {
        synchronized (mInstanceLock) {
            return mActivites.get(index).resolveInfo;
            ensureConsistentState();
            return mActivities.get(index).resolveInfo;
        }
    }

@@ -433,7 +431,9 @@ public class ActivityChooserModel extends DataSetObservable {
     * @return The index if found, -1 otherwise.
     */
    public int getActivityIndex(ResolveInfo activity) {
        List<ActivityResolveInfo> activities = mActivites;
        synchronized (mInstanceLock) {
            ensureConsistentState();
            List<ActivityResolveInfo> activities = mActivities;
            final int activityCount = activities.size();
            for (int i = 0; i < activityCount; i++) {
                ActivityResolveInfo currentActivity = activities.get(i);
@@ -443,6 +443,7 @@ public class ActivityChooserModel extends DataSetObservable {
            }
            return INVALID_INDEX;
        }
    }

    /**
     * Chooses a activity to handle the current intent. This will result in
@@ -462,7 +463,10 @@ public class ActivityChooserModel extends DataSetObservable {
     * @see OnChooseActivityListener
     */
    public Intent chooseActivity(int index) {
        ActivityResolveInfo chosenActivity = mActivites.get(index);
        synchronized (mInstanceLock) {
            ensureConsistentState();

            ActivityResolveInfo chosenActivity = mActivities.get(index);

            ComponentName chosenName = new ComponentName(
                    chosenActivity.resolveInfo.activityInfo.packageName,
@@ -487,6 +491,7 @@ public class ActivityChooserModel extends DataSetObservable {

            return choiceIntent;
        }
    }

    /**
     * Sets the listener for choosing an activity.
@@ -494,8 +499,10 @@ public class ActivityChooserModel extends DataSetObservable {
     * @param listener The listener.
     */
    public void setOnChooseActivityListener(OnChooseActivityListener listener) {
        synchronized (mInstanceLock) {
            mActivityChoserModelPolicy = listener;
        }
    }

    /**
     * Gets the default activity, The default activity is defined as the one
@@ -508,8 +515,9 @@ public class ActivityChooserModel extends DataSetObservable {
     */
    public ResolveInfo getDefaultActivity() {
        synchronized (mInstanceLock) {
            if (!mActivites.isEmpty()) {
                return mActivites.get(0).resolveInfo;
            ensureConsistentState();
            if (!mActivities.isEmpty()) {
                return mActivities.get(0).resolveInfo;
            }
        }
        return null;
@@ -526,8 +534,11 @@ public class ActivityChooserModel extends DataSetObservable {
     * @param index The index of the activity to set as default.
     */
    public void setDefaultActivity(int index) {
        ActivityResolveInfo newDefaultActivity = mActivites.get(index);
        ActivityResolveInfo oldDefaultActivity = mActivites.get(0);
        synchronized (mInstanceLock) {
            ensureConsistentState();

            ActivityResolveInfo newDefaultActivity = mActivities.get(index);
            ActivityResolveInfo oldDefaultActivity = mActivities.get(0);

            final float weight;
            if (oldDefaultActivity != null) {
@@ -545,42 +556,18 @@ public class ActivityChooserModel extends DataSetObservable {
                    System.currentTimeMillis(), weight);
            addHisoricalRecord(historicalRecord);
        }

    /**
     * Reads the history data from the backing file if the latter
     * was provided. Calling this method more than once before a call
     * to {@link #persistHistoricalData()} has been made has no effect.
     * <p>
     * <strong>Note:</strong> Historical data is read asynchronously and
     *       as soon as the reading is completed any registered
     *       {@link DataSetObserver}s will be notified. Also no historical
     *       data is read until this method is invoked.
     * <p>
     */
    private void readHistoricalData() {
        synchronized (mInstanceLock) {
            if (!mCanReadHistoricalData || !mHistoricalRecordsChanged) {
                return;
            }
            mCanReadHistoricalData = false;
            mReadShareHistoryCalled = true;
            if (!TextUtils.isEmpty(mHistoryFileName)) {
                AsyncTask.SERIAL_EXECUTOR.execute(new HistoryLoader());
            }
        }
    }

    /**
     * Persists the history data to the backing file if the latter
     * was provided. Calling this method before a call to {@link #readHistoricalData()}
     * was provided. Calling this method before a call to {@link #readHistoricalDataIfNeeded()}
     * throws an exception. Calling this method more than one without choosing an
     * activity has not effect.
     *
     * @throws IllegalStateException If this method is called before a call to
     *         {@link #readHistoricalData()}.
     *         {@link #readHistoricalDataIfNeeded()}.
     */
    private void persistHistoricalData() {
        synchronized (mInstanceLock) {
    private void persistHistoricalDataIfNeeded() {
        if (!mReadShareHistoryCalled) {
            throw new IllegalStateException("No preceding call to #readHistoricalData");
        }
@@ -588,10 +575,9 @@ public class ActivityChooserModel extends DataSetObservable {
            return;
        }
        mHistoricalRecordsChanged = false;
            mCanReadHistoricalData = true;
        if (!TextUtils.isEmpty(mHistoryFileName)) {
                AsyncTask.SERIAL_EXECUTOR.execute(new HistoryPersister());
            }
            new PersistHistoryAsyncTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
                    new ArrayList<HistoricalRecord>(mHistoricalRecords), mHistoryFileName);
        }
    }

@@ -608,21 +594,7 @@ public class ActivityChooserModel extends DataSetObservable {
                return;
            }
            mActivitySorter = activitySorter;
            sortActivities();
        }
    }

    /**
     * Sorts the activities based on history and an intent. If
     * a sorter is not specified this a default implementation is used.
     *
     * @see #setActivitySorter(ActivitySorter)
     */
    private void sortActivities() {
        synchronized (mInstanceLock) {
            if (mActivitySorter != null && !mActivites.isEmpty()) {
                mActivitySorter.sort(mIntent, mActivites,
                        Collections.unmodifiableList(mHistoricalRecords));
            if (sortActivitiesIfNeeded()) {
                notifyChanged();
            }
        }
@@ -647,8 +619,10 @@ public class ActivityChooserModel extends DataSetObservable {
                return;
            }
            mHistoryMaxSize = historyMaxSize;
            pruneExcessiveHistoricalRecordsLocked();
            sortActivities();
            pruneExcessiveHistoricalRecordsIfNeeded();
            if (sortActivitiesIfNeeded()) {
                notifyChanged();
            }
        }
    }

@@ -670,6 +644,7 @@ public class ActivityChooserModel extends DataSetObservable {
     */
    public int getHistorySize() {
        synchronized (mInstanceLock) {
            ensureConsistentState();
            return mHistoricalRecords.size();
        }
    }
@@ -681,82 +656,110 @@ public class ActivityChooserModel extends DataSetObservable {
    }

    /**
     * Adds a historical record.
     *
     * @param historicalRecord The record to add.
     * @return True if the record was added.
     * Ensures the model is in a consistent state which is the
     * activities for the current intent have been loaded, the
     * most recent history has been read, and the activities
     * are sorted.
     */
    private boolean addHisoricalRecord(HistoricalRecord historicalRecord) {
        synchronized (mInstanceLock) {
            final boolean added = mHistoricalRecords.add(historicalRecord);
            if (added) {
                mHistoricalRecordsChanged = true;
                pruneExcessiveHistoricalRecordsLocked();
                persistHistoricalData();
                sortActivities();
            }
            return added;
    private void ensureConsistentState() {
        boolean stateChanged = loadActivitiesIfNeeded();
        stateChanged |= readHistoricalDataIfNeeded();
        pruneExcessiveHistoricalRecordsIfNeeded();
        if (stateChanged) {
            sortActivitiesIfNeeded();
            notifyChanged();
        }
    }

    /**
     * Prunes older excessive records to guarantee {@link #mHistoryMaxSize}.
     * Sorts the activities if necessary which is if there is a
     * sorter, there are some activities to sort, and there is some
     * historical data.
     *
     * @return Whether sorting was performed.
     */
    private void pruneExcessiveHistoricalRecordsLocked() {
        List<HistoricalRecord> choiceRecords = mHistoricalRecords;
        final int pruneCount = choiceRecords.size() - mHistoryMaxSize;
        if (pruneCount <= 0) {
            return;
        }
        mHistoricalRecordsChanged = true;
        for (int i = 0; i < pruneCount; i++) {
            HistoricalRecord prunedRecord = choiceRecords.remove(0);
            if (DEBUG) {
                Log.i(LOG_TAG, "Pruned: " + prunedRecord);
            }
    private boolean sortActivitiesIfNeeded() {
        if (mActivitySorter != null && mIntent != null
                && !mActivities.isEmpty() && !mHistoricalRecords.isEmpty()) {
            mActivitySorter.sort(mIntent, mActivities,
                    Collections.unmodifiableList(mHistoricalRecords));
            return true;
        }
        return false;
    }

    /**
     * Loads the activities.
     */
    private void loadActivitiesLocked() {
        mActivites.clear();
        if (mIntent != null) {
            List<ResolveInfo> resolveInfos =
                mContext.getPackageManager().queryIntentActivities(mIntent, 0);
     * Loads the activities for the current intent if needed which is
     * if they are not already loaded for the current intent.
     *
     * @return Whether loading was performed.
     */
    private boolean loadActivitiesIfNeeded() {
        if (mReloadActivities && mIntent != null) {
            mReloadActivities = false;
            mActivities.clear();
            List<ResolveInfo> resolveInfos = mContext.getPackageManager()
                    .queryIntentActivities(mIntent, 0);
            final int resolveInfoCount = resolveInfos.size();
            for (int i = 0; i < resolveInfoCount; i++) {
                ResolveInfo resolveInfo = resolveInfos.get(i);
                mActivites.add(new ActivityResolveInfo(resolveInfo));
                mActivities.add(new ActivityResolveInfo(resolveInfo));
            }
            sortActivities();
        } else {
            notifyChanged();
            return true;
        }
        return false;
    }

    /**
     * Prunes historical records for a package that goes away.
     * Reads the historical data if necessary which is it has
     * changed, there is a history file, and there is not persist
     * in progress.
     *
     * @param packageName The name of the package that goes away.
     * @return Whether reading was performed.
     */
    private void pruneHistoricalRecordsForPackageLocked(String packageName) {
        boolean recordsRemoved = false;
    private boolean readHistoricalDataIfNeeded() {
        if (mCanReadHistoricalData && mHistoricalRecordsChanged &&
                !TextUtils.isEmpty(mHistoryFileName)) {
            mCanReadHistoricalData = false;
            mReadShareHistoryCalled = true;
            readHistoricalDataImpl();
            return true;
        }
        return false;
    }

        List<HistoricalRecord> historicalRecords = mHistoricalRecords;
        for (int i = 0; i < historicalRecords.size(); i++) {
            HistoricalRecord historicalRecord = historicalRecords.get(i);
            String recordPackageName = historicalRecord.activity.getPackageName();
            if (recordPackageName.equals(packageName)) {
                historicalRecords.remove(historicalRecord);
                recordsRemoved = true;
    /**
     * Adds a historical record.
     *
     * @param historicalRecord The record to add.
     * @return True if the record was added.
     */
    private boolean addHisoricalRecord(HistoricalRecord historicalRecord) {
        final boolean added = mHistoricalRecords.add(historicalRecord);
        if (added) {
            mHistoricalRecordsChanged = true;
            pruneExcessiveHistoricalRecordsIfNeeded();
            persistHistoricalDataIfNeeded();
            sortActivitiesIfNeeded();
            notifyChanged();
        }
        return added;
    }

        if (recordsRemoved) {
    /**
     * Prunes older excessive records to guarantee maxHistorySize.
     */
    private void pruneExcessiveHistoricalRecordsIfNeeded() {
        final int pruneCount = mHistoricalRecords.size() - mHistoryMaxSize;
        if (pruneCount <= 0) {
            return;
        }
        mHistoricalRecordsChanged = true;
            sortActivities();
        for (int i = 0; i < pruneCount; i++) {
            HistoricalRecord prunedRecord = mHistoricalRecords.remove(0);
            if (DEBUG) {
                Log.i(LOG_TAG, "Pruned: " + prunedRecord);
            }
        }
    }

@@ -974,9 +977,7 @@ public class ActivityChooserModel extends DataSetObservable {
    /**
     * Command for reading the historical records from a file off the UI thread.
     */
    private final class HistoryLoader implements Runnable {

       public void run() {
    private void readHistoricalDataImpl() {
        FileInputStream fis = null;
        try {
            fis = mContext.openFileInput(mHistoryFileName);
@@ -1000,7 +1001,8 @@ public class ActivityChooserModel extends DataSetObservable {
                        + TAG_HISTORICAL_RECORDS + " tag.");
            }

                List<HistoricalRecord> readRecords = new ArrayList<HistoricalRecord>();
            List<HistoricalRecord> historicalRecords = mHistoricalRecords;
            historicalRecords.clear();

            while (true) {
                type = parser.next();
@@ -1020,10 +1022,8 @@ public class ActivityChooserModel extends DataSetObservable {
                    Long.parseLong(parser.getAttributeValue(null, ATTRIBUTE_TIME));
                final float weight =
                    Float.parseFloat(parser.getAttributeValue(null, ATTRIBUTE_WEIGHT));

                    HistoricalRecord readRecord = new HistoricalRecord(activity, time,
                            weight);
                    readRecords.add(readRecord);
                 HistoricalRecord readRecord = new HistoricalRecord(activity, time, weight);
                historicalRecords.add(readRecord);

                if (DEBUG) {
                    Log.i(LOG_TAG, "Read " + readRecord.toString());
@@ -1031,43 +1031,7 @@ public class ActivityChooserModel extends DataSetObservable {
            }

            if (DEBUG) {
                    Log.i(LOG_TAG, "Read " + readRecords.size() + " historical records.");
                }

                synchronized (mInstanceLock) {
                    Set<HistoricalRecord> uniqueShareRecords =
                        new LinkedHashSet<HistoricalRecord>(readRecords);

                    // Make sure no duplicates. Example: Read a file with
                    // one record, add one record, persist the two records,
                    // add a record, read the persisted records - the
                    // read two records should not be added again.
                    List<HistoricalRecord> historicalRecords = mHistoricalRecords;
                    final int historicalRecordsCount = historicalRecords.size();
                    for (int i = historicalRecordsCount - 1; i >= 0; i--) {
                        HistoricalRecord historicalRecord = historicalRecords.get(i);
                        uniqueShareRecords.add(historicalRecord);
                    }

                    if (historicalRecords.size() == uniqueShareRecords.size()) {
                        return;
                    }

                    // Make sure the oldest records go to the end.
                    historicalRecords.clear();
                    historicalRecords.addAll(uniqueShareRecords);

                    mHistoricalRecordsChanged = true;

                    // Do this on the client thread since the client may be on the UI
                    // thread, wait for data changes which happen during sorting, and
                    // perform UI modification based on the data change.
                    mHandler.post(new Runnable() {
                        public void run() {
                            pruneExcessiveHistoricalRecordsLocked();
                            sortActivities();
                        }
                    });
                Log.i(LOG_TAG, "Read " + historicalRecords.size() + " historical records.");
            }
        } catch (XmlPullParserException xppe) {
            Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, xppe);
@@ -1083,26 +1047,25 @@ public class ActivityChooserModel extends DataSetObservable {
            }
        }
    }
    }

    /**
     * Command for persisting the historical records to a file off the UI thread.
     */
    private final class HistoryPersister implements Runnable {
    private final class PersistHistoryAsyncTask extends AsyncTask<Object, Void, Void> {

        public void run() {
            FileOutputStream fos = null;
            List<HistoricalRecord> records = null;
        @Override
        @SuppressWarnings("unchecked")
        public Void doInBackground(Object... args) {
            List<HistoricalRecord> historicalRecords = (List<HistoricalRecord>) args[0];
            String hostoryFileName = (String) args[1];

            synchronized (mInstanceLock) {
                records = new ArrayList<HistoricalRecord>(mHistoricalRecords);
            }
            FileOutputStream fos = null;

            try {
                fos = mContext.openFileOutput(mHistoryFileName, Context.MODE_PRIVATE);
                fos = mContext.openFileOutput(hostoryFileName, Context.MODE_PRIVATE);
            } catch (FileNotFoundException fnfe) {
                Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, fnfe);
                return;
                Log.e(LOG_TAG, "Error writing historical recrod file: " + hostoryFileName, fnfe);
                return null;
            }

            XmlSerializer serializer = Xml.newSerializer();
@@ -1112,11 +1075,12 @@ public class ActivityChooserModel extends DataSetObservable {
                serializer.startDocument("UTF-8", true);
                serializer.startTag(null, TAG_HISTORICAL_RECORDS);

                final int recordCount = records.size();
                final int recordCount = historicalRecords.size();
                for (int i = 0; i < recordCount; i++) {
                    HistoricalRecord record = records.remove(0);
                    HistoricalRecord record = historicalRecords.remove(0);
                    serializer.startTag(null, TAG_HISTORICAL_RECORD);
                    serializer.attribute(null, ATTRIBUTE_ACTIVITY, record.activity.flattenToString());
                    serializer.attribute(null, ATTRIBUTE_ACTIVITY,
                            record.activity.flattenToString());
                    serializer.attribute(null, ATTRIBUTE_TIME, String.valueOf(record.time));
                    serializer.attribute(null, ATTRIBUTE_WEIGHT, String.valueOf(record.weight));
                    serializer.endTag(null, TAG_HISTORICAL_RECORD);
@@ -1138,6 +1102,7 @@ public class ActivityChooserModel extends DataSetObservable {
            } catch (IOException ioe) {
                Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, ioe);
            } finally {
                mCanReadHistoricalData = true;
                if (fos != null) {
                    try {
                        fos.close();
@@ -1146,6 +1111,7 @@ public class ActivityChooserModel extends DataSetObservable {
                    }
                }
            }
            return null;
        }
    }

@@ -1155,33 +1121,8 @@ public class ActivityChooserModel extends DataSetObservable {
    private final class DataModelPackageMonitor extends PackageMonitor {

        @Override
        public void onPackageAdded(String packageName, int uid) {
            synchronized (mInstanceLock) {
                loadActivitiesLocked();
            }
        }

        @Override
        public void onPackageAppeared(String packageName, int reason) {
            synchronized (mInstanceLock) {
                loadActivitiesLocked();
            }
        }

        @Override
        public void onPackageRemoved(String packageName, int uid) {
            synchronized (mInstanceLock) {
                pruneHistoricalRecordsForPackageLocked(packageName);
                loadActivitiesLocked();
            }
        }

        @Override
        public void onPackageDisappeared(String packageName, int reason) {
            synchronized (mInstanceLock) {
                pruneHistoricalRecordsForPackageLocked(packageName);
                loadActivitiesLocked();
            }
        public void onSomePackagesChanged() {
            mReloadActivities = true;
        }
    }
}