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