• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.server.people.prediction;
18 
19 import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
20 
21 import static java.util.Collections.reverseOrder;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.UserIdInt;
26 import android.annotation.WorkerThread;
27 import android.app.prediction.AppPredictionContext;
28 import android.app.prediction.AppPredictionManager;
29 import android.app.prediction.AppPredictor;
30 import android.app.prediction.AppTarget;
31 import android.app.prediction.AppTargetEvent;
32 import android.app.prediction.AppTargetId;
33 import android.content.Context;
34 import android.content.IntentFilter;
35 import android.content.pm.ShortcutInfo;
36 import android.content.pm.ShortcutManager.ShareShortcutInfo;
37 import android.os.UserHandle;
38 import android.provider.DeviceConfig;
39 import android.util.Log;
40 import android.util.Slog;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.app.ChooserActivity;
44 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
45 import com.android.server.people.data.ConversationInfo;
46 import com.android.server.people.data.DataManager;
47 import com.android.server.people.data.EventHistory;
48 import com.android.server.people.data.PackageData;
49 
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.Comparator;
53 import java.util.List;
54 import java.util.function.Consumer;
55 
56 /**
57  * Predictor that predicts the {@link AppTarget} the user is most likely to open on share sheet.
58  */
59 class ShareTargetPredictor extends AppTargetPredictor {
60 
61     private static final String TAG = "ShareTargetPredictor";
62     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
63     private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
64     private final IntentFilter mIntentFilter;
65     private final AppPredictor mRemoteAppPredictor;
66 
ShareTargetPredictor(@onNull AppPredictionContext predictionContext, @NonNull Consumer<List<AppTarget>> updatePredictionsMethod, @NonNull DataManager dataManager, @UserIdInt int callingUserId, @NonNull Context context)67     ShareTargetPredictor(@NonNull AppPredictionContext predictionContext,
68             @NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
69             @NonNull DataManager dataManager,
70             @UserIdInt int callingUserId, @NonNull Context context) {
71         super(predictionContext, updatePredictionsMethod, dataManager, callingUserId);
72         mIntentFilter = predictionContext.getExtras().getParcelable(
73                 ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY);
74         if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
75                 SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
76                 false)) {
77             predictionContext.getExtras().putBoolean(REMOTE_APP_PREDICTOR_KEY, true);
78             mRemoteAppPredictor = context.createContextAsUser(UserHandle.of(callingUserId), 0)
79                     .getSystemService(AppPredictionManager.class)
80                     .createAppPredictionSession(predictionContext);
81         } else {
82             mRemoteAppPredictor = null;
83         }
84     }
85 
86     /** Reports chosen history of direct/app share targets. */
87     @WorkerThread
88     @Override
reportAppTargetEvent(AppTargetEvent event)89     void reportAppTargetEvent(AppTargetEvent event) {
90         if (DEBUG) {
91             Slog.d(TAG, "reportAppTargetEvent");
92         }
93         if (mIntentFilter != null) {
94             getDataManager().reportShareTargetEvent(event, mIntentFilter);
95         }
96         if (mRemoteAppPredictor != null) {
97             mRemoteAppPredictor.notifyAppTargetEvent(event);
98         }
99     }
100 
101     /** Provides prediction on direct share targets */
102     @WorkerThread
103     @Override
predictTargets()104     void predictTargets() {
105         if (DEBUG) {
106             Slog.d(TAG, "predictTargets");
107         }
108         if (mIntentFilter == null) {
109             updatePredictions(List.of());
110             return;
111         }
112         List<ShareTarget> shareTargets = getDirectShareTargets();
113         SharesheetModelScorer.computeScore(shareTargets, getShareEventType(mIntentFilter),
114                 System.currentTimeMillis());
115         Collections.sort(shareTargets,
116                 Comparator.comparing(ShareTarget::getScore, reverseOrder())
117                         .thenComparing(t -> t.getAppTarget().getRank()));
118         List<AppTarget> res = new ArrayList<>();
119         for (int i = 0; i < Math.min(getPredictionContext().getPredictedTargetCount(),
120                 shareTargets.size()); i++) {
121             res.add(shareTargets.get(i).getAppTarget());
122         }
123         updatePredictions(res);
124     }
125 
126     /** Provides prediction on app share targets */
127     @WorkerThread
128     @Override
sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback)129     void sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
130         if (DEBUG) {
131             Slog.d(TAG, "sortTargets");
132         }
133         if (mIntentFilter == null) {
134             callback.accept(targets);
135             return;
136         }
137         List<ShareTarget> shareTargets = getAppShareTargets(targets);
138         SharesheetModelScorer.computeScoreForAppShare(shareTargets,
139                 getShareEventType(mIntentFilter), getPredictionContext().getPredictedTargetCount(),
140                 System.currentTimeMillis(), getDataManager(),
141                 mCallingUserId);
142         Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore()));
143         List<AppTarget> appTargetList = new ArrayList<>();
144         for (ShareTarget shareTarget : shareTargets) {
145             AppTarget appTarget = shareTarget.getAppTarget();
146             appTargetList.add(new AppTarget.Builder(appTarget.getId(), appTarget.getPackageName(),
147                     appTarget.getUser())
148                     .setClassName(appTarget.getClassName())
149                     .setRank(shareTarget.getScore() > 0 ? (int) (shareTarget.getScore()
150                             * 1000) : 0)
151                     .build());
152         }
153         callback.accept(appTargetList);
154     }
155 
156     /** Recycles resources. */
157     @WorkerThread
158     @Override
destroy()159     void destroy() {
160         if (mRemoteAppPredictor != null) {
161             mRemoteAppPredictor.destroy();
162         }
163     }
164 
getDirectShareTargets()165     private List<ShareTarget> getDirectShareTargets() {
166         List<ShareTarget> shareTargets = new ArrayList<>();
167         List<ShareShortcutInfo> shareShortcuts =
168                 getDataManager().getShareShortcuts(mIntentFilter, mCallingUserId);
169 
170         for (ShareShortcutInfo shareShortcut : shareShortcuts) {
171             ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
172             AppTarget appTarget = new AppTarget.Builder(
173                     new AppTargetId(shortcutInfo.getId()),
174                     shortcutInfo)
175                     .setClassName(shareShortcut.getTargetComponent().getClassName())
176                     .setRank(shortcutInfo.getRank())
177                     .build();
178             String packageName = shortcutInfo.getPackage();
179             int userId = shortcutInfo.getUserId();
180             PackageData packageData = getDataManager().getPackage(packageName, userId);
181 
182             ConversationInfo conversationInfo = null;
183             EventHistory eventHistory = null;
184             if (packageData != null) {
185                 String shortcutId = shortcutInfo.getId();
186                 conversationInfo = packageData.getConversationInfo(shortcutId);
187                 if (conversationInfo != null) {
188                     eventHistory = packageData.getEventHistory(shortcutId);
189                 }
190             }
191             shareTargets.add(new ShareTarget(appTarget, eventHistory, conversationInfo));
192         }
193 
194         return shareTargets;
195     }
196 
getAppShareTargets(List<AppTarget> targets)197     private List<ShareTarget> getAppShareTargets(List<AppTarget> targets) {
198         List<ShareTarget> shareTargets = new ArrayList<>();
199         for (AppTarget target : targets) {
200             PackageData packageData = getDataManager().getPackage(target.getPackageName(),
201                     target.getUser().getIdentifier());
202             shareTargets.add(new ShareTarget(target,
203                     packageData == null ? null
204                             : packageData.getClassLevelEventHistory(target.getClassName()), null));
205         }
206         return shareTargets;
207     }
208 
getShareEventType(IntentFilter intentFilter)209     private int getShareEventType(IntentFilter intentFilter) {
210         String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
211         return getDataManager().mimeTypeToShareEventType(mimeType);
212     }
213 
214     @VisibleForTesting
215     static class ShareTarget {
216 
217         @NonNull
218         private final AppTarget mAppTarget;
219         @Nullable
220         private final EventHistory mEventHistory;
221         @Nullable
222         private final ConversationInfo mConversationInfo;
223         private float mScore;
224 
225         @VisibleForTesting
ShareTarget(@onNull AppTarget appTarget, @Nullable EventHistory eventHistory, @Nullable ConversationInfo conversationInfo)226         ShareTarget(@NonNull AppTarget appTarget,
227                 @Nullable EventHistory eventHistory,
228                 @Nullable ConversationInfo conversationInfo) {
229             mAppTarget = appTarget;
230             mEventHistory = eventHistory;
231             mConversationInfo = conversationInfo;
232             mScore = 0f;
233         }
234 
235         @NonNull
236         @VisibleForTesting
getAppTarget()237         AppTarget getAppTarget() {
238             return mAppTarget;
239         }
240 
241         @Nullable
242         @VisibleForTesting
getEventHistory()243         EventHistory getEventHistory() {
244             return mEventHistory;
245         }
246 
247         @Nullable
248         @VisibleForTesting
getConversationInfo()249         ConversationInfo getConversationInfo() {
250             return mConversationInfo;
251         }
252 
253         @VisibleForTesting
getScore()254         float getScore() {
255             return mScore;
256         }
257 
258         @VisibleForTesting
setScore(float score)259         void setScore(float score) {
260             mScore = score;
261         }
262     }
263 }
264