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             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