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             final int callingUid = Binder.getCallingUid();
    158             if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
    159                     callingPackage) != AppOpsManager.MODE_ALLOWED) {
    160                 return;
    161             }
    162             checkDataOwnerLocked(clip, callingUid);
    163             clearActiveOwnersLocked();
    164             PerUserClipboard clipboard = getClipboard();
    165             clipboard.primaryClip = clip;
    166             final long ident = Binder.clearCallingIdentity();
    167             final int n = clipboard.primaryClipListeners.beginBroadcast();
    168             try {
    169                 for (int i = 0; i < n; i++) {
    170                     try {
    171                         ListenerInfo li = (ListenerInfo)
    172                                 clipboard.primaryClipListeners.getBroadcastCookie(i);
    173                         if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid,
    174                                 li.mPackageName) == AppOpsManager.MODE_ALLOWED) {
    175                             clipboard.primaryClipListeners.getBroadcastItem(i)
    176                                     .dispatchPrimaryClipChanged();
    177                         }
    178                     } catch (RemoteException e) {
    179                         // The RemoteCallbackList will take care of removing
    180                         // the dead object for us.
    181                     }
    182                 }
    183             } finally {
    184                 clipboard.primaryClipListeners.finishBroadcast();
    185                 Binder.restoreCallingIdentity(ident);
    186             }
    187         }
    188     }
    189 
    190     public ClipData getPrimaryClip(String pkg) {
    191         synchronized (this) {
    192             if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
    193                     pkg) != AppOpsManager.MODE_ALLOWED) {
    194                 return null;
    195             }
    196             addActiveOwnerLocked(Binder.getCallingUid(), pkg);
    197             return getClipboard().primaryClip;
    198         }
    199     }
    200 
    201     public ClipDescription getPrimaryClipDescription(String callingPackage) {
    202         synchronized (this) {
    203             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
    204                     callingPackage) != AppOpsManager.MODE_ALLOWED) {
    205                 return null;
    206             }
    207             PerUserClipboard clipboard = getClipboard();
    208             return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
    209         }
    210     }
    211 
    212     public boolean hasPrimaryClip(String callingPackage) {
    213         synchronized (this) {
    214             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
    215                     callingPackage) != AppOpsManager.MODE_ALLOWED) {
    216                 return false;
    217             }
    218             return getClipboard().primaryClip != null;
    219         }
    220     }
    221 
    222     public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
    223             String callingPackage) {
    224         synchronized (this) {
    225             getClipboard().primaryClipListeners.register(listener,
    226                     new ListenerInfo(Binder.getCallingUid(), callingPackage));
    227         }
    228     }
    229 
    230     public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
    231         synchronized (this) {
    232             getClipboard().primaryClipListeners.unregister(listener);
    233         }
    234     }
    235 
    236     public boolean hasClipboardText(String callingPackage) {
    237         synchronized (this) {
    238             if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
    239                     callingPackage) != AppOpsManager.MODE_ALLOWED) {
    240                 return false;
    241             }
    242             PerUserClipboard clipboard = getClipboard();
    243             if (clipboard.primaryClip != null) {
    244                 CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
    245                 return text != null && text.length() > 0;
    246             }
    247             return false;
    248         }
    249     }
    250 
    251     private final void checkUriOwnerLocked(Uri uri, int uid) {
    252         if (!"content".equals(uri.getScheme())) {
    253             return;
    254         }
    255         long ident = Binder.clearCallingIdentity();
    256         try {
    257             // This will throw SecurityException for us.
    258             mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    259         } catch (RemoteException e) {
    260         } finally {
    261             Binder.restoreCallingIdentity(ident);
    262         }
    263     }
    264 
    265     private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
    266         if (item.getUri() != null) {
    267             checkUriOwnerLocked(item.getUri(), uid);
    268         }
    269         Intent intent = item.getIntent();
    270         if (intent != null && intent.getData() != null) {
    271             checkUriOwnerLocked(intent.getData(), uid);
    272         }
    273     }
    274 
    275     private final void checkDataOwnerLocked(ClipData data, int uid) {
    276         final int N = data.getItemCount();
    277         for (int i=0; i<N; i++) {
    278             checkItemOwnerLocked(data.getItemAt(i), uid);
    279         }
    280     }
    281 
    282     private final void grantUriLocked(Uri uri, String pkg) {
    283         long ident = Binder.clearCallingIdentity();
    284         try {
    285             mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
    286                     Intent.FLAG_GRANT_READ_URI_PERMISSION);
    287         } catch (RemoteException e) {
    288         } finally {
    289             Binder.restoreCallingIdentity(ident);
    290         }
    291     }
    292 
    293     private final void grantItemLocked(ClipData.Item item, String pkg) {
    294         if (item.getUri() != null) {
    295             grantUriLocked(item.getUri(), pkg);
    296         }
    297         Intent intent = item.getIntent();
    298         if (intent != null && intent.getData() != null) {
    299             grantUriLocked(intent.getData(), pkg);
    300         }
    301     }
    302 
    303     private final void addActiveOwnerLocked(int uid, String pkg) {
    304         final IPackageManager pm = AppGlobals.getPackageManager();
    305         final int targetUserHandle = UserHandle.getCallingUserId();
    306         final long oldIdentity = Binder.clearCallingIdentity();
    307         try {
    308             PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
    309             if (pi == null) {
    310                 throw new IllegalArgumentException("Unknown package " + pkg);
    311             }
    312             if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
    313                 throw new SecurityException("Calling uid " + uid
    314                         + " does not own package " + pkg);
    315             }
    316         } catch (RemoteException e) {
    317             // Can't happen; the package manager is in the same process
    318         } finally {
    319             Binder.restoreCallingIdentity(oldIdentity);
    320         }
    321         PerUserClipboard clipboard = getClipboard();
    322         if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
    323             final int N = clipboard.primaryClip.getItemCount();
    324             for (int i=0; i<N; i++) {
    325                 grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg);
    326             }
    327             clipboard.activePermissionOwners.add(pkg);
    328         }
    329     }
    330 
    331     private final void revokeUriLocked(Uri uri) {
    332         long ident = Binder.clearCallingIdentity();
    333         try {
    334             mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
    335                     Intent.FLAG_GRANT_READ_URI_PERMISSION
    336                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    337         } catch (RemoteException e) {
    338         } finally {
    339             Binder.restoreCallingIdentity(ident);
    340         }
    341     }
    342 
    343     private final void revokeItemLocked(ClipData.Item item) {
    344         if (item.getUri() != null) {
    345             revokeUriLocked(item.getUri());
    346         }
    347         Intent intent = item.getIntent();
    348         if (intent != null && intent.getData() != null) {
    349             revokeUriLocked(intent.getData());
    350         }
    351     }
    352 
    353     private final void clearActiveOwnersLocked() {
    354         PerUserClipboard clipboard = getClipboard();
    355         clipboard.activePermissionOwners.clear();
    356         if (clipboard.primaryClip == null) {
    357             return;
    358         }
    359         final int N = clipboard.primaryClip.getItemCount();
    360         for (int i=0; i<N; i++) {
    361             revokeItemLocked(clipboard.primaryClip.getItemAt(i));
    362         }
    363     }
    364 }
    365