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