1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.service.resolver; 18 19 import android.annotation.SdkConstant; 20 import android.annotation.SystemApi; 21 import android.app.Service; 22 import android.content.ComponentName; 23 import android.content.Intent; 24 import android.os.IBinder; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.os.RemoteException; 28 import android.service.resolver.ResolverTarget; 29 import android.util.Log; 30 31 import java.util.List; 32 import java.util.Map; 33 34 /** 35 * A service to rank apps according to usage stats of apps, when the system is resolving targets for 36 * an Intent. 37 * 38 * <p>To extend this class, you must declare the service in your manifest file with the 39 * {@link android.Manifest.permission#BIND_RESOLVER_RANKER_SERVICE} permission, and include an 40 * intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> 41 * <pre> 42 * <service android:name=".MyResolverRankerService" 43 * android:exported="true" 44 * android:priority="100" 45 * android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE"> 46 * <intent-filter> 47 * <action android:name="android.service.resolver.ResolverRankerService" /> 48 * </intent-filter> 49 * </service> 50 * </pre> 51 * @hide 52 */ 53 @SystemApi 54 public abstract class ResolverRankerService extends Service { 55 56 private static final String TAG = "ResolverRankerService"; 57 58 private static final boolean DEBUG = false; 59 60 /** 61 * The Intent action that a service must respond to. Add it to the intent filter of the service 62 * in its manifest. 63 */ 64 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 65 public static final String SERVICE_INTERFACE = "android.service.resolver.ResolverRankerService"; 66 67 /** 68 * The permission that a service must hold. If the service does not hold the permission, the 69 * system will skip that service. 70 */ 71 public static final String HOLD_PERMISSION = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE"; 72 73 /** 74 * The permission that a service must require to ensure that only Android system can bind to it. 75 * If this permission is not enforced in the AndroidManifest of the service, the system will 76 * skip that service. 77 */ 78 public static final String BIND_PERMISSION = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; 79 80 private ResolverRankerServiceWrapper mWrapper = null; 81 82 /** 83 * Called by the system to retrieve a list of probabilities to rank apps/options. To implement 84 * it, set selectProbability of each input {@link ResolverTarget}. The higher the 85 * selectProbability is, the more likely the {@link ResolverTarget} will be selected by the 86 * user. Override this function to provide prediction results. 87 * 88 * @param targets a list of {@link ResolverTarget}, for the list of apps to be ranked. 89 * 90 * @throws Exception when the prediction task fails. 91 */ onPredictSharingProbabilities(final List<ResolverTarget> targets)92 public void onPredictSharingProbabilities(final List<ResolverTarget> targets) {} 93 94 /** 95 * Called by the system to train/update a ranking service, after the user makes a selection from 96 * the ranked list of apps. Override this function to enable model updates. 97 * 98 * @param targets a list of {@link ResolverTarget}, for the list of apps to be ranked. 99 * @param selectedPosition the position of the selected app in the list. 100 * 101 * @throws Exception when the training task fails. 102 */ onTrainRankingModel( final List<ResolverTarget> targets, final int selectedPosition)103 public void onTrainRankingModel( 104 final List<ResolverTarget> targets, final int selectedPosition) {} 105 106 private static final String HANDLER_THREAD_NAME = "RESOLVER_RANKER_SERVICE"; 107 private volatile Handler mHandler; 108 private HandlerThread mHandlerThread; 109 110 @Override onBind(Intent intent)111 public IBinder onBind(Intent intent) { 112 if (DEBUG) Log.d(TAG, "onBind " + intent); 113 if (!SERVICE_INTERFACE.equals(intent.getAction())) { 114 if (DEBUG) Log.d(TAG, "bad intent action " + intent.getAction() + "; returning null"); 115 return null; 116 } 117 if (mHandlerThread == null) { 118 mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME); 119 mHandlerThread.start(); 120 mHandler = new Handler(mHandlerThread.getLooper()); 121 } 122 if (mWrapper == null) { 123 mWrapper = new ResolverRankerServiceWrapper(); 124 } 125 return mWrapper; 126 } 127 128 @Override onDestroy()129 public void onDestroy() { 130 mHandler = null; 131 if (mHandlerThread != null) { 132 mHandlerThread.quitSafely(); 133 } 134 super.onDestroy(); 135 } 136 sendResult(List<ResolverTarget> targets, IResolverRankerResult result)137 private static void sendResult(List<ResolverTarget> targets, IResolverRankerResult result) { 138 try { 139 result.sendResult(targets); 140 } catch (Exception e) { 141 Log.e(TAG, "failed to send results: " + e); 142 } 143 } 144 145 private class ResolverRankerServiceWrapper extends IResolverRankerService.Stub { 146 147 @Override predict(final List<ResolverTarget> targets, final IResolverRankerResult result)148 public void predict(final List<ResolverTarget> targets, final IResolverRankerResult result) 149 throws RemoteException { 150 Runnable predictRunnable = new Runnable() { 151 @Override 152 public void run() { 153 try { 154 if (DEBUG) { 155 Log.d(TAG, "predict calls onPredictSharingProbabilities."); 156 } 157 onPredictSharingProbabilities(targets); 158 sendResult(targets, result); 159 } catch (Exception e) { 160 Log.e(TAG, "onPredictSharingProbabilities failed; send null results: " + e); 161 sendResult(null, result); 162 } 163 } 164 }; 165 final Handler h = mHandler; 166 if (h != null) { 167 h.post(predictRunnable); 168 } 169 } 170 171 @Override train(final List<ResolverTarget> targets, final int selectedPosition)172 public void train(final List<ResolverTarget> targets, final int selectedPosition) 173 throws RemoteException { 174 Runnable trainRunnable = new Runnable() { 175 @Override 176 public void run() { 177 try { 178 if (DEBUG) { 179 Log.d(TAG, "train calls onTranRankingModel"); 180 } 181 onTrainRankingModel(targets, selectedPosition); 182 } catch (Exception e) { 183 Log.e(TAG, "onTrainRankingModel failed; skip train: " + e); 184 } 185 } 186 }; 187 final Handler h = mHandler; 188 if (h != null) { 189 h.post(trainRunnable); 190 } 191 } 192 } 193 } 194