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