blob: 5772a57d79aacf9df5ad48d17d2f227abef057e4 [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;
Dianne Hackborn1040dc42010-08-26 22:11:06 -070023import android.content.ClipData;
24import android.content.ClipDescription;
Nicolas Prevotd85fc722014-04-16 19:52:08 +010025import android.content.ContentProvider;
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;
Christopher Tatead9833a2012-09-14 13:34:17 -070030import android.content.pm.IPackageManager;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070031import android.content.pm.PackageInfo;
32import android.content.pm.PackageManager;
Nicolas Prevotf1939902014-06-25 09:29:02 +010033import android.content.pm.UserInfo;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070034import android.net.Uri;
35import android.os.Binder;
36import android.os.IBinder;
Nicolas Prevotf1939902014-06-25 09:29:02 +010037import android.os.IUserManager;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070038import android.os.Parcel;
39import android.os.Process;
Dianne Hackborn9f531192010-08-04 17:48:03 -070040import android.os.RemoteCallbackList;
41import android.os.RemoteException;
Nicolas Prevotf1939902014-06-25 09:29:02 +010042import android.os.ServiceManager;
Dianne Hackbornf02b60a2012-08-16 10:48:27 -070043import android.os.UserHandle;
Nicolas Prevotf1939902014-06-25 09:29:02 +010044import android.os.UserManager;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070045import android.util.Slog;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070046import android.util.SparseArray;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070047
Sudheer Shankaad70bc62016-09-29 12:58:04 -070048import com.android.server.SystemService;
49
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070050import java.util.HashSet;
Nicolas Prevotf1939902014-06-25 09:29:02 +010051import java.util.List;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052
53/**
54 * Implementation of the clipboard for copy and paste.
55 */
Sudheer Shankaad70bc62016-09-29 12:58:04 -070056public class ClipboardService extends SystemService {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070057
58 private static final String TAG = "ClipboardService";
59
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070060 private final IActivityManager mAm;
Nicolas Prevotf1939902014-06-25 09:29:02 +010061 private final IUserManager mUm;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070062 private final PackageManager mPm;
Dianne Hackbornefcc1a22013-02-25 18:02:35 -080063 private final AppOpsManager mAppOps;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070064 private final IBinder mPermissionOwner;
65
Sudheer Shankaad70bc62016-09-29 12:58:04 -070066 private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
67
68 /**
69 * Instantiates the clipboard.
70 */
71 public ClipboardService(Context context) {
72 super(context);
73
74 mAm = ActivityManagerNative.getDefault();
75 mPm = getContext().getPackageManager();
76 mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
77 mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
78 IBinder permOwner = null;
79 try {
80 permOwner = mAm.newUriPermissionOwner("clipboard");
81 } catch (RemoteException e) {
82 Slog.w("clipboard", "AM dead", e);
83 }
84 mPermissionOwner = permOwner;
85 }
86
87 @Override
88 public void onStart() {
89 publishBinderService(Context.CLIPBOARD_SERVICE, new ClipboardImpl());
90 }
91
92 @Override
93 public void onCleanupUser(int userId) {
94 synchronized (mClipboards) {
95 mClipboards.remove(userId);
96 }
97 }
98
Dianne Hackbornefcc1a22013-02-25 18:02:35 -080099 private class ListenerInfo {
100 final int mUid;
101 final String mPackageName;
102 ListenerInfo(int uid, String packageName) {
103 mUid = uid;
104 mPackageName = packageName;
105 }
106 }
107
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700108 private class PerUserClipboard {
109 final int userId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700111 final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
112 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700113
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700114 ClipData primaryClip;
115
116 final HashSet<String> activePermissionOwners
117 = new HashSet<String>();
118
119 PerUserClipboard(int userId) {
120 this.userId = userId;
121 }
122 }
123
Sudheer Shankaad70bc62016-09-29 12:58:04 -0700124 private class ClipboardImpl extends IClipboard.Stub {
125 @Override
126 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
127 throws RemoteException {
128 try {
129 return super.onTransact(code, data, reply, flags);
130 } catch (RuntimeException e) {
131 if (!(e instanceof SecurityException)) {
132 Slog.wtf("clipboard", "Exception: ", e);
133 }
134 throw e;
135 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700136
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700137 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700138
Sudheer Shankaad70bc62016-09-29 12:58:04 -0700139 @Override
140 public void setPrimaryClip(ClipData clip, String callingPackage) {
141 synchronized (this) {
142 if (clip != null && clip.getItemCount() <= 0) {
143 throw new IllegalArgumentException("No items");
144 }
145 final int callingUid = Binder.getCallingUid();
146 if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
147 callingPackage) != AppOpsManager.MODE_ALLOWED) {
148 return;
149 }
150 checkDataOwnerLocked(clip, callingUid);
151 final int userId = UserHandle.getUserId(callingUid);
152 PerUserClipboard clipboard = getClipboard(userId);
153 revokeUris(clipboard);
154 setPrimaryClipInternal(clipboard, clip);
155 List<UserInfo> related = getRelatedProfiles(userId);
156 if (related != null) {
157 int size = related.size();
158 if (size > 1) { // Related profiles list include the current profile.
159 boolean canCopy = false;
160 try {
161 canCopy = !mUm.getUserRestrictions(userId).getBoolean(
162 UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
163 } catch (RemoteException e) {
164 Slog.e(TAG, "Remote Exception calling UserManager: " + e);
165 }
166 // Copy clip data to related users if allowed. If disallowed, then remove
167 // primary clip in related users to prevent pasting stale content.
168 if (!canCopy) {
169 clip = null;
170 } else {
171 // We want to fix the uris of the related user's clip without changing the
172 // uris of the current user's clip.
173 // So, copy the ClipData, and then copy all the items, so that nothing
174 // is shared in memmory.
175 clip = new ClipData(clip);
176 for (int i = clip.getItemCount() - 1; i >= 0; i--) {
177 clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
178 }
179 clip.fixUrisLight(userId);
180 }
181 for (int i = 0; i < size; i++) {
182 int id = related.get(i).id;
183 if (id != userId) {
184 setPrimaryClipInternal(getClipboard(id), clip);
185 }
186 }
187 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700188 }
189 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700190 }
Sudheer Shankaad70bc62016-09-29 12:58:04 -0700191
192 @Override
193 public ClipData getPrimaryClip(String pkg) {
194 synchronized (this) {
195 if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
196 pkg) != AppOpsManager.MODE_ALLOWED) {
197 return null;
198 }
199 addActiveOwnerLocked(Binder.getCallingUid(), pkg);
200 return getClipboard().primaryClip;
201 }
202 }
203
204 @Override
205 public ClipDescription getPrimaryClipDescription(String callingPackage) {
206 synchronized (this) {
207 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
208 callingPackage) != AppOpsManager.MODE_ALLOWED) {
209 return null;
210 }
211 PerUserClipboard clipboard = getClipboard();
212 return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
213 }
214 }
215
216 @Override
217 public boolean hasPrimaryClip(String callingPackage) {
218 synchronized (this) {
219 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
220 callingPackage) != AppOpsManager.MODE_ALLOWED) {
221 return false;
222 }
223 return getClipboard().primaryClip != null;
224 }
225 }
226
227 @Override
228 public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
229 String callingPackage) {
230 synchronized (this) {
231 getClipboard().primaryClipListeners.register(listener,
232 new ListenerInfo(Binder.getCallingUid(), callingPackage));
233 }
234 }
235
236 @Override
237 public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
238 synchronized (this) {
239 getClipboard().primaryClipListeners.unregister(listener);
240 }
241 }
242
243 @Override
244 public boolean hasClipboardText(String callingPackage) {
245 synchronized (this) {
246 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
247 callingPackage) != AppOpsManager.MODE_ALLOWED) {
248 return false;
249 }
250 PerUserClipboard clipboard = getClipboard();
251 if (clipboard.primaryClip != null) {
252 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
253 return text != null && text.length() > 0;
254 }
255 return false;
256 }
257 }
258 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700260 private PerUserClipboard getClipboard() {
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700261 return getClipboard(UserHandle.getCallingUserId());
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700262 }
263
264 private PerUserClipboard getClipboard(int userId) {
265 synchronized (mClipboards) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700266 PerUserClipboard puc = mClipboards.get(userId);
267 if (puc == null) {
268 puc = new PerUserClipboard(userId);
269 mClipboards.put(userId, puc);
270 }
271 return puc;
272 }
273 }
274
Nicolas Prevotf1939902014-06-25 09:29:02 +0100275 List<UserInfo> getRelatedProfiles(int userId) {
276 final List<UserInfo> related;
277 final long origId = Binder.clearCallingIdentity();
278 try {
279 related = mUm.getProfiles(userId, true);
280 } catch (RemoteException e) {
281 Slog.e(TAG, "Remote Exception calling UserManager: " + e);
282 return null;
283 } finally{
284 Binder.restoreCallingIdentity(origId);
285 }
286 return related;
287 }
288
289 void setPrimaryClipInternal(PerUserClipboard clipboard, ClipData clip) {
290 clipboard.activePermissionOwners.clear();
291 if (clip == null && clipboard.primaryClip == null) {
292 return;
293 }
294 clipboard.primaryClip = clip;
295 final long ident = Binder.clearCallingIdentity();
296 final int n = clipboard.primaryClipListeners.beginBroadcast();
297 try {
298 for (int i = 0; i < n; i++) {
299 try {
300 ListenerInfo li = (ListenerInfo)
301 clipboard.primaryClipListeners.getBroadcastCookie(i);
302 if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid,
303 li.mPackageName) == AppOpsManager.MODE_ALLOWED) {
304 clipboard.primaryClipListeners.getBroadcastItem(i)
305 .dispatchPrimaryClipChanged();
306 }
307 } catch (RemoteException e) {
308 // The RemoteCallbackList will take care of removing
309 // the dead object for us.
310 }
311 }
312 } finally {
313 clipboard.primaryClipListeners.finishBroadcast();
314 Binder.restoreCallingIdentity(ident);
315 }
316 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700317
318 private final void checkUriOwnerLocked(Uri uri, int uid) {
319 if (!"content".equals(uri.getScheme())) {
320 return;
321 }
322 long ident = Binder.clearCallingIdentity();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700323 try {
324 // This will throw SecurityException for us.
Nicolas Prevotd85fc722014-04-16 19:52:08 +0100325 mAm.checkGrantUriPermission(uid, null, ContentProvider.getUriWithoutUserId(uri),
Nicolas Prevotf1939902014-06-25 09:29:02 +0100326 Intent.FLAG_GRANT_READ_URI_PERMISSION,
327 ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(uid)));
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700328 } catch (RemoteException e) {
329 } finally {
330 Binder.restoreCallingIdentity(ident);
331 }
332 }
333
334 private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
335 if (item.getUri() != null) {
336 checkUriOwnerLocked(item.getUri(), uid);
337 }
338 Intent intent = item.getIntent();
339 if (intent != null && intent.getData() != null) {
340 checkUriOwnerLocked(intent.getData(), uid);
341 }
342 }
343
344 private final void checkDataOwnerLocked(ClipData data, int uid) {
345 final int N = data.getItemCount();
346 for (int i=0; i<N; i++) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800347 checkItemOwnerLocked(data.getItemAt(i), uid);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700348 }
349 }
350
Nicolas Prevotf1939902014-06-25 09:29:02 +0100351 private final void grantUriLocked(Uri uri, String pkg, int userId) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700352 long ident = Binder.clearCallingIdentity();
353 try {
Nicolas Prevotf1939902014-06-25 09:29:02 +0100354 int sourceUserId = ContentProvider.getUserIdFromUri(uri, userId);
355 uri = ContentProvider.getUriWithoutUserId(uri);
Nicolas Prevotd85fc722014-04-16 19:52:08 +0100356 mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg,
Nicolas Prevotf1939902014-06-25 09:29:02 +0100357 uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, userId);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700358 } catch (RemoteException e) {
359 } finally {
360 Binder.restoreCallingIdentity(ident);
361 }
362 }
363
Nicolas Prevotf1939902014-06-25 09:29:02 +0100364 private final void grantItemLocked(ClipData.Item item, String pkg, int userId) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700365 if (item.getUri() != null) {
Nicolas Prevotf1939902014-06-25 09:29:02 +0100366 grantUriLocked(item.getUri(), pkg, userId);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700367 }
368 Intent intent = item.getIntent();
369 if (intent != null && intent.getData() != null) {
Nicolas Prevotf1939902014-06-25 09:29:02 +0100370 grantUriLocked(intent.getData(), pkg, userId);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700371 }
372 }
373
374 private final void addActiveOwnerLocked(int uid, String pkg) {
Christopher Tatead9833a2012-09-14 13:34:17 -0700375 final IPackageManager pm = AppGlobals.getPackageManager();
376 final int targetUserHandle = UserHandle.getCallingUserId();
377 final long oldIdentity = Binder.clearCallingIdentity();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700378 try {
Christopher Tatead9833a2012-09-14 13:34:17 -0700379 PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
380 if (pi == null) {
381 throw new IllegalArgumentException("Unknown package " + pkg);
382 }
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700383 if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700384 throw new SecurityException("Calling uid " + uid
385 + " does not own package " + pkg);
386 }
Christopher Tatead9833a2012-09-14 13:34:17 -0700387 } catch (RemoteException e) {
388 // Can't happen; the package manager is in the same process
389 } finally {
390 Binder.restoreCallingIdentity(oldIdentity);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700391 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700392 PerUserClipboard clipboard = getClipboard();
393 if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
394 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700395 for (int i=0; i<N; i++) {
Nicolas Prevotf1939902014-06-25 09:29:02 +0100396 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg, UserHandle.getUserId(uid));
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700397 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700398 clipboard.activePermissionOwners.add(pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700399 }
400 }
401
402 private final void revokeUriLocked(Uri uri) {
Nicolas Prevotf1939902014-06-25 09:29:02 +0100403 int userId = ContentProvider.getUserIdFromUri(uri,
404 UserHandle.getUserId(Binder.getCallingUid()));
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700405 long ident = Binder.clearCallingIdentity();
406 try {
Nicolas Prevotf1939902014-06-25 09:29:02 +0100407 uri = ContentProvider.getUriWithoutUserId(uri);
408 mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
Nicolas Prevotd85fc722014-04-16 19:52:08 +0100409 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
Nicolas Prevotf1939902014-06-25 09:29:02 +0100410 userId);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700411 } catch (RemoteException e) {
412 } finally {
413 Binder.restoreCallingIdentity(ident);
414 }
415 }
416
417 private final void revokeItemLocked(ClipData.Item item) {
418 if (item.getUri() != null) {
419 revokeUriLocked(item.getUri());
420 }
421 Intent intent = item.getIntent();
422 if (intent != null && intent.getData() != null) {
423 revokeUriLocked(intent.getData());
424 }
425 }
426
Nicolas Prevotf1939902014-06-25 09:29:02 +0100427 private final void revokeUris(PerUserClipboard clipboard) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700428 if (clipboard.primaryClip == null) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700429 return;
430 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700431 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700432 for (int i=0; i<N; i++) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700433 revokeItemLocked(clipboard.primaryClip.getItemAt(i));
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700434 }
435 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800436}