OSDN Git Service

Add explicit method to clear clipboard.
authorJeff Sharkey <jsharkey@android.com>
Sun, 25 Feb 2018 21:43:27 +0000 (14:43 -0700)
committerJeff Sharkey <jsharkey@android.com>
Mon, 26 Feb 2018 21:43:11 +0000 (14:43 -0700)
Fix several bugs along the way:

-- Clipboard permissions weren't being revoked for related users
when a new primary clip was set.
-- checkGrantUriPermissionLocked() wasn't checking to see if an
otherwise-open provider requires permissions on specific paths.
-- When granting Uri permissions for clipboard data, we need to
include the real source UID for the grant; we no longer allow the
system UID to source grants, to avoid confused deputy problems.
-- Use the Handler passed into ClipboardManager constructor so
it lives on the right thread.

Test: cts-tradefed run commandAndExit cts-dev -m CtsContentTestCases -t android.content.cts.ClipboardManagerTest
Test: cts-tradefed run commandAndExit cts-dev -m CtsAppSecurityHostTestCases -t android.appsecurity.cts.AppSecurityTests
Bug: 7171112273797203
Change-Id: I99315035efc0c6a90471c279311294dc86766c8d

api/current.txt
core/java/android/content/ClipboardManager.java
core/java/android/content/IClipboard.aidl
services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/clipboard/ClipboardService.java

index 1c97c66..2a97e00 100644 (file)
@@ -8972,6 +8972,7 @@ package android.content {
 
   public class ClipboardManager extends android.text.ClipboardManager {
     method public void addPrimaryClipChangedListener(android.content.ClipboardManager.OnPrimaryClipChangedListener);
+    method public void clearPrimaryClip();
     method public android.content.ClipData getPrimaryClip();
     method public android.content.ClipDescription getPrimaryClipDescription();
     method public deprecated java.lang.CharSequence getText();
index 718e465..73b6eb2 100644 (file)
 
 package android.content;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.os.Handler;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.ArrayList;
 
 /**
@@ -45,6 +48,7 @@ import java.util.ArrayList;
 @SystemService(Context.CLIPBOARD_SERVICE)
 public class ClipboardManager extends android.text.ClipboardManager {
     private final Context mContext;
+    private final Handler mHandler;
     private final IClipboard mService;
 
     private final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners
@@ -52,20 +56,11 @@ public class ClipboardManager extends android.text.ClipboardManager {
 
     private final IOnPrimaryClipChangedListener.Stub mPrimaryClipChangedServiceListener
             = new IOnPrimaryClipChangedListener.Stub() {
-        public void dispatchPrimaryClipChanged() {
-            mHandler.sendEmptyMessage(MSG_REPORT_PRIMARY_CLIP_CHANGED);
-        }
-    };
-
-    static final int MSG_REPORT_PRIMARY_CLIP_CHANGED = 1;
-
-    private final Handler mHandler = new Handler() {
         @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_REPORT_PRIMARY_CLIP_CHANGED:
-                    reportPrimaryClipChanged();
-            }
+        public void dispatchPrimaryClipChanged() {
+            mHandler.post(() -> {
+                reportPrimaryClipChanged();
+            });
         }
     };
 
@@ -89,6 +84,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
     /** {@hide} */
     public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException {
         mContext = context;
+        mHandler = handler;
         mService = IClipboard.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
     }
@@ -98,12 +94,13 @@ public class ClipboardManager extends android.text.ClipboardManager {
      * is involved in normal cut and paste operations.
      *
      * @param clip The clipped data item to set.
+     * @see #getPrimaryClip()
+     * @see #clearPrimaryClip()
      */
-    public void setPrimaryClip(ClipData clip) {
+    public void setPrimaryClip(@NonNull ClipData clip) {
         try {
-            if (clip != null) {
-                clip.prepareToLeaveProcess(true);
-            }
+            Preconditions.checkNotNull(clip);
+            clip.prepareToLeaveProcess(true);
             mService.setPrimaryClip(clip, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -111,9 +108,24 @@ public class ClipboardManager extends android.text.ClipboardManager {
     }
 
     /**
+     * Clears any current primary clip on the clipboard.
+     *
+     * @see #setPrimaryClip(ClipData)
+     */
+    public void clearPrimaryClip() {
+        try {
+            mService.clearPrimaryClip(mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the current primary clip on the clipboard.
+     *
+     * @see #setPrimaryClip(ClipData)
      */
-    public ClipData getPrimaryClip() {
+    public @Nullable ClipData getPrimaryClip() {
         try {
             return mService.getPrimaryClip(mContext.getOpPackageName());
         } catch (RemoteException e) {
@@ -124,8 +136,10 @@ public class ClipboardManager extends android.text.ClipboardManager {
     /**
      * Returns a description of the current primary clip on the clipboard
      * but not a copy of its data.
+     *
+     * @see #setPrimaryClip(ClipData)
      */
-    public ClipDescription getPrimaryClipDescription() {
+    public @Nullable ClipDescription getPrimaryClipDescription() {
         try {
             return mService.getPrimaryClipDescription(mContext.getOpPackageName());
         } catch (RemoteException e) {
index af0b8f0..135a436 100644 (file)
@@ -27,6 +27,7 @@ import android.content.IOnPrimaryClipChangedListener;
  */
 interface IClipboard {
     void setPrimaryClip(in ClipData clip, String callingPackage);
+    void clearPrimaryClip(String callingPackage);
     ClipData getPrimaryClip(String pkg);
     ClipDescription getPrimaryClipDescription(String callingPackage);
     boolean hasPrimaryClip(String callingPackage);
index d4307d7..0f2a35e 100644 (file)
@@ -9409,6 +9409,25 @@ public class ActivityManagerService extends IActivityManager.Stub
                     allowed = false;
                 }
             }
+            if (pi.pathPermissions != null) {
+                final int N = pi.pathPermissions.length;
+                for (int i=0; i<N; i++) {
+                    if (pi.pathPermissions[i] != null
+                            && pi.pathPermissions[i].match(grantUri.uri.getPath())) {
+                        if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+                            if (pi.pathPermissions[i].getReadPermission() != null) {
+                                allowed = false;
+                            }
+                        }
+                        if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+                            if (pi.pathPermissions[i].getWritePermission() != null) {
+                                allowed = false;
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
             if (allowed) {
                 return -1;
             }
index 0c9d70a..776e93d 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.server.clipboard;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -24,9 +25,9 @@ import android.app.KeyguardManager;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.ContentProvider;
+import android.content.Context;
 import android.content.IClipboard;
 import android.content.IOnPrimaryClipChangedListener;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
@@ -37,7 +38,6 @@ import android.os.Binder;
 import android.os.IBinder;
 import android.os.IUserManager;
 import android.os.Parcel;
-import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -49,14 +49,10 @@ import android.util.SparseArray;
 
 import com.android.server.SystemService;
 
-import java.util.HashSet;
-import java.util.List;
-
-import java.lang.Thread;
-import java.lang.Runnable;
-import java.lang.InterruptedException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.util.HashSet;
+import java.util.List;
 
 // The following class is Android Emulator specific. It is used to read and
 // write contents of the host system's clipboard.
@@ -182,7 +178,8 @@ public class ClipboardService extends SystemService {
                                          new String[]{"text/plain"},
                                          new ClipData.Item(contents));
                         synchronized(mClipboards) {
-                            setPrimaryClipInternal(getClipboard(0), clip);
+                            setPrimaryClipInternal(getClipboard(0), clip,
+                                    android.os.Process.SYSTEM_UID);
                         }
                     }
                 });
@@ -218,7 +215,10 @@ public class ClipboardService extends SystemService {
         final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
                 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
 
+        /** Current primary clip. */
         ClipData primaryClip;
+        /** UID that set {@link #primaryClip}. */
+        int primaryClipUid = android.os.Process.NOBODY_UID;
 
         final HashSet<String> activePermissionOwners
                 = new HashSet<String>();
@@ -246,58 +246,28 @@ public class ClipboardService extends SystemService {
         @Override
         public void setPrimaryClip(ClipData clip, String callingPackage) {
             synchronized (this) {
-                if (clip != null && clip.getItemCount() <= 0) {
+                if (clip == null || clip.getItemCount() <= 0) {
                     throw new IllegalArgumentException("No items");
                 }
-                if (clip.getItemAt(0).getText() != null &&
-                    mHostClipboardMonitor != null) {
-                    mHostClipboardMonitor.setHostClipboard(
-                        clip.getItemAt(0).getText().toString());
-                }
                 final int callingUid = Binder.getCallingUid();
                 if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
                             callingUid)) {
                     return;
                 }
                 checkDataOwnerLocked(clip, callingUid);
-                final int userId = UserHandle.getUserId(callingUid);
-                PerUserClipboard clipboard = getClipboard(userId);
-                revokeUris(clipboard);
-                setPrimaryClipInternal(clipboard, clip);
-                List<UserInfo> related = getRelatedProfiles(userId);
-                if (related != null) {
-                    int size = related.size();
-                    if (size > 1) { // Related profiles list include the current profile.
-                        boolean canCopy = false;
-                        try {
-                            canCopy = !mUm.getUserRestrictions(userId).getBoolean(
-                                    UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Remote Exception calling UserManager: " + e);
-                        }
-                        // Copy clip data to related users if allowed. If disallowed, then remove
-                        // primary clip in related users to prevent pasting stale content.
-                        if (!canCopy) {
-                            clip = null;
-                        } else {
-                            // We want to fix the uris of the related user's clip without changing the
-                            // uris of the current user's clip.
-                            // So, copy the ClipData, and then copy all the items, so that nothing
-                            // is shared in memmory.
-                            clip = new ClipData(clip);
-                            for (int i = clip.getItemCount() - 1; i >= 0; i--) {
-                                clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
-                            }
-                            clip.fixUrisLight(userId);
-                        }
-                        for (int i = 0; i < size; i++) {
-                            int id = related.get(i).id;
-                            if (id != userId) {
-                                setPrimaryClipInternal(getClipboard(id), clip);
-                            }
-                        }
-                    }
+                setPrimaryClipInternal(clip, callingUid);
+            }
+        }
+
+        @Override
+        public void clearPrimaryClip(String callingPackage) {
+            synchronized (this) {
+                final int callingUid = Binder.getCallingUid();
+                if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
+                        callingUid)) {
+                    return;
                 }
+                setPrimaryClipInternal(null, callingUid);
             }
         }
 
@@ -398,13 +368,75 @@ public class ClipboardService extends SystemService {
         return related;
     }
 
-    void setPrimaryClipInternal(PerUserClipboard clipboard, ClipData clip) {
+    void setPrimaryClipInternal(@Nullable ClipData clip, int callingUid) {
+        // Push clipboard to host, if any
+        if (mHostClipboardMonitor != null) {
+            if (clip == null) {
+                // Someone really wants the clipboard cleared, so push empty
+                mHostClipboardMonitor.setHostClipboard("");
+            } else if (clip.getItemCount() > 0) {
+                final CharSequence text = clip.getItemAt(0).getText();
+                if (text != null) {
+                    mHostClipboardMonitor.setHostClipboard(text.toString());
+                }
+            }
+        }
+
+        // Update this user
+        final int userId = UserHandle.getUserId(callingUid);
+        setPrimaryClipInternal(getClipboard(userId), clip, callingUid);
+
+        // Update related users
+        List<UserInfo> related = getRelatedProfiles(userId);
+        if (related != null) {
+            int size = related.size();
+            if (size > 1) { // Related profiles list include the current profile.
+                boolean canCopy = false;
+                try {
+                    canCopy = !mUm.getUserRestrictions(userId).getBoolean(
+                            UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote Exception calling UserManager: " + e);
+                }
+                // Copy clip data to related users if allowed. If disallowed, then remove
+                // primary clip in related users to prevent pasting stale content.
+                if (!canCopy) {
+                    clip = null;
+                } else {
+                    // We want to fix the uris of the related user's clip without changing the
+                    // uris of the current user's clip.
+                    // So, copy the ClipData, and then copy all the items, so that nothing
+                    // is shared in memmory.
+                    clip = new ClipData(clip);
+                    for (int i = clip.getItemCount() - 1; i >= 0; i--) {
+                        clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
+                    }
+                    clip.fixUrisLight(userId);
+                }
+                for (int i = 0; i < size; i++) {
+                    int id = related.get(i).id;
+                    if (id != userId) {
+                        setPrimaryClipInternal(getClipboard(id), clip, callingUid);
+                    }
+                }
+            }
+        }
+    }
+
+    void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip,
+            int callingUid) {
+        revokeUris(clipboard);
         clipboard.activePermissionOwners.clear();
         if (clip == null && clipboard.primaryClip == null) {
             return;
         }
         clipboard.primaryClip = clip;
         if (clip != null) {
+            clipboard.primaryClipUid = callingUid;
+        } else {
+            clipboard.primaryClipUid = android.os.Process.NOBODY_UID;
+        }
+        if (clip != null) {
             final ClipDescription description = clip.getDescription();
             if (description != null) {
                 description.setTimestamp(System.currentTimeMillis());
@@ -479,12 +511,12 @@ public class ClipboardService extends SystemService {
         }
     }
 
-    private final void grantUriLocked(Uri uri, String pkg, int userId) {
+    private final void grantUriLocked(Uri uri, int primaryClipUid, String pkg, int userId) {
         long ident = Binder.clearCallingIdentity();
         try {
             int sourceUserId = ContentProvider.getUserIdFromUri(uri, userId);
             uri = ContentProvider.getUriWithoutUserId(uri);
-            mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg,
+            mAm.grantUriPermissionFromOwner(mPermissionOwner, primaryClipUid, pkg,
                     uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, userId);
         } catch (RemoteException e) {
         } finally {
@@ -492,13 +524,14 @@ public class ClipboardService extends SystemService {
         }
     }
 
-    private final void grantItemLocked(ClipData.Item item, String pkg, int userId) {
+    private final void grantItemLocked(ClipData.Item item, int primaryClipUid, String pkg,
+            int userId) {
         if (item.getUri() != null) {
-            grantUriLocked(item.getUri(), pkg, userId);
+            grantUriLocked(item.getUri(), primaryClipUid, pkg, userId);
         }
         Intent intent = item.getIntent();
         if (intent != null && intent.getData() != null) {
-            grantUriLocked(intent.getData(), pkg, userId);
+            grantUriLocked(intent.getData(), primaryClipUid, pkg, userId);
         }
     }
 
@@ -524,7 +557,8 @@ public class ClipboardService extends SystemService {
         if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
             final int N = clipboard.primaryClip.getItemCount();
             for (int i=0; i<N; i++) {
-                grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg, UserHandle.getUserId(uid));
+                grantItemLocked(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid, pkg,
+                        UserHandle.getUserId(uid));
             }
             clipboard.activePermissionOwners.add(pkg);
         }