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