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