blob: 2d9e772a6b0c6ecbe242e10fde9a2b60a805ef97 [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 com.android.server.slice;
18
Jason Monk74f5e362017-12-06 08:56:33 -050019import static android.content.ContentProvider.getUserIdFromUri;
20import static android.content.ContentProvider.maybeAddUserId;
Jason Monk8f5f7ff2017-10-17 14:12:42 -040021
Jason Monk74f5e362017-12-06 08:56:33 -050022import android.Manifest.permission;
23import android.app.AppOpsManager;
24import android.app.slice.ISliceListener;
25import android.app.slice.ISliceManager;
26import android.app.slice.SliceSpec;
27import android.content.ComponentName;
28import android.content.Context;
29import android.content.Intent;
30import android.content.pm.PackageManagerInternal;
31import android.content.pm.ResolveInfo;
32import android.database.ContentObserver;
33import android.net.Uri;
34import android.os.Binder;
35import android.os.Handler;
36import android.os.Looper;
37import android.os.Process;
38import android.os.RemoteException;
39import android.util.ArrayMap;
40import android.util.Log;
41
42import com.android.internal.annotations.GuardedBy;
43import com.android.internal.annotations.VisibleForTesting;
44import com.android.internal.app.AssistUtils;
45import com.android.internal.util.Preconditions;
46import com.android.server.LocalServices;
47import com.android.server.ServiceThread;
Jason Monk8f5f7ff2017-10-17 14:12:42 -040048import com.android.server.SystemService;
49
Jason Monk74f5e362017-12-06 08:56:33 -050050import java.util.ArrayList;
51import java.util.List;
52import java.util.Objects;
53
Jason Monk8f5f7ff2017-10-17 14:12:42 -040054public class SliceManagerService extends ISliceManager.Stub {
55
Jason Monk74f5e362017-12-06 08:56:33 -050056 private static final String TAG = "SliceManagerService";
57 private final Object mLock = new Object();
Jason Monk8f5f7ff2017-10-17 14:12:42 -040058
Jason Monk74f5e362017-12-06 08:56:33 -050059 private final Context mContext;
60 private final PackageManagerInternal mPackageManagerInternal;
61 private final AppOpsManager mAppOps;
62 private final AssistUtils mAssistUtils;
63
64 @GuardedBy("mLock")
65 private final ArrayMap<Uri, PinnedSliceState> mPinnedSlicesByUri = new ArrayMap<>();
66 private final Handler mHandler;
67 private final ContentObserver mObserver;
68
69 public SliceManagerService(Context context) {
70 this(context, createHandler().getLooper());
Jason Monk8f5f7ff2017-10-17 14:12:42 -040071 }
72
Jason Monk74f5e362017-12-06 08:56:33 -050073 @VisibleForTesting
74 SliceManagerService(Context context, Looper looper) {
75 mContext = context;
76 mPackageManagerInternal = Preconditions.checkNotNull(
77 LocalServices.getService(PackageManagerInternal.class));
78 mAppOps = context.getSystemService(AppOpsManager.class);
79 mAssistUtils = new AssistUtils(context);
80 mHandler = new Handler(looper);
81
82 mObserver = new ContentObserver(mHandler) {
83 @Override
84 public void onChange(boolean selfChange, Uri uri) {
85 try {
86 getPinnedSlice(uri).onChange();
87 } catch (IllegalStateException e) {
88 Log.e(TAG, "Received change for unpinned slice " + uri, e);
89 }
90 }
91 };
92 }
93
94 /// ----- Lifecycle stuff -----
Jason Monk8f5f7ff2017-10-17 14:12:42 -040095 private void systemReady() {
96 }
97
Jason Monk74f5e362017-12-06 08:56:33 -050098 private void onUnlockUser(int userId) {
99 }
100
101 private void onStopUser(int userId) {
102 synchronized (mLock) {
103 mPinnedSlicesByUri.values().removeIf(s -> getUserIdFromUri(s.getUri()) == userId);
104 }
105 }
106
107 /// ----- ISliceManager stuff -----
108 @Override
109 public void addSliceListener(Uri uri, String pkg, ISliceListener listener, SliceSpec[] specs)
110 throws RemoteException {
111 verifyCaller(pkg);
112 uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
113 enforceAccess(pkg, uri);
114 getOrCreatePinnedSlice(uri).addSliceListener(listener, specs);
115 }
116
117 @Override
118 public void removeSliceListener(Uri uri, String pkg, ISliceListener listener)
119 throws RemoteException {
120 verifyCaller(pkg);
121 uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
122 enforceAccess(pkg, uri);
123 if (getPinnedSlice(uri).removeSliceListener(listener)) {
124 removePinnedSlice(uri);
125 }
126 }
127
128 @Override
129 public void pinSlice(String pkg, Uri uri, SliceSpec[] specs) throws RemoteException {
130 verifyCaller(pkg);
131 enforceFullAccess(pkg, "pinSlice", uri);
132 uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
133 getOrCreatePinnedSlice(uri).pin(pkg, specs);
134 }
135
136 @Override
137 public void unpinSlice(String pkg, Uri uri) throws RemoteException {
138 verifyCaller(pkg);
139 enforceFullAccess(pkg, "unpinSlice", uri);
140 uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
141 if (getPinnedSlice(uri).unpin(pkg)) {
142 removePinnedSlice(uri);
143 }
144 }
145
146 @Override
147 public boolean hasSliceAccess(String pkg) throws RemoteException {
148 verifyCaller(pkg);
149 return hasFullSliceAccess(pkg, Binder.getCallingUserHandle().getIdentifier());
150 }
151
152 @Override
153 public SliceSpec[] getPinnedSpecs(Uri uri, String pkg) throws RemoteException {
154 verifyCaller(pkg);
155 enforceAccess(pkg, uri);
156 return getPinnedSlice(uri).getSpecs();
157 }
158
159 /// ----- internal code -----
160 void removePinnedSlice(Uri uri) {
161 synchronized (mLock) {
162 mPinnedSlicesByUri.remove(uri).destroy();
163 }
164 }
165
166 private PinnedSliceState getPinnedSlice(Uri uri) {
167 synchronized (mLock) {
168 PinnedSliceState manager = mPinnedSlicesByUri.get(uri);
169 if (manager == null) {
170 throw new IllegalStateException(String.format("Slice %s not pinned",
171 uri.toString()));
172 }
173 return manager;
174 }
175 }
176
177 private PinnedSliceState getOrCreatePinnedSlice(Uri uri) {
178 synchronized (mLock) {
179 PinnedSliceState manager = mPinnedSlicesByUri.get(uri);
180 if (manager == null) {
181 manager = createPinnedSlice(uri);
182 mPinnedSlicesByUri.put(uri, manager);
183 }
184 return manager;
185 }
186 }
187
188 @VisibleForTesting
189 PinnedSliceState createPinnedSlice(Uri uri) {
190 return new PinnedSliceState(this, uri);
191 }
192
193 public Object getLock() {
194 return mLock;
195 }
196
197 public Context getContext() {
198 return mContext;
199 }
200
201 public Handler getHandler() {
202 return mHandler;
203 }
204
205 private void enforceAccess(String pkg, Uri uri) {
206 getContext().enforceUriPermission(uri, permission.BIND_SLICE,
207 permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
208 Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
209 "Slice binding requires the permission BIND_SLICE");
210 int user = Binder.getCallingUserHandle().getIdentifier();
211 if (getUserIdFromUri(uri, user) != user) {
212 getContext().enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL,
213 "Slice interaction across users requires INTERACT_ACROSS_USERS_FULL");
214 }
215 }
216
217 private void enforceFullAccess(String pkg, String name, Uri uri) {
218 int user = Binder.getCallingUserHandle().getIdentifier();
219 if (!hasFullSliceAccess(pkg, user)) {
220 throw new SecurityException(String.format("Call %s requires full slice access", name));
221 }
222 if (getUserIdFromUri(uri, user) != user) {
223 getContext().enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL,
224 "Slice interaction across users requires INTERACT_ACROSS_USERS_FULL");
225 }
226 }
227
228 private void verifyCaller(String pkg) {
229 mAppOps.checkPackage(Binder.getCallingUid(), pkg);
230 }
231
232 private boolean hasFullSliceAccess(String pkg, int userId) {
233 return isDefaultHomeApp(pkg, userId) || isAssistant(pkg, userId)
234 || isGrantedFullAccess(pkg, userId);
235 }
236
237 private boolean isAssistant(String pkg, int userId) {
238 final ComponentName cn = mAssistUtils.getAssistComponentForUser(userId);
239 if (cn == null) {
240 return false;
241 }
242 return cn.getPackageName().equals(pkg);
243 }
244
245 public void listen(Uri uri) {
246 mContext.getContentResolver().registerContentObserver(uri, true, mObserver);
247 }
248
249 public void unlisten(Uri uri) {
250 mContext.getContentResolver().unregisterContentObserver(mObserver);
251 synchronized (mLock) {
252 mPinnedSlicesByUri.forEach((u, s) -> {
253 if (s.isListening()) {
254 listen(u);
255 }
256 });
257 }
258 }
259
260 private boolean isDefaultHomeApp(String pkg, int userId) {
261 String defaultHome = getDefaultHome(userId);
262 return Objects.equals(pkg, defaultHome);
263 }
264
265 // Based on getDefaultHome in ShortcutService.
266 // TODO: Unify if possible
267 @VisibleForTesting
268 String getDefaultHome(int userId) {
269 final long token = Binder.clearCallingIdentity();
270 try {
271 final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
272
273 // Default launcher from package manager.
274 final ComponentName defaultLauncher = mPackageManagerInternal
275 .getHomeActivitiesAsUser(allHomeCandidates, userId);
276
277 ComponentName detected = null;
278 if (defaultLauncher != null) {
279 detected = defaultLauncher;
280 }
281
282 if (detected == null) {
283 // If we reach here, that means it's the first check since the user was created,
284 // and there's already multiple launchers and there's no default set.
285 // Find the system one with the highest priority.
286 // (We need to check the priority too because of FallbackHome in Settings.)
287 // If there's no system launcher yet, then no one can access slices, until
288 // the user explicitly sets one.
289 final int size = allHomeCandidates.size();
290
291 int lastPriority = Integer.MIN_VALUE;
292 for (int i = 0; i < size; i++) {
293 final ResolveInfo ri = allHomeCandidates.get(i);
294 if (!ri.activityInfo.applicationInfo.isSystemApp()) {
295 continue;
296 }
297 if (ri.priority < lastPriority) {
298 continue;
299 }
300 detected = ri.activityInfo.getComponentName();
301 lastPriority = ri.priority;
302 }
303 }
304 return detected.getPackageName();
305 } finally {
306 Binder.restoreCallingIdentity(token);
307 }
308 }
309
310 private boolean isGrantedFullAccess(String pkg, int userId) {
311 // TODO: This will be user granted access, if we allow this through a prompt.
312 return false;
313 }
314
315 private static ServiceThread createHandler() {
316 ServiceThread handlerThread = new ServiceThread(TAG,
317 Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
318 handlerThread.start();
319 return handlerThread;
Jason Monk8f5f7ff2017-10-17 14:12:42 -0400320 }
321
322 public static class Lifecycle extends SystemService {
323 private SliceManagerService mService;
324
325 public Lifecycle(Context context) {
326 super(context);
327 }
328
329 @Override
330 public void onStart() {
331 mService = new SliceManagerService(getContext());
332 publishBinderService(Context.SLICE_SERVICE, mService);
333 }
334
335 @Override
336 public void onBootPhase(int phase) {
337 if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
338 mService.systemReady();
339 }
340 }
341
342 @Override
343 public void onUnlockUser(int userHandle) {
344 mService.onUnlockUser(userHandle);
345 }
Jason Monk74f5e362017-12-06 08:56:33 -0500346
347 @Override
348 public void onStopUser(int userHandle) {
349 mService.onStopUser(userHandle);
350 }
Jason Monk8f5f7ff2017-10-17 14:12:42 -0400351 }
352}