OSDN Git Service

bdd2d2bc7059834e1ee0ca1dbac7da94f8f83edc
[android-x86/frameworks-base.git] / services / core / java / com / android / server / clipboard / ClipboardService.java
1 /*
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
17 package com.android.server.clipboard;
18
19 import android.app.ActivityManagerNative;
20 import android.app.AppGlobals;
21 import android.app.AppOpsManager;
22 import android.app.IActivityManager;
23 import android.app.KeyguardManager;
24 import android.content.BroadcastReceiver;
25 import android.content.ClipData;
26 import android.content.ClipDescription;
27 import android.content.ContentProvider;
28 import android.content.IClipboard;
29 import android.content.IOnPrimaryClipChangedListener;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.pm.IPackageManager;
34 import android.content.pm.PackageInfo;
35 import android.content.pm.PackageManager;
36 import android.content.pm.UserInfo;
37 import android.net.Uri;
38 import android.os.Binder;
39 import android.os.IBinder;
40 import android.os.IUserManager;
41 import android.os.Parcel;
42 import android.os.Process;
43 import android.os.RemoteCallbackList;
44 import android.os.RemoteException;
45 import android.os.ServiceManager;
46 import android.os.UserHandle;
47 import android.os.UserManager;
48 import android.util.Slog;
49 import android.util.SparseArray;
50
51 import java.util.HashSet;
52 import java.util.List;
53
54 /**
55  * Implementation of the clipboard for copy and paste.
56  */
57 public class ClipboardService extends IClipboard.Stub {
58
59     private static final String TAG = "ClipboardService";
60
61     private final Context mContext;
62     private final IActivityManager mAm;
63     private final IUserManager mUm;
64     private final PackageManager mPm;
65     private final AppOpsManager mAppOps;
66     private final IBinder mPermissionOwner;
67
68     private class ListenerInfo {
69         final int mUid;
70         final String mPackageName;
71         ListenerInfo(int uid, String packageName) {
72             mUid = uid;
73             mPackageName = packageName;
74         }
75     }
76
77     private class PerUserClipboard {
78         final int userId;
79
80         final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
81                 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
82
83         ClipData primaryClip;
84
85         final HashSet<String> activePermissionOwners
86                 = new HashSet<String>();
87
88         PerUserClipboard(int userId) {
89             this.userId = userId;
90         }
91     }
92
93     private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>();
94
95     /**
96      * Instantiates the clipboard.
97      */
98     public ClipboardService(Context context) {
99         mContext = context;
100         mAm = ActivityManagerNative.getDefault();
101         mPm = context.getPackageManager();
102         mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
103         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
104         IBinder permOwner = null;
105         try {
106             permOwner = mAm.newUriPermissionOwner("clipboard");
107         } catch (RemoteException e) {
108             Slog.w("clipboard", "AM dead", e);
109         }
110         mPermissionOwner = permOwner;
111
112         // Remove the clipboard if a user is removed
113         IntentFilter userFilter = new IntentFilter();
114         userFilter.addAction(Intent.ACTION_USER_REMOVED);
115         mContext.registerReceiver(new BroadcastReceiver() {
116             @Override
117             public void onReceive(Context context, Intent intent) {
118                 String action = intent.getAction();
119                 if (Intent.ACTION_USER_REMOVED.equals(action)) {
120                     removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
121                 }
122             }
123         }, userFilter);
124     }
125
126     @Override
127     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
128             throws RemoteException {
129         try {
130             return super.onTransact(code, data, reply, flags);
131         } catch (RuntimeException e) {
132             if (!(e instanceof SecurityException)) {
133                 Slog.wtf("clipboard", "Exception: ", e);
134             }
135             throw e;
136         }
137         
138     }
139
140     private PerUserClipboard getClipboard() {
141         return getClipboard(UserHandle.getCallingUserId());
142     }
143
144     private PerUserClipboard getClipboard(int userId) {
145         synchronized (mClipboards) {
146             PerUserClipboard puc = mClipboards.get(userId);
147             if (puc == null) {
148                 puc = new PerUserClipboard(userId);
149                 mClipboards.put(userId, puc);
150             }
151             return puc;
152         }
153     }
154
155     private void removeClipboard(int userId) {
156         synchronized (mClipboards) {
157             mClipboards.remove(userId);
158         }
159     }
160
161     public void setPrimaryClip(ClipData clip, String callingPackage) {
162         synchronized (this) {
163             if (clip != null && clip.getItemCount() <= 0) {
164                 throw new IllegalArgumentException("No items");
165             }
166             final int callingUid = Binder.getCallingUid();
167             if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
168                     callingPackage) != AppOpsManager.MODE_ALLOWED) {
169                 return;
170             }
171             checkDataOwnerLocked(clip, callingUid);
172             final int userId = UserHandle.getUserId(callingUid);
173             PerUserClipboard clipboard = getClipboard(userId);
174             revokeUris(clipboard);
175             setPrimaryClipInternal(clipboard, clip);
176             List<UserInfo> related = getRelatedProfiles(userId);
177             if (related != null) {
178                 int size = related.size();
179                 if (size > 1) { // Related profiles list include the current profile.
180                     boolean canCopy = false;
181                     try {
182                         canCopy = !mUm.getUserRestrictions(userId).getBoolean(
183                                 UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
184                     } catch (RemoteException e) {
185                         Slog.e(TAG, "Remote Exception calling UserManager: " + e);
186                     }
187                     // Copy clip data to related users if allowed. If disallowed, then remove
188                     // primary clip in related users to prevent pasting stale content.
189                     if (!canCopy) {
190                         clip = null;
191                     } else {
192                         // We want to fix the uris of the related user's clip without changing the
193                         // uris of the current user's clip.
194                         // So, copy the ClipData, and then copy all the items, so that nothing
195                         // is shared in memmory.
196                         clip = new ClipData(clip);
197                         for (int i = clip.getItemCount() - 1; i >= 0; i--) {
198                             clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
199                         }
200                         clip.fixUrisLight(userId);
201                     }
202                     for (int i = 0; i < size; i++) {
203                         int id = related.get(i).id;
204                         if (id != userId) {
205                             setPrimaryClipInternal(getClipboard(id), clip);
206                         }
207                     }
208                 }
209             }
210         }
211     }
212
213     List<UserInfo> getRelatedProfiles(int userId) {
214         final List<UserInfo> related;
215         final long origId = Binder.clearCallingIdentity();
216         try {
217             related = mUm.getProfiles(userId, true);
218         } catch (RemoteException e) {
219             Slog.e(TAG, "Remote Exception calling UserManager: " + e);
220             return null;
221         } finally{
222             Binder.restoreCallingIdentity(origId);
223         }
224         return related;
225     }
226
227     void setPrimaryClipInternal(PerUserClipboard clipboard, ClipData clip) {
228         clipboard.activePermissionOwners.clear();
229         if (clip == null && clipboard.primaryClip == null) {
230             return;
231         }
232         clipboard.primaryClip = clip;
233         final long ident = Binder.clearCallingIdentity();
234         final int n = clipboard.primaryClipListeners.beginBroadcast();
235         try {
236             for (int i = 0; i < n; i++) {
237                 try {
238                     ListenerInfo li = (ListenerInfo)
239                             clipboard.primaryClipListeners.getBroadcastCookie(i);
240                     if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid,
241                             li.mPackageName) == AppOpsManager.MODE_ALLOWED) {
242                         clipboard.primaryClipListeners.getBroadcastItem(i)
243                                 .dispatchPrimaryClipChanged();
244                     }
245                 } catch (RemoteException e) {
246                     // The RemoteCallbackList will take care of removing
247                     // the dead object for us.
248                 }
249             }
250         } finally {
251             clipboard.primaryClipListeners.finishBroadcast();
252             Binder.restoreCallingIdentity(ident);
253         }
254     }
255     
256     public ClipData getPrimaryClip(String pkg) {
257         synchronized (this) {
258             if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
259                     pkg) != AppOpsManager.MODE_ALLOWED || isDeviceLocked()) {
260                 return null;
261             }
262             addActiveOwnerLocked(Binder.getCallingUid(), pkg);
263             return getClipboard().primaryClip;
264         }
265     }
266
267     public ClipDescription getPrimaryClipDescription(String callingPackage) {
268         synchronized (this) {
269             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
270                     callingPackage) != AppOpsManager.MODE_ALLOWED || isDeviceLocked()) {
271                 return null;
272             }
273             PerUserClipboard clipboard = getClipboard();
274             return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
275         }
276     }
277
278     public boolean hasPrimaryClip(String callingPackage) {
279         synchronized (this) {
280             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
281                     callingPackage) != AppOpsManager.MODE_ALLOWED || isDeviceLocked()) {
282                 return false;
283             }
284             return getClipboard().primaryClip != null;
285         }
286     }
287
288     public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
289             String callingPackage) {
290         synchronized (this) {
291             getClipboard().primaryClipListeners.register(listener,
292                     new ListenerInfo(Binder.getCallingUid(), callingPackage));
293         }
294     }
295
296     public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
297         synchronized (this) {
298             getClipboard().primaryClipListeners.unregister(listener);
299         }
300     }
301
302     public boolean hasClipboardText(String callingPackage) {
303         synchronized (this) {
304             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
305                     callingPackage) != AppOpsManager.MODE_ALLOWED || isDeviceLocked()) {
306                 return false;
307             }
308             PerUserClipboard clipboard = getClipboard();
309             if (clipboard.primaryClip != null) {
310                 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
311                 return text != null && text.length() > 0;
312             }
313             return false;
314         }
315     }
316
317     private boolean isDeviceLocked() {
318         final KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
319         return keyguardManager != null && keyguardManager.isDeviceLocked();
320     }
321
322     private final void checkUriOwnerLocked(Uri uri, int uid) {
323         if (!"content".equals(uri.getScheme())) {
324             return;
325         }
326         long ident = Binder.clearCallingIdentity();
327         try {
328             // This will throw SecurityException for us.
329             mAm.checkGrantUriPermission(uid, null, ContentProvider.getUriWithoutUserId(uri),
330                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
331                     ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(uid)));
332         } catch (RemoteException e) {
333         } finally {
334             Binder.restoreCallingIdentity(ident);
335         }
336     }
337
338     private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
339         if (item.getUri() != null) {
340             checkUriOwnerLocked(item.getUri(), uid);
341         }
342         Intent intent = item.getIntent();
343         if (intent != null && intent.getData() != null) {
344             checkUriOwnerLocked(intent.getData(), uid);
345         }
346     }
347
348     private final void checkDataOwnerLocked(ClipData data, int uid) {
349         final int N = data.getItemCount();
350         for (int i=0; i<N; i++) {
351             checkItemOwnerLocked(data.getItemAt(i), uid);
352         }
353     }
354
355     private final void grantUriLocked(Uri uri, String pkg, int userId) {
356         long ident = Binder.clearCallingIdentity();
357         try {
358             int sourceUserId = ContentProvider.getUserIdFromUri(uri, userId);
359             uri = ContentProvider.getUriWithoutUserId(uri);
360             mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg,
361                     uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, userId);
362         } catch (RemoteException e) {
363         } finally {
364             Binder.restoreCallingIdentity(ident);
365         }
366     }
367
368     private final void grantItemLocked(ClipData.Item item, String pkg, int userId) {
369         if (item.getUri() != null) {
370             grantUriLocked(item.getUri(), pkg, userId);
371         }
372         Intent intent = item.getIntent();
373         if (intent != null && intent.getData() != null) {
374             grantUriLocked(intent.getData(), pkg, userId);
375         }
376     }
377
378     private final void addActiveOwnerLocked(int uid, String pkg) {
379         final IPackageManager pm = AppGlobals.getPackageManager();
380         final int targetUserHandle = UserHandle.getCallingUserId();
381         final long oldIdentity = Binder.clearCallingIdentity();
382         try {
383             PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
384             if (pi == null) {
385                 throw new IllegalArgumentException("Unknown package " + pkg);
386             }
387             if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
388                 throw new SecurityException("Calling uid " + uid
389                         + " does not own package " + pkg);
390             }
391         } catch (RemoteException e) {
392             // Can't happen; the package manager is in the same process
393         } finally {
394             Binder.restoreCallingIdentity(oldIdentity);
395         }
396         PerUserClipboard clipboard = getClipboard();
397         if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
398             final int N = clipboard.primaryClip.getItemCount();
399             for (int i=0; i<N; i++) {
400                 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg, UserHandle.getUserId(uid));
401             }
402             clipboard.activePermissionOwners.add(pkg);
403         }
404     }
405
406     private final void revokeUriLocked(Uri uri) {
407         int userId = ContentProvider.getUserIdFromUri(uri,
408                 UserHandle.getUserId(Binder.getCallingUid()));
409         long ident = Binder.clearCallingIdentity();
410         try {
411             uri = ContentProvider.getUriWithoutUserId(uri);
412             mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
413                     Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
414                     userId);
415         } catch (RemoteException e) {
416         } finally {
417             Binder.restoreCallingIdentity(ident);
418         }
419     }
420
421     private final void revokeItemLocked(ClipData.Item item) {
422         if (item.getUri() != null) {
423             revokeUriLocked(item.getUri());
424         }
425         Intent intent = item.getIntent();
426         if (intent != null && intent.getData() != null) {
427             revokeUriLocked(intent.getData());
428         }
429     }
430
431     private final void revokeUris(PerUserClipboard clipboard) {
432         if (clipboard.primaryClip == null) {
433             return;
434         }
435         final int N = clipboard.primaryClip.getItemCount();
436         for (int i=0; i<N; i++) {
437             revokeItemLocked(clipboard.primaryClip.getItemAt(i));
438         }
439     }
440 }