summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyle Cai <kylecai@google.com>2020-01-27 16:20:44 -0800
committerKyle Cai <kylecai@google.com>2020-01-28 16:31:30 -0800
commitd51c9790d787c341d926a46ba24f3f543e097376 (patch)
tree49add1792a05daf52d4d808b2b5df549a23d7277
parente4a57c75f067a48d7f93b481c815b05f710ee1f1 (diff)
downloadOnDeviceAppPrediction-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.bp50
-rw-r--r--AndroidManifest.xml31
-rw-r--r--src/com/android/apppredictionservice/PredictionService.java267
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;
+ }
+}