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.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.IClipboard;
     27 import android.content.IOnPrimaryClipChangedListener;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.content.IntentFilter;
     31 import android.content.pm.IPackageManager;
     32 import android.content.pm.PackageInfo;
     33 import android.content.pm.PackageManager;
     34 import android.content.pm.PackageManager.NameNotFoundException;
     35 import android.net.Uri;
     36 import android.os.Binder;
     37 import android.os.IBinder;
     38 import android.os.Parcel;
     39 import android.os.Process;
     40 import android.os.RemoteCallbackList;
     41 import android.os.RemoteException;
     42 import android.os.UserHandle;
     43 import android.util.Pair;
     44 import android.util.Slog;
     45 import android.util.SparseArray;
     46 
     47 import java.util.HashSet;
     48 
     49 /**
     50  * Implementation of the clipboard for copy and paste.
     51  */
     52 public 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             Slog.w("clipboard", "Exception: ", e);
    126             throw e;
    127         }
    128 
    129     }
    130 
    131     private PerUserClipboard getClipboard() {
    132         return getClipboard(UserHandle.getCallingUserId());
    133     }
    134 
    135     private PerUserClipboard getClipboard(int userId) {
    136         synchronized (mClipboards) {
    137             PerUserClipboard puc = mClipboards.get(userId);
    138             if (puc == null) {
    139                 puc = new PerUserClipboard(userId);
    140                 mClipboards.put(userId, puc);
    141             }
    142             return puc;
    143         }
    144     }
    145 
    146     private void removeClipboard(int userId) {
    147         synchronized (mClipboards) {
    148             mClipboards.remove(userId);
    149         }
    150     }
    151 
    152     public void setPrimaryClip(ClipData clip, String callingPackage) {
    153         synchronized (this) {
    154             if (clip != null && clip.getItemCount() <= 0) {
    155                 throw new IllegalArgumentException("No items");
    156             }
    157             if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, Binder.getCallingUid(),
    158                     callingPackage) != AppOpsManager.MODE_ALLOWED) {
    159                 return;
    160             }
    161             checkDataOwnerLocked(clip, Binder.getCallingUid());
    162             clearActiveOwnersLocked();
    163             PerUserClipboard clipboard = getClipboard();
    164             clipboard.primaryClip = clip;
    165             final int n = clipboard.primaryClipListeners.beginBroadcast();
    166             for (int i = 0; i < n; i++) {
    167                 try {
    168                     ListenerInfo li = (ListenerInfo)
    169                             clipboard.primaryClipListeners.getBroadcastCookie(i);
    170                     if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid,
    171                             li.mPackageName) == AppOpsManager.MODE_ALLOWED) {
    172                         clipboard.primaryClipListeners.getBroadcastItem(i)
    173                                 .dispatchPrimaryClipChanged();
    174                     }
    175                 } catch (RemoteException e) {
    176 
    177                     // The RemoteCallbackList will take care of removing
    178                     // the dead object for us.
    179                 }
    180             }
    181             clipboard.primaryClipListeners.finishBroadcast();
    182         }
    183     }
    184 
    185     public ClipData getPrimaryClip(String pkg) {
    186         synchronized (this) {
    187             if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
    188                     pkg) != AppOpsManager.MODE_ALLOWED) {
    189                 return null;
    190             }
    191             addActiveOwnerLocked(Binder.getCallingUid(), pkg);
    192             return getClipboard().primaryClip;
    193         }
    194     }
    195 
    196     public ClipDescription getPrimaryClipDescription(String callingPackage) {
    197         synchronized (this) {
    198             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
    199                     callingPackage) != AppOpsManager.MODE_ALLOWED) {
    200                 return null;
    201             }
    202             PerUserClipboard clipboard = getClipboard();
    203             return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
    204         }
    205     }
    206 
    207     public boolean hasPrimaryClip(String callingPackage) {
    208         synchronized (this) {
    209             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
    210                     callingPackage) != AppOpsManager.MODE_ALLOWED) {
    211                 return false;
    212             }
    213             return getClipboard().primaryClip != null;
    214         }
    215     }
    216 
    217     public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
    218             String callingPackage) {
    219         synchronized (this) {
    220             getClipboard().primaryClipListeners.register(listener,
    221                     new ListenerInfo(Binder.getCallingUid(), callingPackage));
    222         }
    223     }
    224 
    225     public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
    226         synchronized (this) {
    227             getClipboard().primaryClipListeners.unregister(listener);
    228         }
    229     }
    230 
    231     public boolean hasClipboardText(String callingPackage) {
    232         synchronized (this) {
    233             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
    234                     callingPackage) != AppOpsManager.MODE_ALLOWED) {
    235                 return false;
    236             }
    237             PerUserClipboard clipboard = getClipboard();
    238             if (clipboard.primaryClip != null) {
    239                 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
    240                 return text != null && text.length() > 0;
    241             }
    242             return false;
    243         }
    244     }
    245 
    246     private final void checkUriOwnerLocked(Uri uri, int uid) {
    247         if (!"content".equals(uri.getScheme())) {
    248             return;
    249         }
    250         long ident = Binder.clearCallingIdentity();
    251         try {
    252             // This will throw SecurityException for us.
    253             mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    254         } catch (RemoteException e) {
    255         } finally {
    256             Binder.restoreCallingIdentity(ident);
    257         }
    258     }
    259 
    260     private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
    261         if (item.getUri() != null) {
    262             checkUriOwnerLocked(item.getUri(), uid);
    263         }
    264         Intent intent = item.getIntent();
    265         if (intent != null && intent.getData() != null) {
    266             checkUriOwnerLocked(intent.getData(), uid);
    267         }
    268     }
    269 
    270     private final void checkDataOwnerLocked(ClipData data, int uid) {
    271         final int N = data.getItemCount();
    272         for (int i=0; i<N; i++) {
    273             checkItemOwnerLocked(data.getItemAt(i), uid);
    274         }
    275     }
    276 
    277     private final void grantUriLocked(Uri uri, String pkg) {
    278         long ident = Binder.clearCallingIdentity();
    279         try {
    280             mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
    281                     Intent.FLAG_GRANT_READ_URI_PERMISSION);
    282         } catch (RemoteException e) {
    283         } finally {
    284             Binder.restoreCallingIdentity(ident);
    285         }
    286     }
    287 
    288     private final void grantItemLocked(ClipData.Item item, String pkg) {
    289         if (item.getUri() != null) {
    290             grantUriLocked(item.getUri(), pkg);
    291         }
    292         Intent intent = item.getIntent();
    293         if (intent != null && intent.getData() != null) {
    294             grantUriLocked(intent.getData(), pkg);
    295         }
    296     }
    297 
    298     private final void addActiveOwnerLocked(int uid, String pkg) {
    299         final IPackageManager pm = AppGlobals.getPackageManager();
    300         final int targetUserHandle = UserHandle.getCallingUserId();
    301         final long oldIdentity = Binder.clearCallingIdentity();
    302         try {
    303             PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
    304             if (pi == null) {
    305                 throw new IllegalArgumentException("Unknown package " + pkg);
    306             }
    307             if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
    308                 throw new SecurityException("Calling uid " + uid
    309                         + " does not own package " + pkg);
    310             }
    311         } catch (RemoteException e) {
    312             // Can't happen; the package manager is in the same process
    313         } finally {
    314             Binder.restoreCallingIdentity(oldIdentity);
    315         }
    316         PerUserClipboard clipboard = getClipboard();
    317         if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
    318             final int N = clipboard.primaryClip.getItemCount();
    319             for (int i=0; i<N; i++) {
    320                 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg);
    321             }
    322             clipboard.activePermissionOwners.add(pkg);
    323         }
    324     }
    325 
    326     private final void revokeUriLocked(Uri uri) {
    327         long ident = Binder.clearCallingIdentity();
    328         try {
    329             mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
    330                     Intent.FLAG_GRANT_READ_URI_PERMISSION
    331                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    332         } catch (RemoteException e) {
    333         } finally {
    334             Binder.restoreCallingIdentity(ident);
    335         }
    336     }
    337 
    338     private final void revokeItemLocked(ClipData.Item item) {
    339         if (item.getUri() != null) {
    340             revokeUriLocked(item.getUri());
    341         }
    342         Intent intent = item.getIntent();
    343         if (intent != null && intent.getData() != null) {
    344             revokeUriLocked(intent.getData());
    345         }
    346     }
    347 
    348     private final void clearActiveOwnersLocked() {
    349         PerUserClipboard clipboard = getClipboard();
    350         clipboard.activePermissionOwners.clear();
    351         if (clipboard.primaryClip == null) {
    352             return;
    353         }
    354         final int N = clipboard.primaryClip.getItemCount();
    355         for (int i=0; i<N; i++) {
    356             revokeItemLocked(clipboard.primaryClip.getItemAt(i));
    357         }
    358     }
    359 }
    360