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 */
16package android.content.pm;
17
18import android.annotation.NonNull;
19import android.annotation.TestApi;
20import android.content.Context;
21import android.os.IBinder;
22import android.os.RemoteException;
23import android.os.ServiceManager;
24import android.os.UserHandle;
25
26import com.android.internal.annotations.VisibleForTesting;
27
28import java.util.List;
29
30// TODO Enhance javadoc
31/**
32 * {@link ShortcutManager} manages shortcuts created by applications.
33 *
34 * <h3>Dynamic shortcuts and pinned shortcuts</h3>
35 *
36 * An application can publish shortcuts with {@link #setDynamicShortcuts(List)} and
37 * {@link #addDynamicShortcuts(List)}.  There can be at most
38 * {@link #getMaxDynamicShortcutCount()} number of dynamic shortcuts at a time from the same
39 * application.
40 * A dynamic shortcut can be deleted with {@link #removeDynamicShortcuts(List)}, and apps
41 * can also use {@link #removeAllDynamicShortcuts()} to delete all dynamic shortcuts.
42 *
43 * <p>The shortcuts that are currently published by the above APIs are called "dynamic", because
44 * they can be removed by the creator application at any time.  The user may "pin" dynamic shortcuts
45 * on Launcher to make "pinned" shortcuts.  Pinned shortcuts <b>cannot</b> be removed by the creator
46 * app.  An application can obtain all pinned shortcuts from itself with
47 * {@link #getPinnedShortcuts()}.  Applications should keep the pinned shortcut information
48 * up-to-date using {@link #updateShortcuts(List)}.
49 *
50 * <p>The number of pinned shortcuts does not affect the number of dynamic shortcuts that can be
51 * published by an application at a time.
52 * No matter how many pinned shortcuts that Launcher has for an application, the
53 * application can still always publish {@link #getMaxDynamicShortcutCount()} number of dynamic
54 * shortcuts.
55 *
56 * <h3>Shortcut IDs</h3>
57 *
58 * Each shortcut must have an ID, which must be unique within each application.  When a shortcut is
59 * published, existing shortcuts with the same ID will be updated.  Note this may include a
60 * pinned shortcut.
61 *
62 * <h3>Rate limiting</h3>
63 *
64 * Calls to {@link #setDynamicShortcuts(List)}, {@link #addDynamicShortcuts(List)},
65 * and {@link #updateShortcuts(List)} from <b>background applications</b> will be
66 * rate-limited.  An application can call these methods at most
67 * {@link #getRemainingCallCount()} times until the rate-limiting counter is reset,
68 * which happens at a certain time every day.
69 *
70 * <p>An application can use {@link #getRateLimitResetTime()} to get the next reset time.
71 *
72 * <p>Foreground applications (i.e. ones with a foreground activity or a foreground services)
73 * will not be throttled. Also, when an application comes to foreground,
74 * {@link #getRemainingCallCount()} will be reset to the initial value.
75 *
76 * <p>For testing purposes, use "Developer Options" (found in the Settings menu) to reset the
77 * internal rate-limiting counter.  Automated tests can use the following ADB shell command to
78 * achieve the same effect:</p>
79 * <pre>adb shell cmd shortcut reset-throttling</pre>
80 *
81 * <h3>Backup and Restore</h3>
82 *
83 * Shortcuts will be backed up and restored across devices.  This means all information, including
84 * IDs, must be meaningful on a different device.
85 *
86 * <h3>APIs for launcher</h3>
87 *
88 * Launcher applications should use {@link LauncherApps} to get shortcuts that are published from
89 * applications.  Launcher applications can also pin shortcuts with
90 * {@link LauncherApps#pinShortcuts(String, List, UserHandle)}.
91 *
92 * @hide
93 */
94public class ShortcutManager {
95    private static final String TAG = "ShortcutManager";
96
97    private final Context mContext;
98    private final IShortcutService mService;
99
100    /**
101     * @hide
102     */
103    public ShortcutManager(Context context, IShortcutService service) {
104        mContext = context;
105        mService = service;
106    }
107
108    /**
109     * @hide
110     */
111    @TestApi
112    public ShortcutManager(Context context) {
113        this(context, IShortcutService.Stub.asInterface(
114                ServiceManager.getService(Context.SHORTCUT_SERVICE)));
115    }
116
117    /**
118     * Publish a list of shortcuts.  All existing dynamic shortcuts from the caller application
119     * will be replaced.
120     *
121     * <p>This API will be rate-limited.
122     *
123     * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
124     *
125     * @throws IllegalArgumentException if {@code shortcutInfoList} contains more than
126     * {@link #getMaxDynamicShortcutCount()} shortcuts.
127     */
128    public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
129        try {
130            return mService.setDynamicShortcuts(mContext.getPackageName(),
131                    new ParceledListSlice(shortcutInfoList), injectMyUserId());
132        } catch (RemoteException e) {
133            throw e.rethrowFromSystemServer();
134        }
135    }
136
137    /**
138     * Return all dynamic shortcuts from the caller application.  The number of result items
139     * will not exceed the value returned by {@link #getMaxDynamicShortcutCount()}.
140     */
141    @NonNull
142    public List<ShortcutInfo> getDynamicShortcuts() {
143        try {
144            return mService.getDynamicShortcuts(mContext.getPackageName(), injectMyUserId())
145                    .getList();
146        } catch (RemoteException e) {
147            throw e.rethrowFromSystemServer();
148        }
149    }
150
151    /**
152     * Publish a single dynamic shortcut.  If there's already dynamic or pinned shortcuts with
153     * the same ID, they will all be updated.
154     *
155     * <p>This API will be rate-limited.
156     *
157     * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
158     *
159     * @throws IllegalArgumentException if the caller application has already published the
160     * max number of dynamic shortcuts.
161     */
162    public boolean addDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
163        try {
164            return mService.addDynamicShortcuts(mContext.getPackageName(),
165                    new ParceledListSlice(shortcutInfoList), injectMyUserId());
166        } catch (RemoteException e) {
167            throw e.rethrowFromSystemServer();
168        }
169    }
170
171    /**
172     * Delete a single dynamic shortcut by ID.
173     */
174    public void removeDynamicShortcuts(@NonNull List<String> shortcutIds) {
175        try {
176            mService.removeDynamicShortcuts(mContext.getPackageName(), shortcutIds,
177                    injectMyUserId());
178        } catch (RemoteException e) {
179            throw e.rethrowFromSystemServer();
180        }
181    }
182
183    /**
184     * Delete all dynamic shortcuts from the caller application.
185     */
186    public void removeAllDynamicShortcuts() {
187        try {
188            mService.removeAllDynamicShortcuts(mContext.getPackageName(), injectMyUserId());
189        } catch (RemoteException e) {
190            throw e.rethrowFromSystemServer();
191        }
192    }
193
194    /**
195     * Return all pinned shortcuts from the caller application.
196     */
197    @NonNull
198    public List<ShortcutInfo> getPinnedShortcuts() {
199        try {
200            return mService.getPinnedShortcuts(mContext.getPackageName(), injectMyUserId())
201                    .getList();
202        } catch (RemoteException e) {
203            throw e.rethrowFromSystemServer();
204        }
205    }
206
207    /**
208     * Update all existing shortcuts with the same IDs.  Shortcuts may be pinned and/or dynamic.
209     *
210     * <p>This API will be rate-limited.
211     *
212     * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
213     */
214    public boolean updateShortcuts(List<ShortcutInfo> shortcutInfoList) {
215        try {
216            return mService.updateShortcuts(mContext.getPackageName(),
217                    new ParceledListSlice(shortcutInfoList), injectMyUserId());
218        } catch (RemoteException e) {
219            throw e.rethrowFromSystemServer();
220        }
221    }
222
223    /**
224     * Return the max number of dynamic shortcuts that each application can have at a time.
225     */
226    public int getMaxDynamicShortcutCount() {
227        try {
228            return mService.getMaxDynamicShortcutCount(mContext.getPackageName(), injectMyUserId());
229        } catch (RemoteException e) {
230            throw e.rethrowFromSystemServer();
231        }
232    }
233
234    /**
235     * Return the number of times the caller application can call the rate-limited APIs
236     * before the rate limit counter is reset.
237     *
238     * @see #getRateLimitResetTime()
239     */
240    public int getRemainingCallCount() {
241        try {
242            return mService.getRemainingCallCount(mContext.getPackageName(), injectMyUserId());
243        } catch (RemoteException e) {
244            throw e.rethrowFromSystemServer();
245        }
246    }
247
248    /**
249     * Return when the rate limit count will be reset next time, in milliseconds since the epoch.
250     *
251     * @see #getRemainingCallCount()
252     * @see System#currentTimeMillis()
253     */
254    public long getRateLimitResetTime() {
255        try {
256            return mService.getRateLimitResetTime(mContext.getPackageName(), injectMyUserId());
257        } catch (RemoteException e) {
258            throw e.rethrowFromSystemServer();
259        }
260    }
261
262    /**
263     * Return the max width and height for icons, in pixels.
264     */
265    public int getIconMaxDimensions() {
266        try {
267            return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId());
268        } catch (RemoteException e) {
269            throw e.rethrowFromSystemServer();
270        }
271    }
272
273    /** @hide injection point */
274    @VisibleForTesting
275    protected int injectMyUserId() {
276        return UserHandle.myUserId();
277    }
278}
279