blob: 74ec6e2f84b1dae0c9c9b2f1bcc678939508b6e7 [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 Hackborn90f4aaf2010-09-27 14:58:44 -070021import android.app.IActivityManager;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070022import android.content.BroadcastReceiver;
Dianne Hackborn1040dc42010-08-26 22:11:06 -070023import android.content.ClipData;
24import android.content.ClipDescription;
Dianne Hackborn9f531192010-08-04 17:48:03 -070025import android.content.IClipboard;
26import android.content.IOnPrimaryClipChangedListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.content.Context;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070028import android.content.Intent;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070029import android.content.IntentFilter;
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;
33import android.content.pm.PackageManager.NameNotFoundException;
34import 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.Pair;
43import android.util.Slog;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070044import android.util.SparseArray;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070045
46import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047
48/**
49 * Implementation of the clipboard for copy and paste.
50 */
51public class ClipboardService extends IClipboard.Stub {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070052
53 private static final String TAG = "ClipboardService";
54
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070055 private final Context mContext;
56 private final IActivityManager mAm;
57 private final PackageManager mPm;
58 private final IBinder mPermissionOwner;
59
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070060 private class PerUserClipboard {
61 final int userId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070063 final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
64 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070065
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070066 ClipData primaryClip;
67
68 final HashSet<String> activePermissionOwners
69 = new HashSet<String>();
70
71 PerUserClipboard(int userId) {
72 this.userId = userId;
73 }
74 }
75
76 private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070077
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078 /**
79 * Instantiates the clipboard.
80 */
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070081 public ClipboardService(Context context) {
82 mContext = context;
83 mAm = ActivityManagerNative.getDefault();
84 mPm = context.getPackageManager();
85 IBinder permOwner = null;
86 try {
87 permOwner = mAm.newUriPermissionOwner("clipboard");
88 } catch (RemoteException e) {
89 Slog.w("clipboard", "AM dead", e);
90 }
91 mPermissionOwner = permOwner;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070092
93 // Remove the clipboard if a user is removed
94 IntentFilter userFilter = new IntentFilter();
95 userFilter.addAction(Intent.ACTION_USER_REMOVED);
96 mContext.registerReceiver(new BroadcastReceiver() {
97 @Override
98 public void onReceive(Context context, Intent intent) {
99 String action = intent.getAction();
100 if (Intent.ACTION_USER_REMOVED.equals(action)) {
Amith Yamasani2a003292012-08-14 18:25:45 -0700101 removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700102 }
103 }
104 }, userFilter);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700105 }
106
107 @Override
108 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
109 throws RemoteException {
110 try {
111 return super.onTransact(code, data, reply, flags);
112 } catch (RuntimeException e) {
113 Slog.w("clipboard", "Exception: ", e);
114 throw e;
115 }
116
117 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700119 private PerUserClipboard getClipboard() {
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700120 return getClipboard(UserHandle.getCallingUserId());
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700121 }
122
123 private PerUserClipboard getClipboard(int userId) {
124 synchronized (mClipboards) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700125 PerUserClipboard puc = mClipboards.get(userId);
126 if (puc == null) {
127 puc = new PerUserClipboard(userId);
128 mClipboards.put(userId, puc);
129 }
130 return puc;
131 }
132 }
133
134 private void removeClipboard(int userId) {
135 synchronized (mClipboards) {
136 mClipboards.remove(userId);
137 }
138 }
139
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700140 public void setPrimaryClip(ClipData clip) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141 synchronized (this) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700142 if (clip != null && clip.getItemCount() <= 0) {
143 throw new IllegalArgumentException("No items");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700145 checkDataOwnerLocked(clip, Binder.getCallingUid());
146 clearActiveOwnersLocked();
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700147 PerUserClipboard clipboard = getClipboard();
148 clipboard.primaryClip = clip;
149 final int n = clipboard.primaryClipListeners.beginBroadcast();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700150 for (int i = 0; i < n; i++) {
151 try {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700152 clipboard.primaryClipListeners.getBroadcastItem(i).dispatchPrimaryClipChanged();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700153 } catch (RemoteException e) {
154
155 // The RemoteCallbackList will take care of removing
156 // the dead object for us.
157 }
158 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700159 clipboard.primaryClipListeners.finishBroadcast();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700160 }
161 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700163 public ClipData getPrimaryClip(String pkg) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 synchronized (this) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700165 addActiveOwnerLocked(Binder.getCallingUid(), pkg);
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700166 return getClipboard().primaryClip;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 }
168 }
169
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700170 public ClipDescription getPrimaryClipDescription() {
171 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700172 PerUserClipboard clipboard = getClipboard();
173 return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700174 }
175 }
176
Dianne Hackborn9f531192010-08-04 17:48:03 -0700177 public boolean hasPrimaryClip() {
178 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700179 return getClipboard().primaryClip != null;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700180 }
181 }
182
183 public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
184 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700185 getClipboard().primaryClipListeners.register(listener);
Dianne Hackborn9f531192010-08-04 17:48:03 -0700186 }
187 }
188
189 public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
190 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700191 getClipboard().primaryClipListeners.unregister(listener);
Dianne Hackborn9f531192010-08-04 17:48:03 -0700192 }
193 }
194
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195 public boolean hasClipboardText() {
196 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700197 PerUserClipboard clipboard = getClipboard();
198 if (clipboard.primaryClip != null) {
199 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700200 return text != null && text.length() > 0;
201 }
202 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 }
204 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700205
206 private final void checkUriOwnerLocked(Uri uri, int uid) {
207 if (!"content".equals(uri.getScheme())) {
208 return;
209 }
210 long ident = Binder.clearCallingIdentity();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700211 try {
212 // This will throw SecurityException for us.
213 mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
214 } catch (RemoteException e) {
215 } finally {
216 Binder.restoreCallingIdentity(ident);
217 }
218 }
219
220 private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
221 if (item.getUri() != null) {
222 checkUriOwnerLocked(item.getUri(), uid);
223 }
224 Intent intent = item.getIntent();
225 if (intent != null && intent.getData() != null) {
226 checkUriOwnerLocked(intent.getData(), uid);
227 }
228 }
229
230 private final void checkDataOwnerLocked(ClipData data, int uid) {
231 final int N = data.getItemCount();
232 for (int i=0; i<N; i++) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800233 checkItemOwnerLocked(data.getItemAt(i), uid);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700234 }
235 }
236
237 private final void grantUriLocked(Uri uri, String pkg) {
238 long ident = Binder.clearCallingIdentity();
239 try {
240 mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
241 Intent.FLAG_GRANT_READ_URI_PERMISSION);
242 } catch (RemoteException e) {
243 } finally {
244 Binder.restoreCallingIdentity(ident);
245 }
246 }
247
248 private final void grantItemLocked(ClipData.Item item, String pkg) {
249 if (item.getUri() != null) {
250 grantUriLocked(item.getUri(), pkg);
251 }
252 Intent intent = item.getIntent();
253 if (intent != null && intent.getData() != null) {
254 grantUriLocked(intent.getData(), pkg);
255 }
256 }
257
258 private final void addActiveOwnerLocked(int uid, String pkg) {
Christopher Tatead9833a2012-09-14 13:34:17 -0700259 final IPackageManager pm = AppGlobals.getPackageManager();
260 final int targetUserHandle = UserHandle.getCallingUserId();
261 final long oldIdentity = Binder.clearCallingIdentity();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700262 try {
Christopher Tatead9833a2012-09-14 13:34:17 -0700263 PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
264 if (pi == null) {
265 throw new IllegalArgumentException("Unknown package " + pkg);
266 }
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700267 if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700268 throw new SecurityException("Calling uid " + uid
269 + " does not own package " + pkg);
270 }
Christopher Tatead9833a2012-09-14 13:34:17 -0700271 } catch (RemoteException e) {
272 // Can't happen; the package manager is in the same process
273 } finally {
274 Binder.restoreCallingIdentity(oldIdentity);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700275 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700276 PerUserClipboard clipboard = getClipboard();
277 if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
278 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700279 for (int i=0; i<N; i++) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700280 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700281 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700282 clipboard.activePermissionOwners.add(pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700283 }
284 }
285
286 private final void revokeUriLocked(Uri uri) {
287 long ident = Binder.clearCallingIdentity();
288 try {
289 mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
290 Intent.FLAG_GRANT_READ_URI_PERMISSION
291 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
292 } catch (RemoteException e) {
293 } finally {
294 Binder.restoreCallingIdentity(ident);
295 }
296 }
297
298 private final void revokeItemLocked(ClipData.Item item) {
299 if (item.getUri() != null) {
300 revokeUriLocked(item.getUri());
301 }
302 Intent intent = item.getIntent();
303 if (intent != null && intent.getData() != null) {
304 revokeUriLocked(intent.getData());
305 }
306 }
307
308 private final void clearActiveOwnersLocked() {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700309 PerUserClipboard clipboard = getClipboard();
310 clipboard.activePermissionOwners.clear();
311 if (clipboard.primaryClip == null) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700312 return;
313 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700314 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700315 for (int i=0; i<N; i++) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700316 revokeItemLocked(clipboard.primaryClip.getItemAt(i));
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700317 }
318 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319}