blob: 058857ddb60523178cc046229a289d9d7bbda608 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070019import android.app.ActivityManagerNative;
Christopher Tatead9833a2012-09-14 13:34:17 -070020import android.app.AppGlobals;
Dianne Hackbornefcc1a22013-02-25 18:02:35 -080021import android.app.AppOpsManager;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070022import android.app.IActivityManager;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070023import android.content.BroadcastReceiver;
Dianne Hackborn1040dc42010-08-26 22:11:06 -070024import android.content.ClipData;
25import android.content.ClipDescription;
Dianne Hackborn9f531192010-08-04 17:48:03 -070026import android.content.IClipboard;
27import android.content.IOnPrimaryClipChangedListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.content.Context;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070029import android.content.Intent;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070030import android.content.IntentFilter;
Christopher Tatead9833a2012-09-14 13:34:17 -070031import android.content.pm.IPackageManager;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070032import android.content.pm.PackageInfo;
33import android.content.pm.PackageManager;
34import android.content.pm.PackageManager.NameNotFoundException;
35import android.net.Uri;
36import android.os.Binder;
37import android.os.IBinder;
38import android.os.Parcel;
39import android.os.Process;
Dianne Hackborn9f531192010-08-04 17:48:03 -070040import android.os.RemoteCallbackList;
41import android.os.RemoteException;
Dianne Hackbornf02b60a2012-08-16 10:48:27 -070042import android.os.UserHandle;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070043import android.util.Pair;
44import android.util.Slog;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070045import android.util.SparseArray;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070046
47import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048
49/**
50 * Implementation of the clipboard for copy and paste.
51 */
52public class ClipboardService extends IClipboard.Stub {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070053
54 private static final String TAG = "ClipboardService";
55
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070056 private final Context mContext;
57 private final IActivityManager mAm;
58 private final PackageManager mPm;
Dianne Hackbornefcc1a22013-02-25 18:02:35 -080059 private final AppOpsManager mAppOps;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070060 private final IBinder mPermissionOwner;
61
Dianne Hackbornefcc1a22013-02-25 18:02:35 -080062 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
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070071 private class PerUserClipboard {
72 final int userId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070074 final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
75 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070076
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070077 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>();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070088
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 /**
90 * Instantiates the clipboard.
91 */
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070092 public ClipboardService(Context context) {
93 mContext = context;
94 mAm = ActivityManagerNative.getDefault();
95 mPm = context.getPackageManager();
Dianne Hackbornefcc1a22013-02-25 18:02:35 -080096 mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070097 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;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700104
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)) {
Amith Yamasani2a003292012-08-14 18:25:45 -0700113 removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700114 }
115 }
116 }, userFilter);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700117 }
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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700131 private PerUserClipboard getClipboard() {
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700132 return getClipboard(UserHandle.getCallingUserId());
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700133 }
134
135 private PerUserClipboard getClipboard(int userId) {
136 synchronized (mClipboards) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700137 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
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800152 public void setPrimaryClip(ClipData clip, String callingPackage) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 synchronized (this) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700154 if (clip != null && clip.getItemCount() <= 0) {
155 throw new IllegalArgumentException("No items");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156 }
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800157 if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, Binder.getCallingUid(),
158 callingPackage) != AppOpsManager.MODE_ALLOWED) {
159 return;
160 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700161 checkDataOwnerLocked(clip, Binder.getCallingUid());
162 clearActiveOwnersLocked();
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700163 PerUserClipboard clipboard = getClipboard();
164 clipboard.primaryClip = clip;
165 final int n = clipboard.primaryClipListeners.beginBroadcast();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700166 for (int i = 0; i < n; i++) {
167 try {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800168 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 }
Dianne Hackborn9f531192010-08-04 17:48:03 -0700175 } catch (RemoteException e) {
176
177 // The RemoteCallbackList will take care of removing
178 // the dead object for us.
179 }
180 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700181 clipboard.primaryClipListeners.finishBroadcast();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700182 }
183 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800184
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700185 public ClipData getPrimaryClip(String pkg) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800187 if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
188 pkg) != AppOpsManager.MODE_ALLOWED) {
189 return null;
190 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700191 addActiveOwnerLocked(Binder.getCallingUid(), pkg);
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700192 return getClipboard().primaryClip;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193 }
194 }
195
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800196 public ClipDescription getPrimaryClipDescription(String callingPackage) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700197 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800198 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
199 callingPackage) != AppOpsManager.MODE_ALLOWED) {
200 return null;
201 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700202 PerUserClipboard clipboard = getClipboard();
203 return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700204 }
205 }
206
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800207 public boolean hasPrimaryClip(String callingPackage) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700208 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800209 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
210 callingPackage) != AppOpsManager.MODE_ALLOWED) {
211 return false;
212 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700213 return getClipboard().primaryClip != null;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700214 }
215 }
216
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800217 public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
218 String callingPackage) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700219 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800220 getClipboard().primaryClipListeners.register(listener,
221 new ListenerInfo(Binder.getCallingUid(), callingPackage));
Dianne Hackborn9f531192010-08-04 17:48:03 -0700222 }
223 }
224
225 public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
226 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700227 getClipboard().primaryClipListeners.unregister(listener);
Dianne Hackborn9f531192010-08-04 17:48:03 -0700228 }
229 }
230
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800231 public boolean hasClipboardText(String callingPackage) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800233 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
234 callingPackage) != AppOpsManager.MODE_ALLOWED) {
235 return false;
236 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700237 PerUserClipboard clipboard = getClipboard();
238 if (clipboard.primaryClip != null) {
239 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700240 return text != null && text.length() > 0;
241 }
242 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 }
244 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700245
246 private final void checkUriOwnerLocked(Uri uri, int uid) {
247 if (!"content".equals(uri.getScheme())) {
248 return;
249 }
250 long ident = Binder.clearCallingIdentity();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700251 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++) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800273 checkItemOwnerLocked(data.getItemAt(i), uid);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700274 }
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) {
Christopher Tatead9833a2012-09-14 13:34:17 -0700299 final IPackageManager pm = AppGlobals.getPackageManager();
300 final int targetUserHandle = UserHandle.getCallingUserId();
301 final long oldIdentity = Binder.clearCallingIdentity();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700302 try {
Christopher Tatead9833a2012-09-14 13:34:17 -0700303 PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
304 if (pi == null) {
305 throw new IllegalArgumentException("Unknown package " + pkg);
306 }
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700307 if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700308 throw new SecurityException("Calling uid " + uid
309 + " does not own package " + pkg);
310 }
Christopher Tatead9833a2012-09-14 13:34:17 -0700311 } catch (RemoteException e) {
312 // Can't happen; the package manager is in the same process
313 } finally {
314 Binder.restoreCallingIdentity(oldIdentity);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700315 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700316 PerUserClipboard clipboard = getClipboard();
317 if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
318 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700319 for (int i=0; i<N; i++) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700320 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700321 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700322 clipboard.activePermissionOwners.add(pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700323 }
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() {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700349 PerUserClipboard clipboard = getClipboard();
350 clipboard.activePermissionOwners.clear();
351 if (clipboard.primaryClip == null) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700352 return;
353 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700354 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700355 for (int i=0; i<N; i++) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700356 revokeItemLocked(clipboard.primaryClip.getItemAt(i));
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700357 }
358 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359}