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

Commit b535c5e2 authored by Adam Powell's avatar Adam Powell Committed by Android (Google) Code Review
Browse files

Merge "Start using some better sorting for intent resolution" into mnc-dev

parents fb48b014 d25267c0
Loading
Loading
Loading
Loading
+7 −75
Original line number Diff line number Diff line
@@ -18,8 +18,6 @@ package com.android.internal.app;

import android.app.Activity;
import android.app.ActivityThread;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.os.AsyncTask;
import android.provider.Settings;
import android.text.TextUtils;
@@ -64,14 +62,11 @@ import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.widget.ResolverDrawerLayout;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -100,10 +95,7 @@ public class ResolverActivity extends Activity {
    private boolean mResolvingHome = false;
    private int mProfileSwitchMessageId = -1;
    private final ArrayList<Intent> mIntents = new ArrayList<>();

    private UsageStatsManager mUsm;
    private Map<String, UsageStats> mStats;
    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
    private ResolverComparator mResolverComparator;

    private boolean mRegistered;
    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@@ -222,10 +214,6 @@ public class ResolverActivity extends Activity {
        }

        mPm = getPackageManager();
        mUsm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);

        final long sinceTime = System.currentTimeMillis() - USAGE_STATS_PERIOD;
        mStats = mUsm.queryAndAggregateUsageStats(sinceTime, System.currentTimeMillis());

        mPackageMonitor.register(this, getMainLooper(), false);
        mRegistered = true;
@@ -236,6 +224,10 @@ public class ResolverActivity extends Activity {
        // Add our initial intent as the first item, regardless of what else has already been added.
        mIntents.add(0, new Intent(intent));

        final String referrerPackage = getReferrerPackageName();

        mResolverComparator = new ResolverComparator(this, getTargetIntent(), referrerPackage);

        configureContentView(mIntents, initialIntents, rList, alwaysUseOption);

        // Prevent the Resolver window from becoming the top fullscreen window and thus from taking
@@ -265,7 +257,6 @@ public class ResolverActivity extends Activity {
            // Try to initialize the title icon if we have a view for it and a title to match
            final ImageView titleIcon = (ImageView) findViewById(R.id.title_icon);
            if (titleIcon != null) {
                final String referrerPackage = getReferrerPackageName();
                ApplicationInfo ai = null;
                try {
                    if (!TextUtils.isEmpty(referrerPackage)) {
@@ -1175,8 +1166,8 @@ public class ResolverActivity extends Activity {
                    }
                }
                if (N > 1) {
                    Collections.sort(currentResolveList,
                            new ResolverComparator(ResolverActivity.this, getTargetIntent()));
                    mResolverComparator.compute(currentResolveList);
                    Collections.sort(currentResolveList, mResolverComparator);
                }
                // First put the initial items at the top.
                if (mInitialIntents != null) {
@@ -1651,63 +1642,4 @@ public class ResolverActivity extends Activity {
                && match <= IntentFilter.MATCH_CATEGORY_PATH;
    }

    class ResolverComparator implements Comparator<ResolvedComponentInfo> {
        private final Collator mCollator;
        private final boolean mHttp;

        public ResolverComparator(Context context, Intent intent) {
            mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
            String scheme = intent.getScheme();
            mHttp = "http".equals(scheme) || "https".equals(scheme);
        }

        @Override
        public int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) {
            final ResolveInfo lhs = lhsp.getResolveInfoAt(0);
            final ResolveInfo rhs = rhsp.getResolveInfoAt(0);

            // We want to put the one targeted to another user at the end of the dialog.
            if (lhs.targetUserId != UserHandle.USER_CURRENT) {
                return 1;
            }

            if (mHttp) {
                // Special case: we want filters that match URI paths/schemes to be
                // ordered before others.  This is for the case when opening URIs,
                // to make native apps go above browsers.
                final boolean lhsSpecific = isSpecificUriMatch(lhs.match);
                final boolean rhsSpecific = isSpecificUriMatch(rhs.match);
                if (lhsSpecific != rhsSpecific) {
                    return lhsSpecific ? -1 : 1;
                }
            }

            if (mStats != null) {
                final long timeDiff =
                        getPackageTimeSpent(rhs.activityInfo.packageName) -
                        getPackageTimeSpent(lhs.activityInfo.packageName);

                if (timeDiff != 0) {
                    return timeDiff > 0 ? 1 : -1;
                }
            }

            CharSequence  sa = lhs.loadLabel(mPm);
            if (sa == null) sa = lhs.activityInfo.name;
            CharSequence  sb = rhs.loadLabel(mPm);
            if (sb == null) sb = rhs.activityInfo.name;

            return mCollator.compare(sa.toString(), sb.toString());
        }

        private long getPackageTimeSpent(String packageName) {
            if (mStats != null) {
                final UsageStats stats = mStats.get(packageName);
                if (stats != null) {
                    return stats.getTotalTimeInForeground();
                }
            }
            return 0;
        }
    }
}
+215 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package com.android.internal.app;

import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Ranks and compares packages based on usage stats.
 */
class ResolverComparator implements Comparator<ResolvedComponentInfo> {
    private static final String TAG = "ResolverComparator";

    private static final boolean DEBUG = true;

    // Two weeks
    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;

    private static final long RECENCY_TIME_PERIOD = 1000 * 60 * 60 * 12;

    private static final float RECENCY_MULTIPLIER = 3.f;

    private final Collator mCollator;
    private final boolean mHttp;
    private final PackageManager mPm;
    private final UsageStatsManager mUsm;
    private final Map<String, UsageStats> mStats;
    private final long mCurrentTime;
    private final long mSinceTime;
    private final LinkedHashMap<ComponentName, ScoredTarget> mScoredTargets = new LinkedHashMap<>();
    private final String mReferrerPackage;

    public ResolverComparator(Context context, Intent intent, String referrerPackage) {
        mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
        String scheme = intent.getScheme();
        mHttp = "http".equals(scheme) || "https".equals(scheme);
        mReferrerPackage = referrerPackage;

        mPm = context.getPackageManager();
        mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);

        mCurrentTime = System.currentTimeMillis();
        mSinceTime = mCurrentTime - USAGE_STATS_PERIOD;
        mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime);
    }

    public void compute(List<ResolvedComponentInfo> targets) {
        mScoredTargets.clear();

        final long recentSinceTime = mCurrentTime - RECENCY_TIME_PERIOD;

        long mostRecentlyUsedTime = recentSinceTime + 1;
        long mostTimeSpent = 1;
        int mostLaunched = 1;

        for (ResolvedComponentInfo target : targets) {
            final ScoredTarget scoredTarget
                    = new ScoredTarget(target.getResolveInfoAt(0).activityInfo);
            mScoredTargets.put(target.name, scoredTarget);
            final UsageStats pkStats = mStats.get(target.name.getPackageName());
            if (pkStats != null) {
                // Only count recency for apps that weren't the caller
                // since the caller is always the most recent.
                // Persistent processes muck this up, so omit them too.
                if (!target.name.getPackageName().equals(mReferrerPackage)
                        && !isPersistentProcess(target)) {
                    final long lastTimeUsed = pkStats.getLastTimeUsed();
                    scoredTarget.lastTimeUsed = lastTimeUsed;
                    if (lastTimeUsed > mostRecentlyUsedTime) {
                        mostRecentlyUsedTime = lastTimeUsed;
                    }
                }
                final long timeSpent = pkStats.getTotalTimeInForeground();
                scoredTarget.timeSpent = timeSpent;
                if (timeSpent > mostTimeSpent) {
                    mostTimeSpent = timeSpent;
                }
                final int launched = pkStats.mLaunchCount;
                scoredTarget.launchCount = launched;
                if (launched > mostLaunched) {
                    mostLaunched = launched;
                }
            }
        }


        if (DEBUG) {
            Log.d(TAG, "compute - mostRecentlyUsedTime: " + mostRecentlyUsedTime
                    + " mostTimeSpent: " + mostTimeSpent
                    + " recentSinceTime: " + recentSinceTime
                    + " mostLaunched: " + mostLaunched);
        }

        for (ScoredTarget target : mScoredTargets.values()) {
            final float recency = (float) Math.max(target.lastTimeUsed - recentSinceTime, 0)
                    / (mostRecentlyUsedTime - recentSinceTime);
            final float recencyScore = recency * recency * RECENCY_MULTIPLIER;
            final float usageTimeScore = (float) target.timeSpent / mostTimeSpent;
            final float launchCountScore = (float) target.launchCount / mostLaunched;

            target.score = recencyScore + usageTimeScore + launchCountScore;
            if (DEBUG) {
                Log.d(TAG, "Scores: recencyScore: " + recencyScore
                        + " usageTimeScore: " + usageTimeScore
                        + " launchCountScore: " + launchCountScore
                        + " - " + target);
            }
        }
    }

    static boolean isPersistentProcess(ResolvedComponentInfo rci) {
        if (rci != null && rci.getCount() > 0) {
            return (rci.getResolveInfoAt(0).activityInfo.applicationInfo.flags &
                    ApplicationInfo.FLAG_PERSISTENT) != 0;
        }
        return false;
    }

    @Override
    public int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) {
        final ResolveInfo lhs = lhsp.getResolveInfoAt(0);
        final ResolveInfo rhs = rhsp.getResolveInfoAt(0);

        // We want to put the one targeted to another user at the end of the dialog.
        if (lhs.targetUserId != UserHandle.USER_CURRENT) {
            return 1;
        }

        if (mHttp) {
            // Special case: we want filters that match URI paths/schemes to be
            // ordered before others.  This is for the case when opening URIs,
            // to make native apps go above browsers.
            final boolean lhsSpecific = ResolverActivity.isSpecificUriMatch(lhs.match);
            final boolean rhsSpecific = ResolverActivity.isSpecificUriMatch(rhs.match);
            if (lhsSpecific != rhsSpecific) {
                return lhsSpecific ? -1 : 1;
            }
        }

        if (mStats != null) {
            final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
                    lhs.activityInfo.packageName, lhs.activityInfo.name));
            final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
                    rhs.activityInfo.packageName, rhs.activityInfo.name));
            final float diff = rhsTarget.score - lhsTarget.score;

            if (diff != 0) {
                return diff > 0 ? 1 : -1;
            }
        }

        CharSequence  sa = lhs.loadLabel(mPm);
        if (sa == null) sa = lhs.activityInfo.name;
        CharSequence  sb = rhs.loadLabel(mPm);
        if (sb == null) sb = rhs.activityInfo.name;

        return mCollator.compare(sa.toString().trim(), sb.toString().trim());
    }

    static class ScoredTarget {
        public final ComponentInfo componentInfo;
        public float score;
        public long lastTimeUsed;
        public long timeSpent;
        public long launchCount;

        public ScoredTarget(ComponentInfo ci) {
            componentInfo = ci;
        }

        @Override
        public String toString() {
            return "ScoredTarget{" + componentInfo
                    + " score: " + score
                    + " lastTimeUsed: " + lastTimeUsed
                    + " timeSpent: " + timeSpent
                    + " launchCount: " + launchCount
                    + "}";
        }
    }
}