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.content.BroadcastReceiver;
24 import android.content.ClipData;
25 import android.content.ClipDescription;
26 import android.content.ContentProvider;
27 import android.content.IClipboard;
28 import android.content.IOnPrimaryClipChangedListener;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.pm.IPackageManager;
33 import android.content.pm.PackageInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.UserInfo;
36 import android.net.Uri;
37 import android.os.Binder;
38 import android.os.IBinder;
39 import android.os.IUserManager;
40 import android.os.Parcel;
41 import android.os.Process;
42 import android.os.RemoteCallbackList;
43 import android.os.RemoteException;
44 import android.os.ServiceManager;
45 import android.os.UserHandle;
46 import android.os.UserManager;
47 import android.util.Slog;
48 import android.util.SparseArray;
50 import java.util.HashSet;
51 import java.util.List;
54 * Implementation of the clipboard for copy and paste.
56 public class ClipboardService extends IClipboard.Stub {
58 private static final String TAG = "ClipboardService";
60 private final Context mContext;
61 private final IActivityManager mAm;
62 private final IUserManager mUm;
63 private final PackageManager mPm;
64 private final AppOpsManager mAppOps;
65 private final IBinder mPermissionOwner;
67 private class ListenerInfo {
69 final String mPackageName;
70 ListenerInfo(int uid, String packageName) {
72 mPackageName = packageName;
76 private class PerUserClipboard {
79 final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
80 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
84 final HashSet<String> activePermissionOwners
85 = new HashSet<String>();
87 PerUserClipboard(int userId) {
92 private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>();
95 * Instantiates the clipboard.
97 public ClipboardService(Context context) {
99 mAm = ActivityManagerNative.getDefault();
100 mPm = context.getPackageManager();
101 mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
102 mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
103 IBinder permOwner = null;
105 permOwner = mAm.newUriPermissionOwner("clipboard");
106 } catch (RemoteException e) {
107 Slog.w("clipboard", "AM dead", e);
109 mPermissionOwner = permOwner;
111 // Remove the clipboard if a user is removed
112 IntentFilter userFilter = new IntentFilter();
113 userFilter.addAction(Intent.ACTION_USER_REMOVED);
114 mContext.registerReceiver(new BroadcastReceiver() {
116 public void onReceive(Context context, Intent intent) {
117 String action = intent.getAction();
118 if (Intent.ACTION_USER_REMOVED.equals(action)) {
119 removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
126 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
127 throws RemoteException {
129 return super.onTransact(code, data, reply, flags);
130 } catch (RuntimeException e) {
131 if (!(e instanceof SecurityException)) {
132 Slog.wtf("clipboard", "Exception: ", e);
139 private PerUserClipboard getClipboard() {
140 return getClipboard(UserHandle.getCallingUserId());
143 private PerUserClipboard getClipboard(int userId) {
144 synchronized (mClipboards) {
145 PerUserClipboard puc = mClipboards.get(userId);
147 puc = new PerUserClipboard(userId);
148 mClipboards.put(userId, puc);
154 private void removeClipboard(int userId) {
155 synchronized (mClipboards) {
156 mClipboards.remove(userId);
160 public void setPrimaryClip(ClipData clip, String callingPackage) {
161 synchronized (this) {
162 if (clip != null && clip.getItemCount() <= 0) {
163 throw new IllegalArgumentException("No items");
165 final int callingUid = Binder.getCallingUid();
166 if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
167 callingPackage) != AppOpsManager.MODE_ALLOWED) {
170 checkDataOwnerLocked(clip, callingUid);
171 final int userId = UserHandle.getUserId(callingUid);
172 PerUserClipboard clipboard = getClipboard(userId);
173 revokeUris(clipboard);
174 setPrimaryClipInternal(clipboard, clip);
175 List<UserInfo> related = getRelatedProfiles(userId);
176 if (related != null) {
177 int size = related.size();
178 if (size > 1) { // Related profiles list include the current profile.
179 boolean canCopy = false;
181 canCopy = !mUm.getUserRestrictions(userId).getBoolean(
182 UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
183 } catch (RemoteException e) {
184 Slog.e(TAG, "Remote Exception calling UserManager: " + e);
186 // Copy clip data to related users if allowed. If disallowed, then remove
187 // primary clip in related users to prevent pasting stale content.
191 // We want to fix the uris of the related user's clip without changing the
192 // uris of the current user's clip.
193 // So, copy the ClipData, and then copy all the items, so that nothing
194 // is shared in memmory.
195 clip = new ClipData(clip);
196 for (int i = clip.getItemCount() - 1; i >= 0; i--) {
197 clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
199 clip.fixUrisLight(userId);
201 for (int i = 0; i < size; i++) {
202 int id = related.get(i).id;
204 setPrimaryClipInternal(getClipboard(id), clip);
212 List<UserInfo> getRelatedProfiles(int userId) {
213 final List<UserInfo> related;
214 final long origId = Binder.clearCallingIdentity();
216 related = mUm.getProfiles(userId, true);
217 } catch (RemoteException e) {
218 Slog.e(TAG, "Remote Exception calling UserManager: " + e);
221 Binder.restoreCallingIdentity(origId);
226 void setPrimaryClipInternal(PerUserClipboard clipboard, ClipData clip) {
227 clipboard.activePermissionOwners.clear();
228 if (clip == null && clipboard.primaryClip == null) {
231 clipboard.primaryClip = clip;
232 final long ident = Binder.clearCallingIdentity();
233 final int n = clipboard.primaryClipListeners.beginBroadcast();
235 for (int i = 0; i < n; i++) {
237 ListenerInfo li = (ListenerInfo)
238 clipboard.primaryClipListeners.getBroadcastCookie(i);
239 if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid,
240 li.mPackageName) == AppOpsManager.MODE_ALLOWED) {
241 clipboard.primaryClipListeners.getBroadcastItem(i)
242 .dispatchPrimaryClipChanged();
244 } catch (RemoteException e) {
245 // The RemoteCallbackList will take care of removing
246 // the dead object for us.
250 clipboard.primaryClipListeners.finishBroadcast();
251 Binder.restoreCallingIdentity(ident);
255 public ClipData getPrimaryClip(String pkg) {
256 synchronized (this) {
257 if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
258 pkg) != AppOpsManager.MODE_ALLOWED) {
261 addActiveOwnerLocked(Binder.getCallingUid(), pkg);
262 return getClipboard().primaryClip;
266 public ClipDescription getPrimaryClipDescription(String callingPackage) {
267 synchronized (this) {
268 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
269 callingPackage) != AppOpsManager.MODE_ALLOWED) {
272 PerUserClipboard clipboard = getClipboard();
273 return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
277 public boolean hasPrimaryClip(String callingPackage) {
278 synchronized (this) {
279 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
280 callingPackage) != AppOpsManager.MODE_ALLOWED) {
283 return getClipboard().primaryClip != null;
287 public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
288 String callingPackage) {
289 synchronized (this) {
290 getClipboard().primaryClipListeners.register(listener,
291 new ListenerInfo(Binder.getCallingUid(), callingPackage));
295 public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
296 synchronized (this) {
297 getClipboard().primaryClipListeners.unregister(listener);
301 public boolean hasClipboardText(String callingPackage) {
302 synchronized (this) {
303 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
304 callingPackage) != AppOpsManager.MODE_ALLOWED) {
307 PerUserClipboard clipboard = getClipboard();
308 if (clipboard.primaryClip != null) {
309 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
310 return text != null && text.length() > 0;
316 private final void checkUriOwnerLocked(Uri uri, int uid) {
317 if (!"content".equals(uri.getScheme())) {
320 long ident = Binder.clearCallingIdentity();
322 // This will throw SecurityException for us.
323 mAm.checkGrantUriPermission(uid, null, ContentProvider.getUriWithoutUserId(uri),
324 Intent.FLAG_GRANT_READ_URI_PERMISSION,
325 ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(uid)));
326 } catch (RemoteException e) {
328 Binder.restoreCallingIdentity(ident);
332 private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
333 if (item.getUri() != null) {
334 checkUriOwnerLocked(item.getUri(), uid);
336 Intent intent = item.getIntent();
337 if (intent != null && intent.getData() != null) {
338 checkUriOwnerLocked(intent.getData(), uid);
342 private final void checkDataOwnerLocked(ClipData data, int uid) {
343 final int N = data.getItemCount();
344 for (int i=0; i<N; i++) {
345 checkItemOwnerLocked(data.getItemAt(i), uid);
349 private final void grantUriLocked(Uri uri, String pkg, int userId) {
350 long ident = Binder.clearCallingIdentity();
352 int sourceUserId = ContentProvider.getUserIdFromUri(uri, userId);
353 uri = ContentProvider.getUriWithoutUserId(uri);
354 mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg,
355 uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, userId);
356 } catch (RemoteException e) {
358 Binder.restoreCallingIdentity(ident);
362 private final void grantItemLocked(ClipData.Item item, String pkg, int userId) {
363 if (item.getUri() != null) {
364 grantUriLocked(item.getUri(), pkg, userId);
366 Intent intent = item.getIntent();
367 if (intent != null && intent.getData() != null) {
368 grantUriLocked(intent.getData(), pkg, userId);
372 private final void addActiveOwnerLocked(int uid, String pkg) {
373 final IPackageManager pm = AppGlobals.getPackageManager();
374 final int targetUserHandle = UserHandle.getCallingUserId();
375 final long oldIdentity = Binder.clearCallingIdentity();
377 PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
379 throw new IllegalArgumentException("Unknown package " + pkg);
381 if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
382 throw new SecurityException("Calling uid " + uid
383 + " does not own package " + pkg);
385 } catch (RemoteException e) {
386 // Can't happen; the package manager is in the same process
388 Binder.restoreCallingIdentity(oldIdentity);
390 PerUserClipboard clipboard = getClipboard();
391 if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
392 final int N = clipboard.primaryClip.getItemCount();
393 for (int i=0; i<N; i++) {
394 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg, UserHandle.getUserId(uid));
396 clipboard.activePermissionOwners.add(pkg);
400 private final void revokeUriLocked(Uri uri) {
401 int userId = ContentProvider.getUserIdFromUri(uri,
402 UserHandle.getUserId(Binder.getCallingUid()));
403 long ident = Binder.clearCallingIdentity();
405 uri = ContentProvider.getUriWithoutUserId(uri);
406 mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
407 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
409 } catch (RemoteException e) {
411 Binder.restoreCallingIdentity(ident);
415 private final void revokeItemLocked(ClipData.Item item) {
416 if (item.getUri() != null) {
417 revokeUriLocked(item.getUri());
419 Intent intent = item.getIntent();
420 if (intent != null && intent.getData() != null) {
421 revokeUriLocked(intent.getData());
425 private final void revokeUris(PerUserClipboard clipboard) {
426 if (clipboard.primaryClip == null) {
429 final int N = clipboard.primaryClip.getItemCount();
430 for (int i=0; i<N; i++) {
431 revokeItemLocked(clipboard.primaryClip.getItemAt(i));