Files
Lawnchair/src/com/android/launcher3/popup/PopupDataProvider.java
T
Jon Spivack 89c21c77f5 P2P App Sharing: Add Shareability Cache
This update allows Launcher to check with an external API to determine whether apps
are shareable before displaying the Share App button. To prevent
extraneous calls to the API, the retrieved shareability data
are cached. A job is scheduled to update this cache once a week.

This feature can be toggled with the ENABLE_SHAREABILITY_CHECK flag in
AppSharing.java. It is currently disabled, as the Play API has only been
partially rolled out.

More details on the design are available at go/app-shareability-cache-design

Bug: 168831749
Test: manual (local Wembley device)
Test: m -j RunLauncherGoGoogleRoboTests ROBOTEST_FILTER="AppShareabilityManagerTest|AppSharingTest"
Change-Id: I971a3776e09b6842dedf315ec592317050dd6790
2021-09-20 17:11:04 -07:00

291 lines
12 KiB
Java

/*
* Copyright (C) 2017 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.popup;
import android.content.ComponentName;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Provides data for the popup menu that appears after long-clicking on apps.
*/
public class PopupDataProvider implements NotificationListener.NotificationsChangedListener {
private static final boolean LOGD = false;
private static final String TAG = "PopupDataProvider";
private final Consumer<Predicate<PackageUserKey>> mNotificationDotsChangeListener;
/** Maps launcher activity components to a count of how many shortcuts they have. */
private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
/** Maps packages to their DotInfo's . */
private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
/** All installed widgets. */
private List<WidgetsListBaseEntry> mAllWidgets = List.of();
/** Widgets that can be recommended to the users. */
private List<ItemInfo> mRecommendedWidgets = List.of();
private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE;
public PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener) {
mNotificationDotsChangeListener = notificationDotsChangeListener;
}
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
mNotificationDotsChangeListener.accept(updatedDots);
mChangeListener.onNotificationDotsUpdated(updatedDots);
}
@Override
public void onNotificationPosted(PackageUserKey postedPackageUserKey,
NotificationKeyData notificationKey) {
DotInfo dotInfo = mPackageUserToDotInfos.get(postedPackageUserKey);
if (dotInfo == null) {
dotInfo = new DotInfo();
mPackageUserToDotInfos.put(postedPackageUserKey, dotInfo);
}
if (dotInfo.addOrUpdateNotificationKey(notificationKey)) {
updateNotificationDots(postedPackageUserKey::equals);
}
}
@Override
public void onNotificationRemoved(PackageUserKey removedPackageUserKey,
NotificationKeyData notificationKey) {
DotInfo oldDotInfo = mPackageUserToDotInfos.get(removedPackageUserKey);
if (oldDotInfo != null && oldDotInfo.removeNotificationKey(notificationKey)) {
if (oldDotInfo.getNotificationKeys().size() == 0) {
mPackageUserToDotInfos.remove(removedPackageUserKey);
}
updateNotificationDots(removedPackageUserKey::equals);
trimNotifications(mPackageUserToDotInfos);
}
}
@Override
public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) {
if (activeNotifications == null) return;
// This will contain the PackageUserKeys which have updated dots.
HashMap<PackageUserKey, DotInfo> updatedDots = new HashMap<>(mPackageUserToDotInfos);
mPackageUserToDotInfos.clear();
for (StatusBarNotification notification : activeNotifications) {
PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification);
DotInfo dotInfo = mPackageUserToDotInfos.get(packageUserKey);
if (dotInfo == null) {
dotInfo = new DotInfo();
mPackageUserToDotInfos.put(packageUserKey, dotInfo);
}
dotInfo.addOrUpdateNotificationKey(NotificationKeyData.fromNotification(notification));
}
// Add and remove from updatedDots so it contains the PackageUserKeys of updated dots.
for (PackageUserKey packageUserKey : mPackageUserToDotInfos.keySet()) {
DotInfo prevDot = updatedDots.get(packageUserKey);
DotInfo newDot = mPackageUserToDotInfos.get(packageUserKey);
if (prevDot == null
|| prevDot.getNotificationCount() != newDot.getNotificationCount()) {
updatedDots.put(packageUserKey, newDot);
} else {
// No need to update the dot if it already existed (no visual change).
// Note that if the dot was removed entirely, we wouldn't reach this point because
// this loop only includes active notifications added above.
updatedDots.remove(packageUserKey);
}
}
if (!updatedDots.isEmpty()) {
updateNotificationDots(updatedDots::containsKey);
}
trimNotifications(updatedDots);
}
private void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
mChangeListener.trimNotifications(updatedDots);
}
public void setDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
mDeepShortcutMap = deepShortcutMapCopy;
if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
}
public int getShortcutCountForItem(ItemInfo info) {
if (!ShortcutUtil.supportsDeepShortcuts(info)) {
return 0;
}
ComponentName component = info.getTargetComponent();
if (component == null) {
return 0;
}
Integer count = mDeepShortcutMap.get(new ComponentKey(component, info.user));
return count == null ? 0 : count;
}
public @Nullable DotInfo getDotInfoForItem(@NonNull ItemInfo info) {
if (!ShortcutUtil.supportsShortcuts(info)) {
return null;
}
DotInfo dotInfo = mPackageUserToDotInfos.get(PackageUserKey.fromItemInfo(info));
if (dotInfo == null) {
return null;
}
List<NotificationKeyData> notifications = getNotificationsForItem(
info, dotInfo.getNotificationKeys());
if (notifications.isEmpty()) {
return null;
}
return dotInfo;
}
public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) {
DotInfo dotInfo = getDotInfoForItem(info);
return dotInfo == null ? Collections.EMPTY_LIST
: getNotificationsForItem(info, dotInfo.getNotificationKeys());
}
public void cancelNotification(String notificationKey) {
NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
if (notificationListener == null) {
return;
}
notificationListener.cancelNotificationFromLauncher(notificationKey);
}
/**
* Sets a list of recommended widgets ordered by their order of appearance in the widgets
* recommendation UI.
*/
public void setRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
mRecommendedWidgets = recommendedWidgets;
mChangeListener.onRecommendedWidgetsBound();
}
public void setAllWidgets(List<WidgetsListBaseEntry> allWidgets) {
mAllWidgets = allWidgets;
mChangeListener.onWidgetsBound();
}
public void setChangeListener(PopupDataChangeListener listener) {
mChangeListener = listener == null ? PopupDataChangeListener.INSTANCE : listener;
}
public List<WidgetsListBaseEntry> getAllWidgets() {
return mAllWidgets;
}
/** Returns a list of recommended widgets. */
public List<WidgetItem> getRecommendedWidgets() {
HashMap<ComponentKey, WidgetItem> allWidgetItems = new HashMap<>();
mAllWidgets.stream()
.filter(entry -> entry instanceof WidgetsListContentEntry)
.forEach(entry -> ((WidgetsListContentEntry) entry).mWidgets
.forEach(widget -> allWidgetItems.put(
new ComponentKey(widget.componentName, widget.user), widget)));
return mRecommendedWidgets.stream()
.map(recommendedWidget -> allWidgetItems.get(
new ComponentKey(recommendedWidget.getTargetComponent(),
recommendedWidget.user)))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
return mAllWidgets.stream()
.filter(row -> row instanceof WidgetsListContentEntry
&& row.mPkgItem.packageName.equals(packageUserKey.mPackageName))
.flatMap(row -> ((WidgetsListContentEntry) row).mWidgets.stream())
.filter(widget -> packageUserKey.mUser.equals(widget.user))
.collect(Collectors.toList());
}
/**
* Returns a list of notifications that are relevant to given ItemInfo.
*/
public static @NonNull List<NotificationKeyData> getNotificationsForItem(
@NonNull ItemInfo info, @NonNull List<NotificationKeyData> notifications) {
String shortcutId = ShortcutUtil.getShortcutIdIfPinnedShortcut(info);
if (shortcutId == null) {
return notifications;
}
String[] personKeys = ShortcutUtil.getPersonKeysIfPinnedShortcut(info);
return notifications.stream().filter((NotificationKeyData notification) -> {
if (notification.shortcutId != null) {
return notification.shortcutId.equals(shortcutId);
}
if (notification.personKeysFromNotification.length != 0) {
return Arrays.equals(notification.personKeysFromNotification, personKeys);
}
return false;
}).collect(Collectors.toList());
}
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "PopupDataProvider:");
writer.println(prefix + "\tmPackageUserToDotInfos:" + mPackageUserToDotInfos);
}
/**
* Tells the listener that the system shortcuts have been updated, causing them to be redrawn.
*/
public void redrawSystemShortcuts() {
mChangeListener.onSystemShortcutsUpdated();
}
public interface PopupDataChangeListener {
PopupDataChangeListener INSTANCE = new PopupDataChangeListener() { };
default void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) { }
default void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) { }
default void onWidgetsBound() { }
/** A callback to get notified when recommended widgets are bound. */
default void onRecommendedWidgetsBound() { }
/** A callback to get notified when system shortcuts have been updated. */
default void onSystemShortcutsUpdated() { }
}
}