diff options
author | Kyle Cai <kylecai@google.com> | 2020-01-27 16:20:44 -0800 |
---|---|---|
committer | Kyle Cai <kylecai@google.com> | 2020-01-28 16:31:30 -0800 |
commit | d51c9790d787c341d926a46ba24f3f543e097376 (patch) | |
tree | 49add1792a05daf52d4d808b2b5df549a23d7277 | |
parent | e4a57c75f067a48d7f93b481c815b05f710ee1f1 (diff) | |
download | OnDeviceAppPrediction-d51c9790d787c341d926a46ba24f3f543e097376.tar.gz |
Android One AppPredictionService final version. Used to provide simple prediction as top row of app drawer.r_aml_301500702android-mainline-12.0.0_r55android-mainline-12.0.0_r112android-mainline-11.0.0_r9android-mainline-11.0.0_r8android-mainline-11.0.0_r7android-mainline-11.0.0_r6android-mainline-11.0.0_r5android-mainline-11.0.0_r45android-mainline-11.0.0_r44android-mainline-11.0.0_r43android-mainline-11.0.0_r42android-mainline-11.0.0_r41android-mainline-11.0.0_r40android-mainline-11.0.0_r4android-mainline-11.0.0_r39android-mainline-11.0.0_r38android-mainline-11.0.0_r37android-mainline-11.0.0_r36android-mainline-11.0.0_r35android-mainline-11.0.0_r34android-mainline-11.0.0_r33android-mainline-11.0.0_r32android-mainline-11.0.0_r31android-mainline-11.0.0_r30android-mainline-11.0.0_r3android-mainline-11.0.0_r29android-mainline-11.0.0_r28android-mainline-11.0.0_r27android-mainline-11.0.0_r26android-mainline-11.0.0_r25android-mainline-11.0.0_r24android-mainline-11.0.0_r23android-mainline-11.0.0_r22android-mainline-11.0.0_r21android-mainline-11.0.0_r20android-mainline-11.0.0_r2android-mainline-11.0.0_r19android-mainline-11.0.0_r18android-mainline-11.0.0_r17android-mainline-11.0.0_r16android-mainline-11.0.0_r15android-mainline-11.0.0_r14android-mainline-11.0.0_r13android-mainline-11.0.0_r12android-mainline-11.0.0_r10android-mainline-11.0.0_r1android-11.0.0_r9android-11.0.0_r8android-11.0.0_r7android-11.0.0_r16android-11.0.0_r15android-11.0.0_r14android-11.0.0_r13android-11.0.0_r12android-11.0.0_r11android-11.0.0_r10aml_tz2_305400500aml_tz2_305400300aml_tz2_305400100aml_tz2_304500300aml_tz2_303900110aml_tz2_303900102aml_tz2_303800002aml_tz2_303800001aml_tz2_303200001android12-mainline-tzdata2-releaseandroid11-mainline-tethering-releaseandroid11-mainline-sparse-2021-jan-releaseandroid11-mainline-sparse-2020-dec-releaseandroid11-mainline-releaseandroid11-mainline-permission-releaseandroid11-mainline-os-statsd-releaseandroid11-mainline-networkstack-releaseandroid11-mainline-media-swcodec-releaseandroid11-mainline-media-releaseandroid11-mainline-extservices-releaseandroid11-mainline-documentsui-releaseandroid11-mainline-conscrypt-releaseandroid11-mainline-cellbroadcast-releaseandroid11-mainline-captiveportallogin-releaseandroid11-devandroid11-d1-s7-releaseandroid11-d1-s6-releaseandroid11-d1-s5-releaseandroid11-d1-s1-releaseandroid11-d1-releaseandroid11-d1-b-releaseaml_tz2_305400100
Test: tested on crosshatch
BUG: b/130048609
Change-Id: I5d8d905afe3819f2e6eca0ae5e1914cb3f0ac9a1
-rw-r--r-- | Android.bp | 50 | ||||
-rw-r--r-- | AndroidManifest.xml | 31 | ||||
-rw-r--r-- | src/com/android/apppredictionservice/PredictionService.java | 267 |
3 files changed, 348 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..da9b552 --- /dev/null +++ b/Android.bp @@ -0,0 +1,50 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +java_defaults { + name: "apppredictionservice_defaults", + + privileged: true, + + certificate: "platform", + + optimize: { + }, + + sdk_version: "system_current", +} + +filegroup { + name: "AppPredictionService-srcs", + srcs: [ + "src/**/*.java", + ], +} + +android_app { + name: "AppPredictionService", + + defaults: ["apppredictionservice_defaults"], + + manifest: "AndroidManifest.xml", + + srcs: [ + ":AppPredictionService-srcs", + ], + + resource_dirs: [ + ], + + required: ["privapp_whitelist_com.android.apppredictionservice"], +} diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..c85ddb6 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.apppredictionservice"> + + <application> + <service + android:name=".PredictionService" + android:exported="true"> + <intent-filter> + <!-- This constant must match AppPredictionService.SERVICE_INTERFACE --> + <action android:name="android.service.appprediction.AppPredictionService" /> + </intent-filter> + </service> + + </application> +</manifest> diff --git a/src/com/android/apppredictionservice/PredictionService.java b/src/com/android/apppredictionservice/PredictionService.java new file mode 100644 index 0000000..b25edeb --- /dev/null +++ b/src/com/android/apppredictionservice/PredictionService.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.apppredictionservice; + +import static android.os.Process.myUserHandle; +import static android.text.TextUtils.isEmpty; + +import static java.util.Collections.emptyList; + +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppTargetId; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.CancellationSignal; +import android.service.appprediction.AppPredictionService; +import android.util.Log; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +/* + * New plugin that replaces prediction driven Aiai APK in P + * PredictionService simply populates the top row of the app + * drawer with the 5 most recently used apps. Each time a new + * app is launched, it is added to the left of the top row. + * Duplicates are not added. + */ +public class PredictionService extends AppPredictionService { + + private static final String TAG = PredictionService.class.getSimpleName(); + + private final Set<AppPredictionSessionId> activeLauncherSessions = new HashSet<>(); + + private boolean mAppSuggestionsEnabled = true; + + public static final String MY_PREF = "mypref"; + + private final List<AppTarget> predictionList = new ArrayList<>(5); + + private final List<String> appNames = new ArrayList<>(5); + private final String[] appNameKeys = new String[] { + "first", "second", "third", "fourth", "fifth" }; + + SharedPreferences sharedPreferences; + SharedPreferences.Editor editor; + + @Override + public void onCreate() { + super.onCreate(); + + Intent calendarIntent = new Intent(Intent.ACTION_MAIN); + calendarIntent.addCategory(Intent.CATEGORY_APP_CALENDAR); + + Intent galleryIntent = new Intent(Intent.ACTION_MAIN); + galleryIntent.addCategory(Intent.CATEGORY_APP_GALLERY); + + Intent mapsIntent = new Intent(Intent.ACTION_MAIN); + mapsIntent.addCategory(Intent.CATEGORY_APP_MAPS); + + Intent emailIntent = new Intent(Intent.ACTION_MAIN); + emailIntent.addCategory(Intent.CATEGORY_APP_EMAIL); + + Intent browserIntent = new Intent(Intent.ACTION_MAIN); + browserIntent.addCategory(Intent.CATEGORY_APP_BROWSER); + + String[] DEFAULT_PACKAGES = new String[] { + getDefaultSystemHandlerActivityPackageName(calendarIntent), + getDefaultSystemHandlerActivityPackageName(galleryIntent), + getDefaultSystemHandlerActivityPackageName(mapsIntent), + getDefaultSystemHandlerActivityPackageName(emailIntent), + getDefaultSystemHandlerActivityPackageName(browserIntent), + }; + + Log.d(TAG, "AppPredictionService onCreate"); + this.sharedPreferences = getSharedPreferences(MY_PREF, Context.MODE_PRIVATE); + this.editor = sharedPreferences.edit(); + + if (sharedPreferences.getString(appNameKeys[0], "").isEmpty()) { + // fill the list with defaults if first one is null when devices powers up for the first time + for (int i = 0; i < appNameKeys.length; i++) { + editor.putString(appNameKeys[i], + getLauncherComponent(DEFAULT_PACKAGES[i]).flattenToShortString()); + } + this.editor.apply(); + } + + for (int i = 0; i < appNameKeys.length; i++) { + String appName = sharedPreferences.getString(appNameKeys[i], ""); + ComponentName cn = ComponentName.unflattenFromString(appName); + AppTarget target = new AppTarget.Builder( + new AppTargetId(Integer.toString(i + 1)), cn.getPackageName(), myUserHandle()) + .setClassName(cn.getClassName()) + .build(); + appNames.add(appName); + predictionList.add(target); + } + postPredictionUpdateToAllClients(); + } + + private ComponentName getLauncherComponent(String packageName) { + List<LauncherActivityInfo> infos = getSystemService(LauncherApps.class) + .getActivityList(packageName, myUserHandle()); + if (infos.isEmpty()) { + return new ComponentName(packageName, "#"); + } else { + return infos.get(0).getComponentName(); + } + } + + private void postPredictionUpdate(AppPredictionSessionId sessionId) { + updatePredictions(sessionId, mAppSuggestionsEnabled ? predictionList : emptyList()); + } + + private void postPredictionUpdateToAllClients() { + for (AppPredictionSessionId session : activeLauncherSessions) { + postPredictionUpdate(session); + } + } + + @Override + public void onCreatePredictionSession( + AppPredictionContext context, AppPredictionSessionId sessionId) { + Log.d(TAG, "onCreatePredictionSession"); + + if (context.getUiSurface().equals("home") || context.getUiSurface().equals("overview")) { + activeLauncherSessions.add(sessionId); + postPredictionUpdate(sessionId); + } + } + + @Override + public void onAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) { + + if (!activeLauncherSessions.contains(sessionId)) { + return; + } + + boolean found = false; + Log.d(TAG, "onAppTargetEvent"); + + AppTarget target = event.getTarget(); + if (target == null || isEmpty(target.getPackageName()) || isEmpty(target.getClassName())) { + return; + } + String mostRecentComponent = new ComponentName( + target.getPackageName(), target.getClassName()).flattenToString(); + + // Check if packageName already exists in existing list of appNames + for (String packageName:appNames) { + if (packageName.contains(target.getPackageName())) { + found = true; + break; + } + } + + if (!found) { + appNames.remove(appNames.size() - 1); + appNames.add(0, mostRecentComponent); + + for (int i = 0; i < appNameKeys.length; i++) { + editor.putString(appNameKeys[i], appNames.get(i)); + } + editor.apply(); + + predictionList.remove(predictionList.size() - 1); + predictionList.add(0, event.getTarget()); + + Log.d(TAG, "onAppTargetEvent:: update predictions"); + postPredictionUpdateToAllClients(); + } + } + + @Override + public void onLaunchLocationShown( + AppPredictionSessionId sessionId, String launchLocation, List<AppTargetId> targetIds) { + Log.d(TAG, "onLaunchLocationShown"); + } + + @Override + public void onSortAppTargets( + AppPredictionSessionId sessionId, + List<AppTarget> targets, + CancellationSignal cancellationSignal, + Consumer<List<AppTarget>> callback) { + + Log.d(TAG, "onSortAppTargets"); + if (!activeLauncherSessions.contains(sessionId)) { + callback.accept(emptyList()); + } else { + // No-op + callback.accept(targets); + } + } + + @Override + public void onRequestPredictionUpdate(AppPredictionSessionId sessionId) { + Log.d(TAG, "onRequestPredictionUpdate"); + + if (!activeLauncherSessions.contains(sessionId)) { + updatePredictions(sessionId, emptyList()); + } else { + postPredictionUpdate(sessionId); + Log.d(TAG, "update predictions"); + } + } + + @Override + public void onDestroyPredictionSession(AppPredictionSessionId sessionId) { + Log.d(TAG, "onDestroyPredictionSession"); + activeLauncherSessions.remove(sessionId); + } + + @Override + public void onStartPredictionUpdates() { + Log.d(TAG, "onStartPredictionUpdates"); + } + + @Override + public void onStopPredictionUpdates() { + Log.d(TAG, "onStopPredictionUpdates"); + } + + public void setAppSuggestionsEnabled(boolean enabled) { + mAppSuggestionsEnabled = enabled; + postPredictionUpdateToAllClients(); + } + + private String getDefaultSystemHandlerActivityPackageName(Intent intent) { + return getDefaultSystemHandlerActivityPackageName(intent, 0); + } + + private String getDefaultSystemHandlerActivityPackageName(Intent intent, int flags) { + ResolveInfo handler = getPackageManager().resolveActivity(intent, flags | PackageManager.MATCH_SYSTEM_ONLY); + if (handler == null) { + return null; + } + if ((handler.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + return handler.activityInfo.packageName; + } + return null; + } +} |