1 /*
2  * Copyright (C) 2020 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.launcher3.hybridhotseat;
17 
18 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
19 
20 import android.app.prediction.AppTarget;
21 import android.app.prediction.AppTargetEvent;
22 import android.app.prediction.AppTargetId;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.os.Bundle;
26 
27 import com.android.launcher3.LauncherAppState;
28 import com.android.launcher3.LauncherSettings;
29 import com.android.launcher3.Workspace;
30 import com.android.launcher3.model.AllAppsList;
31 import com.android.launcher3.model.BaseModelUpdateTask;
32 import com.android.launcher3.model.BgDataModel;
33 import com.android.launcher3.model.PredictionModel;
34 import com.android.launcher3.model.data.ItemInfo;
35 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
36 import com.android.launcher3.model.data.WorkspaceItemInfo;
37 import com.android.launcher3.shortcuts.ShortcutKey;
38 
39 import java.util.ArrayList;
40 import java.util.Locale;
41 import java.util.function.Consumer;
42 
43 /**
44  * Model helper for app predictions in workspace
45  */
46 public class HotseatPredictionModel extends PredictionModel {
47     private static final String APP_LOCATION_HOTSEAT = "hotseat";
48     private static final String APP_LOCATION_WORKSPACE = "workspace";
49 
50     private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events";
51     private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items";
52 
53 
HotseatPredictionModel(Context context)54     public HotseatPredictionModel(Context context) { }
55 
56     /**
57      * Creates and returns bundle using workspace items and cached items
58      */
createBundle(Consumer<Bundle> cb)59     public void createBundle(Consumer<Bundle> cb) {
60         LauncherAppState appState = LauncherAppState.getInstance(mContext);
61         appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
62             @Override
63             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
64                 Bundle bundle = new Bundle();
65                 ArrayList<AppTargetEvent> events = new ArrayList<>();
66                 ArrayList<ItemInfo> workspaceItems = new ArrayList<>(dataModel.workspaceItems);
67                 workspaceItems.addAll(dataModel.appWidgets);
68                 for (ItemInfo item : workspaceItems) {
69                     AppTarget target = getAppTargetFromInfo(item);
70                     if (target != null && !isTrackedForPrediction(item)) continue;
71                     events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item));
72                 }
73                 ArrayList<AppTarget> currentTargets = new ArrayList<>();
74                 for (ItemInfo itemInfo : dataModel.cachedPredictedItems) {
75                     AppTarget target = getAppTargetFromInfo(itemInfo);
76                     if (target != null) currentTargets.add(target);
77                 }
78                 bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events);
79                 bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets);
80                 MAIN_EXECUTOR.execute(() -> cb.accept(bundle));
81             }
82         });
83     }
84 
85     /**
86      * Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null
87      * if item is not supported prediction
88      */
getAppTargetFromInfo(ItemInfo info)89     public AppTarget getAppTargetFromInfo(ItemInfo info) {
90         if (info == null) return null;
91         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
92                 && info instanceof LauncherAppWidgetInfo
93                 && ((LauncherAppWidgetInfo) info).providerName != null) {
94             ComponentName cn = ((LauncherAppWidgetInfo) info).providerName;
95             return new AppTarget.Builder(new AppTargetId("widget:" + cn.getPackageName()),
96                     cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
97         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
98                 && info.getTargetComponent() != null) {
99             ComponentName cn = info.getTargetComponent();
100             return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()),
101                     cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
102         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
103                 && info instanceof WorkspaceItemInfo) {
104             ShortcutKey shortcutKey = ShortcutKey.fromItemInfo(info);
105             //TODO: switch to using full shortcut info
106             return new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutKey.getId()),
107                     shortcutKey.componentName.getPackageName(), shortcutKey.user).build();
108         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
109             return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
110                     mContext.getPackageName(), info.user).build();
111         }
112         return null;
113     }
114 
115 
116     /**
117      * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item
118      * location using {@link ItemInfo}
119      */
wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info)120     public AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info) {
121         String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]",
122                 info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
123                         ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE,
124                 info.screenId, info.cellX, info.cellY, info.spanX, info.spanY);
125         return new AppTargetEvent.Builder(target, action).setLaunchLocation(location).build();
126     }
127 
128     /**
129      * Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors
130      */
isTrackedForPrediction(ItemInfo info)131     public static boolean isTrackedForPrediction(ItemInfo info) {
132         return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT || (
133                 info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
134                         && info.screenId == Workspace.FIRST_SCREEN_ID);
135     }
136 }
137