Merge "Add a service to rank apps for ResolverActivity." into oc-dev

This commit is contained in:
TreeHugger Robot
2017-04-06 20:25:42 +00:00
committed by Android (Google) Code Review
13 changed files with 1107 additions and 205 deletions

View File

@ -321,6 +321,8 @@ LOCAL_SRC_FILES += \
core/java/android/service/wallpaper/IWallpaperService.aidl \
core/java/android/service/chooser/IChooserTargetService.aidl \
core/java/android/service/chooser/IChooserTargetResult.aidl \
core/java/android/service/resolver/IResolverRankerService.aidl \
core/java/android/service/resolver/IResolverRankerResult.aidl \
core/java/android/text/ITextClassificationService.aidl \
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
@ -729,6 +731,7 @@ aidl_files := \
frameworks/base/core/java/android/service/notification/SnoozeCriterion.aidl \
frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \
frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \
frameworks/base/core/java/android/service/resolver/ResolverTarget.aidl \
frameworks/base/core/java/android/speech/tts/Voice.aidl \
frameworks/base/core/java/android/app/usage/CacheQuotaHint.aidl \
frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \

View File

@ -53,6 +53,7 @@ package android {
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
field public static final java.lang.String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE";
field public static final java.lang.String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
@ -40627,6 +40628,36 @@ package android.service.quicksettings {
}
package android.service.resolver {
public abstract class ResolverRankerService extends android.app.Service {
ctor public ResolverRankerService();
method public android.os.IBinder onBind(android.content.Intent);
method public void onPredictSharingProbabilities(java.util.List<android.service.resolver.ResolverTarget>);
method public void onTrainRankingModel(java.util.List<android.service.resolver.ResolverTarget>, int);
field public static final java.lang.String BIND_PERMISSION = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.resolver.ResolverRankerService";
}
public final class ResolverTarget implements android.os.Parcelable {
ctor public ResolverTarget();
method public int describeContents();
method public float getChooserScore();
method public float getLaunchScore();
method public float getRecencyScore();
method public float getSelectProbability();
method public float getTimeSpentScore();
method public void setChooserScore(float);
method public void setLaunchScore(float);
method public void setRecencyScore(float);
method public void setSelectProbability(float);
method public void setTimeSpentScore(float);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.resolver.ResolverTarget> CREATOR;
}
}
package android.service.restrictions {
public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2017 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 android.service.resolver;
import android.service.resolver.ResolverTarget;
/**
* @hide
*/
oneway interface IResolverRankerResult
{
void sendResult(in List<ResolverTarget> results);
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2017 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 android.service.resolver;
import android.service.resolver.IResolverRankerResult;
import android.service.resolver.ResolverTarget;
/**
* @hide
*/
oneway interface IResolverRankerService
{
void predict(in List<ResolverTarget> targets, IResolverRankerResult result);
void train(in List<ResolverTarget> targets, int selectedPosition);
}

View File

@ -0,0 +1,187 @@
/*
* Copyright (C) 2017 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 android.service.resolver;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.service.resolver.ResolverTarget;
import android.util.Log;
import java.util.List;
import java.util.Map;
/**
* A service to rank apps according to usage stats of apps, when the system is resolving targets for
* an Intent.
*
* <p>To extend this class, you must declare the service in your manifest file with the
* {@link android.Manifest.permission#BIND_RESOLVER_RANKER_SERVICE} permission, and include an
* intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
* <pre>
* &lt;service android:name=".MyResolverRankerService"
* android:exported="true"
* android:priority="100"
* android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE"&gt;
* &lt;intent-filter&gt;
* &lt;action android:name="android.service.resolver.ResolverRankerService" /&gt;
* &lt;/intent-filter&gt;
* &lt;/service&gt;
* </pre>
* @hide
*/
@SystemApi
public abstract class ResolverRankerService extends Service {
private static final String TAG = "ResolverRankerService";
private static final boolean DEBUG = false;
/**
* The Intent action that a service must respond to. Add it to the intent filter of the service
* in its manifest.
*/
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.service.resolver.ResolverRankerService";
/**
* The permission that a service must require to ensure that only Android system can bind to it.
* If this permission is not enforced in the AndroidManifest of the service, the system will
* skip that service.
*/
public static final String BIND_PERMISSION = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
private ResolverRankerServiceWrapper mWrapper = null;
/**
* Called by the system to retrieve a list of probabilities to rank apps/options. To implement
* it, set selectProbability of each input {@link ResolverTarget}. The higher the
* selectProbability is, the more likely the {@link ResolverTarget} will be selected by the
* user. Override this function to provide prediction results.
*
* @param targets a list of {@link ResolverTarget}, for the list of apps to be ranked.
*
* @throws Exception when the prediction task fails.
*/
public void onPredictSharingProbabilities(final List<ResolverTarget> targets) {}
/**
* Called by the system to train/update a ranking service, after the user makes a selection from
* the ranked list of apps. Override this function to enable model updates.
*
* @param targets a list of {@link ResolverTarget}, for the list of apps to be ranked.
* @param selectedPosition the position of the selected app in the list.
*
* @throws Exception when the training task fails.
*/
public void onTrainRankingModel(
final List<ResolverTarget> targets, final int selectedPosition) {}
private static final String HANDLER_THREAD_NAME = "RESOLVER_RANKER_SERVICE";
private volatile Handler mHandler;
private HandlerThread mHandlerThread;
@Override
public IBinder onBind(Intent intent) {
if (DEBUG) Log.d(TAG, "onBind " + intent);
if (!SERVICE_INTERFACE.equals(intent.getAction())) {
if (DEBUG) Log.d(TAG, "bad intent action " + intent.getAction() + "; returning null");
return null;
}
if (mHandlerThread == null) {
mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
}
if (mWrapper == null) {
mWrapper = new ResolverRankerServiceWrapper();
}
return mWrapper;
}
@Override
public void onDestroy() {
mHandler = null;
if (mHandlerThread != null) {
mHandlerThread.quitSafely();
}
super.onDestroy();
}
private static void sendResult(List<ResolverTarget> targets, IResolverRankerResult result) {
try {
result.sendResult(targets);
} catch (Exception e) {
Log.e(TAG, "failed to send results: " + e);
}
}
private class ResolverRankerServiceWrapper extends IResolverRankerService.Stub {
@Override
public void predict(final List<ResolverTarget> targets, final IResolverRankerResult result)
throws RemoteException {
Runnable predictRunnable = new Runnable() {
@Override
public void run() {
try {
if (DEBUG) {
Log.d(TAG, "predict calls onPredictSharingProbabilities.");
}
onPredictSharingProbabilities(targets);
sendResult(targets, result);
} catch (Exception e) {
Log.e(TAG, "onPredictSharingProbabilities failed; send null results: " + e);
sendResult(null, result);
}
}
};
final Handler h = mHandler;
if (h != null) {
h.post(predictRunnable);
}
}
@Override
public void train(final List<ResolverTarget> targets, final int selectedPosition)
throws RemoteException {
Runnable trainRunnable = new Runnable() {
@Override
public void run() {
try {
if (DEBUG) {
Log.d(TAG, "train calls onTranRankingModel");
}
onTrainRankingModel(targets, selectedPosition);
} catch (Exception e) {
Log.e(TAG, "onTrainRankingModel failed; skip train: " + e);
}
}
};
final Handler h = mHandler;
if (h != null) {
h.post(trainRunnable);
}
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2017 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 android.service.resolver;
/**
* @hide
*/
parcelable ResolverTarget;

View File

@ -0,0 +1,216 @@
/*
* Copyright (C) 2017 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 android.service.resolver;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
import java.util.Map;
/**
* A ResolverTarget contains features by which an app or option will be ranked, in
* {@link ResolverRankerService}.
* @hide
*/
@SystemApi
public final class ResolverTarget implements Parcelable {
private static final String TAG = "ResolverTarget";
/**
* a float score for recency of last use.
*/
private float mRecencyScore;
/**
* a float score for total time spent.
*/
private float mTimeSpentScore;
/**
* a float score for number of launches.
*/
private float mLaunchScore;
/**
* a float score for number of selected.
*/
private float mChooserScore;
/**
* a float score for the probability to be selected.
*/
private float mSelectProbability;
// constructor for the class.
public ResolverTarget() {}
ResolverTarget(Parcel in) {
mRecencyScore = in.readFloat();
mTimeSpentScore = in.readFloat();
mLaunchScore = in.readFloat();
mChooserScore = in.readFloat();
mSelectProbability = in.readFloat();
}
/**
* Gets the score for how recently the target was used in the foreground.
*
* @return a float score whose range is [0, 1]. The higher the score is, the more recently the
* target was used.
*/
public float getRecencyScore() {
return mRecencyScore;
}
/**
* Sets the score for how recently the target was used in the foreground.
*
* @param recencyScore a float score whose range is [0, 1]. The higher the score is, the more
* recently the target was used.
*/
public void setRecencyScore(float recencyScore) {
this.mRecencyScore = recencyScore;
}
/**
* Gets the score for how long the target has been used in the foreground.
*
* @return a float score whose range is [0, 1]. The higher the score is, the longer the target
* has been used for.
*/
public float getTimeSpentScore() {
return mTimeSpentScore;
}
/**
* Sets the score for how long the target has been used in the foreground.
*
* @param timeSpentScore a float score whose range is [0, 1]. The higher the score is, the
* longer the target has been used for.
*/
public void setTimeSpentScore(float timeSpentScore) {
this.mTimeSpentScore = timeSpentScore;
}
/**
* Gets the score for how many times the target has been launched to the foreground.
*
* @return a float score whose range is [0, 1]. The higher the score is, the more times the
* target has been launched.
*/
public float getLaunchScore() {
return mLaunchScore;
}
/**
* Sets the score for how many times the target has been launched to the foreground.
*
* @param launchScore a float score whose range is [0, 1]. The higher the score is, the more
* times the target has been launched.
*/
public void setLaunchScore(float launchScore) {
this.mLaunchScore = launchScore;
}
/**
* Gets the score for how many times the target has been selected by the user to share the same
* types of content.
*
* @return a float score whose range is [0, 1]. The higher the score is, the
* more times the target has been selected by the user to share the same types of content for.
*/
public float getChooserScore() {
return mChooserScore;
}
/**
* Sets the score for how many times the target has been selected by the user to share the same
* types of content.
*
* @param chooserScore a float score whose range is [0, 1]. The higher the score is, the more
* times the target has been selected by the user to share the same types
* of content for.
*/
public void setChooserScore(float chooserScore) {
this.mChooserScore = chooserScore;
}
/**
* Gets the probability of how likely this target will be selected by the user.
*
* @return a float score whose range is [0, 1]. The higher the score is, the more likely the
* user is going to select this target.
*/
public float getSelectProbability() {
return mSelectProbability;
}
/**
* Sets the probability for how like this target will be selected by the user.
*
* @param selectProbability a float score whose range is [0, 1]. The higher the score is, the
* more likely tht user is going to select this target.
*/
public void setSelectProbability(float selectProbability) {
this.mSelectProbability = selectProbability;
}
// serialize the class to a string.
@Override
public String toString() {
return "ResolverTarget{"
+ mRecencyScore + ", "
+ mTimeSpentScore + ", "
+ mLaunchScore + ", "
+ mChooserScore + ", "
+ mSelectProbability + "}";
}
// describes the kinds of special objects contained in this Parcelable instance's marshaled
// representation.
@Override
public int describeContents() {
return 0;
}
// flattens this object in to a Parcel.
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(mRecencyScore);
dest.writeFloat(mTimeSpentScore);
dest.writeFloat(mLaunchScore);
dest.writeFloat(mChooserScore);
dest.writeFloat(mSelectProbability);
}
// creator definition for the class.
public static final Creator<ResolverTarget> CREATOR
= new Creator<ResolverTarget>() {
@Override
public ResolverTarget createFromParcel(Parcel source) {
return new ResolverTarget(source);
}
@Override
public ResolverTarget[] newArray(int size) {
return new ResolverTarget[size];
}
};
}

View File

@ -0,0 +1,199 @@
/*
* Copyright (C) 2017 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.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Environment;
import android.os.IBinder;
import android.os.storage.StorageManager;
import android.service.resolver.ResolverRankerService;
import android.service.resolver.ResolverTarget;
import android.util.ArrayMap;
import android.util.Log;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* A Logistic Regression based {@link android.service.resolver.ResolverRankerService}, to be used
* in {@link ResolverComparator}.
*/
public final class LRResolverRankerService extends ResolverRankerService {
private static final String TAG = "LRResolverRankerService";
private static final boolean DEBUG = false;
private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params";
private static final String BIAS_PREF_KEY = "bias";
private static final String VERSION_PREF_KEY = "version";
private static final String LAUNCH_SCORE = "launch";
private static final String TIME_SPENT_SCORE = "timeSpent";
private static final String RECENCY_SCORE = "recency";
private static final String CHOOSER_SCORE = "chooser";
// parameters for a pre-trained model, to initialize the app ranker. When updating the
// pre-trained model, please update these params, as well as initModel().
private static final int CURRENT_VERSION = 1;
private static final float LEARNING_RATE = 0.0001f;
private static final float REGULARIZER_PARAM = 0.0001f;
private SharedPreferences mParamSharedPref;
private ArrayMap<String, Float> mFeatureWeights;
private float mBias;
@Override
public IBinder onBind(Intent intent) {
initModel();
return super.onBind(intent);
}
@Override
public void onPredictSharingProbabilities(List<ResolverTarget> targets) {
final int size = targets.size();
for (int i = 0; i < size; ++i) {
ResolverTarget target = targets.get(i);
ArrayMap<String, Float> features = getFeatures(target);
target.setSelectProbability(predict(features));
}
}
@Override
public void onTrainRankingModel(List<ResolverTarget> targets, int selectedPosition) {
final int size = targets.size();
if (selectedPosition < 0 || selectedPosition >= size) {
if (DEBUG) {
Log.d(TAG, "Invalid Position of Selected App " + selectedPosition);
}
return;
}
final ArrayMap<String, Float> positive = getFeatures(targets.get(selectedPosition));
final float positiveProbability = targets.get(selectedPosition).getSelectProbability();
final int targetSize = targets.size();
for (int i = 0; i < targetSize; ++i) {
if (i == selectedPosition) {
continue;
}
final ArrayMap<String, Float> negative = getFeatures(targets.get(i));
final float negativeProbability = targets.get(i).getSelectProbability();
if (negativeProbability > positiveProbability) {
update(negative, negativeProbability, false);
update(positive, positiveProbability, true);
}
}
commitUpdate();
}
private void initModel() {
mParamSharedPref = getParamSharedPref();
mFeatureWeights = new ArrayMap<>(4);
if (mParamSharedPref == null ||
mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) {
// Initializing the app ranker to a pre-trained model. When updating the pre-trained
// model, please increment CURRENT_VERSION, and update LEARNING_RATE and
// REGULARIZER_PARAM.
mBias = -1.6568f;
mFeatureWeights.put(LAUNCH_SCORE, 2.5543f);
mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f);
mFeatureWeights.put(RECENCY_SCORE, 0.269f);
mFeatureWeights.put(CHOOSER_SCORE, 4.2222f);
} else {
mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f);
mFeatureWeights.put(LAUNCH_SCORE, mParamSharedPref.getFloat(LAUNCH_SCORE, 0.0f));
mFeatureWeights.put(
TIME_SPENT_SCORE, mParamSharedPref.getFloat(TIME_SPENT_SCORE, 0.0f));
mFeatureWeights.put(RECENCY_SCORE, mParamSharedPref.getFloat(RECENCY_SCORE, 0.0f));
mFeatureWeights.put(CHOOSER_SCORE, mParamSharedPref.getFloat(CHOOSER_SCORE, 0.0f));
}
}
private ArrayMap<String, Float> getFeatures(ResolverTarget target) {
ArrayMap<String, Float> features = new ArrayMap<>(4);
features.put(RECENCY_SCORE, target.getRecencyScore());
features.put(TIME_SPENT_SCORE, target.getTimeSpentScore());
features.put(LAUNCH_SCORE, target.getLaunchScore());
features.put(CHOOSER_SCORE, target.getChooserScore());
return features;
}
private float predict(ArrayMap<String, Float> target) {
if (target == null) {
return 0.0f;
}
final int featureSize = target.size();
float sum = 0.0f;
for (int i = 0; i < featureSize; i++) {
String featureName = target.keyAt(i);
float weight = mFeatureWeights.getOrDefault(featureName, 0.0f);
sum += weight * target.valueAt(i);
}
return (float) (1.0 / (1.0 + Math.exp(-mBias - sum)));
}
private void update(ArrayMap<String, Float> target, float predict, boolean isSelected) {
if (target == null) {
return;
}
final int featureSize = target.size();
float error = isSelected ? 1.0f - predict : -predict;
for (int i = 0; i < featureSize; i++) {
String featureName = target.keyAt(i);
float currentWeight = mFeatureWeights.getOrDefault(featureName, 0.0f);
mBias += LEARNING_RATE * error;
currentWeight = currentWeight - LEARNING_RATE * REGULARIZER_PARAM * currentWeight +
LEARNING_RATE * error * target.valueAt(i);
mFeatureWeights.put(featureName, currentWeight);
}
if (DEBUG) {
Log.d(TAG, "Weights: " + mFeatureWeights + " Bias: " + mBias);
}
}
private void commitUpdate() {
try {
SharedPreferences.Editor editor = mParamSharedPref.edit();
editor.putFloat(BIAS_PREF_KEY, mBias);
final int size = mFeatureWeights.size();
for (int i = 0; i < size; i++) {
editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i));
}
editor.putInt(VERSION_PREF_KEY, CURRENT_VERSION);
editor.apply();
} catch (Exception e) {
Log.e(TAG, "Failed to commit update" + e);
}
}
private SharedPreferences getParamSharedPref() {
// The package info in the context isn't initialized in the way it is for normal apps,
// so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
// build the path manually below using the same policy that appears in ContextImpl.
if (DEBUG) {
Log.d(TAG, "Context Package Name: " + getPackageName());
}
final File prefsFile = new File(new File(
Environment.getDataUserCePackageDirectory(
StorageManager.UUID_PRIVATE_INTERNAL, getUserId(), getPackageName()),
"shared_prefs"),
PARAM_SHARED_PREF_NAME + ".xml");
return getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
}
}

View File

@ -530,6 +530,9 @@ public class ResolverActivity extends Activity {
getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
mPostListReadyRunnable = null;
}
if (mAdapter != null && mAdapter.mResolverListController != null) {
mAdapter.mResolverListController.destroy();
}
}
@Override

View File

@ -26,20 +26,34 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.SharedPreferences;
import android.content.ServiceConnection;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.storage.StorageManager;
import android.os.UserHandle;
import android.service.resolver.IResolverRankerService;
import android.service.resolver.IResolverRankerResult;
import android.service.resolver.ResolverRankerService;
import android.service.resolver.ResolverTarget;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import java.io.File;
import java.lang.InterruptedException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -61,11 +75,15 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
private static final float RECENCY_MULTIPLIER = 2.f;
// feature names used in ranking.
private static final String LAUNCH_SCORE = "launch";
private static final String TIME_SPENT_SCORE = "timeSpent";
private static final String RECENCY_SCORE = "recency";
private static final String CHOOSER_SCORE = "chooser";
// message types
private static final int RESOLVER_RANKER_SERVICE_RESULT = 0;
private static final int RESOLVER_RANKER_RESULT_TIMEOUT = 1;
// timeout for establishing connections with a ResolverRankerService.
private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200;
// timeout for establishing connections with a ResolverRankerService, collecting features and
// predicting ranking scores.
private static final int WATCHDOG_TIMEOUT_MILLIS = 500;
private final Collator mCollator;
private final boolean mHttp;
@ -74,18 +92,74 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
private final Map<String, UsageStats> mStats;
private final long mCurrentTime;
private final long mSinceTime;
private final LinkedHashMap<ComponentName, ScoredTarget> mScoredTargets = new LinkedHashMap<>();
private final LinkedHashMap<ComponentName, ResolverTarget> mTargetsDict = new LinkedHashMap<>();
private final String mReferrerPackage;
private final Object mLock = new Object();
private ArrayList<ResolverTarget> mTargets;
private String mContentType;
private String[] mAnnotations;
private String mAction;
private LogisticRegressionAppRanker mRanker;
private IResolverRankerService mRanker;
private ResolverRankerServiceConnection mConnection;
private AfterCompute mAfterCompute;
private Context mContext;
private CountDownLatch mConnectSignal;
public ResolverComparator(Context context, Intent intent, String referrerPackage) {
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
public void handleMessage(Message msg) {
switch (msg.what) {
case RESOLVER_RANKER_SERVICE_RESULT:
if (DEBUG) {
Log.d(TAG, "RESOLVER_RANKER_SERVICE_RESULT");
}
if (mHandler.hasMessages(RESOLVER_RANKER_RESULT_TIMEOUT)) {
if (msg.obj != null) {
final List<ResolverTarget> receivedTargets =
(List<ResolverTarget>) msg.obj;
if (receivedTargets != null && mTargets != null
&& receivedTargets.size() == mTargets.size()) {
final int size = mTargets.size();
for (int i = 0; i < size; ++i) {
mTargets.get(i).setSelectProbability(
receivedTargets.get(i).getSelectProbability());
}
} else {
Log.e(TAG, "Sizes of sent and received ResolverTargets diff.");
}
} else {
Log.e(TAG, "Receiving null prediction results.");
}
mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT);
mAfterCompute.afterCompute();
}
break;
case RESOLVER_RANKER_RESULT_TIMEOUT:
if (DEBUG) {
Log.d(TAG, "RESOLVER_RANKER_RESULT_TIMEOUT; unbinding services");
}
mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT);
mAfterCompute.afterCompute();
break;
default:
super.handleMessage(msg);
}
}
};
public interface AfterCompute {
public void afterCompute ();
}
public ResolverComparator(Context context, Intent intent, String referrerPackage,
AfterCompute afterCompute) {
mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
String scheme = intent.getScheme();
mHttp = "http".equals(scheme) || "https".equals(scheme);
mReferrerPackage = referrerPackage;
mAfterCompute = afterCompute;
mContext = context;
mPm = context.getPackageManager();
mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
@ -96,9 +170,9 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
mContentType = intent.getType();
getContentAnnotations(intent);
mAction = intent.getAction();
mRanker = new LogisticRegressionAppRanker(context);
}
// get annotations of content from intent.
public void getContentAnnotations(Intent intent) {
ArrayList<String> annotations = intent.getStringArrayListExtra(
Intent.EXTRA_CONTENT_ANNOTATIONS);
@ -114,20 +188,24 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
}
}
public void setCallBack(AfterCompute afterCompute) {
mAfterCompute = afterCompute;
}
// compute features for each target according to usage stats of targets.
public void compute(List<ResolvedComponentInfo> targets) {
mScoredTargets.clear();
reset();
final long recentSinceTime = mCurrentTime - RECENCY_TIME_PERIOD;
long mostRecentlyUsedTime = recentSinceTime + 1;
long mostTimeSpent = 1;
int mostLaunched = 1;
int mostSelected = 1;
float mostRecencyScore = 1.0f;
float mostTimeSpentScore = 1.0f;
float mostLaunchScore = 1.0f;
float mostChooserScore = 1.0f;
for (ResolvedComponentInfo target : targets) {
final ScoredTarget scoredTarget
= new ScoredTarget(target.getResolveInfoAt(0).activityInfo);
mScoredTargets.put(target.name, scoredTarget);
final ResolverTarget resolverTarget = new ResolverTarget();
mTargetsDict.put(target.name, resolverTarget);
final UsageStats pkStats = mStats.get(target.name.getPackageName());
if (pkStats != null) {
// Only count recency for apps that weren't the caller
@ -135,31 +213,33 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
// 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 float recencyScore =
(float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
resolverTarget.setRecencyScore(recencyScore);
if (recencyScore > mostRecencyScore) {
mostRecencyScore = recencyScore;
}
}
final long timeSpent = pkStats.getTotalTimeInForeground();
scoredTarget.timeSpent = timeSpent;
if (timeSpent > mostTimeSpent) {
mostTimeSpent = timeSpent;
final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
resolverTarget.setTimeSpentScore(timeSpentScore);
if (timeSpentScore > mostTimeSpentScore) {
mostTimeSpentScore = timeSpentScore;
}
final int launched = pkStats.mLaunchCount;
scoredTarget.launchCount = launched;
if (launched > mostLaunched) {
mostLaunched = launched;
final float launchScore = (float) pkStats.mLaunchCount;
resolverTarget.setLaunchScore(launchScore);
if (launchScore > mostLaunchScore) {
mostLaunchScore = launchScore;
}
int selected = 0;
float chooserScore = 0.0f;
if (pkStats.mChooserCounts != null && mAction != null
&& pkStats.mChooserCounts.get(mAction) != null) {
selected = pkStats.mChooserCounts.get(mAction).getOrDefault(mContentType, 0);
chooserScore = (float) pkStats.mChooserCounts.get(mAction)
.getOrDefault(mContentType, 0);
if (mAnnotations != null) {
final int size = mAnnotations.length;
for (int i = 0; i < size; i++) {
selected += pkStats.mChooserCounts.get(mAction)
chooserScore += (float) pkStats.mChooserCounts.get(mAction)
.getOrDefault(mAnnotations[i], 0);
}
}
@ -169,44 +249,37 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
Log.d(TAG, "Action type is null");
} else {
Log.d(TAG, "Chooser Count of " + mAction + ":" +
target.name.getPackageName() + " is " + Integer.toString(selected));
target.name.getPackageName() + " is " +
Float.toString(chooserScore));
}
}
scoredTarget.chooserCount = selected;
if (selected > mostSelected) {
mostSelected = selected;
resolverTarget.setChooserScore(chooserScore);
if (chooserScore > mostChooserScore) {
mostChooserScore = chooserScore;
}
}
}
if (DEBUG) {
Log.d(TAG, "compute - mostRecentlyUsedTime: " + mostRecentlyUsedTime
+ " mostTimeSpent: " + mostTimeSpent
+ " recentSinceTime: " + recentSinceTime
+ " mostLaunched: " + mostLaunched);
Log.d(TAG, "compute - mostRecencyScore: " + mostRecencyScore
+ " mostTimeSpentScore: " + mostTimeSpentScore
+ " mostLaunchScore: " + mostLaunchScore
+ " mostChooserScore: " + mostChooserScore);
}
for (ScoredTarget target : mScoredTargets.values()) {
final float recency = (float) Math.max(target.lastTimeUsed - recentSinceTime, 0)
/ (mostRecentlyUsedTime - recentSinceTime);
target.setFeatures((float) target.launchCount / mostLaunched,
(float) target.timeSpent / mostTimeSpent,
recency * recency * RECENCY_MULTIPLIER,
(float) target.chooserCount / mostSelected);
target.selectProb = mRanker.predict(target.getFeatures());
mTargets = new ArrayList<>(mTargetsDict.values());
for (ResolverTarget target : mTargets) {
final float recency = target.getRecencyScore() / mostRecencyScore;
setFeatures(target, recency * recency * RECENCY_MULTIPLIER,
target.getLaunchScore() / mostLaunchScore,
target.getTimeSpentScore() / mostTimeSpentScore,
target.getChooserScore() / mostChooserScore);
addDefaultSelectProbability(target);
if (DEBUG) {
Log.d(TAG, "Scores: " + 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;
predictSelectProbabilities(mTargets);
}
@Override
@ -245,16 +318,16 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
// Pinned items stay stable within a normal lexical sort and ignore scoring.
if (!lPinned && !rPinned) {
if (mStats != null) {
final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
lhs.activityInfo.packageName, lhs.activityInfo.name));
final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName(
rhs.activityInfo.packageName, rhs.activityInfo.name));
final int selectProbDiff = Float.compare(
rhsTarget.selectProb, lhsTarget.selectProb);
final int selectProbabilityDiff = Float.compare(
rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
if (selectProbDiff != 0) {
return selectProbDiff > 0 ? 1 : -1;
if (selectProbabilityDiff != 0) {
return selectProbabilityDiff > 0 ? 1 : -1;
}
}
}
@ -268,177 +341,234 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
}
public float getScore(ComponentName name) {
final ScoredTarget target = mScoredTargets.get(name);
final ResolverTarget target = mTargetsDict.get(name);
if (target != null) {
return target.selectProb;
return target.getSelectProbability();
}
return 0;
}
static class ScoredTarget {
public final ComponentInfo componentInfo;
public long lastTimeUsed;
public long timeSpent;
public long launchCount;
public long chooserCount;
public ArrayMap<String, Float> features;
public float selectProb;
public ScoredTarget(ComponentInfo ci) {
componentInfo = ci;
features = new ArrayMap<>(5);
}
@Override
public String toString() {
return "ScoredTarget{" + componentInfo
+ " lastTimeUsed: " + lastTimeUsed
+ " timeSpent: " + timeSpent
+ " launchCount: " + launchCount
+ " chooserCount: " + chooserCount
+ " selectProb: " + selectProb
+ "}";
}
public void setFeatures(float launchCountScore, float usageTimeScore, float recencyScore,
float chooserCountScore) {
features.put(LAUNCH_SCORE, launchCountScore);
features.put(TIME_SPENT_SCORE, usageTimeScore);
features.put(RECENCY_SCORE, recencyScore);
features.put(CHOOSER_SCORE, chooserCountScore);
}
public ArrayMap<String, Float> getFeatures() {
return features;
}
}
public void updateChooserCounts(String packageName, int userId, String action) {
if (mUsm != null) {
mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
}
}
// update ranking model when the connection to it is valid.
public void updateModel(ComponentName componentName) {
if (mScoredTargets == null || componentName == null ||
!mScoredTargets.containsKey(componentName)) {
return;
}
ScoredTarget selected = mScoredTargets.get(componentName);
for (ComponentName targetComponent : mScoredTargets.keySet()) {
if (targetComponent.equals(componentName)) {
continue;
}
ScoredTarget target = mScoredTargets.get(targetComponent);
// A potential point of optimization. Save updates or derive a closed form for the
// positive case, to avoid calculating them repeatedly.
if (target.selectProb >= selected.selectProb) {
mRanker.update(target.getFeatures(), target.selectProb, false);
mRanker.update(selected.getFeatures(), selected.selectProb, true);
synchronized (mLock) {
if (mRanker != null) {
try {
int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
.indexOf(componentName);
if (selectedPos > 0) {
mRanker.train(mTargets, selectedPos);
} else {
if (DEBUG) {
Log.d(TAG, "Selected a unknown component: " + componentName);
}
}
} catch (RemoteException e) {
Log.e(TAG, "Error in Train: " + e);
}
} else {
if (DEBUG) {
Log.d(TAG, "Ranker is null; skip updateModel.");
}
}
}
mRanker.commitUpdate();
}
class LogisticRegressionAppRanker {
private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params";
private static final String BIAS_PREF_KEY = "bias";
private static final String VERSION_PREF_KEY = "version";
// parameters for a pre-trained model, to initialize the app ranker. When updating the
// pre-trained model, please update these params, as well as initModel().
private static final int CURRENT_VERSION = 1;
private static final float LEARNING_RATE = 0.0001f;
private static final float REGULARIZER_PARAM = 0.0001f;
private SharedPreferences mParamSharedPref;
private ArrayMap<String, Float> mFeatureWeights;
private float mBias;
public LogisticRegressionAppRanker(Context context) {
mParamSharedPref = getParamSharedPref(context);
initModel();
// unbind the service and clear unhandled messges.
public void destroy() {
mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT);
mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT);
if (mConnection != null) {
mContext.unbindService(mConnection);
mConnection.destroy();
}
public float predict(ArrayMap<String, Float> target) {
if (target == null) {
return 0.0f;
}
final int featureSize = target.size();
float sum = 0.0f;
for (int i = 0; i < featureSize; i++) {
String featureName = target.keyAt(i);
float weight = mFeatureWeights.getOrDefault(featureName, 0.0f);
sum += weight * target.valueAt(i);
}
return (float) (1.0 / (1.0 + Math.exp(-mBias - sum)));
if (DEBUG) {
Log.d(TAG, "Unbinded Resolver Ranker.");
}
}
public void update(ArrayMap<String, Float> target, float predict, boolean isSelected) {
if (target == null) {
// connect to a ranking service.
private void initRanker(Context context) {
synchronized (mLock) {
if (mConnection != null && mRanker != null) {
if (DEBUG) {
Log.d(TAG, "Ranker still exists; reusing the existing one.");
}
return;
}
final int featureSize = target.size();
float error = isSelected ? 1.0f - predict : -predict;
for (int i = 0; i < featureSize; i++) {
String featureName = target.keyAt(i);
float currentWeight = mFeatureWeights.getOrDefault(featureName, 0.0f);
mBias += LEARNING_RATE * error;
currentWeight = currentWeight - LEARNING_RATE * REGULARIZER_PARAM * currentWeight +
LEARNING_RATE * error * target.valueAt(i);
mFeatureWeights.put(featureName, currentWeight);
}
Intent intent = resolveRankerService();
if (intent == null) {
return;
}
mConnectSignal = new CountDownLatch(1);
mConnection = new ResolverRankerServiceConnection(mConnectSignal);
context.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
}
// resolve the service for ranking.
private Intent resolveRankerService() {
Intent intent = new Intent(ResolverRankerService.SERVICE_INTERFACE);
final List<ResolveInfo> resolveInfos = mPm.queryIntentServices(intent, 0);
for (ResolveInfo resolveInfo : resolveInfos) {
if (resolveInfo == null || resolveInfo.serviceInfo == null
|| resolveInfo.serviceInfo.applicationInfo == null) {
if (DEBUG) {
Log.d(TAG, "Failed to retrieve a ranker: " + resolveInfo);
}
continue;
}
ComponentName componentName = new ComponentName(
resolveInfo.serviceInfo.applicationInfo.packageName,
resolveInfo.serviceInfo.name);
try {
final String perm = mPm.getServiceInfo(componentName, 0).permission;
if (!ResolverRankerService.BIND_PERMISSION.equals(perm)) {
Log.w(TAG, "ResolverRankerService " + componentName + " does not require"
+ " permission " + ResolverRankerService.BIND_PERMISSION
+ " - this service will not be queried for ResolverComparator."
+ " add android:permission=\""
+ ResolverRankerService.BIND_PERMISSION + "\""
+ " to the <service> tag for " + componentName
+ " in the manifest.");
continue;
}
} catch (NameNotFoundException e) {
Log.e(TAG, "Could not look up service " + componentName
+ "; component name not found");
continue;
}
if (DEBUG) {
Log.d(TAG, "Weights: " + mFeatureWeights + " Bias: " + mBias);
Log.d(TAG, "Succeeded to retrieve a ranker: " + componentName);
}
intent.setComponent(componentName);
return intent;
}
return null;
}
// set a watchdog, to avoid waiting for ranking service for too long.
private void startWatchDog(int timeOutLimit) {
if (DEBUG) Log.d(TAG, "Setting watchdog timer for " + timeOutLimit + "ms");
if (mHandler == null) {
Log.d(TAG, "Error: Handler is Null; Needs to be initialized.");
}
mHandler.sendEmptyMessageDelayed(RESOLVER_RANKER_RESULT_TIMEOUT, timeOutLimit);
}
private class ResolverRankerServiceConnection implements ServiceConnection {
private final CountDownLatch mConnectSignal;
public ResolverRankerServiceConnection(CountDownLatch connectSignal) {
mConnectSignal = connectSignal;
}
public void commitUpdate() {
SharedPreferences.Editor editor = mParamSharedPref.edit();
editor.putFloat(BIAS_PREF_KEY, mBias);
final int size = mFeatureWeights.size();
for (int i = 0; i < size; i++) {
editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i));
public final IResolverRankerResult resolverRankerResult =
new IResolverRankerResult.Stub() {
@Override
public void sendResult(List<ResolverTarget> targets) throws RemoteException {
if (DEBUG) {
Log.d(TAG, "Sending Result back to Resolver: " + targets);
}
synchronized (mLock) {
final Message msg = Message.obtain();
msg.what = RESOLVER_RANKER_SERVICE_RESULT;
msg.obj = targets;
mHandler.sendMessage(msg);
}
}
editor.putInt(VERSION_PREF_KEY, CURRENT_VERSION);
editor.apply();
}
};
private SharedPreferences getParamSharedPref(Context context) {
// The package info in the context isn't initialized in the way it is for normal apps,
// so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
// build the path manually below using the same policy that appears in ContextImpl.
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) {
Log.d(TAG, "Context Package Name: " + context.getPackageName());
Log.d(TAG, "onServiceConnected: " + name);
}
synchronized (mLock) {
mRanker = IResolverRankerService.Stub.asInterface(service);
mConnectSignal.countDown();
}
final File prefsFile = new File(new File(
Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
context.getUserId(), context.getPackageName()),
"shared_prefs"),
PARAM_SHARED_PREF_NAME + ".xml");
return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
}
private void initModel() {
mFeatureWeights = new ArrayMap<>(4);
if (mParamSharedPref == null ||
mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) {
// Initializing the app ranker to a pre-trained model. When updating the pre-trained
// model, please increment CURRENT_VERSION, and update LEARNING_RATE and
// REGULARIZER_PARAM.
mBias = -1.6568f;
mFeatureWeights.put(LAUNCH_SCORE, 2.5543f);
mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f);
mFeatureWeights.put(RECENCY_SCORE, 0.269f);
mFeatureWeights.put(CHOOSER_SCORE, 4.2222f);
} else {
mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f);
mFeatureWeights.put(LAUNCH_SCORE, mParamSharedPref.getFloat(LAUNCH_SCORE, 0.0f));
mFeatureWeights.put(
TIME_SPENT_SCORE, mParamSharedPref.getFloat(TIME_SPENT_SCORE, 0.0f));
mFeatureWeights.put(RECENCY_SCORE, mParamSharedPref.getFloat(RECENCY_SCORE, 0.0f));
mFeatureWeights.put(CHOOSER_SCORE, mParamSharedPref.getFloat(CHOOSER_SCORE, 0.0f));
@Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) {
Log.d(TAG, "onServiceDisconnected: " + name);
}
synchronized (mLock) {
destroy();
}
}
public void destroy() {
synchronized (mLock) {
mRanker = null;
}
}
}
private void reset() {
mTargetsDict.clear();
mTargets = null;
startWatchDog(WATCHDOG_TIMEOUT_MILLIS);
initRanker(mContext);
}
// predict select probabilities if ranking service is valid.
private void predictSelectProbabilities(List<ResolverTarget> targets) {
if (mConnection == null) {
if (DEBUG) {
Log.d(TAG, "Has not found valid ResolverRankerService; Skip Prediction");
}
return;
} else {
try {
mConnectSignal.await(CONNECTION_COST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
synchronized (mLock) {
if (mRanker != null) {
mRanker.predict(targets, mConnection.resolverRankerResult);
return;
} else {
if (DEBUG) {
Log.d(TAG, "Ranker has not been initialized; skip predict.");
}
}
}
} catch (InterruptedException e) {
Log.e(TAG, "Error in Wait for Service Connection.");
} catch (RemoteException e) {
Log.e(TAG, "Error in Predict: " + e);
}
}
mAfterCompute.afterCompute();
}
// adds select prob as the default values, according to a pre-trained Logistic Regression model.
private void addDefaultSelectProbability(ResolverTarget target) {
float sum = 2.5543f * target.getLaunchScore() + 2.8412f * target.getTimeSpentScore() +
0.269f * target.getRecencyScore() + 4.2222f * target.getChooserScore();
target.setSelectProbability((float) (1.0 / (1.0 + Math.exp(1.6568f - sum))));
}
// sets features for each target
private void setFeatures(ResolverTarget target, float recencyScore, float launchScore,
float timeSpentScore, float chooserScore) {
target.setRecencyScore(recencyScore);
target.setLaunchScore(launchScore);
target.setTimeSpentScore(timeSpentScore);
target.setChooserScore(chooserScore);
}
static boolean isPersistentProcess(ResolvedComponentInfo rci) {
if (rci != null && rci.getCount() > 0) {
return (rci.getResolveInfoAt(0).activityInfo.applicationInfo.flags &
ApplicationInfo.FLAG_PERSISTENT) != 0;
}
return false;
}
}

View File

@ -32,8 +32,10 @@ import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.InterruptedException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.List;
/**
@ -205,14 +207,42 @@ public class ResolverListController {
return listToReturn;
}
private class ComputeCallback implements ResolverComparator.AfterCompute {
private CountDownLatch mFinishComputeSignal;
public ComputeCallback(CountDownLatch finishComputeSignal) {
mFinishComputeSignal = finishComputeSignal;
}
public void afterCompute () {
mFinishComputeSignal.countDown();
}
}
@VisibleForTesting
@WorkerThread
public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) {
final CountDownLatch finishComputeSignal = new CountDownLatch(1);
ComputeCallback callback = new ComputeCallback(finishComputeSignal);
if (mResolverComparator == null) {
mResolverComparator = new ResolverComparator(mContext, mTargetIntent, mReferrerPackage);
mResolverComparator =
new ResolverComparator(mContext, mTargetIntent, mReferrerPackage, callback);
} else {
mResolverComparator.setCallBack(callback);
}
try {
long beforeRank = System.currentTimeMillis();
mResolverComparator.compute(inputList);
finishComputeSignal.await();
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);
}
mResolverComparator.compute(inputList);
Collections.sort(inputList, mResolverComparator);
}
private static boolean isSameResolvedComponent(ResolveInfo a,
@ -233,7 +263,7 @@ public class ResolverListController {
@VisibleForTesting
public float getScore(ResolverActivity.DisplayResolveInfo target) {
if (mResolverComparator == null) {
mResolverComparator = new ResolverComparator(mContext, mTargetIntent, mReferrerPackage);
return 0.0f;
}
return mResolverComparator.getScore(target.getResolvedComponentName());
}
@ -249,4 +279,10 @@ public class ResolverListController {
mResolverComparator.updateChooserCounts(packageName, userId, action);
}
}
public void destroy() {
if (mResolverComparator != null) {
mResolverComparator.destroy();
}
}
}

View File

@ -3130,6 +3130,15 @@
<permission android:name="android.permission.BIND_CHOOSER_TARGET_SERVICE"
android:protectionLevel="signature" />
<!-- @SystemApi Must be required by services that extend
{@link android.service.resolver.ResolverRankerService}, to ensure that only the system can
bind to them.
<p>Protection level: signature
@hide
-->
<permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE"
android:protectionLevel="signature" />
<!-- Must be required by a {@link
android.service.notification.ConditionProviderService},
to ensure that only the system can bind to it.
@ -3641,6 +3650,13 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<service android:name="com.android.internal.app.LRResolverRankerService"
android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE"
android:priority="-1" >
<intent-filter>
<action android:name="android.service.resolver.ResolverRankerService" />
</intent-filter>
</service>
</application>
</manifest>

View File

@ -183,6 +183,9 @@
<!-- to control accessibility volume -->
<uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
<!-- to access ResolverRankerServices -->
<uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" />
<application
android:name=".SystemUIApplication"
android:persistent="true"