blob: 9e91b9d6e8350716b331f9f32da8e4faaa627fbe [file] [log] [blame]
/*
* Copyright (C) 2018 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.model;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.os.Process.myUserHandle;
import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller.SessionInfo;
import android.os.UserHandle;
import android.util.Log;
import androidx.annotation.AnyThread;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Helper class to send broadcasts to package installers that have:
* - Items on the first screen
* - Items with an active install session
*
* The packages are broken down by: folder items, workspace items, hotseat items, and widgets.
*
* Package installers only receive data for items that they are installing.
*/
public class FirstScreenBroadcast {
private static final String TAG = "FirstScreenBroadcast";
private static final boolean DEBUG = false;
private static final String ACTION_FIRST_SCREEN_ACTIVE_INSTALLS
= "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS";
private static final String FOLDER_ITEM_EXTRA = "folderItem";
private static final String WORKSPACE_ITEM_EXTRA = "workspaceItem";
private static final String HOTSEAT_ITEM_EXTRA = "hotseatItem";
private static final String WIDGET_ITEM_EXTRA = "widgetItem";
private static final String VERIFICATION_TOKEN_EXTRA = "verificationToken";
private final HashMap<PackageUserKey, SessionInfo> mSessionInfoForPackage;
public FirstScreenBroadcast(HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
mSessionInfoForPackage = sessionInfoForPackage;
}
/**
* Sends a broadcast to all package installers that have items with active sessions on the users
* first screen.
*/
@WorkerThread
public void sendBroadcasts(Context context, List<ItemInfo> firstScreenItems) {
UserHandle myUser = myUserHandle();
mSessionInfoForPackage
.values()
.stream()
.filter(info -> myUser.equals(getUserHandle(info)))
.collect(groupingBy(SessionInfo::getInstallerPackageName,
mapping(SessionInfo::getAppPackageName, Collectors.toSet())))
.forEach((installer, packages) ->
sendBroadcastToInstaller(context, installer, packages, firstScreenItems));
}
/**
* @param installerPackageName Package name of the package installer.
* @param packages List of packages with active sessions for this package installer.
* @param firstScreenItems List of items on the first screen.
*/
@WorkerThread
private void sendBroadcastToInstaller(Context context, String installerPackageName,
Set<String> packages, List<ItemInfo> firstScreenItems) {
Set<String> folderItems = new HashSet<>();
Set<String> workspaceItems = new HashSet<>();
Set<String> hotseatItems = new HashSet<>();
Set<String> widgetItems = new HashSet<>();
for (ItemInfo info : firstScreenItems) {
if (info instanceof FolderInfo) {
FolderInfo folderInfo = (FolderInfo) info;
String folderItemInfoPackage;
for (ItemInfo folderItemInfo : cloneOnMainThread(folderInfo.contents)) {
folderItemInfoPackage = getPackageName(folderItemInfo);
if (folderItemInfoPackage != null
&& packages.contains(folderItemInfoPackage)) {
folderItems.add(folderItemInfoPackage);
}
}
}
String packageName = getPackageName(info);
if (packageName == null || !packages.contains(packageName)) {
continue;
}
if (info instanceof LauncherAppWidgetInfo) {
widgetItems.add(packageName);
} else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
hotseatItems.add(packageName);
} else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
workspaceItems.add(packageName);
}
}
if (DEBUG) {
printList(installerPackageName, "Folder item", folderItems);
printList(installerPackageName, "Workspace item", workspaceItems);
printList(installerPackageName, "Hotseat item", hotseatItems);
printList(installerPackageName, "Widget item", widgetItems);
}
if (folderItems.isEmpty()
&& workspaceItems.isEmpty()
&& hotseatItems.isEmpty()
&& widgetItems.isEmpty()) {
// Avoid sending broadcast if there is nothing to send.
return;
}
context.sendBroadcast(new Intent(ACTION_FIRST_SCREEN_ACTIVE_INSTALLS)
.setPackage(installerPackageName)
.putStringArrayListExtra(FOLDER_ITEM_EXTRA, new ArrayList<>(folderItems))
.putStringArrayListExtra(WORKSPACE_ITEM_EXTRA, new ArrayList<>(workspaceItems))
.putStringArrayListExtra(HOTSEAT_ITEM_EXTRA, new ArrayList<>(hotseatItems))
.putStringArrayListExtra(WIDGET_ITEM_EXTRA, new ArrayList<>(widgetItems))
.putExtra(VERIFICATION_TOKEN_EXTRA, PendingIntent.getActivity(context, 0,
new Intent(), FLAG_ONE_SHOT | FLAG_IMMUTABLE)));
}
private static String getPackageName(ItemInfo info) {
String packageName = null;
if (info instanceof LauncherAppWidgetInfo) {
LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
if (widgetInfo.providerName != null) {
packageName = widgetInfo.providerName.getPackageName();
}
} else if (info.getTargetComponent() != null){
packageName = info.getTargetComponent().getPackageName();
}
return packageName;
}
private static void printList(String packageInstaller, String label, Set<String> packages) {
for (String pkg : packages) {
Log.d(TAG, packageInstaller + ":" + label + ":" + pkg);
}
}
/**
* Clone the provided list on UI thread. This is used for {@link FolderInfo#contents} which
* is always modified on UI thread.
*/
@AnyThread
private static List<WorkspaceItemInfo> cloneOnMainThread(ArrayList<WorkspaceItemInfo> list) {
try {
return MAIN_EXECUTOR.submit(() -> new ArrayList(list)).get();
} catch (Exception e) {
return Collections.emptyList();
}
}
}