• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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 com.android.intentresolver.model;
18 
19 import static android.app.prediction.AppTargetEvent.ACTION_LAUNCH;
20 
21 import android.annotation.Nullable;
22 import android.app.prediction.AppPredictor;
23 import android.app.prediction.AppTarget;
24 import android.app.prediction.AppTargetEvent;
25 import android.app.prediction.AppTargetId;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.ResolveInfo;
30 import android.os.Message;
31 import android.os.UserHandle;
32 import android.util.Log;
33 
34 import com.android.intentresolver.ChooserActivityLogger;
35 import com.android.intentresolver.ResolvedComponentInfo;
36 
37 import java.util.ArrayList;
38 import java.util.Comparator;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.concurrent.Executors;
43 
44 /**
45  * Uses an {@link AppPredictor} to sort Resolver targets. If the AppPredictionService appears to be
46  * disabled by returning an empty sorted target list, {@link AppPredictionServiceResolverComparator}
47  * will fallback to using a {@link ResolverRankerServiceResolverComparator}.
48  */
49 public class AppPredictionServiceResolverComparator extends AbstractResolverComparator {
50 
51     private static final String TAG = "APSResolverComparator";
52 
53     private final AppPredictor mAppPredictor;
54     private final Context mContext;
55     private final Map<ComponentName, Integer> mTargetRanks = new HashMap<>();
56     private final Map<ComponentName, Integer> mTargetScores = new HashMap<>();
57     private final UserHandle mUser;
58     private final Intent mIntent;
59     private final String mReferrerPackage;
60     // If this is non-null (and this is not destroyed), it means APS is disabled and we should fall
61     // back to using the ResolverRankerService.
62     // TODO: responsibility for this fallback behavior can live outside of the AppPrediction client.
63     private ResolverRankerServiceResolverComparator mResolverRankerService;
64     private AppPredictionServiceComparatorModel mComparatorModel;
65 
AppPredictionServiceResolverComparator( Context context, Intent intent, String referrerPackage, AppPredictor appPredictor, UserHandle user, ChooserActivityLogger chooserActivityLogger)66     public AppPredictionServiceResolverComparator(
67             Context context,
68             Intent intent,
69             String referrerPackage,
70             AppPredictor appPredictor,
71             UserHandle user,
72             ChooserActivityLogger chooserActivityLogger) {
73         super(context, intent);
74         mContext = context;
75         mIntent = intent;
76         mAppPredictor = appPredictor;
77         mUser = user;
78         mReferrerPackage = referrerPackage;
79         setChooserActivityLogger(chooserActivityLogger);
80         mComparatorModel = buildUpdatedModel();
81     }
82 
83     @Override
compare(ResolveInfo lhs, ResolveInfo rhs)84     int compare(ResolveInfo lhs, ResolveInfo rhs) {
85         return mComparatorModel.getComparator().compare(lhs, rhs);
86     }
87 
88     @Override
doCompute(List<ResolvedComponentInfo> targets)89     void doCompute(List<ResolvedComponentInfo> targets) {
90         if (targets.isEmpty()) {
91             mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT);
92             return;
93         }
94         List<AppTarget> appTargets = new ArrayList<>();
95         for (ResolvedComponentInfo target : targets) {
96             appTargets.add(
97                     new AppTarget.Builder(
98                         new AppTargetId(target.name.flattenToString()),
99                             target.name.getPackageName(),
100                             mUser)
101                     .setClassName(target.name.getClassName())
102                     .build());
103         }
104         mAppPredictor.sortTargets(appTargets, Executors.newSingleThreadExecutor(),
105                 sortedAppTargets -> {
106                     if (sortedAppTargets.isEmpty()) {
107                         Log.i(TAG, "AppPredictionService disabled. Using resolver.");
108                         // APS for chooser is disabled. Fallback to resolver.
109                         mResolverRankerService =
110                                 new ResolverRankerServiceResolverComparator(
111                                         mContext, mIntent, mReferrerPackage,
112                                         () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT),
113                                         getChooserActivityLogger());
114                         mComparatorModel = buildUpdatedModel();
115                         mResolverRankerService.compute(targets);
116                     } else {
117                         Log.i(TAG, "AppPredictionService response received");
118                         // Skip sending to Handler which takes extra time to dispatch messages.
119                         handleResult(sortedAppTargets);
120                     }
121                 }
122         );
123     }
124 
125     @Override
handleResultMessage(Message msg)126     void handleResultMessage(Message msg) {
127         // Null value is okay if we have defaulted to the ResolverRankerService.
128         if (msg.what == RANKER_SERVICE_RESULT && msg.obj != null) {
129             final List<AppTarget> sortedAppTargets = (List<AppTarget>) msg.obj;
130             handleSortedAppTargets(sortedAppTargets);
131         } else if (msg.obj == null && mResolverRankerService == null) {
132             Log.e(TAG, "Unexpected null result");
133         }
134     }
135 
handleResult(List<AppTarget> sortedAppTargets)136     private void handleResult(List<AppTarget> sortedAppTargets) {
137         if (mHandler.hasMessages(RANKER_RESULT_TIMEOUT)) {
138             handleSortedAppTargets(sortedAppTargets);
139             mHandler.removeMessages(RANKER_RESULT_TIMEOUT);
140             afterCompute();
141         }
142     }
143 
handleSortedAppTargets(List<AppTarget> sortedAppTargets)144     private void handleSortedAppTargets(List<AppTarget> sortedAppTargets) {
145         if (checkAppTargetRankValid(sortedAppTargets)) {
146             sortedAppTargets.forEach(target -> mTargetScores.put(
147                     new ComponentName(target.getPackageName(), target.getClassName()),
148                     target.getRank()));
149         }
150         for (int i = 0; i < sortedAppTargets.size(); i++) {
151             ComponentName componentName = new ComponentName(
152                     sortedAppTargets.get(i).getPackageName(),
153                     sortedAppTargets.get(i).getClassName());
154             mTargetRanks.put(componentName, i);
155             Log.i(TAG, "handleSortedAppTargets, sortedAppTargets #" + i + ": " + componentName);
156         }
157         mComparatorModel = buildUpdatedModel();
158     }
159 
checkAppTargetRankValid(List<AppTarget> sortedAppTargets)160     private boolean checkAppTargetRankValid(List<AppTarget> sortedAppTargets) {
161         for (AppTarget target : sortedAppTargets) {
162             if (target.getRank() != 0) {
163                 return true;
164             }
165         }
166         return false;
167     }
168 
169     @Override
getScore(ComponentName name)170     public float getScore(ComponentName name) {
171         return mComparatorModel.getScore(name);
172     }
173 
174     @Override
updateModel(ComponentName componentName)175     public void updateModel(ComponentName componentName) {
176         mComparatorModel.notifyOnTargetSelected(componentName);
177     }
178 
179     @Override
destroy()180     public void destroy() {
181         if (mResolverRankerService != null) {
182             mResolverRankerService.destroy();
183             mResolverRankerService = null;
184             mComparatorModel = buildUpdatedModel();
185         }
186     }
187 
188     /**
189      * Re-construct an {@code AppPredictionServiceComparatorModel} to replace the current model
190      * instance (if any) using the up-to-date {@code AppPredictionServiceResolverComparator} ivar
191      * values.
192      *
193      * TODO: each time we replace the model instance, we're either updating the model to use
194      * adjusted data (which is appropriate), or we're providing a (late) value for one of our ivars
195      * that wasn't available the last time the model was updated. For those latter cases, we should
196      * just avoid creating the model altogether until we have all the prerequisites we'll need. Then
197      * we can probably simplify the logic in {@code AppPredictionServiceComparatorModel} since we
198      * won't need to handle edge cases when the model data isn't fully prepared.
199      * (In some cases, these kinds of "updates" might interleave -- e.g., we might have finished
200      * initializing the first time and now want to adjust some data, but still need to wait for
201      * changes to propagate to the other ivars before rebuilding the model.)
202      */
buildUpdatedModel()203     private AppPredictionServiceComparatorModel buildUpdatedModel() {
204         return new AppPredictionServiceComparatorModel(
205                 mAppPredictor, mResolverRankerService, mUser, mTargetRanks);
206     }
207 
208     // TODO: Finish separating behaviors of AbstractResolverComparator, then (probably) make this a
209     // standalone class once clients are written in terms of ResolverComparatorModel.
210     static class AppPredictionServiceComparatorModel implements ResolverComparatorModel {
211         private final AppPredictor mAppPredictor;
212         private final ResolverRankerServiceResolverComparator mResolverRankerService;
213         private final UserHandle mUser;
214         private final Map<ComponentName, Integer> mTargetRanks;  // Treat as immutable.
215 
AppPredictionServiceComparatorModel( AppPredictor appPredictor, @Nullable ResolverRankerServiceResolverComparator resolverRankerService, UserHandle user, Map<ComponentName, Integer> targetRanks)216         AppPredictionServiceComparatorModel(
217                 AppPredictor appPredictor,
218                 @Nullable ResolverRankerServiceResolverComparator resolverRankerService,
219                 UserHandle user,
220                 Map<ComponentName, Integer> targetRanks) {
221             mAppPredictor = appPredictor;
222             mResolverRankerService = resolverRankerService;
223             mUser = user;
224             mTargetRanks = targetRanks;
225         }
226 
227         @Override
getComparator()228         public Comparator<ResolveInfo> getComparator() {
229             return (lhs, rhs) -> {
230                 if (mResolverRankerService != null) {
231                     return mResolverRankerService.compare(lhs, rhs);
232                 }
233                 Integer lhsRank = mTargetRanks.get(new ComponentName(lhs.activityInfo.packageName,
234                         lhs.activityInfo.name));
235                 Integer rhsRank = mTargetRanks.get(new ComponentName(rhs.activityInfo.packageName,
236                         rhs.activityInfo.name));
237                 if (lhsRank == null && rhsRank == null) {
238                     return 0;
239                 } else if (lhsRank == null) {
240                     return -1;
241                 } else if (rhsRank == null) {
242                     return 1;
243                 }
244                 return lhsRank - rhsRank;
245             };
246         }
247 
248         @Override
getScore(ComponentName name)249         public float getScore(ComponentName name) {
250             if (mResolverRankerService != null) {
251                 return mResolverRankerService.getScore(name);
252             }
253             Integer rank = mTargetRanks.get(name);
254             if (rank == null) {
255                 Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
256                 return 0f;
257             }
258             int consecutiveSumOfRanks = (mTargetRanks.size() - 1) * (mTargetRanks.size()) / 2;
259             return 1.0f - (((float) rank) / consecutiveSumOfRanks);
260         }
261 
262         @Override
notifyOnTargetSelected(ComponentName componentName)263         public void notifyOnTargetSelected(ComponentName componentName) {
264             if (mResolverRankerService != null) {
265                 mResolverRankerService.updateModel(componentName);
266                 return;
267             }
268             mAppPredictor.notifyAppTargetEvent(
269                     new AppTargetEvent.Builder(
270                         new AppTarget.Builder(
271                             new AppTargetId(componentName.toString()),
272                             componentName.getPackageName(), mUser)
273                             .setClassName(componentName.getClassName()).build(),
274                         ACTION_LAUNCH).build());
275         }
276     }
277 }
278