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