blob: 09c420c3e66a9839fc2e45d78015bef23b612135 [file] [log] [blame]
Jason Monk8f5f7ff2017-10-17 14:12:42 -04001/*
2 * Copyright (C) 2017 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 android.app.slice;
18
Jason Monke2c64512017-12-11 15:14:54 -050019import android.annotation.NonNull;
Jason Monkb9e06a82018-01-16 15:32:53 -050020import android.annotation.Nullable;
Jason Monk8f5f7ff2017-10-17 14:12:42 -040021import android.annotation.SystemService;
Jason Monkb9e06a82018-01-16 15:32:53 -050022import android.content.ContentResolver;
Jason Monk8f5f7ff2017-10-17 14:12:42 -040023import android.content.Context;
Jason Monkb9e06a82018-01-16 15:32:53 -050024import android.content.IContentProvider;
25import android.content.Intent;
26import android.content.pm.ResolveInfo;
Jason Monk74f5e362017-12-06 08:56:33 -050027import android.net.Uri;
Jason Monkb9e06a82018-01-16 15:32:53 -050028import android.os.Bundle;
Jason Monk8f5f7ff2017-10-17 14:12:42 -040029import android.os.Handler;
Jason Monk74f5e362017-12-06 08:56:33 -050030import android.os.RemoteException;
Jason Monk8f5f7ff2017-10-17 14:12:42 -040031import android.os.ServiceManager;
32import android.os.ServiceManager.ServiceNotFoundException;
Jason Monke2c64512017-12-11 15:14:54 -050033import android.util.ArrayMap;
Jason Monk5f8cc272018-01-16 17:57:20 -050034import android.util.Log;
Jason Monke2c64512017-12-11 15:14:54 -050035import android.util.Pair;
36
Jason Monkb9e06a82018-01-16 15:32:53 -050037import com.android.internal.util.Preconditions;
38
39import java.util.ArrayList;
Jason Monke2c64512017-12-11 15:14:54 -050040import java.util.Arrays;
Jason Monk5f8cc272018-01-16 17:57:20 -050041import java.util.Collection;
42import java.util.Collections;
Jason Monke2c64512017-12-11 15:14:54 -050043import java.util.List;
44import java.util.concurrent.Executor;
Jason Monk8f5f7ff2017-10-17 14:12:42 -040045
46/**
Jason Monke2c64512017-12-11 15:14:54 -050047 * Class to handle interactions with {@link Slice}s.
48 * <p>
49 * The SliceManager manages permissions and pinned state for slices.
Jason Monk8f5f7ff2017-10-17 14:12:42 -040050 */
51@SystemService(Context.SLICE_SERVICE)
52public class SliceManager {
53
Jason Monk5f8cc272018-01-16 17:57:20 -050054 private static final String TAG = "SliceManager";
55
Jason Monke8f8be72018-01-21 10:10:35 -050056 /**
57 * @hide
58 */
59 public static final String ACTION_REQUEST_SLICE_PERMISSION =
60 "android.intent.action.REQUEST_SLICE_PERMISSION";
61
Jason Monk8f5f7ff2017-10-17 14:12:42 -040062 private final ISliceManager mService;
63 private final Context mContext;
Jason Monke2c64512017-12-11 15:14:54 -050064 private final ArrayMap<Pair<Uri, SliceCallback>, ISliceListener> mListenerLookup =
65 new ArrayMap<>();
Jason Monk8f5f7ff2017-10-17 14:12:42 -040066
Jason Monke2c64512017-12-11 15:14:54 -050067 /**
Jason Monke8f8be72018-01-21 10:10:35 -050068 * Permission denied.
69 * @hide
70 */
71 public static final int PERMISSION_DENIED = -1;
72 /**
73 * Permission granted.
74 * @hide
75 */
76 public static final int PERMISSION_GRANTED = 0;
77 /**
78 * Permission just granted by the user, and should be granted uri permission as well.
79 * @hide
80 */
81 public static final int PERMISSION_USER_GRANTED = 1;
82
83 /**
Jason Monke2c64512017-12-11 15:14:54 -050084 * @hide
85 */
Jason Monk8f5f7ff2017-10-17 14:12:42 -040086 public SliceManager(Context context, Handler handler) throws ServiceNotFoundException {
87 mContext = context;
88 mService = ISliceManager.Stub.asInterface(
89 ServiceManager.getServiceOrThrow(Context.SLICE_SERVICE));
90 }
Jason Monk74f5e362017-12-06 08:56:33 -050091
92 /**
Jason Monke2c64512017-12-11 15:14:54 -050093 * Adds a callback to a specific slice uri.
94 * <p>
95 * This is a convenience that performs a few slice actions at once. It will put
96 * the slice in a pinned state since there is a callback attached. It will also
97 * listen for content changes, when a content change observes, the android system
98 * will bind the new slice and provide it to all registered {@link SliceCallback}s.
99 *
100 * @param uri The uri of the slice being listened to.
101 * @param callback The listener that should receive the callbacks.
102 * @param specs The list of supported {@link SliceSpec}s of the callback.
103 * @see SliceProvider#onSlicePinned(Uri)
Jason Monk74f5e362017-12-06 08:56:33 -0500104 */
Jason Monke2c64512017-12-11 15:14:54 -0500105 public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback,
106 @NonNull List<SliceSpec> specs) {
107 registerSliceCallback(uri, callback, specs, Handler.getMain());
108 }
109
110 /**
111 * Adds a callback to a specific slice uri.
112 * <p>
113 * This is a convenience that performs a few slice actions at once. It will put
114 * the slice in a pinned state since there is a callback attached. It will also
115 * listen for content changes, when a content change observes, the android system
116 * will bind the new slice and provide it to all registered {@link SliceCallback}s.
117 *
118 * @param uri The uri of the slice being listened to.
119 * @param callback The listener that should receive the callbacks.
120 * @param specs The list of supported {@link SliceSpec}s of the callback.
121 * @see SliceProvider#onSlicePinned(Uri)
122 */
123 public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback,
124 @NonNull List<SliceSpec> specs, Handler handler) {
Jason Monk74f5e362017-12-06 08:56:33 -0500125 try {
Jason Monke2c64512017-12-11 15:14:54 -0500126 mService.addSliceListener(uri, mContext.getPackageName(),
127 getListener(uri, callback, new ISliceListener.Stub() {
128 @Override
129 public void onSliceUpdated(Slice s) throws RemoteException {
130 handler.post(() -> callback.onSliceUpdated(s));
131 }
132 }), specs.toArray(new SliceSpec[specs.size()]));
Jason Monk74f5e362017-12-06 08:56:33 -0500133 } catch (RemoteException e) {
134 throw e.rethrowFromSystemServer();
135 }
136 }
137
138 /**
Jason Monke2c64512017-12-11 15:14:54 -0500139 * Adds a callback to a specific slice uri.
140 * <p>
141 * This is a convenience that performs a few slice actions at once. It will put
142 * the slice in a pinned state since there is a callback attached. It will also
143 * listen for content changes, when a content change observes, the android system
144 * will bind the new slice and provide it to all registered {@link SliceCallback}s.
145 *
146 * @param uri The uri of the slice being listened to.
147 * @param callback The listener that should receive the callbacks.
148 * @param specs The list of supported {@link SliceSpec}s of the callback.
149 * @see SliceProvider#onSlicePinned(Uri)
Jason Monk74f5e362017-12-06 08:56:33 -0500150 */
Jason Monke2c64512017-12-11 15:14:54 -0500151 public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback,
152 @NonNull List<SliceSpec> specs, Executor executor) {
Jason Monk74f5e362017-12-06 08:56:33 -0500153 try {
Jason Monke2c64512017-12-11 15:14:54 -0500154 mService.addSliceListener(uri, mContext.getPackageName(),
155 getListener(uri, callback, new ISliceListener.Stub() {
156 @Override
157 public void onSliceUpdated(Slice s) throws RemoteException {
158 executor.execute(() -> callback.onSliceUpdated(s));
159 }
160 }), specs.toArray(new SliceSpec[specs.size()]));
161 } catch (RemoteException e) {
162 throw e.rethrowFromSystemServer();
163 }
164 }
165
166 private ISliceListener getListener(Uri uri, SliceCallback callback,
167 ISliceListener listener) {
168 Pair<Uri, SliceCallback> key = new Pair<>(uri, callback);
169 if (mListenerLookup.containsKey(key)) {
170 try {
171 mService.removeSliceListener(uri, mContext.getPackageName(),
172 mListenerLookup.get(key));
173 } catch (RemoteException e) {
174 throw e.rethrowFromSystemServer();
175 }
176 }
177 mListenerLookup.put(key, listener);
178 return listener;
179 }
180
181 /**
182 * Removes a callback for a specific slice uri.
183 * <p>
184 * Removes the app from the pinned state (if there are no other apps/callbacks pinning it)
185 * in addition to removing the callback.
186 *
187 * @param uri The uri of the slice being listened to
188 * @param callback The listener that should no longer receive callbacks.
189 * @see #registerSliceCallback
190 */
191 public void unregisterSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback) {
192 try {
193 mService.removeSliceListener(uri, mContext.getPackageName(),
194 mListenerLookup.remove(new Pair<>(uri, callback)));
Jason Monk74f5e362017-12-06 08:56:33 -0500195 } catch (RemoteException e) {
196 throw e.rethrowFromSystemServer();
197 }
198 }
199
200 /**
Jason Monke2c64512017-12-11 15:14:54 -0500201 * Ensures that a slice is in a pinned state.
202 * <p>
203 * Pinned state is not persisted across reboots, so apps are expected to re-pin any slices
204 * they still care about after a reboot.
205 *
206 * @param uri The uri of the slice being pinned.
207 * @param specs The list of supported {@link SliceSpec}s of the callback.
208 * @see SliceProvider#onSlicePinned(Uri)
Jason Monk74f5e362017-12-06 08:56:33 -0500209 */
Jason Monke2c64512017-12-11 15:14:54 -0500210 public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) {
Jason Monk74f5e362017-12-06 08:56:33 -0500211 try {
Jason Monke2c64512017-12-11 15:14:54 -0500212 mService.pinSlice(mContext.getPackageName(), uri,
213 specs.toArray(new SliceSpec[specs.size()]));
Jason Monk74f5e362017-12-06 08:56:33 -0500214 } catch (RemoteException e) {
215 throw e.rethrowFromSystemServer();
216 }
217 }
218
219 /**
Jason Monke2c64512017-12-11 15:14:54 -0500220 * Remove a pin for a slice.
221 * <p>
222 * If the slice has no other pins/callbacks then the slice will be unpinned.
223 *
224 * @param uri The uri of the slice being unpinned.
225 * @see #pinSlice
226 * @see SliceProvider#onSliceUnpinned(Uri)
Jason Monk74f5e362017-12-06 08:56:33 -0500227 */
Jason Monke2c64512017-12-11 15:14:54 -0500228 public void unpinSlice(@NonNull Uri uri) {
Jason Monk74f5e362017-12-06 08:56:33 -0500229 try {
230 mService.unpinSlice(mContext.getPackageName(), uri);
231 } catch (RemoteException e) {
232 throw e.rethrowFromSystemServer();
233 }
234 }
235
236 /**
Jason Monke2c64512017-12-11 15:14:54 -0500237 * @hide
Jason Monk74f5e362017-12-06 08:56:33 -0500238 */
239 public boolean hasSliceAccess() {
240 try {
241 return mService.hasSliceAccess(mContext.getPackageName());
242 } catch (RemoteException e) {
243 throw e.rethrowFromSystemServer();
244 }
245 }
246
247 /**
Jason Monke2c64512017-12-11 15:14:54 -0500248 * Get the current set of specs for a pinned slice.
249 * <p>
250 * This is the set of specs supported for a specific pinned slice. It will take
251 * into account all clients and returns only specs supported by all.
252 * @see SliceSpec
Jason Monk74f5e362017-12-06 08:56:33 -0500253 */
Jason Monke2c64512017-12-11 15:14:54 -0500254 public @NonNull List<SliceSpec> getPinnedSpecs(Uri uri) {
Jason Monk74f5e362017-12-06 08:56:33 -0500255 try {
Jason Monke2c64512017-12-11 15:14:54 -0500256 return Arrays.asList(mService.getPinnedSpecs(uri, mContext.getPackageName()));
Jason Monk74f5e362017-12-06 08:56:33 -0500257 } catch (RemoteException e) {
258 throw e.rethrowFromSystemServer();
259 }
260 }
261
262 /**
Jason Monk5f8cc272018-01-16 17:57:20 -0500263 * Obtains a list of slices that are descendants of the specified Uri.
264 * <p>
265 * Not all slice providers will implement this functionality, in which case,
266 * an empty collection will be returned.
267 *
268 * @param uri The uri to look for descendants under.
269 * @return All slices within the space.
270 * @see SliceProvider#onGetSliceDescendants(Uri)
271 */
272 public @NonNull Collection<Uri> getSliceDescendants(@NonNull Uri uri) {
273 ContentResolver resolver = mContext.getContentResolver();
274 IContentProvider provider = resolver.acquireProvider(uri);
275 try {
276 Bundle extras = new Bundle();
277 extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
278 final Bundle res = provider.call(resolver.getPackageName(),
279 SliceProvider.METHOD_GET_DESCENDANTS, null, extras);
280 return res.getParcelableArrayList(SliceProvider.EXTRA_SLICE_DESCENDANTS);
281 } catch (RemoteException e) {
282 Log.e(TAG, "Unable to get slice descendants", e);
283 } finally {
284 resolver.releaseProvider(provider);
285 }
286 return Collections.emptyList();
287 }
288
289 /**
Jason Monkb9e06a82018-01-16 15:32:53 -0500290 * Turns a slice Uri into slice content.
291 *
292 * @param uri The URI to a slice provider
293 * @param supportedSpecs List of supported specs.
294 * @return The Slice provided by the app or null if none is given.
295 * @see Slice
296 */
297 public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) {
298 Preconditions.checkNotNull(uri, "uri");
299 ContentResolver resolver = mContext.getContentResolver();
300 IContentProvider provider = resolver.acquireProvider(uri);
301 if (provider == null) {
302 throw new IllegalArgumentException("Unknown URI " + uri);
303 }
304 try {
305 Bundle extras = new Bundle();
306 extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
307 extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
308 new ArrayList<>(supportedSpecs));
Jason Monke8f8be72018-01-21 10:10:35 -0500309 final Bundle res = provider.call(mContext.getPackageName(), SliceProvider.METHOD_SLICE,
Jason Monkb9e06a82018-01-16 15:32:53 -0500310 null, extras);
311 Bundle.setDefusable(res, true);
312 if (res == null) {
313 return null;
314 }
315 return res.getParcelable(SliceProvider.EXTRA_SLICE);
316 } catch (RemoteException e) {
317 // Arbitrary and not worth documenting, as Activity
318 // Manager will kill this process shortly anyway.
319 return null;
320 } finally {
321 resolver.releaseProvider(provider);
322 }
323 }
324
325 /**
326 * Turns a slice intent into slice content. Expects an explicit intent. If there is no
327 * {@link android.content.ContentProvider} associated with the given intent this will throw
328 * {@link IllegalArgumentException}.
329 *
330 * @param intent The intent associated with a slice.
331 * @param supportedSpecs List of supported specs.
332 * @return The Slice provided by the app or null if none is given.
333 * @see Slice
334 * @see SliceProvider#onMapIntentToUri(Intent)
335 * @see Intent
336 */
337 public @Nullable Slice bindSlice(@NonNull Intent intent,
338 @NonNull List<SliceSpec> supportedSpecs) {
339 Preconditions.checkNotNull(intent, "intent");
340 Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null,
341 "Slice intent must be explicit " + intent);
342 ContentResolver resolver = mContext.getContentResolver();
343
344 // Check if the intent has data for the slice uri on it and use that
345 final Uri intentData = intent.getData();
346 if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
347 return bindSlice(intentData, supportedSpecs);
348 }
349 // Otherwise ask the app
350 List<ResolveInfo> providers =
351 mContext.getPackageManager().queryIntentContentProviders(intent, 0);
352 if (providers == null) {
353 throw new IllegalArgumentException("Unable to resolve intent " + intent);
354 }
355 String authority = providers.get(0).providerInfo.authority;
356 Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
357 .authority(authority).build();
358 IContentProvider provider = resolver.acquireProvider(uri);
359 if (provider == null) {
360 throw new IllegalArgumentException("Unknown URI " + uri);
361 }
362 try {
363 Bundle extras = new Bundle();
364 extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
365 extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
366 new ArrayList<>(supportedSpecs));
Jason Monke8f8be72018-01-21 10:10:35 -0500367 final Bundle res = provider.call(mContext.getPackageName(),
Jason Monkb9e06a82018-01-16 15:32:53 -0500368 SliceProvider.METHOD_MAP_INTENT, null, extras);
369 if (res == null) {
370 return null;
371 }
372 return res.getParcelable(SliceProvider.EXTRA_SLICE);
373 } catch (RemoteException e) {
374 // Arbitrary and not worth documenting, as Activity
375 // Manager will kill this process shortly anyway.
376 return null;
377 } finally {
378 resolver.releaseProvider(provider);
379 }
380 }
381
382 /**
Jason Monke8f8be72018-01-21 10:10:35 -0500383 * Does the permission check to see if a caller has access to a specific slice.
384 * @hide
385 */
386 public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid) {
387 try {
388 if (pkg == null) {
389 throw new SecurityException("No pkg specified");
390 }
391 int result = mService.checkSlicePermission(uri, pkg, pid, uid);
392 if (result == PERMISSION_DENIED) {
393 throw new SecurityException("User " + uid + " does not have slice permission for "
394 + uri + ".");
395 }
396 if (result == PERMISSION_USER_GRANTED) {
397 // We just had a user grant of this permission and need to grant this to the app
398 // permanently.
399 mContext.grantUriPermission(pkg, uri.buildUpon().path("").build(),
400 Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
401 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
402 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
403 }
404 } catch (RemoteException e) {
405 throw e.rethrowFromSystemServer();
406 }
407 }
408
409 /**
410 * Called by SystemUI to grant a slice permission after a dialog is shown.
411 * @hide
412 */
413 public void grantPermissionFromUser(Uri uri, String pkg, boolean allSlices) {
414 try {
415 mService.grantPermissionFromUser(uri, pkg, mContext.getPackageName(), allSlices);
416 } catch (RemoteException e) {
417 throw e.rethrowFromSystemServer();
418 }
419 }
420
421 /**
Jason Monke2c64512017-12-11 15:14:54 -0500422 * Class that listens to changes in {@link Slice}s.
Jason Monk74f5e362017-12-06 08:56:33 -0500423 */
Jason Monke2c64512017-12-11 15:14:54 -0500424 public interface SliceCallback {
Jason Monk74f5e362017-12-06 08:56:33 -0500425
426 /**
Jason Monke2c64512017-12-11 15:14:54 -0500427 * Called when slice is updated.
428 *
429 * @param s The updated slice.
430 * @see #registerSliceCallback
Jason Monk74f5e362017-12-06 08:56:33 -0500431 */
Jason Monke2c64512017-12-11 15:14:54 -0500432 void onSliceUpdated(Slice s);
Jason Monk74f5e362017-12-06 08:56:33 -0500433 }
Jason Monk8f5f7ff2017-10-17 14:12:42 -0400434}