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