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