2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.clipboard;
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;
51 import java.util.HashSet;
52 import java.util.List;
55 * Implementation of the clipboard for copy and paste.
57 public class ClipboardService extends IClipboard.Stub {
59 private static final String TAG = "ClipboardService";
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;
68 private class ListenerInfo {
70 final String mPackageName;
71 ListenerInfo(int uid, String packageName) {
73 mPackageName = packageName;
77 private class PerUserClipboard {
80 final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
81 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
85 final HashSet<String> activePermissionOwners
86 = new HashSet<String>();
88 PerUserClipboard(int userId) {
93 private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>();
96 * Instantiates the clipboard.
98 public ClipboardService(Context 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;
106 permOwner = mAm.newUriPermissionOwner("clipboard");
107 } catch (RemoteException e) {
108 Slog.w("clipboard", "AM dead", e);
110 mPermissionOwner = permOwner;
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() {
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));
127 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
128 throws RemoteException {
130 return super.onTransact(code, data, reply, flags);
131 } catch (RuntimeException e) {
132 if (!(e instanceof SecurityException)) {
133 Slog.wtf("clipboard", "Exception: ", e);
140 private PerUserClipboard getClipboard() {
141 return getClipboard(UserHandle.getCallingUserId());
144 private PerUserClipboard getClipboard(int userId) {
145 synchronized (mClipboards) {
146 PerUserClipboard puc = mClipboards.get(userId);
148 puc = new PerUserClipboard(userId);
149 mClipboards.put(userId, puc);
155 private void removeClipboard(int userId) {
156 synchronized (mClipboards) {
157 mClipboards.remove(userId);
161 public void setPrimaryClip(ClipData clip, String callingPackage) {
162 synchronized (this) {
163 if (clip != null && clip.getItemCount() <= 0) {
164 throw new IllegalArgumentException("No items");
166 final int callingUid = Binder.getCallingUid();
167 if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
168 callingPackage) != AppOpsManager.MODE_ALLOWED) {
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;
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);
187 // Copy clip data to related users if allowed. If disallowed, then remove
188 // primary clip in related users to prevent pasting stale content.
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)));
200 clip.fixUrisLight(userId);
202 for (int i = 0; i < size; i++) {
203 int id = related.get(i).id;
205 setPrimaryClipInternal(getClipboard(id), clip);
213 List<UserInfo> getRelatedProfiles(int userId) {
214 final List<UserInfo> related;
215 final long origId = Binder.clearCallingIdentity();
217 related = mUm.getProfiles(userId, true);
218 } catch (RemoteException e) {
219 Slog.e(TAG, "Remote Exception calling UserManager: " + e);
222 Binder.restoreCallingIdentity(origId);
227 void setPrimaryClipInternal(PerUserClipboard clipboard, ClipData clip) {
228 clipboard.activePermissionOwners.clear();
229 if (clip == null && clipboard.primaryClip == null) {
232 clipboard.primaryClip = clip;
233 final long ident = Binder.clearCallingIdentity();
234 final int n = clipboard.primaryClipListeners.beginBroadcast();
236 for (int i = 0; i < n; i++) {
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();
245 } catch (RemoteException e) {
246 // The RemoteCallbackList will take care of removing
247 // the dead object for us.
251 clipboard.primaryClipListeners.finishBroadcast();
252 Binder.restoreCallingIdentity(ident);
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()) {
262 addActiveOwnerLocked(Binder.getCallingUid(), pkg);
263 return getClipboard().primaryClip;
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()) {
273 PerUserClipboard clipboard = getClipboard();
274 return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
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()) {
284 return getClipboard().primaryClip != null;
288 public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
289 String callingPackage) {
290 synchronized (this) {
291 getClipboard().primaryClipListeners.register(listener,
292 new ListenerInfo(Binder.getCallingUid(), callingPackage));
296 public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
297 synchronized (this) {
298 getClipboard().primaryClipListeners.unregister(listener);
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()) {
308 PerUserClipboard clipboard = getClipboard();
309 if (clipboard.primaryClip != null) {
310 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
311 return text != null && text.length() > 0;
317 private boolean isDeviceLocked() {
318 final KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
319 return keyguardManager != null && keyguardManager.isDeviceLocked();
322 private final void checkUriOwnerLocked(Uri uri, int uid) {
323 if (!"content".equals(uri.getScheme())) {
326 long ident = Binder.clearCallingIdentity();
328 // This will throw SecurityException for us.
329 mAm.checkGrantUriPermission(uid, null, ContentProvider.getUriWithoutUserId(uri),
330 Intent.FLAG_GRANT_READ_URI_PERMISSION,
331 ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(uid)));
332 } catch (RemoteException e) {
334 Binder.restoreCallingIdentity(ident);
338 private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
339 if (item.getUri() != null) {
340 checkUriOwnerLocked(item.getUri(), uid);
342 Intent intent = item.getIntent();
343 if (intent != null && intent.getData() != null) {
344 checkUriOwnerLocked(intent.getData(), uid);
348 private final void checkDataOwnerLocked(ClipData data, int uid) {
349 final int N = data.getItemCount();
350 for (int i=0; i<N; i++) {
351 checkItemOwnerLocked(data.getItemAt(i), uid);
355 private final void grantUriLocked(Uri uri, String pkg, int userId) {
356 long ident = Binder.clearCallingIdentity();
358 int sourceUserId = ContentProvider.getUserIdFromUri(uri, userId);
359 uri = ContentProvider.getUriWithoutUserId(uri);
360 mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg,
361 uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, userId);
362 } catch (RemoteException e) {
364 Binder.restoreCallingIdentity(ident);
368 private final void grantItemLocked(ClipData.Item item, String pkg, int userId) {
369 if (item.getUri() != null) {
370 grantUriLocked(item.getUri(), pkg, userId);
372 Intent intent = item.getIntent();
373 if (intent != null && intent.getData() != null) {
374 grantUriLocked(intent.getData(), pkg, userId);
378 private final void addActiveOwnerLocked(int uid, String pkg) {
379 final IPackageManager pm = AppGlobals.getPackageManager();
380 final int targetUserHandle = UserHandle.getCallingUserId();
381 final long oldIdentity = Binder.clearCallingIdentity();
383 PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
385 throw new IllegalArgumentException("Unknown package " + pkg);
387 if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
388 throw new SecurityException("Calling uid " + uid
389 + " does not own package " + pkg);
391 } catch (RemoteException e) {
392 // Can't happen; the package manager is in the same process
394 Binder.restoreCallingIdentity(oldIdentity);
396 PerUserClipboard clipboard = getClipboard();
397 if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
398 final int N = clipboard.primaryClip.getItemCount();
399 for (int i=0; i<N; i++) {
400 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg, UserHandle.getUserId(uid));
402 clipboard.activePermissionOwners.add(pkg);
406 private final void revokeUriLocked(Uri uri) {
407 int userId = ContentProvider.getUserIdFromUri(uri,
408 UserHandle.getUserId(Binder.getCallingUid()));
409 long ident = Binder.clearCallingIdentity();
411 uri = ContentProvider.getUriWithoutUserId(uri);
412 mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
413 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
415 } catch (RemoteException e) {
417 Binder.restoreCallingIdentity(ident);
421 private final void revokeItemLocked(ClipData.Item item) {
422 if (item.getUri() != null) {
423 revokeUriLocked(item.getUri());
425 Intent intent = item.getIntent();
426 if (intent != null && intent.getData() != null) {
427 revokeUriLocked(intent.getData());
431 private final void revokeUris(PerUserClipboard clipboard) {
432 if (clipboard.primaryClip == null) {
435 final int N = clipboard.primaryClip.getItemCount();
436 for (int i=0; i<N; i++) {
437 revokeItemLocked(clipboard.primaryClip.getItemAt(i));