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