1 /*
2  * Copyright (C) 2019 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 package com.android.apppredictionservice;
17 
18 import static android.os.Process.myUserHandle;
19 import static android.text.TextUtils.isEmpty;
20 
21 import static java.util.Collections.emptyList;
22 
23 import android.app.prediction.AppPredictionContext;
24 import android.app.prediction.AppPredictionSessionId;
25 import android.app.prediction.AppTarget;
26 import android.app.prediction.AppTargetEvent;
27 import android.app.prediction.AppTargetId;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.SharedPreferences;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.LauncherActivityInfo;
34 import android.content.pm.LauncherApps;
35 import android.content.pm.PackageManager;
36 import android.content.pm.ResolveInfo;
37 import android.os.CancellationSignal;
38 import android.service.appprediction.AppPredictionService;
39 import android.util.Log;
40 import java.util.ArrayList;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Set;
44 import java.util.function.Consumer;
45 
46 /*
47  * New plugin that replaces prediction driven Aiai APK in P
48  * PredictionService simply populates the top row of the app
49  * drawer with the 5 most recently used apps. Each time a new
50  * app is launched, it is added to the left of the top row.
51  * Duplicates are not added.
52  */
53 public class PredictionService extends AppPredictionService {
54 
55     private static final String TAG = PredictionService.class.getSimpleName();
56 
57     private final Set<AppPredictionSessionId> activeLauncherSessions = new HashSet<>();
58 
59     private boolean mAppSuggestionsEnabled = true;
60 
61     public static final String MY_PREF = "mypref";
62 
63     private final List<AppTarget> predictionList = new ArrayList<>(5);
64 
65     private final List<String> appNames = new ArrayList<>(5);
66     private final String[] appNameKeys = new String[] {
67             "first", "second", "third", "fourth", "fifth" };
68 
69     SharedPreferences sharedPreferences;
70     SharedPreferences.Editor editor;
71 
72     @Override
onCreate()73     public void onCreate() {
74         super.onCreate();
75 
76         Intent calendarIntent = new Intent(Intent.ACTION_MAIN);
77         calendarIntent.addCategory(Intent.CATEGORY_APP_CALENDAR);
78 
79         Intent galleryIntent = new Intent(Intent.ACTION_MAIN);
80         galleryIntent.addCategory(Intent.CATEGORY_APP_GALLERY);
81 
82         Intent mapsIntent = new Intent(Intent.ACTION_MAIN);
83         mapsIntent.addCategory(Intent.CATEGORY_APP_MAPS);
84 
85         Intent emailIntent = new Intent(Intent.ACTION_MAIN);
86         emailIntent.addCategory(Intent.CATEGORY_APP_EMAIL);
87 
88         Intent browserIntent = new Intent(Intent.ACTION_MAIN);
89         browserIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
90 
91         String[] DEFAULT_PACKAGES = new String[] {
92               getDefaultSystemHandlerActivityPackageName(calendarIntent),
93               getDefaultSystemHandlerActivityPackageName(galleryIntent),
94               getDefaultSystemHandlerActivityPackageName(mapsIntent),
95               getDefaultSystemHandlerActivityPackageName(emailIntent),
96               getDefaultSystemHandlerActivityPackageName(browserIntent),
97         };
98 
99         Log.d(TAG, "AppPredictionService onCreate");
100         this.sharedPreferences = getSharedPreferences(MY_PREF, Context.MODE_PRIVATE);
101         this.editor = sharedPreferences.edit();
102 
103         if (sharedPreferences.getString(appNameKeys[0], "").isEmpty()) {
104             // fill the list with defaults if first one is null when devices powers up for the first time
105             for (int i = 0; i < appNameKeys.length; i++) {
106                 editor.putString(appNameKeys[i],
107                         getLauncherComponent(DEFAULT_PACKAGES[i]).flattenToShortString());
108             }
109             this.editor.apply();
110         }
111 
112         for (int i = 0; i < appNameKeys.length; i++) {
113             String appName = sharedPreferences.getString(appNameKeys[i], "");
114             ComponentName cn = ComponentName.unflattenFromString(appName);
115             AppTarget target = new AppTarget.Builder(
116                     new AppTargetId(Integer.toString(i + 1)), cn.getPackageName(), myUserHandle())
117                     .setClassName(cn.getClassName())
118                     .build();
119             appNames.add(appName);
120             predictionList.add(target);
121         }
122         postPredictionUpdateToAllClients();
123     }
124 
getLauncherComponent(String packageName)125     private ComponentName getLauncherComponent(String packageName) {
126         List<LauncherActivityInfo> infos = getSystemService(LauncherApps.class)
127                 .getActivityList(packageName, myUserHandle());
128         if (infos.isEmpty()) {
129             return new ComponentName(packageName, "#");
130         } else {
131             return infos.get(0).getComponentName();
132         }
133     }
134 
postPredictionUpdate(AppPredictionSessionId sessionId)135     private void postPredictionUpdate(AppPredictionSessionId sessionId) {
136         updatePredictions(sessionId, mAppSuggestionsEnabled ? predictionList : emptyList());
137     }
138 
postPredictionUpdateToAllClients()139     private void postPredictionUpdateToAllClients() {
140         for (AppPredictionSessionId session : activeLauncherSessions) {
141             postPredictionUpdate(session);
142         }
143     }
144 
145     @Override
onCreatePredictionSession( AppPredictionContext context, AppPredictionSessionId sessionId)146     public void onCreatePredictionSession(
147             AppPredictionContext context, AppPredictionSessionId sessionId) {
148         Log.d(TAG, "onCreatePredictionSession");
149 
150         if (context.getUiSurface().equals("home") || context.getUiSurface().equals("overview")) {
151             activeLauncherSessions.add(sessionId);
152             postPredictionUpdate(sessionId);
153         }
154     }
155 
156     @Override
onAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event)157     public void onAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) {
158 
159         if (!activeLauncherSessions.contains(sessionId)) {
160             return;
161         }
162 
163         boolean found = false;
164         Log.d(TAG, "onAppTargetEvent");
165 
166         AppTarget target = event.getTarget();
167         if (target == null || isEmpty(target.getPackageName()) || isEmpty(target.getClassName())) {
168             return;
169         }
170         String mostRecentComponent = new ComponentName(
171                 target.getPackageName(), target.getClassName()).flattenToString();
172 
173         // Check if packageName already exists in existing list of appNames
174         for (String packageName:appNames) {
175             if (packageName.contains(target.getPackageName())) {
176                 found = true;
177                 break;
178             }
179         }
180 
181         if (!found) {
182             appNames.remove(appNames.size() - 1);
183             appNames.add(0, mostRecentComponent);
184 
185             for (int i = 0; i < appNameKeys.length; i++) {
186                 editor.putString(appNameKeys[i], appNames.get(i));
187             }
188             editor.apply();
189 
190             predictionList.remove(predictionList.size() - 1);
191             predictionList.add(0, event.getTarget());
192 
193             Log.d(TAG, "onAppTargetEvent:: update predictions");
194             postPredictionUpdateToAllClients();
195         }
196     }
197 
198     @Override
onLaunchLocationShown( AppPredictionSessionId sessionId, String launchLocation, List<AppTargetId> targetIds)199     public void onLaunchLocationShown(
200             AppPredictionSessionId sessionId, String launchLocation, List<AppTargetId> targetIds) {
201         Log.d(TAG, "onLaunchLocationShown");
202     }
203 
204     @Override
onSortAppTargets( AppPredictionSessionId sessionId, List<AppTarget> targets, CancellationSignal cancellationSignal, Consumer<List<AppTarget>> callback)205     public void onSortAppTargets(
206             AppPredictionSessionId sessionId,
207             List<AppTarget> targets,
208             CancellationSignal cancellationSignal,
209             Consumer<List<AppTarget>> callback) {
210 
211         Log.d(TAG, "onSortAppTargets");
212         if (!activeLauncherSessions.contains(sessionId)) {
213             callback.accept(emptyList());
214         } else {
215             // No-op
216             callback.accept(targets);
217         }
218     }
219 
220     @Override
onRequestPredictionUpdate(AppPredictionSessionId sessionId)221     public void onRequestPredictionUpdate(AppPredictionSessionId sessionId) {
222         Log.d(TAG, "onRequestPredictionUpdate");
223 
224         if (!activeLauncherSessions.contains(sessionId)) {
225             updatePredictions(sessionId, emptyList());
226         } else {
227             postPredictionUpdate(sessionId);
228             Log.d(TAG, "update predictions");
229         }
230     }
231 
232     @Override
onDestroyPredictionSession(AppPredictionSessionId sessionId)233     public void onDestroyPredictionSession(AppPredictionSessionId sessionId) {
234         Log.d(TAG, "onDestroyPredictionSession");
235         activeLauncherSessions.remove(sessionId);
236     }
237 
238     @Override
onStartPredictionUpdates()239     public void onStartPredictionUpdates() {
240         Log.d(TAG, "onStartPredictionUpdates");
241     }
242 
243     @Override
onStopPredictionUpdates()244     public void onStopPredictionUpdates() {
245         Log.d(TAG, "onStopPredictionUpdates");
246     }
247 
setAppSuggestionsEnabled(boolean enabled)248     public void setAppSuggestionsEnabled(boolean enabled) {
249         mAppSuggestionsEnabled = enabled;
250         postPredictionUpdateToAllClients();
251     }
252 
getDefaultSystemHandlerActivityPackageName(Intent intent)253     private String getDefaultSystemHandlerActivityPackageName(Intent intent) {
254         return getDefaultSystemHandlerActivityPackageName(intent, 0);
255     }
256 
getDefaultSystemHandlerActivityPackageName(Intent intent, int flags)257     private String getDefaultSystemHandlerActivityPackageName(Intent intent, int flags) {
258         ResolveInfo handler = getPackageManager().resolveActivity(intent, flags | PackageManager.MATCH_SYSTEM_ONLY);
259         if (handler == null) {
260             return null;
261         }
262         if ((handler.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
263             return handler.activityInfo.packageName;
264         }
265         return null;
266     }
267 }
268