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 Slog.w("clipboard", "Exception: ", e); 126 throw e; 127 } 128 129 } 130 131 private PerUserClipboard getClipboard() { 132 return getClipboard(UserHandle.getCallingUserId()); 133 } 134 135 private PerUserClipboard getClipboard(int userId) { 136 synchronized (mClipboards) { 137 PerUserClipboard puc = mClipboards.get(userId); 138 if (puc == null) { 139 puc = new PerUserClipboard(userId); 140 mClipboards.put(userId, puc); 141 } 142 return puc; 143 } 144 } 145 146 private void removeClipboard(int userId) { 147 synchronized (mClipboards) { 148 mClipboards.remove(userId); 149 } 150 } 151 152 public void setPrimaryClip(ClipData clip, String callingPackage) { 153 synchronized (this) { 154 if (clip != null && clip.getItemCount() <= 0) { 155 throw new IllegalArgumentException("No items"); 156 } 157 final int callingUid = Binder.getCallingUid(); 158 if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid, 159 callingPackage) != AppOpsManager.MODE_ALLOWED) { 160 return; 161 } 162 checkDataOwnerLocked(clip, callingUid); 163 clearActiveOwnersLocked(); 164 PerUserClipboard clipboard = getClipboard(); 165 clipboard.primaryClip = clip; 166 final long ident = Binder.clearCallingIdentity(); 167 final int n = clipboard.primaryClipListeners.beginBroadcast(); 168 try { 169 for (int i = 0; i < n; i++) { 170 try { 171 ListenerInfo li = (ListenerInfo) 172 clipboard.primaryClipListeners.getBroadcastCookie(i); 173 if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid, 174 li.mPackageName) == AppOpsManager.MODE_ALLOWED) { 175 clipboard.primaryClipListeners.getBroadcastItem(i) 176 .dispatchPrimaryClipChanged(); 177 } 178 } catch (RemoteException e) { 179 // The RemoteCallbackList will take care of removing 180 // the dead object for us. 181 } 182 } 183 } finally { 184 clipboard.primaryClipListeners.finishBroadcast(); 185 Binder.restoreCallingIdentity(ident); 186 } 187 } 188 } 189 190 public ClipData getPrimaryClip(String pkg) { 191 synchronized (this) { 192 if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(), 193 pkg) != AppOpsManager.MODE_ALLOWED) { 194 return null; 195 } 196 addActiveOwnerLocked(Binder.getCallingUid(), pkg); 197 return getClipboard().primaryClip; 198 } 199 } 200 201 public ClipDescription getPrimaryClipDescription(String callingPackage) { 202 synchronized (this) { 203 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(), 204 callingPackage) != AppOpsManager.MODE_ALLOWED) { 205 return null; 206 } 207 PerUserClipboard clipboard = getClipboard(); 208 return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null; 209 } 210 } 211 212 public boolean hasPrimaryClip(String callingPackage) { 213 synchronized (this) { 214 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(), 215 callingPackage) != AppOpsManager.MODE_ALLOWED) { 216 return false; 217 } 218 return getClipboard().primaryClip != null; 219 } 220 } 221 222 public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener, 223 String callingPackage) { 224 synchronized (this) { 225 getClipboard().primaryClipListeners.register(listener, 226 new ListenerInfo(Binder.getCallingUid(), callingPackage)); 227 } 228 } 229 230 public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { 231 synchronized (this) { 232 getClipboard().primaryClipListeners.unregister(listener); 233 } 234 } 235 236 public boolean hasClipboardText(String callingPackage) { 237 synchronized (this) { 238 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(), 239 callingPackage) != AppOpsManager.MODE_ALLOWED) { 240 return false; 241 } 242 PerUserClipboard clipboard = getClipboard(); 243 if (clipboard.primaryClip != null) { 244 CharSequence text = clipboard.primaryClip.getItemAt(0).getText(); 245 return text != null && text.length() > 0; 246 } 247 return false; 248 } 249 } 250 251 private final void checkUriOwnerLocked(Uri uri, int uid) { 252 if (!"content".equals(uri.getScheme())) { 253 return; 254 } 255 long ident = Binder.clearCallingIdentity(); 256 try { 257 // This will throw SecurityException for us. 258 mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); 259 } catch (RemoteException e) { 260 } finally { 261 Binder.restoreCallingIdentity(ident); 262 } 263 } 264 265 private final void checkItemOwnerLocked(ClipData.Item item, int uid) { 266 if (item.getUri() != null) { 267 checkUriOwnerLocked(item.getUri(), uid); 268 } 269 Intent intent = item.getIntent(); 270 if (intent != null && intent.getData() != null) { 271 checkUriOwnerLocked(intent.getData(), uid); 272 } 273 } 274 275 private final void checkDataOwnerLocked(ClipData data, int uid) { 276 final int N = data.getItemCount(); 277 for (int i=0; i<N; i++) { 278 checkItemOwnerLocked(data.getItemAt(i), uid); 279 } 280 } 281 282 private final void grantUriLocked(Uri uri, String pkg) { 283 long ident = Binder.clearCallingIdentity(); 284 try { 285 mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri, 286 Intent.FLAG_GRANT_READ_URI_PERMISSION); 287 } catch (RemoteException e) { 288 } finally { 289 Binder.restoreCallingIdentity(ident); 290 } 291 } 292 293 private final void grantItemLocked(ClipData.Item item, String pkg) { 294 if (item.getUri() != null) { 295 grantUriLocked(item.getUri(), pkg); 296 } 297 Intent intent = item.getIntent(); 298 if (intent != null && intent.getData() != null) { 299 grantUriLocked(intent.getData(), pkg); 300 } 301 } 302 303 private final void addActiveOwnerLocked(int uid, String pkg) { 304 final IPackageManager pm = AppGlobals.getPackageManager(); 305 final int targetUserHandle = UserHandle.getCallingUserId(); 306 final long oldIdentity = Binder.clearCallingIdentity(); 307 try { 308 PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle); 309 if (pi == null) { 310 throw new IllegalArgumentException("Unknown package " + pkg); 311 } 312 if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) { 313 throw new SecurityException("Calling uid " + uid 314 + " does not own package " + pkg); 315 } 316 } catch (RemoteException e) { 317 // Can't happen; the package manager is in the same process 318 } finally { 319 Binder.restoreCallingIdentity(oldIdentity); 320 } 321 PerUserClipboard clipboard = getClipboard(); 322 if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) { 323 final int N = clipboard.primaryClip.getItemCount(); 324 for (int i=0; i<N; i++) { 325 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg); 326 } 327 clipboard.activePermissionOwners.add(pkg); 328 } 329 } 330 331 private final void revokeUriLocked(Uri uri) { 332 long ident = Binder.clearCallingIdentity(); 333 try { 334 mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri, 335 Intent.FLAG_GRANT_READ_URI_PERMISSION 336 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 337 } catch (RemoteException e) { 338 } finally { 339 Binder.restoreCallingIdentity(ident); 340 } 341 } 342 343 private final void revokeItemLocked(ClipData.Item item) { 344 if (item.getUri() != null) { 345 revokeUriLocked(item.getUri()); 346 } 347 Intent intent = item.getIntent(); 348 if (intent != null && intent.getData() != null) { 349 revokeUriLocked(intent.getData()); 350 } 351 } 352 353 private final void clearActiveOwnersLocked() { 354 PerUserClipboard clipboard = getClipboard(); 355 clipboard.activePermissionOwners.clear(); 356 if (clipboard.primaryClip == null) { 357 return; 358 } 359 final int N = clipboard.primaryClip.getItemCount(); 360 for (int i=0; i<N; i++) { 361 revokeItemLocked(clipboard.primaryClip.getItemAt(i)); 362 } 363 } 364 } 365