blob: 5f92cddbaa38c0f3df37f15d75a10f3ddf01ebe7 [file] [log] [blame]
/*
* Copyright (C) 2016 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.annotation.WorkerThread;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* A helper for the ResolverActivity that exposes methods to retrieve, filter and sort its list of
* resolvers.
*/
public class ResolverListController {
private final Context mContext;
private final PackageManager mpm;
private final int mLaunchedFromUid;
// Needed for sorting resolvers.
private final Intent mTargetIntent;
private final String mReferrerPackage;
private static final String TAG = "ResolverListController";
private static final boolean DEBUG = false;
private AbstractResolverComparator mResolverComparator;
private boolean isComputed = false;
public ResolverListController(
Context context,
PackageManager pm,
Intent targetIntent,
String referrerPackage,
int launchedFromUid) {
this(context, pm, targetIntent, referrerPackage, launchedFromUid,
new ResolverRankerServiceResolverComparator(
context, targetIntent, referrerPackage, null));
}
public ResolverListController(
Context context,
PackageManager pm,
Intent targetIntent,
String referrerPackage,
int launchedFromUid,
AbstractResolverComparator resolverComparator) {
mContext = context;
mpm = pm;
mLaunchedFromUid = launchedFromUid;
mTargetIntent = targetIntent;
mReferrerPackage = referrerPackage;
mResolverComparator = resolverComparator;
}
@VisibleForTesting
public ResolveInfo getLastChosen() throws RemoteException {
return AppGlobals.getPackageManager().getLastChosenActivity(
mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
PackageManager.MATCH_DEFAULT_ONLY);
}
@VisibleForTesting
public void setLastChosen(Intent intent, IntentFilter filter, int match)
throws RemoteException {
AppGlobals.getPackageManager().setLastChosenActivity(intent,
intent.resolveType(mContext.getContentResolver()),
PackageManager.MATCH_DEFAULT_ONLY,
filter, match, intent.getComponent());
}
@VisibleForTesting
public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent(
boolean shouldGetResolvedFilter,
boolean shouldGetActivityMetadata,
List<Intent> intents) {
List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null;
for (int i = 0, N = intents.size(); i < N; i++) {
final Intent intent = intents.get(i);
int flags = PackageManager.MATCH_DEFAULT_ONLY
| (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
| (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
if (intent.isWebIntent()
|| (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
flags |= PackageManager.MATCH_INSTANT;
}
final List<ResolveInfo> infos = mpm.queryIntentActivities(intent, flags);
// Remove any activities that are not exported.
int totalSize = infos.size();
for (int j = totalSize - 1; j >= 0 ; j--) {
ResolveInfo info = infos.get(j);
if (info.activityInfo != null && !info.activityInfo.exported) {
infos.remove(j);
}
}
if (infos != null) {
if (resolvedComponents == null) {
resolvedComponents = new ArrayList<>();
}
addResolveListDedupe(resolvedComponents, intent, infos);
}
}
return resolvedComponents;
}
@VisibleForTesting
public void addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into,
Intent intent,
List<ResolveInfo> from) {
final int fromCount = from.size();
final int intoCount = into.size();
for (int i = 0; i < fromCount; i++) {
final ResolveInfo newInfo = from.get(i);
boolean found = false;
// Only loop to the end of into as it was before we started; no dupes in from.
for (int j = 0; j < intoCount; j++) {
final ResolverActivity.ResolvedComponentInfo rci = into.get(j);
if (isSameResolvedComponent(newInfo, rci)) {
found = true;
rci.add(intent, newInfo);
break;
}
}
if (!found) {
final ComponentName name = new ComponentName(
newInfo.activityInfo.packageName, newInfo.activityInfo.name);
final ResolverActivity.ResolvedComponentInfo rci =
new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo);
into.add(rci);
}
}
}
// Filter out any activities that the launched uid does not have permission for.
// To preserve the inputList, optionally will return the original list if any modification has
// been made.
@VisibleForTesting
public ArrayList<ResolverActivity.ResolvedComponentInfo> filterIneligibleActivities(
List<ResolverActivity.ResolvedComponentInfo> inputList,
boolean returnCopyOfOriginalListIfModified) {
ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null;
for (int i = inputList.size()-1; i >= 0; i--) {
ActivityInfo ai = inputList.get(i)
.getResolveInfoAt(0).activityInfo;
int granted = ActivityManager.checkComponentPermission(
ai.permission, mLaunchedFromUid,
ai.applicationInfo.uid, ai.exported);
if (granted != PackageManager.PERMISSION_GRANTED
|| isComponentFiltered(ai.getComponentName())) {
// Access not allowed! We're about to filter an item,
// so modify the unfiltered version if it hasn't already been modified.
if (returnCopyOfOriginalListIfModified && listToReturn == null) {
listToReturn = new ArrayList<>(inputList);
}
inputList.remove(i);
}
}
return listToReturn;
}
// Filter out any low priority items.
//
// To preserve the inputList, optionally will return the original list if any modification has
// been made.
@VisibleForTesting
public ArrayList<ResolverActivity.ResolvedComponentInfo> filterLowPriority(
List<ResolverActivity.ResolvedComponentInfo> inputList,
boolean returnCopyOfOriginalListIfModified) {
ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null;
// Only display the first matches that are either of equal
// priority or have asked to be default options.
ResolverActivity.ResolvedComponentInfo rci0 = inputList.get(0);
ResolveInfo r0 = rci0.getResolveInfoAt(0);
int N = inputList.size();
for (int i = 1; i < N; i++) {
ResolveInfo ri = inputList.get(i).getResolveInfoAt(0);
if (DEBUG) Log.v(
TAG,
r0.activityInfo.name + "=" +
r0.priority + "/" + r0.isDefault + " vs " +
ri.activityInfo.name + "=" +
ri.priority + "/" + ri.isDefault);
if (r0.priority != ri.priority ||
r0.isDefault != ri.isDefault) {
while (i < N) {
if (returnCopyOfOriginalListIfModified && listToReturn == null) {
listToReturn = new ArrayList<>(inputList);
}
inputList.remove(i);
N--;
}
}
}
return listToReturn;
}
private class ComputeCallback implements AbstractResolverComparator.AfterCompute {
private CountDownLatch mFinishComputeSignal;
public ComputeCallback(CountDownLatch finishComputeSignal) {
mFinishComputeSignal = finishComputeSignal;
}
public void afterCompute () {
mFinishComputeSignal.countDown();
}
}
@VisibleForTesting
@WorkerThread
public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) {
if (mResolverComparator == null) {
Log.d(TAG, "Comparator has already been destroyed; skipped.");
return;
}
try {
long beforeRank = System.currentTimeMillis();
if (!isComputed) {
final CountDownLatch finishComputeSignal = new CountDownLatch(1);
ComputeCallback callback = new ComputeCallback(finishComputeSignal);
mResolverComparator.setCallBack(callback);
mResolverComparator.compute(inputList);
finishComputeSignal.await();
isComputed = true;
}
Collections.sort(inputList, mResolverComparator);
long afterRank = System.currentTimeMillis();
if (DEBUG) {
Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank));
}
} catch (InterruptedException e) {
Log.e(TAG, "Compute & Sort was interrupted: " + e);
}
}
private static boolean isSameResolvedComponent(ResolveInfo a,
ResolverActivity.ResolvedComponentInfo b) {
final ActivityInfo ai = a.activityInfo;
return ai.packageName.equals(b.name.getPackageName())
&& ai.name.equals(b.name.getClassName());
}
boolean isComponentFiltered(ComponentName componentName) {
return false;
}
@VisibleForTesting
public float getScore(ResolverActivity.DisplayResolveInfo target) {
return mResolverComparator.getScore(target.getResolvedComponentName());
}
public void updateModel(ComponentName componentName) {
mResolverComparator.updateModel(componentName);
}
public void updateChooserCounts(String packageName, int userId, String action) {
mResolverComparator.updateChooserCounts(packageName, userId, action);
}
public void destroy() {
mResolverComparator.destroy();
}
}