Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d60e07f0 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Add explicit method to clear clipboard.

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: 71711122, 73797203
Change-Id: I99315035efc0c6a90471c279311294dc86766c8d
parent d0f517b9
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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();
+34 −20
Original line number Diff line number Diff line
@@ -16,13 +16,16 @@

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:
        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,22 +94,38 @@ 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) {
            Preconditions.checkNotNull(clip);
            clip.prepareToLeaveProcess(true);
            }
            mService.setPrimaryClip(clip, mContext.getOpPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * 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) {
+1 −0
Original line number Diff line number Diff line
@@ -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);
+19 −0
Original line number Diff line number Diff line
@@ -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;
            }
+93 −59
Original line number Diff line number Diff line
@@ -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,12 +368,74 @@ 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) {
@@ -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);
        }