• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
18 
19 import android.app.ActivityManagerNative;
20 import android.app.IActivityManager;
21 import android.content.ClipData;
22 import android.content.ClipDescription;
23 import android.content.IClipboard;
24 import android.content.IOnPrimaryClipChangedListener;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.net.Uri;
31 import android.os.Binder;
32 import android.os.IBinder;
33 import android.os.Parcel;
34 import android.os.Process;
35 import android.os.RemoteCallbackList;
36 import android.os.RemoteException;
37 import android.util.Pair;
38 import android.util.Slog;
39 
40 import java.util.HashSet;
41 
42 /**
43  * Implementation of the clipboard for copy and paste.
44  */
45 public class ClipboardService extends IClipboard.Stub {
46     private final Context mContext;
47     private final IActivityManager mAm;
48     private final PackageManager mPm;
49     private final IBinder mPermissionOwner;
50 
51     private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners
52             = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
53 
54     private ClipData mPrimaryClip;
55 
56     private final HashSet<String> mActivePermissionOwners
57             = new HashSet<String>();
58 
59     /**
60      * Instantiates the clipboard.
61      */
ClipboardService(Context context)62     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
onTransact(int code, Parcel data, Parcel reply, int flags)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     }
86 
setPrimaryClip(ClipData clip)87     public void setPrimaryClip(ClipData clip) {
88         synchronized (this) {
89             if (clip != null && clip.getItemCount() <= 0) {
90                 throw new IllegalArgumentException("No items");
91             }
92             checkDataOwnerLocked(clip, Binder.getCallingUid());
93             clearActiveOwnersLocked();
94             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     }
108 
getPrimaryClip(String pkg)109     public ClipData getPrimaryClip(String pkg) {
110         synchronized (this) {
111             addActiveOwnerLocked(Binder.getCallingUid(), pkg);
112             return mPrimaryClip;
113         }
114     }
115 
getPrimaryClipDescription()116     public ClipDescription getPrimaryClipDescription() {
117         synchronized (this) {
118             return mPrimaryClip != null ? mPrimaryClip.getDescription() : null;
119         }
120     }
121 
hasPrimaryClip()122     public boolean hasPrimaryClip() {
123         synchronized (this) {
124             return mPrimaryClip != null;
125         }
126     }
127 
addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener)128     public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
129         synchronized (this) {
130             mPrimaryClipListeners.register(listener);
131         }
132     }
133 
removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener)134     public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
135         synchronized (this) {
136             mPrimaryClipListeners.unregister(listener);
137         }
138     }
139 
hasClipboardText()140     public boolean hasClipboardText() {
141         synchronized (this) {
142             if (mPrimaryClip != null) {
143                 CharSequence text = mPrimaryClip.getItemAt(0).getText();
144                 return text != null && text.length() > 0;
145             }
146             return false;
147         }
148     }
149 
checkUriOwnerLocked(Uri uri, int uid)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 
checkItemOwnerLocked(ClipData.Item item, int uid)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 
checkDataOwnerLocked(ClipData data, int uid)175     private final void checkDataOwnerLocked(ClipData data, int uid) {
176         final int N = data.getItemCount();
177         for (int i=0; i<N; i++) {
178             checkItemOwnerLocked(data.getItemAt(i), uid);
179         }
180     }
181 
grantUriLocked(Uri uri, String pkg)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 
grantItemLocked(ClipData.Item item, String pkg)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 
addActiveOwnerLocked(int uid, String pkg)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         }
214         if (mPrimaryClip != null && !mActivePermissionOwners.contains(pkg)) {
215             final int N = mPrimaryClip.getItemCount();
216             for (int i=0; i<N; i++) {
217                 grantItemLocked(mPrimaryClip.getItemAt(i), pkg);
218             }
219             mActivePermissionOwners.add(pkg);
220         }
221     }
222 
revokeUriLocked(Uri uri)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 
revokeItemLocked(ClipData.Item item)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 
clearActiveOwnersLocked()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++) {
252             revokeItemLocked(mPrimaryClip.getItemAt(i));
253         }
254     }
255 }
256