OSDN Git Service

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