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