Merge "Add a service to rank apps for ResolverActivity." into oc-dev
This commit is contained in:
@@ -320,6 +320,8 @@ LOCAL_SRC_FILES += \
|
|||||||
core/java/android/service/wallpaper/IWallpaperService.aidl \
|
core/java/android/service/wallpaper/IWallpaperService.aidl \
|
||||||
core/java/android/service/chooser/IChooserTargetService.aidl \
|
core/java/android/service/chooser/IChooserTargetService.aidl \
|
||||||
core/java/android/service/chooser/IChooserTargetResult.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/text/ITextClassificationService.aidl \
|
||||||
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
|
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
|
||||||
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
|
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
|
||||||
@@ -728,6 +730,7 @@ aidl_files := \
|
|||||||
frameworks/base/core/java/android/service/notification/SnoozeCriterion.aidl \
|
frameworks/base/core/java/android/service/notification/SnoozeCriterion.aidl \
|
||||||
frameworks/base/core/java/android/service/notification/StatusBarNotification.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/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/speech/tts/Voice.aidl \
|
||||||
frameworks/base/core/java/android/app/usage/CacheQuotaHint.aidl \
|
frameworks/base/core/java/android/app/usage/CacheQuotaHint.aidl \
|
||||||
frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \
|
frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \
|
||||||
|
|||||||
@@ -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_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_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_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_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_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
|
||||||
field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
|
field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
|
||||||
@@ -40478,6 +40479,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 {
|
package android.service.restrictions {
|
||||||
|
|
||||||
public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
|
public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
187
core/java/android/service/resolver/ResolverRankerService.java
Normal file
187
core/java/android/service/resolver/ResolverRankerService.java
Normal 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>
|
||||||
|
* <service android:name=".MyResolverRankerService"
|
||||||
|
* android:exported="true"
|
||||||
|
* android:priority="100"
|
||||||
|
* android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE">
|
||||||
|
* <intent-filter>
|
||||||
|
* <action android:name="android.service.resolver.ResolverRankerService" />
|
||||||
|
* </intent-filter>
|
||||||
|
* </service>
|
||||||
|
* </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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
core/java/android/service/resolver/ResolverTarget.aidl
Normal file
22
core/java/android/service/resolver/ResolverTarget.aidl
Normal 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;
|
||||||
216
core/java/android/service/resolver/ResolverTarget.java
Normal file
216
core/java/android/service/resolver/ResolverTarget.java
Normal 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];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
199
core/java/com/android/internal/app/LRResolverRankerService.java
Normal file
199
core/java/com/android/internal/app/LRResolverRankerService.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -530,6 +530,9 @@ public class ResolverActivity extends Activity {
|
|||||||
getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
|
getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
|
||||||
mPostListReadyRunnable = null;
|
mPostListReadyRunnable = null;
|
||||||
}
|
}
|
||||||
|
if (mAdapter != null && mAdapter.mResolverListController != null) {
|
||||||
|
mAdapter.mResolverListController.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -26,20 +26,34 @@ import android.content.IntentFilter;
|
|||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.ComponentInfo;
|
import android.content.pm.ComponentInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.os.Environment;
|
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.storage.StorageManager;
|
||||||
import android.os.UserHandle;
|
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.text.TextUtils;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
|
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.lang.InterruptedException;
|
||||||
import java.text.Collator;
|
import java.text.Collator;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -61,11 +75,15 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
|
|||||||
|
|
||||||
private static final float RECENCY_MULTIPLIER = 2.f;
|
private static final float RECENCY_MULTIPLIER = 2.f;
|
||||||
|
|
||||||
// feature names used in ranking.
|
// message types
|
||||||
private static final String LAUNCH_SCORE = "launch";
|
private static final int RESOLVER_RANKER_SERVICE_RESULT = 0;
|
||||||
private static final String TIME_SPENT_SCORE = "timeSpent";
|
private static final int RESOLVER_RANKER_RESULT_TIMEOUT = 1;
|
||||||
private static final String RECENCY_SCORE = "recency";
|
|
||||||
private static final String CHOOSER_SCORE = "chooser";
|
// 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 Collator mCollator;
|
||||||
private final boolean mHttp;
|
private final boolean mHttp;
|
||||||
@@ -74,18 +92,74 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
|
|||||||
private final Map<String, UsageStats> mStats;
|
private final Map<String, UsageStats> mStats;
|
||||||
private final long mCurrentTime;
|
private final long mCurrentTime;
|
||||||
private final long mSinceTime;
|
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 String mReferrerPackage;
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
private ArrayList<ResolverTarget> mTargets;
|
||||||
private String mContentType;
|
private String mContentType;
|
||||||
private String[] mAnnotations;
|
private String[] mAnnotations;
|
||||||
private String mAction;
|
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);
|
mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
|
||||||
String scheme = intent.getScheme();
|
String scheme = intent.getScheme();
|
||||||
mHttp = "http".equals(scheme) || "https".equals(scheme);
|
mHttp = "http".equals(scheme) || "https".equals(scheme);
|
||||||
mReferrerPackage = referrerPackage;
|
mReferrerPackage = referrerPackage;
|
||||||
|
mAfterCompute = afterCompute;
|
||||||
|
mContext = context;
|
||||||
|
|
||||||
mPm = context.getPackageManager();
|
mPm = context.getPackageManager();
|
||||||
mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
|
mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
|
||||||
@@ -96,9 +170,9 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
|
|||||||
mContentType = intent.getType();
|
mContentType = intent.getType();
|
||||||
getContentAnnotations(intent);
|
getContentAnnotations(intent);
|
||||||
mAction = intent.getAction();
|
mAction = intent.getAction();
|
||||||
mRanker = new LogisticRegressionAppRanker(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get annotations of content from intent.
|
||||||
public void getContentAnnotations(Intent intent) {
|
public void getContentAnnotations(Intent intent) {
|
||||||
ArrayList<String> annotations = intent.getStringArrayListExtra(
|
ArrayList<String> annotations = intent.getStringArrayListExtra(
|
||||||
Intent.EXTRA_CONTENT_ANNOTATIONS);
|
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) {
|
public void compute(List<ResolvedComponentInfo> targets) {
|
||||||
mScoredTargets.clear();
|
reset();
|
||||||
|
|
||||||
final long recentSinceTime = mCurrentTime - RECENCY_TIME_PERIOD;
|
final long recentSinceTime = mCurrentTime - RECENCY_TIME_PERIOD;
|
||||||
|
|
||||||
long mostRecentlyUsedTime = recentSinceTime + 1;
|
float mostRecencyScore = 1.0f;
|
||||||
long mostTimeSpent = 1;
|
float mostTimeSpentScore = 1.0f;
|
||||||
int mostLaunched = 1;
|
float mostLaunchScore = 1.0f;
|
||||||
int mostSelected = 1;
|
float mostChooserScore = 1.0f;
|
||||||
|
|
||||||
for (ResolvedComponentInfo target : targets) {
|
for (ResolvedComponentInfo target : targets) {
|
||||||
final ScoredTarget scoredTarget
|
final ResolverTarget resolverTarget = new ResolverTarget();
|
||||||
= new ScoredTarget(target.getResolveInfoAt(0).activityInfo);
|
mTargetsDict.put(target.name, resolverTarget);
|
||||||
mScoredTargets.put(target.name, scoredTarget);
|
|
||||||
final UsageStats pkStats = mStats.get(target.name.getPackageName());
|
final UsageStats pkStats = mStats.get(target.name.getPackageName());
|
||||||
if (pkStats != null) {
|
if (pkStats != null) {
|
||||||
// Only count recency for apps that weren't the caller
|
// 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.
|
// Persistent processes muck this up, so omit them too.
|
||||||
if (!target.name.getPackageName().equals(mReferrerPackage)
|
if (!target.name.getPackageName().equals(mReferrerPackage)
|
||||||
&& !isPersistentProcess(target)) {
|
&& !isPersistentProcess(target)) {
|
||||||
final long lastTimeUsed = pkStats.getLastTimeUsed();
|
final float recencyScore =
|
||||||
scoredTarget.lastTimeUsed = lastTimeUsed;
|
(float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
|
||||||
if (lastTimeUsed > mostRecentlyUsedTime) {
|
resolverTarget.setRecencyScore(recencyScore);
|
||||||
mostRecentlyUsedTime = lastTimeUsed;
|
if (recencyScore > mostRecencyScore) {
|
||||||
|
mostRecencyScore = recencyScore;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final long timeSpent = pkStats.getTotalTimeInForeground();
|
final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
|
||||||
scoredTarget.timeSpent = timeSpent;
|
resolverTarget.setTimeSpentScore(timeSpentScore);
|
||||||
if (timeSpent > mostTimeSpent) {
|
if (timeSpentScore > mostTimeSpentScore) {
|
||||||
mostTimeSpent = timeSpent;
|
mostTimeSpentScore = timeSpentScore;
|
||||||
}
|
}
|
||||||
final int launched = pkStats.mLaunchCount;
|
final float launchScore = (float) pkStats.mLaunchCount;
|
||||||
scoredTarget.launchCount = launched;
|
resolverTarget.setLaunchScore(launchScore);
|
||||||
if (launched > mostLaunched) {
|
if (launchScore > mostLaunchScore) {
|
||||||
mostLaunched = launched;
|
mostLaunchScore = launchScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
int selected = 0;
|
float chooserScore = 0.0f;
|
||||||
if (pkStats.mChooserCounts != null && mAction != null
|
if (pkStats.mChooserCounts != null && mAction != null
|
||||||
&& pkStats.mChooserCounts.get(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) {
|
if (mAnnotations != null) {
|
||||||
final int size = mAnnotations.length;
|
final int size = mAnnotations.length;
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
selected += pkStats.mChooserCounts.get(mAction)
|
chooserScore += (float) pkStats.mChooserCounts.get(mAction)
|
||||||
.getOrDefault(mAnnotations[i], 0);
|
.getOrDefault(mAnnotations[i], 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,44 +249,37 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
|
|||||||
Log.d(TAG, "Action type is null");
|
Log.d(TAG, "Action type is null");
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Chooser Count of " + mAction + ":" +
|
Log.d(TAG, "Chooser Count of " + mAction + ":" +
|
||||||
target.name.getPackageName() + " is " + Integer.toString(selected));
|
target.name.getPackageName() + " is " +
|
||||||
|
Float.toString(chooserScore));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scoredTarget.chooserCount = selected;
|
resolverTarget.setChooserScore(chooserScore);
|
||||||
if (selected > mostSelected) {
|
if (chooserScore > mostChooserScore) {
|
||||||
mostSelected = selected;
|
mostChooserScore = chooserScore;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "compute - mostRecentlyUsedTime: " + mostRecentlyUsedTime
|
Log.d(TAG, "compute - mostRecencyScore: " + mostRecencyScore
|
||||||
+ " mostTimeSpent: " + mostTimeSpent
|
+ " mostTimeSpentScore: " + mostTimeSpentScore
|
||||||
+ " recentSinceTime: " + recentSinceTime
|
+ " mostLaunchScore: " + mostLaunchScore
|
||||||
+ " mostLaunched: " + mostLaunched);
|
+ " mostChooserScore: " + mostChooserScore);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ScoredTarget target : mScoredTargets.values()) {
|
mTargets = new ArrayList<>(mTargetsDict.values());
|
||||||
final float recency = (float) Math.max(target.lastTimeUsed - recentSinceTime, 0)
|
for (ResolverTarget target : mTargets) {
|
||||||
/ (mostRecentlyUsedTime - recentSinceTime);
|
final float recency = target.getRecencyScore() / mostRecencyScore;
|
||||||
target.setFeatures((float) target.launchCount / mostLaunched,
|
setFeatures(target, recency * recency * RECENCY_MULTIPLIER,
|
||||||
(float) target.timeSpent / mostTimeSpent,
|
target.getLaunchScore() / mostLaunchScore,
|
||||||
recency * recency * RECENCY_MULTIPLIER,
|
target.getTimeSpentScore() / mostTimeSpentScore,
|
||||||
(float) target.chooserCount / mostSelected);
|
target.getChooserScore() / mostChooserScore);
|
||||||
target.selectProb = mRanker.predict(target.getFeatures());
|
addDefaultSelectProbability(target);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Scores: " + target);
|
Log.d(TAG, "Scores: " + target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
predictSelectProbabilities(mTargets);
|
||||||
|
|
||||||
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
|
@Override
|
||||||
@@ -245,16 +318,16 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
|
|||||||
// Pinned items stay stable within a normal lexical sort and ignore scoring.
|
// Pinned items stay stable within a normal lexical sort and ignore scoring.
|
||||||
if (!lPinned && !rPinned) {
|
if (!lPinned && !rPinned) {
|
||||||
if (mStats != null) {
|
if (mStats != null) {
|
||||||
final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
|
final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
|
||||||
lhs.activityInfo.packageName, lhs.activityInfo.name));
|
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));
|
rhs.activityInfo.packageName, rhs.activityInfo.name));
|
||||||
|
|
||||||
final int selectProbDiff = Float.compare(
|
final int selectProbabilityDiff = Float.compare(
|
||||||
rhsTarget.selectProb, lhsTarget.selectProb);
|
rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
|
||||||
|
|
||||||
if (selectProbDiff != 0) {
|
if (selectProbabilityDiff != 0) {
|
||||||
return selectProbDiff > 0 ? 1 : -1;
|
return selectProbabilityDiff > 0 ? 1 : -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,177 +341,234 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public float getScore(ComponentName name) {
|
public float getScore(ComponentName name) {
|
||||||
final ScoredTarget target = mScoredTargets.get(name);
|
final ResolverTarget target = mTargetsDict.get(name);
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
return target.selectProb;
|
return target.getSelectProbability();
|
||||||
}
|
}
|
||||||
return 0;
|
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) {
|
public void updateChooserCounts(String packageName, int userId, String action) {
|
||||||
if (mUsm != null) {
|
if (mUsm != null) {
|
||||||
mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
|
mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update ranking model when the connection to it is valid.
|
||||||
public void updateModel(ComponentName componentName) {
|
public void updateModel(ComponentName componentName) {
|
||||||
if (mScoredTargets == null || componentName == null ||
|
synchronized (mLock) {
|
||||||
!mScoredTargets.containsKey(componentName)) {
|
if (mRanker != null) {
|
||||||
return;
|
try {
|
||||||
}
|
int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
|
||||||
ScoredTarget selected = mScoredTargets.get(componentName);
|
.indexOf(componentName);
|
||||||
for (ComponentName targetComponent : mScoredTargets.keySet()) {
|
if (selectedPos > 0) {
|
||||||
if (targetComponent.equals(componentName)) {
|
mRanker.train(mTargets, selectedPos);
|
||||||
continue;
|
} else {
|
||||||
}
|
if (DEBUG) {
|
||||||
ScoredTarget target = mScoredTargets.get(targetComponent);
|
Log.d(TAG, "Selected a unknown component: " + componentName);
|
||||||
// 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) {
|
} catch (RemoteException e) {
|
||||||
mRanker.update(target.getFeatures(), target.selectProb, false);
|
Log.e(TAG, "Error in Train: " + e);
|
||||||
mRanker.update(selected.getFeatures(), selected.selectProb, true);
|
}
|
||||||
|
} else {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Ranker is null; skip updateModel.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mRanker.commitUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogisticRegressionAppRanker {
|
// unbind the service and clear unhandled messges.
|
||||||
private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params";
|
public void destroy() {
|
||||||
private static final String BIAS_PREF_KEY = "bias";
|
mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT);
|
||||||
private static final String VERSION_PREF_KEY = "version";
|
mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT);
|
||||||
|
if (mConnection != null) {
|
||||||
// parameters for a pre-trained model, to initialize the app ranker. When updating the
|
mContext.unbindService(mConnection);
|
||||||
// pre-trained model, please update these params, as well as initModel().
|
mConnection.destroy();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
if (DEBUG) {
|
||||||
public float predict(ArrayMap<String, Float> target) {
|
Log.d(TAG, "Unbinded Resolver Ranker.");
|
||||||
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)));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void update(ArrayMap<String, Float> target, float predict, boolean isSelected) {
|
// connect to a ranking service.
|
||||||
if (target == null) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
final int featureSize = target.size();
|
}
|
||||||
float error = isSelected ? 1.0f - predict : -predict;
|
Intent intent = resolveRankerService();
|
||||||
for (int i = 0; i < featureSize; i++) {
|
if (intent == null) {
|
||||||
String featureName = target.keyAt(i);
|
return;
|
||||||
float currentWeight = mFeatureWeights.getOrDefault(featureName, 0.0f);
|
}
|
||||||
mBias += LEARNING_RATE * error;
|
mConnectSignal = new CountDownLatch(1);
|
||||||
currentWeight = currentWeight - LEARNING_RATE * REGULARIZER_PARAM * currentWeight +
|
mConnection = new ResolverRankerServiceConnection(mConnectSignal);
|
||||||
LEARNING_RATE * error * target.valueAt(i);
|
context.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
|
||||||
mFeatureWeights.put(featureName, currentWeight);
|
}
|
||||||
|
|
||||||
|
// 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) {
|
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() {
|
public final IResolverRankerResult resolverRankerResult =
|
||||||
SharedPreferences.Editor editor = mParamSharedPref.edit();
|
new IResolverRankerResult.Stub() {
|
||||||
editor.putFloat(BIAS_PREF_KEY, mBias);
|
@Override
|
||||||
final int size = mFeatureWeights.size();
|
public void sendResult(List<ResolverTarget> targets) throws RemoteException {
|
||||||
for (int i = 0; i < size; i++) {
|
if (DEBUG) {
|
||||||
editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i));
|
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) {
|
@Override
|
||||||
// The package info in the context isn't initialized in the way it is for normal apps,
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
// 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) {
|
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() {
|
@Override
|
||||||
mFeatureWeights = new ArrayMap<>(4);
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
if (mParamSharedPref == null ||
|
if (DEBUG) {
|
||||||
mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) {
|
Log.d(TAG, "onServiceDisconnected: " + name);
|
||||||
// Initializing the app ranker to a pre-trained model. When updating the pre-trained
|
}
|
||||||
// model, please increment CURRENT_VERSION, and update LEARNING_RATE and
|
synchronized (mLock) {
|
||||||
// REGULARIZER_PARAM.
|
destroy();
|
||||||
mBias = -1.6568f;
|
}
|
||||||
mFeatureWeights.put(LAUNCH_SCORE, 2.5543f);
|
}
|
||||||
mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f);
|
|
||||||
mFeatureWeights.put(RECENCY_SCORE, 0.269f);
|
public void destroy() {
|
||||||
mFeatureWeights.put(CHOOSER_SCORE, 4.2222f);
|
synchronized (mLock) {
|
||||||
} else {
|
mRanker = null;
|
||||||
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 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ import android.os.RemoteException;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.lang.InterruptedException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,14 +207,42 @@ public class ResolverListController {
|
|||||||
return listToReturn;
|
return listToReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ComputeCallback implements ResolverComparator.AfterCompute {
|
||||||
|
|
||||||
|
private CountDownLatch mFinishComputeSignal;
|
||||||
|
|
||||||
|
public ComputeCallback(CountDownLatch finishComputeSignal) {
|
||||||
|
mFinishComputeSignal = finishComputeSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterCompute () {
|
||||||
|
mFinishComputeSignal.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) {
|
public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) {
|
||||||
|
final CountDownLatch finishComputeSignal = new CountDownLatch(1);
|
||||||
|
ComputeCallback callback = new ComputeCallback(finishComputeSignal);
|
||||||
if (mResolverComparator == null) {
|
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,
|
private static boolean isSameResolvedComponent(ResolveInfo a,
|
||||||
@@ -233,7 +263,7 @@ public class ResolverListController {
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public float getScore(ResolverActivity.DisplayResolveInfo target) {
|
public float getScore(ResolverActivity.DisplayResolveInfo target) {
|
||||||
if (mResolverComparator == null) {
|
if (mResolverComparator == null) {
|
||||||
mResolverComparator = new ResolverComparator(mContext, mTargetIntent, mReferrerPackage);
|
return 0.0f;
|
||||||
}
|
}
|
||||||
return mResolverComparator.getScore(target.getResolvedComponentName());
|
return mResolverComparator.getScore(target.getResolvedComponentName());
|
||||||
}
|
}
|
||||||
@@ -249,4 +279,10 @@ public class ResolverListController {
|
|||||||
mResolverComparator.updateChooserCounts(packageName, userId, action);
|
mResolverComparator.updateChooserCounts(packageName, userId, action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void destroy() {
|
||||||
|
if (mResolverComparator != null) {
|
||||||
|
mResolverComparator.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3129,6 +3129,15 @@
|
|||||||
<permission android:name="android.permission.BIND_CHOOSER_TARGET_SERVICE"
|
<permission android:name="android.permission.BIND_CHOOSER_TARGET_SERVICE"
|
||||||
android:protectionLevel="signature" />
|
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
|
<!-- Must be required by a {@link
|
||||||
android.service.notification.ConditionProviderService},
|
android.service.notification.ConditionProviderService},
|
||||||
to ensure that only the system can bind to it.
|
to ensure that only the system can bind to it.
|
||||||
@@ -3640,6 +3649,14 @@
|
|||||||
android:permission="android.permission.BIND_JOB_SERVICE" >
|
android:permission="android.permission.BIND_JOB_SERVICE" >
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service android:name="com.android.internal.app.LRResolverRankerService"
|
||||||
|
android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE"
|
||||||
|
android:exported="false"
|
||||||
|
android:priority="-1" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.resolver.ResolverRankerService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
Reference in New Issue
Block a user