blob: 1786f0e1746b367974d74b062fc2a62fa482c3f1 [file] [log] [blame]
Jason Monkdcb5e2f2017-11-15 20:19:43 -05001/*
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 */
16package androidx.app.slice.compat;
17
18import static android.app.slice.SliceProvider.SLICE_TYPE;
19
20import android.Manifest.permission;
21import android.content.ContentProvider;
22import android.content.ContentProviderClient;
23import android.content.ContentResolver;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.ResolveInfo;
28import android.database.Cursor;
29import android.net.Uri;
30import android.os.Binder;
31import android.os.Bundle;
32import android.os.CancellationSignal;
33import android.os.Handler;
34import android.os.Looper;
35import android.os.Parcelable;
36import android.os.Process;
37import android.os.RemoteException;
38import android.os.StrictMode;
39import android.os.StrictMode.ThreadPolicy;
40import android.support.annotation.RestrictTo;
41import android.support.annotation.RestrictTo.Scope;
42import android.util.Log;
43
Jason Monk6fa0c9b2017-12-12 20:55:35 -050044import java.util.ArrayList;
Jason Monkdcb5e2f2017-11-15 20:19:43 -050045import java.util.List;
46import java.util.concurrent.CountDownLatch;
47
48import androidx.app.slice.Slice;
49import androidx.app.slice.SliceProvider;
Jason Monk6fa0c9b2017-12-12 20:55:35 -050050import androidx.app.slice.SliceSpec;
Jason Monkdcb5e2f2017-11-15 20:19:43 -050051
52/**
53 * @hide
54 */
55@RestrictTo(Scope.LIBRARY)
56public class SliceProviderCompat extends ContentProvider {
57
58 private static final String TAG = "SliceProvider";
59
60 public static final String EXTRA_BIND_URI = "slice_uri";
61 public static final String METHOD_SLICE = "bind_slice";
62 public static final String METHOD_MAP_INTENT = "map_slice";
63 public static final String EXTRA_INTENT = "slice_intent";
64 public static final String EXTRA_SLICE = "slice";
Jason Monk6fa0c9b2017-12-12 20:55:35 -050065 public static final String EXTRA_SUPPORTED_SPECS = "specs";
66 public static final String EXTRA_SUPPORTED_SPECS_REVS = "revs";
Jason Monkdcb5e2f2017-11-15 20:19:43 -050067
68 private static final boolean DEBUG = false;
69 private final Handler mHandler = new Handler(Looper.getMainLooper());
70 private SliceProvider mSliceProvider;
71
72 public SliceProviderCompat(SliceProvider provider) {
73 mSliceProvider = provider;
74 }
75
76 @Override
77 public boolean onCreate() {
78 return mSliceProvider.onCreateSliceProvider();
79 }
80
81 @Override
82 public final int update(Uri uri, ContentValues values, String selection,
83 String[] selectionArgs) {
84 if (DEBUG) Log.d(TAG, "update " + uri);
85 return 0;
86 }
87
88 @Override
89 public final int delete(Uri uri, String selection, String[] selectionArgs) {
90 if (DEBUG) Log.d(TAG, "delete " + uri);
91 return 0;
92 }
93
94 @Override
95 public final Cursor query(Uri uri, String[] projection, String selection,
96 String[] selectionArgs, String sortOrder) {
97 if (DEBUG) Log.d(TAG, "query " + uri);
98 return null;
99 }
100
101 @Override
102 public final Cursor query(Uri uri, String[] projection, String selection, String[]
103 selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
104 if (DEBUG) Log.d(TAG, "query " + uri);
105 return null;
106 }
107
108 @Override
109 public final Cursor query(Uri uri, String[] projection, Bundle queryArgs,
110 CancellationSignal cancellationSignal) {
111 if (DEBUG) Log.d(TAG, "query " + uri);
112 return null;
113 }
114
115 @Override
116 public final Uri insert(Uri uri, ContentValues values) {
117 if (DEBUG) Log.d(TAG, "insert " + uri);
118 return null;
119 }
120
121 @Override
122 public final String getType(Uri uri) {
123 if (DEBUG) Log.d(TAG, "getFormat " + uri);
124 return SLICE_TYPE;
125 }
126
127 @Override
128 public Bundle call(String method, String arg, Bundle extras) {
129 if (method.equals(METHOD_SLICE)) {
130 Uri uri = extras.getParcelable(EXTRA_BIND_URI);
131 if (Binder.getCallingUid() != Process.myUid()) {
132 getContext().enforceUriPermission(uri, permission.BIND_SLICE,
133 permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
134 Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
135 "Slice binding requires the permission BIND_SLICE");
136 }
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500137 List<SliceSpec> specs = getSpecs(extras);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500138
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500139 Slice s = handleBindSlice(uri, specs);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500140 Bundle b = new Bundle();
141 b.putParcelable(EXTRA_SLICE, s.toBundle());
142 return b;
143 } else if (method.equals(METHOD_MAP_INTENT)) {
Jason Monked974952017-11-27 13:48:04 -0500144 if (Binder.getCallingUid() != Process.myUid()) {
145 getContext().enforceCallingPermission(permission.BIND_SLICE,
146 "Slice binding requires the permission BIND_SLICE");
147 }
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500148 Intent intent = extras.getParcelable(EXTRA_INTENT);
149 Uri uri = mSliceProvider.onMapIntentToUri(intent);
150 Bundle b = new Bundle();
151 if (uri != null) {
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500152 List<SliceSpec> specs = getSpecs(extras);
153 Slice s = handleBindSlice(uri, specs);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500154 b.putParcelable(EXTRA_SLICE, s.toBundle());
155 } else {
156 b.putParcelable(EXTRA_SLICE, null);
157 }
158 return b;
159 }
160 return super.call(method, arg, extras);
161 }
162
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500163 private Slice handleBindSlice(final Uri sliceUri, final List<SliceSpec> specs) {
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500164 if (Looper.myLooper() == Looper.getMainLooper()) {
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500165 return onBindSliceStrict(sliceUri, specs);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500166 } else {
Jason Monk2a7d0fc2017-11-15 10:10:24 -0500167 final CountDownLatch latch = new CountDownLatch(1);
168 final Slice[] output = new Slice[1];
169 mHandler.post(new Runnable() {
170 @Override
171 public void run() {
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500172 output[0] = onBindSliceStrict(sliceUri, specs);
Jason Monk2a7d0fc2017-11-15 10:10:24 -0500173 latch.countDown();
174 }
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500175 });
176 try {
177 latch.await();
178 return output[0];
179 } catch (InterruptedException e) {
180 throw new RuntimeException(e);
181 }
182 }
183 }
184
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500185 private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> specs) {
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500186 ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
187 try {
188 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
189 .detectAll()
190 .penaltyDeath()
191 .build());
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500192 SliceProvider.setSpecs(specs);
Jason Monka09cb672018-01-08 13:17:36 -0500193 try {
194 return mSliceProvider.onBindSlice(sliceUri);
195 } finally {
196 SliceProvider.setSpecs(null);
197 }
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500198 } finally {
199 StrictMode.setThreadPolicy(oldPolicy);
200 }
201 }
202
203 /**
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500204 * Compat version of {@link Slice#bindSlice}.
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500205 */
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500206 public static Slice bindSlice(Context context, Uri uri,
207 List<SliceSpec> supportedSpecs) {
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500208 ContentProviderClient provider = context.getContentResolver()
209 .acquireContentProviderClient(uri);
210 if (provider == null) {
211 throw new IllegalArgumentException("Unknown URI " + uri);
212 }
213 try {
214 Bundle extras = new Bundle();
215 extras.putParcelable(EXTRA_BIND_URI, uri);
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500216 addSpecs(extras, supportedSpecs);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500217 final Bundle res = provider.call(METHOD_SLICE, null, extras);
218 if (res == null) {
219 return null;
220 }
221 Parcelable bundle = res.getParcelable(EXTRA_SLICE);
222 if (!(bundle instanceof Bundle)) {
223 return null;
224 }
225 return new Slice((Bundle) bundle);
226 } catch (RemoteException e) {
227 // Arbitrary and not worth documenting, as Activity
228 // Manager will kill this process shortly anyway.
229 return null;
230 } finally {
231 provider.close();
232 }
233 }
234
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500235 private static void addSpecs(Bundle extras, List<SliceSpec> supportedSpecs) {
236 ArrayList<String> types = new ArrayList<>();
237 ArrayList<Integer> revs = new ArrayList<>();
238 for (SliceSpec spec : supportedSpecs) {
239 types.add(spec.getType());
240 revs.add(spec.getRevision());
241 }
242 extras.putStringArrayList(EXTRA_SUPPORTED_SPECS, types);
243 extras.putIntegerArrayList(EXTRA_SUPPORTED_SPECS_REVS, revs);
244 }
245
246 private static List<SliceSpec> getSpecs(Bundle extras) {
247 ArrayList<SliceSpec> specs = new ArrayList<>();
248 ArrayList<String> types = extras.getStringArrayList(EXTRA_SUPPORTED_SPECS);
249 ArrayList<Integer> revs = extras.getIntegerArrayList(EXTRA_SUPPORTED_SPECS_REVS);
250 for (int i = 0; i < types.size(); i++) {
251 specs.add(new SliceSpec(types.get(i), revs.get(i)));
252 }
253 return specs;
254 }
255
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500256 /**
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500257 * Compat version of {@link Slice#bindSlice}.
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500258 */
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500259 public static Slice bindSlice(Context context, Intent intent,
260 List<SliceSpec> supportedSpecs) {
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500261 ContentResolver resolver = context.getContentResolver();
262
263 // Check if the intent has data for the slice uri on it and use that
264 final Uri intentData = intent.getData();
265 if (intentData != null && SLICE_TYPE.equals(resolver.getType(intentData))) {
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500266 return bindSlice(context, intentData, supportedSpecs);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500267 }
268 // Otherwise ask the app
269 List<ResolveInfo> providers =
270 context.getPackageManager().queryIntentContentProviders(intent, 0);
271 if (providers == null) {
272 throw new IllegalArgumentException("Unable to resolve intent " + intent);
273 }
274 String authority = providers.get(0).providerInfo.authority;
275 Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
276 .authority(authority).build();
277 ContentProviderClient provider = resolver.acquireContentProviderClient(uri);
278 if (provider == null) {
279 throw new IllegalArgumentException("Unknown URI " + uri);
280 }
281 try {
282 Bundle extras = new Bundle();
283 extras.putParcelable(EXTRA_INTENT, intent);
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500284 addSpecs(extras, supportedSpecs);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500285 final Bundle res = provider.call(METHOD_MAP_INTENT, null, extras);
286 if (res == null) {
287 return null;
288 }
289 Parcelable bundle = res.getParcelable(EXTRA_SLICE);
290 if (!(bundle instanceof Bundle)) {
291 return null;
292 }
293 return new Slice((Bundle) bundle);
294 } catch (RemoteException e) {
295 // Arbitrary and not worth documenting, as Activity
296 // Manager will kill this process shortly anyway.
297 return null;
298 } finally {
299 provider.close();
300 }
301 }
302}