| /** |
| * 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.launcher3.appprediction; |
| |
| import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID; |
| import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; |
| |
| import android.annotation.TargetApi; |
| import android.app.prediction.AppPredictionContext; |
| import android.app.prediction.AppPredictionManager; |
| import android.app.prediction.AppPredictor; |
| 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.os.Build; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.UserHandle; |
| import android.util.Log; |
| |
| import androidx.annotation.Nullable; |
| import androidx.annotation.UiThread; |
| import androidx.annotation.WorkerThread; |
| |
| import com.android.launcher3.InvariantDeviceProfile; |
| import com.android.launcher3.appprediction.PredictionUiStateManager.Client; |
| import com.android.launcher3.model.AppLaunchTracker; |
| |
| /** |
| * Subclass of app tracker which publishes the data to the prediction engine and gets back results. |
| */ |
| @TargetApi(Build.VERSION_CODES.Q) |
| public class PredictionAppTracker extends AppLaunchTracker { |
| |
| private static final String TAG = "PredictionAppTracker"; |
| private static final boolean DBG = false; |
| |
| private static final int MSG_INIT = 0; |
| private static final int MSG_DESTROY = 1; |
| private static final int MSG_LAUNCH = 2; |
| private static final int MSG_PREDICT = 3; |
| |
| protected final Context mContext; |
| private final Handler mMessageHandler; |
| |
| // Accessed only on worker thread |
| private AppPredictor mHomeAppPredictor; |
| private AppPredictor mRecentsOverviewPredictor; |
| |
| public PredictionAppTracker(Context context) { |
| mContext = context; |
| mMessageHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessage); |
| InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged); |
| |
| mMessageHandler.sendEmptyMessage(MSG_INIT); |
| } |
| |
| @UiThread |
| private void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { |
| if ((changeFlags & CHANGE_FLAG_GRID) != 0) { |
| // Reinitialize everything |
| mMessageHandler.sendEmptyMessage(MSG_INIT); |
| } |
| } |
| |
| @WorkerThread |
| private void destroy() { |
| if (mHomeAppPredictor != null) { |
| mHomeAppPredictor.destroy(); |
| mHomeAppPredictor = null; |
| } |
| if (mRecentsOverviewPredictor != null) { |
| mRecentsOverviewPredictor.destroy(); |
| mRecentsOverviewPredictor = null; |
| } |
| } |
| |
| @WorkerThread |
| private AppPredictor createPredictor(Client client, int count) { |
| AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class); |
| |
| if (apm == null) { |
| return null; |
| } |
| |
| AppPredictor predictor = apm.createAppPredictionSession( |
| new AppPredictionContext.Builder(mContext) |
| .setUiSurface(client.id) |
| .setPredictedTargetCount(count) |
| .setExtras(getAppPredictionContextExtras(client)) |
| .build()); |
| predictor.registerPredictionUpdates(mContext.getMainExecutor(), |
| PredictionUiStateManager.INSTANCE.get(mContext).appPredictorCallback(client)); |
| predictor.requestPredictionUpdate(); |
| return predictor; |
| } |
| |
| /** |
| * Override to add custom extras. |
| */ |
| @WorkerThread |
| @Nullable |
| public Bundle getAppPredictionContextExtras(Client client){ |
| return null; |
| } |
| |
| @WorkerThread |
| private boolean handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_INIT: { |
| // Destroy any existing clients |
| destroy(); |
| |
| // Initialize the clients |
| int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns; |
| mHomeAppPredictor = createPredictor(Client.HOME, count); |
| mRecentsOverviewPredictor = createPredictor(Client.OVERVIEW, count); |
| return true; |
| } |
| case MSG_DESTROY: { |
| destroy(); |
| return true; |
| } |
| case MSG_LAUNCH: { |
| if (mHomeAppPredictor != null) { |
| mHomeAppPredictor.notifyAppTargetEvent((AppTargetEvent) msg.obj); |
| } |
| return true; |
| } |
| case MSG_PREDICT: { |
| if (mHomeAppPredictor != null) { |
| String client = (String) msg.obj; |
| if (Client.HOME.id.equals(client)) { |
| mHomeAppPredictor.requestPredictionUpdate(); |
| } else { |
| mRecentsOverviewPredictor.requestPredictionUpdate(); |
| } |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| @UiThread |
| public void onReturnedToHome() { |
| String client = Client.HOME.id; |
| mMessageHandler.removeMessages(MSG_PREDICT, client); |
| Message.obtain(mMessageHandler, MSG_PREDICT, client).sendToTarget(); |
| if (DBG) { |
| Log.d(TAG, String.format("Sent immediate message to update %s", client)); |
| } |
| } |
| |
| @Override |
| @UiThread |
| public void onStartShortcut(String packageName, String shortcutId, UserHandle user, |
| String container) { |
| // TODO: Use the full shortcut info |
| AppTarget target = new AppTarget.Builder( |
| new AppTargetId("shortcut:" + shortcutId), packageName, user) |
| .setClassName(shortcutId) |
| .build(); |
| |
| sendLaunch(target, container); |
| } |
| |
| @Override |
| @UiThread |
| public void onStartApp(ComponentName cn, UserHandle user, String container) { |
| if (cn != null) { |
| AppTarget target = new AppTarget.Builder( |
| new AppTargetId("app:" + cn), cn.getPackageName(), user) |
| .setClassName(cn.getClassName()) |
| .build(); |
| sendLaunch(target, container); |
| } |
| } |
| |
| @Override |
| @UiThread |
| public void onDismissApp(ComponentName cn, UserHandle user, String container) { |
| if (cn == null) return; |
| AppTarget target = new AppTarget.Builder( |
| new AppTargetId("app: " + cn), cn.getPackageName(), user) |
| .setClassName(cn.getClassName()) |
| .build(); |
| sendDismiss(target, container); |
| } |
| |
| @UiThread |
| private void sendEvent(AppTarget target, String container, int eventId) { |
| AppTargetEvent event = new AppTargetEvent.Builder(target, eventId) |
| .setLaunchLocation(container == null ? CONTAINER_DEFAULT : container) |
| .build(); |
| Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget(); |
| } |
| |
| @UiThread |
| private void sendLaunch(AppTarget target, String container) { |
| sendEvent(target, container, AppTargetEvent.ACTION_LAUNCH); |
| } |
| |
| @UiThread |
| private void sendDismiss(AppTarget target, String container) { |
| sendEvent(target, container, AppTargetEvent.ACTION_DISMISS); |
| } |
| } |