blob: baf33a9c23d5939bf72ba977475ca6b1268fcd6f [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;
20import android.app.IActivityManager;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070021import android.content.BroadcastReceiver;
Dianne Hackborn1040dc42010-08-26 22:11:06 -070022import android.content.ClipData;
23import android.content.ClipDescription;
Dianne Hackborn9f531192010-08-04 17:48:03 -070024import android.content.IClipboard;
25import android.content.IOnPrimaryClipChangedListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.content.Context;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070027import android.content.Intent;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070028import android.content.IntentFilter;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070029import android.content.pm.PackageInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.PackageManager.NameNotFoundException;
32import android.net.Uri;
33import android.os.Binder;
34import android.os.IBinder;
35import android.os.Parcel;
36import android.os.Process;
Dianne Hackborn9f531192010-08-04 17:48:03 -070037import android.os.RemoteCallbackList;
38import android.os.RemoteException;
Dianne Hackbornf02b60a2012-08-16 10:48:27 -070039import android.os.UserHandle;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070040import android.util.Pair;
41import android.util.Slog;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070042import android.util.SparseArray;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070043
44import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045
46/**
47 * Implementation of the clipboard for copy and paste.
48 */
49public class ClipboardService extends IClipboard.Stub {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070050
51 private static final String TAG = "ClipboardService";
52
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070053 private final Context mContext;
54 private final IActivityManager mAm;
55 private final PackageManager mPm;
56 private final IBinder mPermissionOwner;
57
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070058 private class PerUserClipboard {
59 final int userId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070061 final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
62 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070063
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070064 ClipData primaryClip;
65
66 final HashSet<String> activePermissionOwners
67 = new HashSet<String>();
68
69 PerUserClipboard(int userId) {
70 this.userId = userId;
71 }
72 }
73
74 private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070075
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076 /**
77 * Instantiates the clipboard.
78 */
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070079 public ClipboardService(Context context) {
80 mContext = context;
81 mAm = ActivityManagerNative.getDefault();
82 mPm = context.getPackageManager();
83 IBinder permOwner = null;
84 try {
85 permOwner = mAm.newUriPermissionOwner("clipboard");
86 } catch (RemoteException e) {
87 Slog.w("clipboard", "AM dead", e);
88 }
89 mPermissionOwner = permOwner;
Amith Yamasanie9e26cc2012-04-20 19:01:50 -070090
91 // Remove the clipboard if a user is removed
92 IntentFilter userFilter = new IntentFilter();
93 userFilter.addAction(Intent.ACTION_USER_REMOVED);
94 mContext.registerReceiver(new BroadcastReceiver() {
95 @Override
96 public void onReceive(Context context, Intent intent) {
97 String action = intent.getAction();
98 if (Intent.ACTION_USER_REMOVED.equals(action)) {
99 removeClipboard(intent.getIntExtra(Intent.EXTRA_USERID, 0));
100 }
101 }
102 }, userFilter);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700103 }
104
105 @Override
106 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
107 throws RemoteException {
108 try {
109 return super.onTransact(code, data, reply, flags);
110 } catch (RuntimeException e) {
111 Slog.w("clipboard", "Exception: ", e);
112 throw e;
113 }
114
115 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700117 private PerUserClipboard getClipboard() {
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700118 return getClipboard(UserHandle.getCallingUserId());
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700119 }
120
121 private PerUserClipboard getClipboard(int userId) {
122 synchronized (mClipboards) {
123 Slog.i(TAG, "Got clipboard for user=" + userId);
124 PerUserClipboard puc = mClipboards.get(userId);
125 if (puc == null) {
126 puc = new PerUserClipboard(userId);
127 mClipboards.put(userId, puc);
128 }
129 return puc;
130 }
131 }
132
133 private void removeClipboard(int userId) {
134 synchronized (mClipboards) {
135 mClipboards.remove(userId);
136 }
137 }
138
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700139 public void setPrimaryClip(ClipData clip) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 synchronized (this) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700141 if (clip != null && clip.getItemCount() <= 0) {
142 throw new IllegalArgumentException("No items");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700144 checkDataOwnerLocked(clip, Binder.getCallingUid());
145 clearActiveOwnersLocked();
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700146 PerUserClipboard clipboard = getClipboard();
147 clipboard.primaryClip = clip;
148 final int n = clipboard.primaryClipListeners.beginBroadcast();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700149 for (int i = 0; i < n; i++) {
150 try {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700151 clipboard.primaryClipListeners.getBroadcastItem(i).dispatchPrimaryClipChanged();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700152 } catch (RemoteException e) {
153
154 // The RemoteCallbackList will take care of removing
155 // the dead object for us.
156 }
157 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700158 clipboard.primaryClipListeners.finishBroadcast();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700159 }
160 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700162 public ClipData getPrimaryClip(String pkg) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800163 synchronized (this) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700164 addActiveOwnerLocked(Binder.getCallingUid(), pkg);
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700165 return getClipboard().primaryClip;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 }
167 }
168
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700169 public ClipDescription getPrimaryClipDescription() {
170 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700171 PerUserClipboard clipboard = getClipboard();
172 return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700173 }
174 }
175
Dianne Hackborn9f531192010-08-04 17:48:03 -0700176 public boolean hasPrimaryClip() {
177 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700178 return getClipboard().primaryClip != null;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700179 }
180 }
181
182 public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
183 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700184 getClipboard().primaryClipListeners.register(listener);
Dianne Hackborn9f531192010-08-04 17:48:03 -0700185 }
186 }
187
188 public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
189 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700190 getClipboard().primaryClipListeners.unregister(listener);
Dianne Hackborn9f531192010-08-04 17:48:03 -0700191 }
192 }
193
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194 public boolean hasClipboardText() {
195 synchronized (this) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700196 PerUserClipboard clipboard = getClipboard();
197 if (clipboard.primaryClip != null) {
198 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700199 return text != null && text.length() > 0;
200 }
201 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800202 }
203 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700204
205 private final void checkUriOwnerLocked(Uri uri, int uid) {
206 if (!"content".equals(uri.getScheme())) {
207 return;
208 }
209 long ident = Binder.clearCallingIdentity();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700210 try {
211 // This will throw SecurityException for us.
212 mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
213 } catch (RemoteException e) {
214 } finally {
215 Binder.restoreCallingIdentity(ident);
216 }
217 }
218
219 private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
220 if (item.getUri() != null) {
221 checkUriOwnerLocked(item.getUri(), uid);
222 }
223 Intent intent = item.getIntent();
224 if (intent != null && intent.getData() != null) {
225 checkUriOwnerLocked(intent.getData(), uid);
226 }
227 }
228
229 private final void checkDataOwnerLocked(ClipData data, int uid) {
230 final int N = data.getItemCount();
231 for (int i=0; i<N; i++) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800232 checkItemOwnerLocked(data.getItemAt(i), uid);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700233 }
234 }
235
236 private final void grantUriLocked(Uri uri, String pkg) {
237 long ident = Binder.clearCallingIdentity();
238 try {
239 mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
240 Intent.FLAG_GRANT_READ_URI_PERMISSION);
241 } catch (RemoteException e) {
242 } finally {
243 Binder.restoreCallingIdentity(ident);
244 }
245 }
246
247 private final void grantItemLocked(ClipData.Item item, String pkg) {
248 if (item.getUri() != null) {
249 grantUriLocked(item.getUri(), pkg);
250 }
251 Intent intent = item.getIntent();
252 if (intent != null && intent.getData() != null) {
253 grantUriLocked(intent.getData(), pkg);
254 }
255 }
256
257 private final void addActiveOwnerLocked(int uid, String pkg) {
258 PackageInfo pi;
259 try {
260 pi = mPm.getPackageInfo(pkg, 0);
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700261 if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700262 throw new SecurityException("Calling uid " + uid
263 + " does not own package " + pkg);
264 }
265 } catch (NameNotFoundException e) {
266 throw new IllegalArgumentException("Unknown package " + pkg, e);
267 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700268 PerUserClipboard clipboard = getClipboard();
269 if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
270 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700271 for (int i=0; i<N; i++) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700272 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700273 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700274 clipboard.activePermissionOwners.add(pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700275 }
276 }
277
278 private final void revokeUriLocked(Uri uri) {
279 long ident = Binder.clearCallingIdentity();
280 try {
281 mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
282 Intent.FLAG_GRANT_READ_URI_PERMISSION
283 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
284 } catch (RemoteException e) {
285 } finally {
286 Binder.restoreCallingIdentity(ident);
287 }
288 }
289
290 private final void revokeItemLocked(ClipData.Item item) {
291 if (item.getUri() != null) {
292 revokeUriLocked(item.getUri());
293 }
294 Intent intent = item.getIntent();
295 if (intent != null && intent.getData() != null) {
296 revokeUriLocked(intent.getData());
297 }
298 }
299
300 private final void clearActiveOwnersLocked() {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700301 PerUserClipboard clipboard = getClipboard();
302 clipboard.activePermissionOwners.clear();
303 if (clipboard.primaryClip == null) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700304 return;
305 }
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700306 final int N = clipboard.primaryClip.getItemCount();
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700307 for (int i=0; i<N; i++) {
Amith Yamasanie9e26cc2012-04-20 19:01:50 -0700308 revokeItemLocked(clipboard.primaryClip.getItemAt(i));
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700309 }
310 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311}