1 /*
2  * Copyright (C) 2016 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 
17 package com.android.launcher3.shortcuts;
18 
19 import android.annotation.TargetApi;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.pm.LauncherApps;
23 import android.content.pm.LauncherApps.ShortcutQuery;
24 import android.content.pm.ShortcutInfo;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.os.Bundle;
28 import android.os.UserHandle;
29 import android.util.Log;
30 
31 import com.android.launcher3.ItemInfo;
32 import com.android.launcher3.LauncherSettings;
33 import com.android.launcher3.Utilities;
34 
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.List;
38 
39 /**
40  * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
41  */
42 public class DeepShortcutManager {
43     private static final String TAG = "DeepShortcutManager";
44 
45     private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC
46             | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
47 
48     private static DeepShortcutManager sInstance;
49     private static final Object sInstanceLock = new Object();
50 
getInstance(Context context)51     public static DeepShortcutManager getInstance(Context context) {
52         synchronized (sInstanceLock) {
53             if (sInstance == null) {
54                 sInstance = new DeepShortcutManager(context.getApplicationContext());
55             }
56             return sInstance;
57         }
58     }
59 
60     private final LauncherApps mLauncherApps;
61     private boolean mWasLastCallSuccess;
62 
DeepShortcutManager(Context context)63     private DeepShortcutManager(Context context) {
64         mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
65     }
66 
supportsShortcuts(ItemInfo info)67     public static boolean supportsShortcuts(ItemInfo info) {
68         return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
69                 && !info.isDisabled();
70     }
71 
wasLastCallSuccess()72     public boolean wasLastCallSuccess() {
73         return mWasLastCallSuccess;
74     }
75 
onShortcutsChanged(List<ShortcutInfoCompat> shortcuts)76     public void onShortcutsChanged(List<ShortcutInfoCompat> shortcuts) {
77         // mShortcutCache.removeShortcuts(shortcuts);
78     }
79 
80     /**
81      * Queries for the shortcuts with the package name and provided ids.
82      *
83      * This method is intended to get the full details for shortcuts when they are added or updated,
84      * because we only get "key" fields in onShortcutsChanged().
85      */
queryForFullDetails(String packageName, List<String> shortcutIds, UserHandle user)86     public List<ShortcutInfoCompat> queryForFullDetails(String packageName,
87             List<String> shortcutIds, UserHandle user) {
88         return query(FLAG_GET_ALL, packageName, null, shortcutIds, user);
89     }
90 
91     /**
92      * Gets all the manifest and dynamic shortcuts associated with the given package and user,
93      * to be displayed in the shortcuts container on long press.
94      */
queryForShortcutsContainer(ComponentName activity, List<String> ids, UserHandle user)95     public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
96             List<String> ids, UserHandle user) {
97         return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
98                 activity.getPackageName(), activity, ids, user);
99     }
100 
101     /**
102      * Removes the given shortcut from the current list of pinned shortcuts.
103      * (Runs on background thread)
104      */
105     @TargetApi(25)
unpinShortcut(final ShortcutKey key)106     public void unpinShortcut(final ShortcutKey key) {
107         if (Utilities.ATLEAST_NOUGAT_MR1) {
108             String packageName = key.componentName.getPackageName();
109             String id = key.getId();
110             UserHandle user = key.user;
111             List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
112             pinnedIds.remove(id);
113             try {
114                 mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
115                 mWasLastCallSuccess = true;
116             } catch (SecurityException|IllegalStateException e) {
117                 Log.w(TAG, "Failed to unpin shortcut", e);
118                 mWasLastCallSuccess = false;
119             }
120         }
121     }
122 
123     /**
124      * Adds the given shortcut to the current list of pinned shortcuts.
125      * (Runs on background thread)
126      */
127     @TargetApi(25)
pinShortcut(final ShortcutKey key)128     public void pinShortcut(final ShortcutKey key) {
129         if (Utilities.ATLEAST_NOUGAT_MR1) {
130             String packageName = key.componentName.getPackageName();
131             String id = key.getId();
132             UserHandle user = key.user;
133             List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
134             pinnedIds.add(id);
135             try {
136                 mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
137                 mWasLastCallSuccess = true;
138             } catch (SecurityException|IllegalStateException e) {
139                 Log.w(TAG, "Failed to pin shortcut", e);
140                 mWasLastCallSuccess = false;
141             }
142         }
143     }
144 
145     @TargetApi(25)
startShortcut(String packageName, String id, Rect sourceBounds, Bundle startActivityOptions, UserHandle user)146     public void startShortcut(String packageName, String id, Rect sourceBounds,
147           Bundle startActivityOptions, UserHandle user) {
148         if (Utilities.ATLEAST_NOUGAT_MR1) {
149             try {
150                 mLauncherApps.startShortcut(packageName, id, sourceBounds,
151                         startActivityOptions, user);
152                 mWasLastCallSuccess = true;
153             } catch (SecurityException|IllegalStateException e) {
154                 Log.e(TAG, "Failed to start shortcut", e);
155                 mWasLastCallSuccess = false;
156             }
157         }
158     }
159 
160     @TargetApi(25)
getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density)161     public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
162         if (Utilities.ATLEAST_NOUGAT_MR1) {
163             try {
164                 Drawable icon = mLauncherApps.getShortcutIconDrawable(
165                         shortcutInfo.getShortcutInfo(), density);
166                 mWasLastCallSuccess = true;
167                 return icon;
168             } catch (SecurityException|IllegalStateException e) {
169                 Log.e(TAG, "Failed to get shortcut icon", e);
170                 mWasLastCallSuccess = false;
171             }
172         }
173         return null;
174     }
175 
176     /**
177      * Returns the id's of pinned shortcuts associated with the given package and user.
178      *
179      * If packageName is null, returns all pinned shortcuts regardless of package.
180      */
queryForPinnedShortcuts(String packageName, UserHandle user)181     public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName, UserHandle user) {
182         return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, null, user);
183     }
184 
queryForAllShortcuts(UserHandle user)185     public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandle user) {
186         return query(FLAG_GET_ALL, null, null, null, user);
187     }
188 
extractIds(List<ShortcutInfoCompat> shortcuts)189     private List<String> extractIds(List<ShortcutInfoCompat> shortcuts) {
190         List<String> shortcutIds = new ArrayList<>(shortcuts.size());
191         for (ShortcutInfoCompat shortcut : shortcuts) {
192             shortcutIds.add(shortcut.getId());
193         }
194         return shortcutIds;
195     }
196 
197     /**
198      * Query the system server for all the shortcuts matching the given parameters.
199      * If packageName == null, we query for all shortcuts with the passed flags, regardless of app.
200      *
201      * TODO: Use the cache to optimize this so we don't make an RPC every time.
202      */
203     @TargetApi(25)
query(int flags, String packageName, ComponentName activity, List<String> shortcutIds, UserHandle user)204     private List<ShortcutInfoCompat> query(int flags, String packageName,
205             ComponentName activity, List<String> shortcutIds, UserHandle user) {
206         if (Utilities.ATLEAST_NOUGAT_MR1) {
207             ShortcutQuery q = new ShortcutQuery();
208             q.setQueryFlags(flags);
209             if (packageName != null) {
210                 q.setPackage(packageName);
211                 q.setActivity(activity);
212                 q.setShortcutIds(shortcutIds);
213             }
214             List<ShortcutInfo> shortcutInfos = null;
215             try {
216                 shortcutInfos = mLauncherApps.getShortcuts(q, user);
217                 mWasLastCallSuccess = true;
218             } catch (SecurityException|IllegalStateException e) {
219                 Log.e(TAG, "Failed to query for shortcuts", e);
220                 mWasLastCallSuccess = false;
221             }
222             if (shortcutInfos == null) {
223                 return Collections.EMPTY_LIST;
224             }
225             List<ShortcutInfoCompat> shortcutInfoCompats = new ArrayList<>(shortcutInfos.size());
226             for (ShortcutInfo shortcutInfo : shortcutInfos) {
227                 shortcutInfoCompats.add(new ShortcutInfoCompat(shortcutInfo));
228             }
229             return shortcutInfoCompats;
230         } else {
231             return Collections.EMPTY_LIST;
232         }
233     }
234 
235     @TargetApi(25)
hasHostPermission()236     public boolean hasHostPermission() {
237         if (Utilities.ATLEAST_NOUGAT_MR1) {
238             try {
239                 return mLauncherApps.hasShortcutHostPermission();
240             } catch (SecurityException|IllegalStateException e) {
241                 Log.e(TAG, "Failed to make shortcut manager call", e);
242             }
243         }
244         return false;
245     }
246 }
247