blob: 062ab74ff406de64d36f7f7af76f078e1bbcbc98 [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;
Dianne Hackborn1040dc42010-08-26 22:11:06 -070021import android.content.ClipData;
22import android.content.ClipDescription;
Dianne Hackborn9f531192010-08-04 17:48:03 -070023import android.content.IClipboard;
24import android.content.IOnPrimaryClipChangedListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.Context;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070026import android.content.Intent;
27import android.content.pm.PackageInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.PackageManager.NameNotFoundException;
30import android.net.Uri;
31import android.os.Binder;
32import android.os.IBinder;
33import android.os.Parcel;
34import android.os.Process;
Dianne Hackborn9f531192010-08-04 17:48:03 -070035import android.os.RemoteCallbackList;
36import android.os.RemoteException;
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070037import android.util.Pair;
38import android.util.Slog;
39
40import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041
42/**
43 * Implementation of the clipboard for copy and paste.
44 */
45public class ClipboardService extends IClipboard.Stub {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070046 private final Context mContext;
47 private final IActivityManager mAm;
48 private final PackageManager mPm;
49 private final IBinder mPermissionOwner;
50
Dianne Hackborn9f531192010-08-04 17:48:03 -070051 private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners
52 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070054 private ClipData mPrimaryClip;
55
56 private final HashSet<String> mActivePermissionOwners
57 = new HashSet<String>();
58
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059 /**
60 * Instantiates the clipboard.
61 */
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070062 public ClipboardService(Context context) {
63 mContext = context;
64 mAm = ActivityManagerNative.getDefault();
65 mPm = context.getPackageManager();
66 IBinder permOwner = null;
67 try {
68 permOwner = mAm.newUriPermissionOwner("clipboard");
69 } catch (RemoteException e) {
70 Slog.w("clipboard", "AM dead", e);
71 }
72 mPermissionOwner = permOwner;
73 }
74
75 @Override
76 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
77 throws RemoteException {
78 try {
79 return super.onTransact(code, data, reply, flags);
80 } catch (RuntimeException e) {
81 Slog.w("clipboard", "Exception: ", e);
82 throw e;
83 }
84
85 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086
Dianne Hackborn1040dc42010-08-26 22:11:06 -070087 public void setPrimaryClip(ClipData clip) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 synchronized (this) {
Dianne Hackborn9f531192010-08-04 17:48:03 -070089 if (clip != null && clip.getItemCount() <= 0) {
90 throw new IllegalArgumentException("No items");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -070092 checkDataOwnerLocked(clip, Binder.getCallingUid());
93 clearActiveOwnersLocked();
Dianne Hackborn9f531192010-08-04 17:48:03 -070094 mPrimaryClip = clip;
95 final int n = mPrimaryClipListeners.beginBroadcast();
96 for (int i = 0; i < n; i++) {
97 try {
98 mPrimaryClipListeners.getBroadcastItem(i).dispatchPrimaryClipChanged();
99 } catch (RemoteException e) {
100
101 // The RemoteCallbackList will take care of removing
102 // the dead object for us.
103 }
104 }
105 mPrimaryClipListeners.finishBroadcast();
106 }
107 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700109 public ClipData getPrimaryClip(String pkg) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 synchronized (this) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700111 addActiveOwnerLocked(Binder.getCallingUid(), pkg);
Dianne Hackborn9f531192010-08-04 17:48:03 -0700112 return mPrimaryClip;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 }
114 }
115
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700116 public ClipDescription getPrimaryClipDescription() {
117 synchronized (this) {
Dianne Hackborn3b99ede2010-12-22 17:37:34 -0800118 return mPrimaryClip != null ? mPrimaryClip.getDescription() : null;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700119 }
120 }
121
Dianne Hackborn9f531192010-08-04 17:48:03 -0700122 public boolean hasPrimaryClip() {
123 synchronized (this) {
124 return mPrimaryClip != null;
125 }
126 }
127
128 public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
129 synchronized (this) {
130 mPrimaryClipListeners.register(listener);
131 }
132 }
133
134 public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
135 synchronized (this) {
136 mPrimaryClipListeners.unregister(listener);
137 }
138 }
139
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 public boolean hasClipboardText() {
141 synchronized (this) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700142 if (mPrimaryClip != null) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800143 CharSequence text = mPrimaryClip.getItemAt(0).getText();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700144 return text != null && text.length() > 0;
145 }
146 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 }
148 }
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700149
150 private final void checkUriOwnerLocked(Uri uri, int uid) {
151 if (!"content".equals(uri.getScheme())) {
152 return;
153 }
154 long ident = Binder.clearCallingIdentity();
155 boolean allowed = false;
156 try {
157 // This will throw SecurityException for us.
158 mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
159 } catch (RemoteException e) {
160 } finally {
161 Binder.restoreCallingIdentity(ident);
162 }
163 }
164
165 private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
166 if (item.getUri() != null) {
167 checkUriOwnerLocked(item.getUri(), uid);
168 }
169 Intent intent = item.getIntent();
170 if (intent != null && intent.getData() != null) {
171 checkUriOwnerLocked(intent.getData(), uid);
172 }
173 }
174
175 private final void checkDataOwnerLocked(ClipData data, int uid) {
176 final int N = data.getItemCount();
177 for (int i=0; i<N; i++) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800178 checkItemOwnerLocked(data.getItemAt(i), uid);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700179 }
180 }
181
182 private final void grantUriLocked(Uri uri, String pkg) {
183 long ident = Binder.clearCallingIdentity();
184 try {
185 mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
186 Intent.FLAG_GRANT_READ_URI_PERMISSION);
187 } catch (RemoteException e) {
188 } finally {
189 Binder.restoreCallingIdentity(ident);
190 }
191 }
192
193 private final void grantItemLocked(ClipData.Item item, String pkg) {
194 if (item.getUri() != null) {
195 grantUriLocked(item.getUri(), pkg);
196 }
197 Intent intent = item.getIntent();
198 if (intent != null && intent.getData() != null) {
199 grantUriLocked(intent.getData(), pkg);
200 }
201 }
202
203 private final void addActiveOwnerLocked(int uid, String pkg) {
204 PackageInfo pi;
205 try {
206 pi = mPm.getPackageInfo(pkg, 0);
207 if (pi.applicationInfo.uid != uid) {
208 throw new SecurityException("Calling uid " + uid
209 + " does not own package " + pkg);
210 }
211 } catch (NameNotFoundException e) {
212 throw new IllegalArgumentException("Unknown package " + pkg, e);
213 }
Dianne Hackborn3b99ede2010-12-22 17:37:34 -0800214 if (mPrimaryClip != null && !mActivePermissionOwners.contains(pkg)) {
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700215 final int N = mPrimaryClip.getItemCount();
216 for (int i=0; i<N; i++) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800217 grantItemLocked(mPrimaryClip.getItemAt(i), pkg);
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700218 }
219 mActivePermissionOwners.add(pkg);
220 }
221 }
222
223 private final void revokeUriLocked(Uri uri) {
224 long ident = Binder.clearCallingIdentity();
225 try {
226 mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
227 Intent.FLAG_GRANT_READ_URI_PERMISSION
228 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
229 } catch (RemoteException e) {
230 } finally {
231 Binder.restoreCallingIdentity(ident);
232 }
233 }
234
235 private final void revokeItemLocked(ClipData.Item item) {
236 if (item.getUri() != null) {
237 revokeUriLocked(item.getUri());
238 }
239 Intent intent = item.getIntent();
240 if (intent != null && intent.getData() != null) {
241 revokeUriLocked(intent.getData());
242 }
243 }
244
245 private final void clearActiveOwnersLocked() {
246 mActivePermissionOwners.clear();
247 if (mPrimaryClip == null) {
248 return;
249 }
250 final int N = mPrimaryClip.getItemCount();
251 for (int i=0; i<N; i++) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800252 revokeItemLocked(mPrimaryClip.getItemAt(i));
Dianne Hackborn90f4aaf2010-09-27 14:58:44 -0700253 }
254 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255}