ClipboardService.java revision d85fc72fb810858f7502e7e7f1bad53e1bf03edd
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 17package com.android.server.clipboard; 18 19import android.app.ActivityManagerNative; 20import android.app.AppGlobals; 21import android.app.AppOpsManager; 22import android.app.IActivityManager; 23import android.content.BroadcastReceiver; 24import android.content.ClipData; 25import android.content.ClipDescription; 26import android.content.ContentProvider; 27import android.content.IClipboard; 28import android.content.IOnPrimaryClipChangedListener; 29import android.content.Context; 30import android.content.Intent; 31import android.content.IntentFilter; 32import android.content.pm.IPackageManager; 33import android.content.pm.PackageInfo; 34import android.content.pm.PackageManager; 35import android.net.Uri; 36import android.os.Binder; 37import android.os.IBinder; 38import android.os.Parcel; 39import android.os.Process; 40import android.os.RemoteCallbackList; 41import android.os.RemoteException; 42import android.os.UserHandle; 43import android.util.Slog; 44import android.util.SparseArray; 45 46import java.util.HashSet; 47 48/** 49 * Implementation of the clipboard for copy and paste. 50 */ 51public class ClipboardService extends IClipboard.Stub { 52 53 private static final String TAG = "ClipboardService"; 54 55 private final Context mContext; 56 private final IActivityManager mAm; 57 private final PackageManager mPm; 58 private final AppOpsManager mAppOps; 59 private final IBinder mPermissionOwner; 60 61 private class ListenerInfo { 62 final int mUid; 63 final String mPackageName; 64 ListenerInfo(int uid, String packageName) { 65 mUid = uid; 66 mPackageName = packageName; 67 } 68 } 69 70 private class PerUserClipboard { 71 final int userId; 72 73 final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners 74 = new RemoteCallbackList<IOnPrimaryClipChangedListener>(); 75 76 ClipData primaryClip; 77 78 final HashSet<String> activePermissionOwners 79 = new HashSet<String>(); 80 81 PerUserClipboard(int userId) { 82 this.userId = userId; 83 } 84 } 85 86 private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>(); 87 88 /** 89 * Instantiates the clipboard. 90 */ 91 public ClipboardService(Context context) { 92 mContext = context; 93 mAm = ActivityManagerNative.getDefault(); 94 mPm = context.getPackageManager(); 95 mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); 96 IBinder permOwner = null; 97 try { 98 permOwner = mAm.newUriPermissionOwner("clipboard"); 99 } catch (RemoteException e) { 100 Slog.w("clipboard", "AM dead", e); 101 } 102 mPermissionOwner = permOwner; 103 104 // Remove the clipboard if a user is removed 105 IntentFilter userFilter = new IntentFilter(); 106 userFilter.addAction(Intent.ACTION_USER_REMOVED); 107 mContext.registerReceiver(new BroadcastReceiver() { 108 @Override 109 public void onReceive(Context context, Intent intent) { 110 String action = intent.getAction(); 111 if (Intent.ACTION_USER_REMOVED.equals(action)) { 112 removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); 113 } 114 } 115 }, userFilter); 116 } 117 118 @Override 119 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 120 throws RemoteException { 121 try { 122 return super.onTransact(code, data, reply, flags); 123 } catch (RuntimeException e) { 124 if (!(e instanceof SecurityException)) { 125 Slog.wtf("clipboard", "Exception: ", e); 126 } 127 throw e; 128 } 129 130 } 131 132 private PerUserClipboard getClipboard() { 133 return getClipboard(UserHandle.getCallingUserId()); 134 } 135 136 private PerUserClipboard getClipboard(int userId) { 137 synchronized (mClipboards) { 138 PerUserClipboard puc = mClipboards.get(userId); 139 if (puc == null) { 140 puc = new PerUserClipboard(userId); 141 mClipboards.put(userId, puc); 142 } 143 return puc; 144 } 145 } 146 147 private void removeClipboard(int userId) { 148 synchronized (mClipboards) { 149 mClipboards.remove(userId); 150 } 151 } 152 153 public void setPrimaryClip(ClipData clip, String callingPackage) { 154 synchronized (this) { 155 if (clip != null && clip.getItemCount() <= 0) { 156 throw new IllegalArgumentException("No items"); 157 } 158 final int callingUid = Binder.getCallingUid(); 159 if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid, 160 callingPackage) != AppOpsManager.MODE_ALLOWED) { 161 return; 162 } 163 checkDataOwnerLocked(clip, callingUid); 164 clearActiveOwnersLocked(); 165 PerUserClipboard clipboard = getClipboard(); 166 clipboard.primaryClip = clip; 167 final long ident = Binder.clearCallingIdentity(); 168 final int n = clipboard.primaryClipListeners.beginBroadcast(); 169 try { 170 for (int i = 0; i < n; i++) { 171 try { 172 ListenerInfo li = (ListenerInfo) 173 clipboard.primaryClipListeners.getBroadcastCookie(i); 174 if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid, 175 li.mPackageName) == AppOpsManager.MODE_ALLOWED) { 176 clipboard.primaryClipListeners.getBroadcastItem(i) 177 .dispatchPrimaryClipChanged(); 178 } 179 } catch (RemoteException e) { 180 // The RemoteCallbackList will take care of removing 181 // the dead object for us. 182 } 183 } 184 } finally { 185 clipboard.primaryClipListeners.finishBroadcast(); 186 Binder.restoreCallingIdentity(ident); 187 } 188 } 189 } 190 191 public ClipData getPrimaryClip(String pkg) { 192 synchronized (this) { 193 if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(), 194 pkg) != AppOpsManager.MODE_ALLOWED) { 195 return null; 196 } 197 addActiveOwnerLocked(Binder.getCallingUid(), pkg); 198 return getClipboard().primaryClip; 199 } 200 } 201 202 public ClipDescription getPrimaryClipDescription(String callingPackage) { 203 synchronized (this) { 204 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(), 205 callingPackage) != AppOpsManager.MODE_ALLOWED) { 206 return null; 207 } 208 PerUserClipboard clipboard = getClipboard(); 209 return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null; 210 } 211 } 212 213 public boolean hasPrimaryClip(String callingPackage) { 214 synchronized (this) { 215 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(), 216 callingPackage) != AppOpsManager.MODE_ALLOWED) { 217 return false; 218 } 219 return getClipboard().primaryClip != null; 220 } 221 } 222 223 public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener, 224 String callingPackage) { 225 synchronized (this) { 226 getClipboard().primaryClipListeners.register(listener, 227 new ListenerInfo(Binder.getCallingUid(), callingPackage)); 228 } 229 } 230 231 public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { 232 synchronized (this) { 233 getClipboard().primaryClipListeners.unregister(listener); 234 } 235 } 236 237 public boolean hasClipboardText(String callingPackage) { 238 synchronized (this) { 239 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(), 240 callingPackage) != AppOpsManager.MODE_ALLOWED) { 241 return false; 242 } 243 PerUserClipboard clipboard = getClipboard(); 244 if (clipboard.primaryClip != null) { 245 CharSequence text = clipboard.primaryClip.getItemAt(0).getText(); 246 return text != null && text.length() > 0; 247 } 248 return false; 249 } 250 } 251 252 private final void checkUriOwnerLocked(Uri uri, int uid) { 253 if (!"content".equals(uri.getScheme())) { 254 return; 255 } 256 long ident = Binder.clearCallingIdentity(); 257 try { 258 // This will throw SecurityException for us. 259 mAm.checkGrantUriPermission(uid, null, ContentProvider.getUriWithoutUserId(uri), 260 Intent.FLAG_GRANT_READ_URI_PERMISSION, resolveUserId(uri, uid)); 261 } catch (RemoteException e) { 262 } finally { 263 Binder.restoreCallingIdentity(ident); 264 } 265 } 266 267 private final void checkItemOwnerLocked(ClipData.Item item, int uid) { 268 if (item.getUri() != null) { 269 checkUriOwnerLocked(item.getUri(), uid); 270 } 271 Intent intent = item.getIntent(); 272 if (intent != null && intent.getData() != null) { 273 checkUriOwnerLocked(intent.getData(), uid); 274 } 275 } 276 277 private final void checkDataOwnerLocked(ClipData data, int uid) { 278 final int N = data.getItemCount(); 279 for (int i=0; i<N; i++) { 280 checkItemOwnerLocked(data.getItemAt(i), uid); 281 } 282 } 283 284 private final void grantUriLocked(Uri uri, String pkg) { 285 long ident = Binder.clearCallingIdentity(); 286 try { 287 mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, 288 ContentProvider.getUriWithoutUserId(uri), 289 Intent.FLAG_GRANT_READ_URI_PERMISSION, 290 resolveUserId(uri, Process.myUid())); 291 } catch (RemoteException e) { 292 } finally { 293 Binder.restoreCallingIdentity(ident); 294 } 295 } 296 297 private final void grantItemLocked(ClipData.Item item, String pkg) { 298 if (item.getUri() != null) { 299 grantUriLocked(item.getUri(), pkg); 300 } 301 Intent intent = item.getIntent(); 302 if (intent != null && intent.getData() != null) { 303 grantUriLocked(intent.getData(), pkg); 304 } 305 } 306 307 private final void addActiveOwnerLocked(int uid, String pkg) { 308 final IPackageManager pm = AppGlobals.getPackageManager(); 309 final int targetUserHandle = UserHandle.getCallingUserId(); 310 final long oldIdentity = Binder.clearCallingIdentity(); 311 try { 312 PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle); 313 if (pi == null) { 314 throw new IllegalArgumentException("Unknown package " + pkg); 315 } 316 if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) { 317 throw new SecurityException("Calling uid " + uid 318 + " does not own package " + pkg); 319 } 320 } catch (RemoteException e) { 321 // Can't happen; the package manager is in the same process 322 } finally { 323 Binder.restoreCallingIdentity(oldIdentity); 324 } 325 PerUserClipboard clipboard = getClipboard(); 326 if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) { 327 final int N = clipboard.primaryClip.getItemCount(); 328 for (int i=0; i<N; i++) { 329 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg); 330 } 331 clipboard.activePermissionOwners.add(pkg); 332 } 333 } 334 335 private final void revokeUriLocked(Uri uri) { 336 long ident = Binder.clearCallingIdentity(); 337 try { 338 mAm.revokeUriPermissionFromOwner(mPermissionOwner, 339 ContentProvider.getUriWithoutUserId(uri), 340 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION, 341 resolveUserId(uri, Process.myUid())); 342 } catch (RemoteException e) { 343 } finally { 344 Binder.restoreCallingIdentity(ident); 345 } 346 } 347 348 private final void revokeItemLocked(ClipData.Item item) { 349 if (item.getUri() != null) { 350 revokeUriLocked(item.getUri()); 351 } 352 Intent intent = item.getIntent(); 353 if (intent != null && intent.getData() != null) { 354 revokeUriLocked(intent.getData()); 355 } 356 } 357 358 private final void clearActiveOwnersLocked() { 359 PerUserClipboard clipboard = getClipboard(); 360 clipboard.activePermissionOwners.clear(); 361 if (clipboard.primaryClip == null) { 362 return; 363 } 364 final int N = clipboard.primaryClip.getItemCount(); 365 for (int i=0; i<N; i++) { 366 revokeItemLocked(clipboard.primaryClip.getItemAt(i)); 367 } 368 } 369 370 private final int resolveUserId(Uri uri, int uid) { 371 return ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(uid)); 372 } 373} 374