blob: f47d66df1bd38d4b7ff52b32f3e9a32a913ed965 [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
Amith Yamasani30f8eb42013-11-06 14:54:50 -080017package com.android.server.clipboard;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080018
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;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070034import android.net.Uri;
35import android.os.Binder;
36import android.os.IBinder;
37import android.os.Parcel;
38import android.os.Process;
Dianne Hackborn9f531192010-08-04 17:48:03 -070039import android.os.RemoteCallbackList;
40import android.os.RemoteException;
Dianne Hackbornf02b60a2012-08-16 10:48:27 -070041import android.os.UserHandle;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070042import android.util.Slog;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070043import android.util.SparseArray;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070044
45import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046
47/**
48 * Implementation of the clipboard for copy and paste.
49 */
50public class ClipboardService extends IClipboard.Stub {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070051
52 private static final String TAG = "ClipboardService";
53
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070054 private final Context mContext;
55 private final IActivityManager mAm;
56 private final PackageManager mPm;
Dianne Hackbornefcc1a22013-02-25 18:02:35 -080057 private final AppOpsManager mAppOps;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070058 private final IBinder mPermissionOwner;
59
Dianne Hackbornefcc1a22013-02-25 18:02:35 -080060 private class ListenerInfo {
61 final int mUid;
62 final String mPackageName;
63 ListenerInfo(int uid, String packageName) {
64 mUid = uid;
65 mPackageName = packageName;
66 }
67 }
68
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070069 private class PerUserClipboard {
70 final int userId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070072 final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
73 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070074
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070075 ClipData primaryClip;
76
77 final HashSet<String> activePermissionOwners
78 = new HashSet<String>();
79
80 PerUserClipboard(int userId) {
81 this.userId = userId;
82 }
83 }
84
85 private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070086
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087 /**
88 * Instantiates the clipboard.
89 */
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070090 public ClipboardService(Context context) {
91 mContext = context;
92 mAm = ActivityManagerNative.getDefault();
93 mPm = context.getPackageManager();
Dianne Hackbornefcc1a22013-02-25 18:02:35 -080094 mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070095 IBinder permOwner = null;
96 try {
97 permOwner = mAm.newUriPermissionOwner("clipboard");
98 } catch (RemoteException e) {
99 Slog.w("clipboard", "AM dead", e);
100 }
101 mPermissionOwner = permOwner;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700102
103 // Remove the clipboard if a user is removed
104 IntentFilter userFilter = new IntentFilter();
105 userFilter.addAction(Intent.ACTION_USER_REMOVED);
106 mContext.registerReceiver(new BroadcastReceiver() {
107 @Override
108 public void onReceive(Context context, Intent intent) {
109 String action = intent.getAction();
110 if (Intent.ACTION_USER_REMOVED.equals(action)) {
Amith Yamasani2a003292012-08-14 18:25:45 -0700111 removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700112 }
113 }
114 }, userFilter);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700115 }
116
117 @Override
118 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
119 throws RemoteException {
120 try {
121 return super.onTransact(code, data, reply, flags);
122 } catch (RuntimeException e) {
Dianne Hackborn164371f2013-10-01 19:10:13 -0700123 if (!(e instanceof SecurityException)) {
124 Slog.wtf("clipboard", "Exception: ", e);
125 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700126 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 Hackbornf0989842013-07-29 18:11:02 -0700157 final int callingUid = Binder.getCallingUid();
158 if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800159 callingPackage) != AppOpsManager.MODE_ALLOWED) {
160 return;
161 }
Dianne Hackbornf0989842013-07-29 18:11:02 -0700162 checkDataOwnerLocked(clip, callingUid);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700163 clearActiveOwnersLocked();
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700164 PerUserClipboard clipboard = getClipboard();
165 clipboard.primaryClip = clip;
Dianne Hackbornf0989842013-07-29 18:11:02 -0700166 final long ident = Binder.clearCallingIdentity();
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700167 final int n = clipboard.primaryClipListeners.beginBroadcast();
Dianne Hackbornf0989842013-07-29 18:11:02 -0700168 try {
169 for (int i = 0; i < n; i++) {
170 try {
171 ListenerInfo li = (ListenerInfo)
172 clipboard.primaryClipListeners.getBroadcastCookie(i);
173 if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid,
174 li.mPackageName) == AppOpsManager.MODE_ALLOWED) {
175 clipboard.primaryClipListeners.getBroadcastItem(i)
176 .dispatchPrimaryClipChanged();
177 }
178 } catch (RemoteException e) {
179 // The RemoteCallbackList will take care of removing
180 // the dead object for us.
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800181 }
Dianne Hackborn9f531192010-08-04 17:48:03 -0700182 }
Dianne Hackbornf0989842013-07-29 18:11:02 -0700183 } finally {
184 clipboard.primaryClipListeners.finishBroadcast();
185 Binder.restoreCallingIdentity(ident);
Dianne Hackborn9f531192010-08-04 17:48:03 -0700186 }
Dianne Hackborn9f531192010-08-04 17:48:03 -0700187 }
188 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700190 public ClipData getPrimaryClip(String pkg) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800192 if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
193 pkg) != AppOpsManager.MODE_ALLOWED) {
194 return null;
195 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700196 addActiveOwnerLocked(Binder.getCallingUid(), pkg);
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700197 return getClipboard().primaryClip;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800198 }
199 }
200
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800201 public ClipDescription getPrimaryClipDescription(String callingPackage) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700202 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800203 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
204 callingPackage) != AppOpsManager.MODE_ALLOWED) {
205 return null;
206 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700207 PerUserClipboard clipboard = getClipboard();
208 return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700209 }
210 }
211
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800212 public boolean hasPrimaryClip(String callingPackage) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700213 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800214 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
215 callingPackage) != AppOpsManager.MODE_ALLOWED) {
216 return false;
217 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700218 return getClipboard().primaryClip != null;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700219 }
220 }
221
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800222 public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
223 String callingPackage) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700224 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800225 getClipboard().primaryClipListeners.register(listener,
226 new ListenerInfo(Binder.getCallingUid(), callingPackage));
Dianne Hackborn9f531192010-08-04 17:48:03 -0700227 }
228 }
229
230 public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
231 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700232 getClipboard().primaryClipListeners.unregister(listener);
Dianne Hackborn9f531192010-08-04 17:48:03 -0700233 }
234 }
235
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800236 public boolean hasClipboardText(String callingPackage) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800238 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
239 callingPackage) != AppOpsManager.MODE_ALLOWED) {
240 return false;
241 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700242 PerUserClipboard clipboard = getClipboard();
243 if (clipboard.primaryClip != null) {
244 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700245 return text != null && text.length() > 0;
246 }
247 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800248 }
249 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700250
251 private final void checkUriOwnerLocked(Uri uri, int uid) {
252 if (!"content".equals(uri.getScheme())) {
253 return;
254 }
255 long ident = Binder.clearCallingIdentity();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700256 try {
257 // This will throw SecurityException for us.
258 mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
259 } catch (RemoteException e) {
260 } finally {
261 Binder.restoreCallingIdentity(ident);
262 }
263 }
264
265 private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
266 if (item.getUri() != null) {
267 checkUriOwnerLocked(item.getUri(), uid);
268 }
269 Intent intent = item.getIntent();
270 if (intent != null && intent.getData() != null) {
271 checkUriOwnerLocked(intent.getData(), uid);
272 }
273 }
274
275 private final void checkDataOwnerLocked(ClipData data, int uid) {
276 final int N = data.getItemCount();
277 for (int i=0; i<N; i++) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800278 checkItemOwnerLocked(data.getItemAt(i), uid);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700279 }
280 }
281
282 private final void grantUriLocked(Uri uri, String pkg) {
283 long ident = Binder.clearCallingIdentity();
284 try {
285 mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
286 Intent.FLAG_GRANT_READ_URI_PERMISSION);
287 } catch (RemoteException e) {
288 } finally {
289 Binder.restoreCallingIdentity(ident);
290 }
291 }
292
293 private final void grantItemLocked(ClipData.Item item, String pkg) {
294 if (item.getUri() != null) {
295 grantUriLocked(item.getUri(), pkg);
296 }
297 Intent intent = item.getIntent();
298 if (intent != null && intent.getData() != null) {
299 grantUriLocked(intent.getData(), pkg);
300 }
301 }
302
303 private final void addActiveOwnerLocked(int uid, String pkg) {
Christopher Tatead9833a2012-09-14 13:34:17 -0700304 final IPackageManager pm = AppGlobals.getPackageManager();
305 final int targetUserHandle = UserHandle.getCallingUserId();
306 final long oldIdentity = Binder.clearCallingIdentity();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700307 try {
Christopher Tatead9833a2012-09-14 13:34:17 -0700308 PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
309 if (pi == null) {
310 throw new IllegalArgumentException("Unknown package " + pkg);
311 }
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700312 if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700313 throw new SecurityException("Calling uid " + uid
314 + " does not own package " + pkg);
315 }
Christopher Tatead9833a2012-09-14 13:34:17 -0700316 } catch (RemoteException e) {
317 // Can't happen; the package manager is in the same process
318 } finally {
319 Binder.restoreCallingIdentity(oldIdentity);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700320 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700321 PerUserClipboard clipboard = getClipboard();
322 if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
323 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700324 for (int i=0; i<N; i++) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700325 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700326 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700327 clipboard.activePermissionOwners.add(pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700328 }
329 }
330
331 private final void revokeUriLocked(Uri uri) {
332 long ident = Binder.clearCallingIdentity();
333 try {
334 mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
335 Intent.FLAG_GRANT_READ_URI_PERMISSION
336 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
337 } catch (RemoteException e) {
338 } finally {
339 Binder.restoreCallingIdentity(ident);
340 }
341 }
342
343 private final void revokeItemLocked(ClipData.Item item) {
344 if (item.getUri() != null) {
345 revokeUriLocked(item.getUri());
346 }
347 Intent intent = item.getIntent();
348 if (intent != null && intent.getData() != null) {
349 revokeUriLocked(intent.getData());
350 }
351 }
352
353 private final void clearActiveOwnersLocked() {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700354 PerUserClipboard clipboard = getClipboard();
355 clipboard.activePermissionOwners.clear();
356 if (clipboard.primaryClip == null) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700357 return;
358 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700359 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700360 for (int i=0; i<N; i++) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700361 revokeItemLocked(clipboard.primaryClip.getItemAt(i));
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700362 }
363 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364}