blob: 1d484382293110dff8ce9cb322d8d064b9a00f9e [file] [log] [blame]
Mady Mellor56515c42020-02-18 17:58:36 -08001/*
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
17package com.android.server.notification;
18
19import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
20import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
21import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
22
23import android.annotation.NonNull;
Julia Reynoldsfa273072020-04-14 15:31:21 -040024import android.content.Intent;
25import android.content.IntentFilter;
Mady Mellor56515c42020-02-18 17:58:36 -080026import android.content.pm.LauncherApps;
27import android.content.pm.ShortcutInfo;
Julia Reynoldsfa273072020-04-14 15:31:21 -040028import android.content.pm.ShortcutServiceInternal;
Mady Mellor56515c42020-02-18 17:58:36 -080029import android.os.Binder;
30import android.os.Handler;
31import android.os.UserHandle;
Julia Reynoldsfa273072020-04-14 15:31:21 -040032import android.util.Slog;
Mady Mellor56515c42020-02-18 17:58:36 -080033
34import com.android.internal.annotations.VisibleForTesting;
35
36import java.util.ArrayList;
37import java.util.Arrays;
38import java.util.HashMap;
39import java.util.List;
40
41/**
42 * Helper for querying shortcuts.
43 */
44class ShortcutHelper {
Julia Reynoldsfa273072020-04-14 15:31:21 -040045 private static final String TAG = "ShortcutHelper";
Mady Mellor56515c42020-02-18 17:58:36 -080046
47 /**
48 * Listener to call when a shortcut we're tracking has been removed.
49 */
50 interface ShortcutListener {
51 void onShortcutRemoved(String key);
52 }
53
54 private LauncherApps mLauncherAppsService;
55 private ShortcutListener mShortcutListener;
Julia Reynoldsfa273072020-04-14 15:31:21 -040056 private ShortcutServiceInternal mShortcutServiceInternal;
57 private IntentFilter mSharingFilter;
Mady Mellor56515c42020-02-18 17:58:36 -080058
59 // Key: packageName Value: <shortcutId, notifId>
60 private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
61 private boolean mLauncherAppsCallbackRegistered;
62
63 // Bubbles can be created based on a shortcut, we need to listen for changes to
64 // that shortcut so that we may update the bubble appropriately.
65 private final LauncherApps.Callback mLauncherAppsCallback = new LauncherApps.Callback() {
66 @Override
67 public void onPackageRemoved(String packageName, UserHandle user) {
68 }
69
70 @Override
71 public void onPackageAdded(String packageName, UserHandle user) {
72 }
73
74 @Override
75 public void onPackageChanged(String packageName, UserHandle user) {
76 }
77
78 @Override
79 public void onPackagesAvailable(String[] packageNames, UserHandle user,
80 boolean replacing) {
81 }
82
83 @Override
84 public void onPackagesUnavailable(String[] packageNames, UserHandle user,
85 boolean replacing) {
86 }
87
88 @Override
89 public void onShortcutsChanged(@NonNull String packageName,
90 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
91 HashMap<String, String> shortcutBubbles = mActiveShortcutBubbles.get(packageName);
92 ArrayList<String> bubbleKeysToRemove = new ArrayList<>();
93 if (shortcutBubbles != null) {
94 // If we can't find one of our bubbles in the shortcut list, that bubble needs
95 // to be removed.
96 for (String shortcutId : shortcutBubbles.keySet()) {
97 boolean foundShortcut = false;
98 for (int i = 0; i < shortcuts.size(); i++) {
99 if (shortcuts.get(i).getId().equals(shortcutId)) {
100 foundShortcut = true;
101 break;
102 }
103 }
104 if (!foundShortcut) {
105 bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId));
106 }
107 }
108 }
109
110 // Let NoMan know about the updates
111 for (int i = 0; i < bubbleKeysToRemove.size(); i++) {
112 // update flag bubble
113 String bubbleKey = bubbleKeysToRemove.get(i);
114 if (mShortcutListener != null) {
115 mShortcutListener.onShortcutRemoved(bubbleKey);
116 }
117 }
118 }
119 };
120
Julia Reynoldsfa273072020-04-14 15:31:21 -0400121 ShortcutHelper(LauncherApps launcherApps, ShortcutListener listener,
122 ShortcutServiceInternal shortcutServiceInternal) {
Mady Mellor56515c42020-02-18 17:58:36 -0800123 mLauncherAppsService = launcherApps;
124 mShortcutListener = listener;
Julia Reynoldsfa273072020-04-14 15:31:21 -0400125 mSharingFilter = new IntentFilter();
126 try {
127 mSharingFilter.addDataType("*/*");
128 } catch (IntentFilter.MalformedMimeTypeException e) {
129 Slog.e(TAG, "Bad mime type", e);
130 }
131 mShortcutServiceInternal = shortcutServiceInternal;
Mady Mellor56515c42020-02-18 17:58:36 -0800132 }
133
134 @VisibleForTesting
135 void setLauncherApps(LauncherApps launcherApps) {
136 mLauncherAppsService = launcherApps;
137 }
138
Julia Reynoldsfa273072020-04-14 15:31:21 -0400139 @VisibleForTesting
140 void setShortcutServiceInternal(ShortcutServiceInternal shortcutServiceInternal) {
141 mShortcutServiceInternal = shortcutServiceInternal;
142 }
143
Mady Mellor774d5fe2020-03-05 11:35:03 -0800144 /**
145 * Only returns shortcut info if it's found and if it's {@link ShortcutInfo#isLongLived()}.
146 */
147 ShortcutInfo getValidShortcutInfo(String shortcutId, String packageName, UserHandle user) {
Mady Mellor56515c42020-02-18 17:58:36 -0800148 if (mLauncherAppsService == null) {
149 return null;
150 }
151 final long token = Binder.clearCallingIdentity();
152 try {
153 if (shortcutId == null || packageName == null || user == null) {
154 return null;
155 }
156 LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
157 query.setPackage(packageName);
158 query.setShortcutIds(Arrays.asList(shortcutId));
159 query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_CACHED);
160 List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(query, user);
Mady Mellor774d5fe2020-03-05 11:35:03 -0800161 ShortcutInfo info = shortcuts != null && shortcuts.size() > 0
Mady Mellor56515c42020-02-18 17:58:36 -0800162 ? shortcuts.get(0)
163 : null;
Julia Reynoldsfa273072020-04-14 15:31:21 -0400164 if (info == null || !info.isLongLived() || !info.isEnabled()) {
165 return null;
166 }
167 if (mShortcutServiceInternal.isSharingShortcut(user.getIdentifier(),
168 "android", packageName, shortcutId, user.getIdentifier(), mSharingFilter)) {
169 return info;
170 }
171 return null;
Mady Mellor56515c42020-02-18 17:58:36 -0800172 } finally {
173 Binder.restoreCallingIdentity(token);
174 }
175 }
176
Mady Mellor56515c42020-02-18 17:58:36 -0800177 /**
178 * Shortcut based bubbles require some extra work to listen for shortcut changes.
179 *
180 * @param r the notification record to check
181 * @param removedNotification true if this notification is being removed
182 * @param handler handler to register the callback with
183 */
184 void maybeListenForShortcutChangesForBubbles(NotificationRecord r, boolean removedNotification,
185 Handler handler) {
186 final String shortcutId = r.getNotification().getBubbleMetadata() != null
187 ? r.getNotification().getBubbleMetadata().getShortcutId()
188 : null;
189 if (shortcutId == null) {
190 return;
191 }
192 if (r.getNotification().isBubbleNotification() && !removedNotification) {
193 // Must track shortcut based bubbles in case the shortcut is removed
194 HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
195 r.getSbn().getPackageName());
196 if (packageBubbles == null) {
197 packageBubbles = new HashMap<>();
198 }
199 packageBubbles.put(shortcutId, r.getKey());
200 mActiveShortcutBubbles.put(r.getSbn().getPackageName(), packageBubbles);
201 if (!mLauncherAppsCallbackRegistered) {
202 mLauncherAppsService.registerCallback(mLauncherAppsCallback, handler);
203 mLauncherAppsCallbackRegistered = true;
204 }
205 } else {
206 // No longer track shortcut
207 HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
208 r.getSbn().getPackageName());
209 if (packageBubbles != null) {
210 packageBubbles.remove(shortcutId);
211 }
212 if (packageBubbles != null && packageBubbles.isEmpty()) {
213 mActiveShortcutBubbles.remove(r.getSbn().getPackageName());
214 }
215 if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
216 mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
217 mLauncherAppsCallbackRegistered = false;
218 }
219 }
220 }
221}