最近在Android R的一个项目上,Google 在基于Launcher3的基础上,集成了一个OnDeviceAppPrediction服务。功能呢,就是在All App View界面上端显示常用的应用和近期使用的应用。在所接触的项目中,经测试同事的辛苦挖掘,找出了不少问题:
1.Google对出现在近期列表中的应用执行卸载操作后做一个填充列表的动作,导致列表出现空缺。
2.使用其他应用后,没有实时改刷新界面显示。
一下便是我针对这两个问题的做的优化:
1.卸载应用后,做填充
/*
* 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 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.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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;
import static android.os.Process.myUserHandle;
import static android.text.TextUtils.isEmpty;
import static java.util.Collections.emptyList;
/*
* 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 {
public static final String MY_PREF = "mypref";
private static final String TAG = PredictionService.class.getSimpleName();
private static final int MAX_NUM = 5;
private static final String[] DEFAULT_PACKAGES = new String[]{
"com.google.android.apps.photos", // Photos
"com.google.android.apps.maps", // Maps
"com.google.android.gm", // Gmail
"com.android.settings", // Settings
"com.google.android.calculator" //Calculator
};
private final Set<AppPredictionSessionId> activeLauncherSessions = new HashSet<>();
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;
private PackageRemovedBroadcastReceiver mPackageRemovedBroadcastReceiver;
private String mMostRecentComponent;
private AppTarget mAppTarget;
private String mPackageName;
private Context mContext;
private boolean mRemovedLast = true;
private boolean mDebug = false;
private boolean mAppSuggestionsEnabled = true;
@Override
public void onCreate() {
super.onCreate();
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++) {
if (appNameKeys[i] != null && appNameKeys[i].length() > 3) {
String appName = sharedPreferences.getString(appNameKeys[i], "");
ComponentName cn = ComponentName.unflattenFromString(appName);
if (cn != null) {
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();
mContext = this;
if (mPackageRemovedBroadcastReceiver == null) {
mPackageRemovedBroadcastReceiver = new PackageRemovedBroadcastReceiver();
IntentFilter packageRemovedfilter = new IntentFilter();
packageRemovedfilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
packageRemovedfilter.addDataScheme("package");
registerReceiver(mPackageRemovedBroadcastReceiver, packageRemovedfilter);
}
}
@Override
public void onDestroy() {
if (mPackageRemovedBroadcastReceiver != null) {
unregisterReceiver(mPackageRemovedBroadcastReceiver);
mPackageRemovedBroadcastReceiver = null;
}
}
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 boolean isComponentNameLauncherable(ComponentName cn) {
List<LauncherActivityInfo> infos = getSystemService(LauncherApps.class)
.getActivityList(cn.getPackageName(), myUserHandle());
if (infos.isEmpty()) {
return false;
} else {
for(int i =0; i < infos.size(); i++){
Log.d(TAG, "isComponentNameLauncherable infos.get(i).getComponentName()="+infos.get(i).getComponentName());
if(infos.get(i).getComponentName().equals(cn)){
return true;
}
}
}
return false;
}
private boolean isLauncherable(String packageName) {
List<LauncherActivityInfo> infos = getSystemService(LauncherApps.class)
.getActivityList(packageName, myUserHandle());
if (infos.isEmpty()) {
return false;
} else {
return true;
}
}
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;
}
boolean isPackageLauncherable = isLauncherable(target.getPackageName());
if (mDebug) Log.i(TAG, "onAppTargetEvent isPackageLauncherable = " + isPackageLauncherable + " target.getPackageName()="+target.getPackageName());
if (!isPackageLauncherable) {
Log.d(TAG, "onAppTargetEvent isPackageLauncherable = " + isPackageLauncherable);
return;
}
ComponentName mostRecentCN = new ComponentName(
target.getPackageName(), target.getClassName());
if (mDebug)Log.d(TAG, "onAppTargetEvent mostRecentCN = " + mostRecentCN);
String mostRecentComponent = new ComponentName(
target.getPackageName(), target.getClassName()).flattenToString();
boolean isComponentNameLauncherable = isComponentNameLauncherable(mostRecentCN);
ComponentName launcherableComponent = getLauncherComponent(target.getPackageName());
String launcherableComponentString = launcherableComponent.flattenToString();
boolean isReplaceTarget = false;
AppTarget launcherableTarget = event.getTarget();
if(!isComponentNameLauncherable){
if(!mostRecentComponent.equals(launcherableComponentString)){
Log.d(TAG, "onAppTargetEvent launcherableComponentString = " + launcherableComponentString + " mostRecentComponent== " + mostRecentComponent);
launcherableTarget = new AppTarget.Builder(
new AppTargetId("app:" + launcherableComponent), launcherableComponent.getPackageName(), myUserHandle())
.setClassName(launcherableComponent.getClassName())
.build();
mostRecentComponent = launcherableComponentString;
isReplaceTarget = true;
}
}
if (mDebug)
Log.i(TAG, "onAppTargetEvent mostRecentComponent = " + mostRecentComponent);
// Check if packageName already exists in existing list of appNames
for (int i = 0; i < appNames.size(); i++) {
if (appNames.get(i).contains(target.getPackageName()) || appNames.get(i).equals(mostRecentComponent)) {
found = true;
break;
}
}
if (mDebug)
Log.i(TAG, "onAppTargetEvent found== " + found + " target.getPackageName()== " + target.getPackageName() + "/" + target.getClassName());
if (mDebug) Log.i(TAG, "onAppTargetEvent appNames.size()== " + appNames.size());
mRemovedLast = appNames.size() >= 4;
if (mDebug) Log.i(TAG, "onAppTargetEvent mRemovedLast== " + mRemovedLast);
if (!found) {
if (mRemovedLast) {
appNames.remove(appNames.size() - 1);
}
appNames.add(0, mostRecentComponent);
//Put the recent five app into sharedPreferences to store it
for (int i = 0; i < MAX_NUM; i++) {
if(i < appNames.size()){
editor.putString(appNameKeys[i], appNames.get(i));
}
}
editor.apply();
if (mRemovedLast) {
predictionList.remove(predictionList.size() - 1);
}
if(isReplaceTarget){
predictionList.add(0, launcherableTarget);
}else{
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;
}
private void removePackage(String packageName) {
if (null != packageName) {
boolean isRemovePackage = false;
for (int i = 0; i < appNames.size(); i++) {
if (appNames.get(i).contains(packageName)) {
if (mDebug)Log.i(TAG, "removePackage 111 i== " + i + " appNames.get(i) == " + appNames.get(i) + " packageName == " + packageName);
appNames.remove(i);
predictionList.remove(i);
isRemovePackage = true;
break;
}
}
List<ComponentName> AppNmaeExt = new ArrayList<>(5);
if(isRemovePackage) {
for(int i = 0;i < DEFAULT_PACKAGES.length;i++){
ComponentName mComponentName = getLauncherComponent(DEFAULT_PACKAGES[i]);
if(mComponentName != null){
boolean isHas = false;
if (mDebug) Log.i(TAG, "removePackage 222 i== " + i + " packageName" + DEFAULT_PACKAGES[i] );
for(int j = 0;j < appNames.size();j++){
if (mDebug) Log.d(TAG,"removePackage 3333 j== " + j + " appNames.get(j) == " + appNames.get(j));
if(appNames.get(j).contains(DEFAULT_PACKAGES[i])){
isHas = true;
continue;
}
}
if(!isHas){
AppNmaeExt.add(mComponentName);
}
}
}
if (mDebug) Log.d(TAG,"AppNmaeExt.size() ="+AppNmaeExt.size());
for(int i = 0; i < AppNmaeExt.size();i++){
if (mDebug) Log.d(TAG,"AppNmaeExt.appName ="+AppNmaeExt.get(i).flattenToShortString());
if(appNames.size() < MAX_NUM){
appNames.add(AppNmaeExt.get(i).flattenToShortString());
AppTarget target = new AppTarget.Builder(
new AppTargetId(Integer.toString(i+1)), AppNmaeExt.get(i).getPackageName(), myUserHandle())
.setClassName(AppNmaeExt.get(i).getClassName())
.build();
predictionList.add(target);
}
}
}
for (int i = 0; i < MAX_NUM; i++) {
if(i < appNames.size()){
editor.putString(appNameKeys[i], appNames.get(i));
}
}
editor.apply();
Log.d(TAG,"removePackage end");
postPredictionUpdateToAllClients();
}
}
private class PackageRemovedBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)) {
String packageName = intent.getData().getSchemeSpecificPart();
mPackageName = packageName;
removePackage(mPackageName);
if (mDebug) Log.i(TAG, "PackageRemovedBroadcastReceiver onReceive ACTION_PACKAGE_REMOVED == " + packageName);
}
}
}
}
2.改变列表做实时刷新
packages/apps/Launcher3/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
diff --git
@@ -184,6 +184,11 @@ public class PredictionUiStateManager implements StateListener<LauncherState>,
// Add a listener and wait until appsView is invisible again.
Launcher.getLauncher(mAppsView.getContext()).getStateManager().addStateListener(this);
}
+ //add .begin
+ if(mPendingState != null){
+ applyState(mPendingState);
+ }
+ //add .end
}