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