blob: 069ae23f55962bd27fd045217804a273eefa032b [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) {
Dianne Hackborn164371f2013-10-01 19:10:13 -0700125 if (!(e instanceof SecurityException)) {
126 Slog.wtf("clipboard", "Exception: ", e);
127 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700128 throw e;
129 }
130
131 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700133 private PerUserClipboard getClipboard() {
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700134 return getClipboard(UserHandle.getCallingUserId());
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700135 }
136
137 private PerUserClipboard getClipboard(int userId) {
138 synchronized (mClipboards) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700139 PerUserClipboard puc = mClipboards.get(userId);
140 if (puc == null) {
141 puc = new PerUserClipboard(userId);
142 mClipboards.put(userId, puc);
143 }
144 return puc;
145 }
146 }
147
148 private void removeClipboard(int userId) {
149 synchronized (mClipboards) {
150 mClipboards.remove(userId);
151 }
152 }
153
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800154 public void setPrimaryClip(ClipData clip, String callingPackage) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 synchronized (this) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700156 if (clip != null && clip.getItemCount() <= 0) {
157 throw new IllegalArgumentException("No items");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 }
Dianne Hackbornf0989842013-07-29 18:11:02 -0700159 final int callingUid = Binder.getCallingUid();
160 if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800161 callingPackage) != AppOpsManager.MODE_ALLOWED) {
162 return;
163 }
Dianne Hackbornf0989842013-07-29 18:11:02 -0700164 checkDataOwnerLocked(clip, callingUid);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700165 clearActiveOwnersLocked();
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700166 PerUserClipboard clipboard = getClipboard();
167 clipboard.primaryClip = clip;
Dianne Hackbornf0989842013-07-29 18:11:02 -0700168 final long ident = Binder.clearCallingIdentity();
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700169 final int n = clipboard.primaryClipListeners.beginBroadcast();
Dianne Hackbornf0989842013-07-29 18:11:02 -0700170 try {
171 for (int i = 0; i < n; i++) {
172 try {
173 ListenerInfo li = (ListenerInfo)
174 clipboard.primaryClipListeners.getBroadcastCookie(i);
175 if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid,
176 li.mPackageName) == AppOpsManager.MODE_ALLOWED) {
177 clipboard.primaryClipListeners.getBroadcastItem(i)
178 .dispatchPrimaryClipChanged();
179 }
180 } catch (RemoteException e) {
181 // The RemoteCallbackList will take care of removing
182 // the dead object for us.
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800183 }
Dianne Hackborn9f531192010-08-04 17:48:03 -0700184 }
Dianne Hackbornf0989842013-07-29 18:11:02 -0700185 } finally {
186 clipboard.primaryClipListeners.finishBroadcast();
187 Binder.restoreCallingIdentity(ident);
Dianne Hackborn9f531192010-08-04 17:48:03 -0700188 }
Dianne Hackborn9f531192010-08-04 17:48:03 -0700189 }
190 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700192 public ClipData getPrimaryClip(String pkg) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800194 if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
195 pkg) != AppOpsManager.MODE_ALLOWED) {
196 return null;
197 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700198 addActiveOwnerLocked(Binder.getCallingUid(), pkg);
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700199 return getClipboard().primaryClip;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 }
201 }
202
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800203 public ClipDescription getPrimaryClipDescription(String callingPackage) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700204 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800205 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
206 callingPackage) != AppOpsManager.MODE_ALLOWED) {
207 return null;
208 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700209 PerUserClipboard clipboard = getClipboard();
210 return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700211 }
212 }
213
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800214 public boolean hasPrimaryClip(String callingPackage) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700215 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800216 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
217 callingPackage) != AppOpsManager.MODE_ALLOWED) {
218 return false;
219 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700220 return getClipboard().primaryClip != null;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700221 }
222 }
223
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800224 public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
225 String callingPackage) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700226 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800227 getClipboard().primaryClipListeners.register(listener,
228 new ListenerInfo(Binder.getCallingUid(), callingPackage));
Dianne Hackborn9f531192010-08-04 17:48:03 -0700229 }
230 }
231
232 public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
233 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700234 getClipboard().primaryClipListeners.unregister(listener);
Dianne Hackborn9f531192010-08-04 17:48:03 -0700235 }
236 }
237
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800238 public boolean hasClipboardText(String callingPackage) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 synchronized (this) {
Dianne Hackbornefcc1a22013-02-25 18:02:35 -0800240 if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
241 callingPackage) != AppOpsManager.MODE_ALLOWED) {
242 return false;
243 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700244 PerUserClipboard clipboard = getClipboard();
245 if (clipboard.primaryClip != null) {
246 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700247 return text != null && text.length() > 0;
248 }
249 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 }
251 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700252
253 private final void checkUriOwnerLocked(Uri uri, int uid) {
254 if (!"content".equals(uri.getScheme())) {
255 return;
256 }
257 long ident = Binder.clearCallingIdentity();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700258 try {
259 // This will throw SecurityException for us.
260 mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
261 } catch (RemoteException e) {
262 } finally {
263 Binder.restoreCallingIdentity(ident);
264 }
265 }
266
267 private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
268 if (item.getUri() != null) {
269 checkUriOwnerLocked(item.getUri(), uid);
270 }
271 Intent intent = item.getIntent();
272 if (intent != null && intent.getData() != null) {
273 checkUriOwnerLocked(intent.getData(), uid);
274 }
275 }
276
277 private final void checkDataOwnerLocked(ClipData data, int uid) {
278 final int N = data.getItemCount();
279 for (int i=0; i<N; i++) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800280 checkItemOwnerLocked(data.getItemAt(i), uid);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700281 }
282 }
283
284 private final void grantUriLocked(Uri uri, String pkg) {
285 long ident = Binder.clearCallingIdentity();
286 try {
287 mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
288 Intent.FLAG_GRANT_READ_URI_PERMISSION);
289 } catch (RemoteException e) {
290 } finally {
291 Binder.restoreCallingIdentity(ident);
292 }
293 }
294
295 private final void grantItemLocked(ClipData.Item item, String pkg) {
296 if (item.getUri() != null) {
297 grantUriLocked(item.getUri(), pkg);
298 }
299 Intent intent = item.getIntent();
300 if (intent != null && intent.getData() != null) {
301 grantUriLocked(intent.getData(), pkg);
302 }
303 }
304
305 private final void addActiveOwnerLocked(int uid, String pkg) {
Christopher Tatead9833a2012-09-14 13:34:17 -0700306 final IPackageManager pm = AppGlobals.getPackageManager();
307 final int targetUserHandle = UserHandle.getCallingUserId();
308 final long oldIdentity = Binder.clearCallingIdentity();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700309 try {
Christopher Tatead9833a2012-09-14 13:34:17 -0700310 PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
311 if (pi == null) {
312 throw new IllegalArgumentException("Unknown package " + pkg);
313 }
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700314 if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700315 throw new SecurityException("Calling uid " + uid
316 + " does not own package " + pkg);
317 }
Christopher Tatead9833a2012-09-14 13:34:17 -0700318 } catch (RemoteException e) {
319 // Can't happen; the package manager is in the same process
320 } finally {
321 Binder.restoreCallingIdentity(oldIdentity);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700322 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700323 PerUserClipboard clipboard = getClipboard();
324 if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
325 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700326 for (int i=0; i<N; i++) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700327 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700328 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700329 clipboard.activePermissionOwners.add(pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700330 }
331 }
332
333 private final void revokeUriLocked(Uri uri) {
334 long ident = Binder.clearCallingIdentity();
335 try {
336 mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
337 Intent.FLAG_GRANT_READ_URI_PERMISSION
338 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
339 } catch (RemoteException e) {
340 } finally {
341 Binder.restoreCallingIdentity(ident);
342 }
343 }
344
345 private final void revokeItemLocked(ClipData.Item item) {
346 if (item.getUri() != null) {
347 revokeUriLocked(item.getUri());
348 }
349 Intent intent = item.getIntent();
350 if (intent != null && intent.getData() != null) {
351 revokeUriLocked(intent.getData());
352 }
353 }
354
355 private final void clearActiveOwnersLocked() {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700356 PerUserClipboard clipboard = getClipboard();
357 clipboard.activePermissionOwners.clear();
358 if (clipboard.primaryClip == null) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700359 return;
360 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700361 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700362 for (int i=0; i<N; i++) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700363 revokeItemLocked(clipboard.primaryClip.getItemAt(i));
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700364 }
365 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366}