• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.model;
17 
18 import static android.app.PendingIntent.FLAG_IMMUTABLE;
19 import static android.app.PendingIntent.FLAG_ONE_SHOT;
20 import static android.os.Process.myUserHandle;
21 
22 import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
23 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
24 
25 import static java.util.stream.Collectors.groupingBy;
26 import static java.util.stream.Collectors.mapping;
27 
28 import android.app.PendingIntent;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.pm.PackageInstaller.SessionInfo;
32 import android.os.UserHandle;
33 import android.util.Log;
34 
35 import androidx.annotation.AnyThread;
36 import androidx.annotation.WorkerThread;
37 
38 import com.android.launcher3.LauncherSettings;
39 import com.android.launcher3.model.data.FolderInfo;
40 import com.android.launcher3.model.data.ItemInfo;
41 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
42 import com.android.launcher3.model.data.WorkspaceItemInfo;
43 import com.android.launcher3.util.PackageUserKey;
44 
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.List;
50 import java.util.Set;
51 import java.util.stream.Collectors;
52 
53 /**
54  * Helper class to send broadcasts to package installers that have:
55  * - Items on the first screen
56  * - Items with an active install session
57  *
58  * The packages are broken down by: folder items, workspace items, hotseat items, and widgets.
59  *
60  * Package installers only receive data for items that they are installing.
61  */
62 public class FirstScreenBroadcast {
63 
64     private static final String TAG = "FirstScreenBroadcast";
65     private static final boolean DEBUG = false;
66 
67     private static final String ACTION_FIRST_SCREEN_ACTIVE_INSTALLS
68             = "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS";
69 
70     private static final String FOLDER_ITEM_EXTRA = "folderItem";
71     private static final String WORKSPACE_ITEM_EXTRA = "workspaceItem";
72     private static final String HOTSEAT_ITEM_EXTRA = "hotseatItem";
73     private static final String WIDGET_ITEM_EXTRA = "widgetItem";
74 
75     private static final String VERIFICATION_TOKEN_EXTRA = "verificationToken";
76 
77     private final HashMap<PackageUserKey, SessionInfo> mSessionInfoForPackage;
78 
FirstScreenBroadcast(HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage)79     public FirstScreenBroadcast(HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
80         mSessionInfoForPackage = sessionInfoForPackage;
81     }
82 
83     /**
84      * Sends a broadcast to all package installers that have items with active sessions on the users
85      * first screen.
86      */
87     @WorkerThread
sendBroadcasts(Context context, List<ItemInfo> firstScreenItems)88     public void sendBroadcasts(Context context, List<ItemInfo> firstScreenItems) {
89         UserHandle myUser = myUserHandle();
90         mSessionInfoForPackage
91                 .values()
92                 .stream()
93                 .filter(info -> myUser.equals(getUserHandle(info)))
94                 .collect(groupingBy(SessionInfo::getInstallerPackageName,
95                         mapping(SessionInfo::getAppPackageName, Collectors.toSet())))
96                 .forEach((installer, packages) ->
97                     sendBroadcastToInstaller(context, installer, packages, firstScreenItems));
98     }
99 
100     /**
101      * @param installerPackageName Package name of the package installer.
102      * @param packages List of packages with active sessions for this package installer.
103      * @param firstScreenItems List of items on the first screen.
104      */
105     @WorkerThread
sendBroadcastToInstaller(Context context, String installerPackageName, Set<String> packages, List<ItemInfo> firstScreenItems)106     private void sendBroadcastToInstaller(Context context, String installerPackageName,
107             Set<String> packages, List<ItemInfo> firstScreenItems) {
108         Set<String> folderItems = new HashSet<>();
109         Set<String> workspaceItems = new HashSet<>();
110         Set<String> hotseatItems = new HashSet<>();
111         Set<String> widgetItems = new HashSet<>();
112 
113         for (ItemInfo info : firstScreenItems) {
114             if (info instanceof FolderInfo) {
115                 FolderInfo folderInfo = (FolderInfo) info;
116                 String folderItemInfoPackage;
117                 for (ItemInfo folderItemInfo : cloneOnMainThread(folderInfo.contents)) {
118                     folderItemInfoPackage = getPackageName(folderItemInfo);
119                     if (folderItemInfoPackage != null
120                             && packages.contains(folderItemInfoPackage)) {
121                         folderItems.add(folderItemInfoPackage);
122                     }
123                 }
124             }
125 
126             String packageName = getPackageName(info);
127             if (packageName == null || !packages.contains(packageName)) {
128                 continue;
129             }
130             if (info instanceof LauncherAppWidgetInfo) {
131                 widgetItems.add(packageName);
132             } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
133                 hotseatItems.add(packageName);
134             } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
135                 workspaceItems.add(packageName);
136             }
137         }
138 
139         if (DEBUG) {
140             printList(installerPackageName, "Folder item", folderItems);
141             printList(installerPackageName, "Workspace item", workspaceItems);
142             printList(installerPackageName, "Hotseat item", hotseatItems);
143             printList(installerPackageName, "Widget item", widgetItems);
144         }
145 
146         if (folderItems.isEmpty()
147                 && workspaceItems.isEmpty()
148                 && hotseatItems.isEmpty()
149                 && widgetItems.isEmpty()) {
150             // Avoid sending broadcast if there is nothing to send.
151             return;
152         }
153         context.sendBroadcast(new Intent(ACTION_FIRST_SCREEN_ACTIVE_INSTALLS)
154                 .setPackage(installerPackageName)
155                 .putStringArrayListExtra(FOLDER_ITEM_EXTRA, new ArrayList<>(folderItems))
156                 .putStringArrayListExtra(WORKSPACE_ITEM_EXTRA, new ArrayList<>(workspaceItems))
157                 .putStringArrayListExtra(HOTSEAT_ITEM_EXTRA, new ArrayList<>(hotseatItems))
158                 .putStringArrayListExtra(WIDGET_ITEM_EXTRA, new ArrayList<>(widgetItems))
159                 .putExtra(VERIFICATION_TOKEN_EXTRA, PendingIntent.getActivity(context, 0,
160                         new Intent(), FLAG_ONE_SHOT | FLAG_IMMUTABLE)));
161     }
162 
getPackageName(ItemInfo info)163     private static String getPackageName(ItemInfo info) {
164         String packageName = null;
165         if (info instanceof LauncherAppWidgetInfo) {
166             LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
167             if (widgetInfo.providerName != null) {
168                 packageName = widgetInfo.providerName.getPackageName();
169             }
170         } else if (info.getTargetComponent() != null){
171             packageName = info.getTargetComponent().getPackageName();
172         }
173         return packageName;
174     }
175 
printList(String packageInstaller, String label, Set<String> packages)176     private static void printList(String packageInstaller, String label, Set<String> packages) {
177         for (String pkg : packages) {
178             Log.d(TAG, packageInstaller + ":" + label + ":" + pkg);
179         }
180     }
181 
182     /**
183      * Clone the provided list on UI thread. This is used for {@link FolderInfo#contents} which
184      * is always modified on UI thread.
185      */
186     @AnyThread
cloneOnMainThread(ArrayList<WorkspaceItemInfo> list)187     private static List<WorkspaceItemInfo> cloneOnMainThread(ArrayList<WorkspaceItemInfo> list) {
188         try {
189             return MAIN_EXECUTOR.submit(() -> new ArrayList(list)).get();
190         } catch (Exception e) {
191             return Collections.emptyList();
192         }
193     }
194 }
195