OSDN Git Service

Fix ClipboardService device lock check for cross profile
[android-x86/frameworks-base.git] / services / core / java / com / android / server / clipboard / ClipboardService.java
1 /*
2  * Copyright (C) 2008 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
17 package com.android.server.clipboard;
18
19 import android.app.ActivityManagerNative;
20 import android.app.AppGlobals;
21 import android.app.AppOpsManager;
22 import android.app.IActivityManager;
23 import android.app.KeyguardManager;
24 import android.content.BroadcastReceiver;
25 import android.content.ClipData;
26 import android.content.ClipDescription;
27 import android.content.ContentProvider;
28 import android.content.IClipboard;
29 import android.content.IOnPrimaryClipChangedListener;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.pm.IPackageManager;
34 import android.content.pm.PackageInfo;
35 import android.content.pm.PackageManager;
36 import android.content.pm.UserInfo;
37 import android.net.Uri;
38 import android.os.Binder;
39 import android.os.IBinder;
40 import android.os.IUserManager;
41 import android.os.Parcel;
42 import android.os.Process;
43 import android.os.RemoteCallbackList;
44 import android.os.RemoteException;
45 import android.os.ServiceManager;
46 import android.os.UserHandle;
47 import android.os.UserManager;
48 import android.util.Slog;
49 import android.util.SparseArray;
50
51 import java.util.HashSet;
52 import java.util.List;
53
54 /**
55  * Implementation of the clipboard for copy and paste.
56  */
57 public class ClipboardService extends IClipboard.Stub {
58
59     private static final String TAG = "ClipboardService";
60
61     private final Context mContext;
62     private final IActivityManager mAm;
63     private final IUserManager mUm;
64     private final PackageManager mPm;
65     private final AppOpsManager mAppOps;
66     private final IBinder mPermissionOwner;
67
68     private class ListenerInfo {
69         final int mUid;
70         final String mPackageName;
71         ListenerInfo(int uid, String packageName) {
72             mUid = uid;
73             mPackageName = packageName;
74         }
75     }
76
77     private class PerUserClipboard {
78         final int userId;
79
80         final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
81                 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
82
83         ClipData primaryClip;
84
85         final HashSet<String> activePermissionOwners
86                 = new HashSet<String>();
87
88         PerUserClipboard(int userId) {
89             this.userId = userId;
90         }
91     }
92
93     private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>();
94
95     /**
96      * Instantiates the clipboard.
97      */
98     public ClipboardService(Context context) {
99         mContext = context;
100         mAm = ActivityManagerNative.getDefault();
101         mPm = context.getPackageManager();
102         mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
103         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
104         IBinder permOwner = null;
105         try {
106             permOwner = mAm.newUriPermissionOwner("clipboard");
107         } catch (RemoteException e) {
108             Slog.w("clipboard", "AM dead", e);
109         }
110         mPermissionOwner = permOwner;
111
112         // Remove the clipboard if a user is removed
113         IntentFilter userFilter = new IntentFilter();
114         userFilter.addAction(Intent.ACTION_USER_REMOVED);
115         mContext.registerReceiver(new BroadcastReceiver() {
116             @Override
117             public void onReceive(Context context, Intent intent) {
118                 String action = intent.getAction();
119                 if (Intent.ACTION_USER_REMOVED.equals(action)) {
120                     removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
121                 }
122             }
123         }, userFilter);
124     }
125
126     @Override
127     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
128             throws RemoteException {
129         try {
130             return super.onTransact(code, data, reply, flags);
131         } catch (RuntimeException e) {
132             if (!(e instanceof SecurityException)) {
133                 Slog.wtf("clipboard", "Exception: ", e);
134             }
135             throw e;
136         }
137         
138     }
139
140     private PerUserClipboard getClipboard() {
141         return getClipboard(UserHandle.getCallingUserId());
142     }
143
144     private PerUserClipboard getClipboard(int userId) {
145         synchronized (mClipboards) {
146             PerUserClipboard puc = mClipboards.get(userId);
147             if (puc == null) {
148                 puc = new PerUserClipboard(userId);
149                 mClipboards.put(userId, puc);
150             }
151             return puc;
152         }
153     }
154
155     private void removeClipboard(int userId) {
156         synchronized (mClipboards) {
157             mClipboards.remove(userId);
158         }
159     }
160
161     public void setPrimaryClip(ClipData clip, String callingPackage) {
162         synchronized (this) {
163             if (clip != null && clip.getItemCount() <= 0) {
164                 throw new IllegalArgumentException("No items");
165             }
166             final int callingUid = Binder.getCallingUid();
167             if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
168                     callingPackage) != AppOpsManager.MODE_ALLOWED) {
169                 return;
170             }
171             checkDataOwnerLocked(clip, callingUid);
172             final int userId = UserHandle.getUserId(callingUid);
173             PerUserClipboard clipboard = getClipboard(userId);
174             revokeUris(clipboard);
175             setPrimaryClipInternal(clipboard, clip);
176             List<UserInfo> related = getRelatedProfiles(userId);
177             if (related != null) {
178                 int size = related.size();
179                 if (size > 1) { // Related profiles list include the current profile.
180                     boolean canCopy = false;
181                     try {
182                         canCopy = !mUm.getUserRestrictions(userId).getBoolean(
183                                 UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
184                     } catch (RemoteException e) {
185                         Slog.e(TAG, "Remote Exception calling UserManager: " + e);
186                     }
187                     // Copy clip data to related users if allowed. If disallowed, then remove
188                     // primary clip in related users to prevent pasting stale content.
189                     if (!canCopy) {
190                         clip = null;
191                     } else {
192                         // We want to fix the uris of the related user's clip without changing the
193                         // uris of the current user's clip.
194                         // So, copy the ClipData, and then copy all the items, so that nothing
195                         // is shared in memmory.
196                         clip = new ClipData(clip);
197                         for (int i = clip.getItemCount() - 1; i >= 0; i--) {
198                             clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
199                         }
200                         clip.fixUrisLight(userId);
201                     }
202                     for (int i = 0; i < size; i++) {
203                         int id = related.get(i).id;
204                         if (id != userId) {
205                             setPrimaryClipInternal(getClipboard(id), clip);
206                         }
207                     }
208                 }
209             }
210         }
211     }
212
213     List<UserInfo> getRelatedProfiles(int userId) {
214         final List<UserInfo> related;
215         final long origId = Binder.clearCallingIdentity();
216         try {
217             related = mUm.getProfiles(userId, true);
218         } catch (RemoteException e) {
219             Slog.e(TAG, "Remote Exception calling UserManager: " + e);
220             return null;
221         } finally{
222             Binder.restoreCallingIdentity(origId);
223         }
224         return related;
225     }
226
227     void setPrimaryClipInternal(PerUserClipboard clipboard, ClipData clip) {
228         clipboard.activePermissionOwners.clear();
229         if (clip == null && clipboard.primaryClip == null) {
230             return;
231         }
232         clipboard.primaryClip = clip;
233         final long ident = Binder.clearCallingIdentity();
234         final int n = clipboard.primaryClipListeners.beginBroadcast();
235         try {
236             for (int i = 0; i < n; i++) {
237                 try {
238                     ListenerInfo li = (ListenerInfo)
239                             clipboard.primaryClipListeners.getBroadcastCookie(i);
240                     if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid,
241                             li.mPackageName) == AppOpsManager.MODE_ALLOWED) {
242                         clipboard.primaryClipListeners.getBroadcastItem(i)
243                                 .dispatchPrimaryClipChanged();
244                     }
245                 } catch (RemoteException e) {
246                     // The RemoteCallbackList will take care of removing
247                     // the dead object for us.
248                 }
249             }
250         } finally {
251             clipboard.primaryClipListeners.finishBroadcast();
252             Binder.restoreCallingIdentity(ident);
253         }
254     }
255     
256     public ClipData getPrimaryClip(String pkg) {
257         synchronized (this) {
258             if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
259                     pkg) != AppOpsManager.MODE_ALLOWED || isDeviceLocked()) {
260                 return null;
261             }
262             addActiveOwnerLocked(Binder.getCallingUid(), pkg);
263             return getClipboard().primaryClip;
264         }
265     }
266
267     public ClipDescription getPrimaryClipDescription(String callingPackage) {
268         synchronized (this) {
269             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
270                     callingPackage) != AppOpsManager.MODE_ALLOWED || isDeviceLocked()) {
271                 return null;
272             }
273             PerUserClipboard clipboard = getClipboard();
274             return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
275         }
276     }
277
278     public boolean hasPrimaryClip(String callingPackage) {
279         synchronized (this) {
280             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
281                     callingPackage) != AppOpsManager.MODE_ALLOWED || isDeviceLocked()) {
282                 return false;
283             }
284             return getClipboard().primaryClip != null;
285         }
286     }
287
288     public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
289             String callingPackage) {
290         synchronized (this) {
291             getClipboard().primaryClipListeners.register(listener,
292                     new ListenerInfo(Binder.getCallingUid(), callingPackage));
293         }
294     }
295
296     public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
297         synchronized (this) {
298             getClipboard().primaryClipListeners.unregister(listener);
299         }
300     }
301
302     public boolean hasClipboardText(String callingPackage) {
303         synchronized (this) {
304             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
305                     callingPackage) != AppOpsManager.MODE_ALLOWED || isDeviceLocked()) {
306                 return false;
307             }
308             PerUserClipboard clipboard = getClipboard();
309             if (clipboard.primaryClip != null) {
310                 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
311                 return text != null && text.length() > 0;
312             }
313             return false;
314         }
315     }
316
317     private boolean isDeviceLocked() {
318         final long token = Binder.clearCallingIdentity();
319         try {
320             final KeyguardManager keyguardManager = mContext.getSystemService(
321                     KeyguardManager.class);
322             return keyguardManager != null && keyguardManager.isDeviceLocked();
323         } finally {
324             Binder.restoreCallingIdentity(token);
325         }
326     }
327
328     private final void checkUriOwnerLocked(Uri uri, int uid) {
329         if (!"content".equals(uri.getScheme())) {
330             return;
331         }
332         long ident = Binder.clearCallingIdentity();
333         try {
334             // This will throw SecurityException for us.
335             mAm.checkGrantUriPermission(uid, null, ContentProvider.getUriWithoutUserId(uri),
336                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
337                     ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(uid)));
338         } catch (RemoteException e) {
339         } finally {
340             Binder.restoreCallingIdentity(ident);
341         }
342     }
343
344     private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
345         if (item.getUri() != null) {
346             checkUriOwnerLocked(item.getUri(), uid);
347         }
348         Intent intent = item.getIntent();
349         if (intent != null && intent.getData() != null) {
350             checkUriOwnerLocked(intent.getData(), uid);
351         }
352     }
353
354     private final void checkDataOwnerLocked(ClipData data, int uid) {
355         final int N = data.getItemCount();
356         for (int i=0; i<N; i++) {
357             checkItemOwnerLocked(data.getItemAt(i), uid);
358         }
359     }
360
361     private final void grantUriLocked(Uri uri, String pkg, int userId) {
362         long ident = Binder.clearCallingIdentity();
363         try {
364             int sourceUserId = ContentProvider.getUserIdFromUri(uri, userId);
365             uri = ContentProvider.getUriWithoutUserId(uri);
366             mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg,
367                     uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, userId);
368         } catch (RemoteException e) {
369         } finally {
370             Binder.restoreCallingIdentity(ident);
371         }
372     }
373
374     private final void grantItemLocked(ClipData.Item item, String pkg, int userId) {
375         if (item.getUri() != null) {
376             grantUriLocked(item.getUri(), pkg, userId);
377         }
378         Intent intent = item.getIntent();
379         if (intent != null && intent.getData() != null) {
380             grantUriLocked(intent.getData(), pkg, userId);
381         }
382     }
383
384     private final void addActiveOwnerLocked(int uid, String pkg) {
385         final IPackageManager pm = AppGlobals.getPackageManager();
386         final int targetUserHandle = UserHandle.getCallingUserId();
387         final long oldIdentity = Binder.clearCallingIdentity();
388         try {
389             PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
390             if (pi == null) {
391                 throw new IllegalArgumentException("Unknown package " + pkg);
392             }
393             if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
394                 throw new SecurityException("Calling uid " + uid
395                         + " does not own package " + pkg);
396             }
397         } catch (RemoteException e) {
398             // Can't happen; the package manager is in the same process
399         } finally {
400             Binder.restoreCallingIdentity(oldIdentity);
401         }
402         PerUserClipboard clipboard = getClipboard();
403         if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
404             final int N = clipboard.primaryClip.getItemCount();
405             for (int i=0; i<N; i++) {
406                 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg, UserHandle.getUserId(uid));
407             }
408             clipboard.activePermissionOwners.add(pkg);
409         }
410     }
411
412     private final void revokeUriLocked(Uri uri) {
413         int userId = ContentProvider.getUserIdFromUri(uri,
414                 UserHandle.getUserId(Binder.getCallingUid()));
415         long ident = Binder.clearCallingIdentity();
416         try {
417             uri = ContentProvider.getUriWithoutUserId(uri);
418             mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
419                     Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
420                     userId);
421         } catch (RemoteException e) {
422         } finally {
423             Binder.restoreCallingIdentity(ident);
424         }
425     }
426
427     private final void revokeItemLocked(ClipData.Item item) {
428         if (item.getUri() != null) {
429             revokeUriLocked(item.getUri());
430         }
431         Intent intent = item.getIntent();
432         if (intent != null && intent.getData() != null) {
433             revokeUriLocked(intent.getData());
434         }
435     }
436
437     private final void revokeUris(PerUserClipboard clipboard) {
438         if (clipboard.primaryClip == null) {
439             return;
440         }
441         final int N = clipboard.primaryClip.getItemCount();
442         for (int i=0; i<N; i++) {
443             revokeItemLocked(clipboard.primaryClip.getItemAt(i));
444         }
445     }
446 }