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

Commit 54c65485 authored by Oli Lan's avatar Oli Lan
Browse files

Add system API to attribute source of clip data.

This adds a system API to allow the source package to be included
when clip data is set on CLipboardManager.

This is needed to ensure that the clipboard access notifications can be
properly attributed. For example, when a copy is performed through the
share sheet, the data should be attributed to the app that opened the
share sheet, not Android System.

Bug: 180577866
Test: new tests added to ClipboardManagerTest in CTS
Change-Id: Ic0ef2ea218e1dcb738b401f61cd20cd5bba6baec
parent 64589cc6
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -2120,6 +2120,10 @@ package android.content {
    method @NonNull public final android.os.UserHandle getSendingUser();
  }
  public class ClipboardManager extends android.text.ClipboardManager {
    method @RequiresPermission(android.Manifest.permission.SET_CLIP_SOURCE) public void setPrimaryClipAsPackage(@NonNull android.content.ClipData, @NonNull String);
  }
  public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
    method public int checkUriPermission(@NonNull android.net.Uri, int, int);
  }
+4 −0
Original line number Diff line number Diff line
@@ -658,6 +658,10 @@ package android.content {
    field @Nullable public android.util.ArraySet<android.content.ComponentName> whitelistedActivitiesForAugmentedAutofill;
  }

  public class ClipboardManager extends android.text.ClipboardManager {
    method @Nullable @RequiresPermission(android.Manifest.permission.SET_CLIP_SOURCE) public String getPrimaryClipSource();
  }

  public final class ContentCaptureOptions implements android.os.Parcelable {
    ctor public ContentCaptureOptions(int);
    ctor public ContentCaptureOptions(int, int, int, int, int, @Nullable android.util.ArraySet<android.content.ComponentName>);
+46 −0
Original line number Diff line number Diff line
@@ -16,9 +16,13 @@

package android.content;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.RemoteException;
@@ -108,6 +112,31 @@ public class ClipboardManager extends android.text.ClipboardManager {
        }
    }

    /**
     * Sets the current primary clip on the clipboard, attributed to the specified {@code
     * sourcePackage}. The primary clip is the clip that is involved in normal cut and paste
     * operations.
     *
     * @param clip The clipped data item to set.
     * @param sourcePackage The package name of the app that is the source of the clip data.
     * @throws IllegalArgumentException if the clip is null or contains no items.
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.SET_CLIP_SOURCE)
    public void setPrimaryClipAsPackage(@NonNull ClipData clip, @NonNull String sourcePackage) {
        try {
            Objects.requireNonNull(clip);
            Objects.requireNonNull(sourcePackage);
            clip.prepareToLeaveProcess(true);
            mService.setPrimaryClipAsPackage(
                    clip, mContext.getOpPackageName(), mContext.getUserId(), sourcePackage);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Clears any current primary clip on the clipboard.
     *
@@ -234,6 +263,23 @@ public class ClipboardManager extends android.text.ClipboardManager {
        }
    }

    /**
     * Returns the package name of the source of the current primary clip, or null if there is no
     * primary clip or if a source is not available.
     *
     * @hide
     */
    @TestApi
    @Nullable
    @RequiresPermission(Manifest.permission.SET_CLIP_SOURCE)
    public String getPrimaryClipSource() {
        try {
            return mService.getPrimaryClipSource(mContext.getOpPackageName(), mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @UnsupportedAppUsage
    void reportPrimaryClipChanged() {
        Object[] listeners;
+4 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.content.IOnPrimaryClipChangedListener;
 */
interface IClipboard {
    void setPrimaryClip(in ClipData clip, String callingPackage, int userId);
    void setPrimaryClipAsPackage(in ClipData clip, String callingPackage, int userId,
            String sourcePackage);
    void clearPrimaryClip(String callingPackage, int userId);
    ClipData getPrimaryClip(String pkg, int userId);
    ClipDescription getPrimaryClipDescription(String callingPackage, int userId);
@@ -40,4 +42,6 @@ interface IClipboard {
     * Returns true if the clipboard contains text; false otherwise.
     */
    boolean hasClipboardText(String callingPackage, int userId);

    String getPrimaryClipSource(String callingPackage, int userId);
}
+48 −11
Original line number Diff line number Diff line
@@ -379,6 +379,24 @@ public class ClipboardService extends SystemService {
        @Override
        public void setPrimaryClip(ClipData clip, String callingPackage, @UserIdInt int userId) {
            synchronized (this) {
                checkAndSetPrimaryClipLocked(clip, callingPackage, userId, callingPackage);
            }
        }

        @Override
        public void setPrimaryClipAsPackage(
                ClipData clip, String callingPackage, @UserIdInt int userId, String sourcePackage) {
            getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
                    "Requires SET_CLIP_SOURCE permission");

            synchronized (this) {
                checkAndSetPrimaryClipLocked(clip, callingPackage, userId, sourcePackage);
            }
        }

        @GuardedBy("this")
        private void checkAndSetPrimaryClipLocked(
                ClipData clip, String callingPackage, @UserIdInt int userId, String sourcePackage) {
            if (clip == null || clip.getItemCount() <= 0) {
                throw new IllegalArgumentException("No items");
            }
@@ -389,8 +407,7 @@ public class ClipboardService extends SystemService {
                return;
            }
            checkDataOwnerLocked(clip, intendingUid);
                setPrimaryClipInternal(clip, intendingUid, callingPackage);
            }
            setPrimaryClipInternal(clip, intendingUid, sourcePackage);
        }

        @Override
@@ -492,6 +509,26 @@ public class ClipboardService extends SystemService {
                return false;
            }
        }

        @Override
        public String getPrimaryClipSource(String callingPackage, int userId) {
            getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
                    "Requires SET_CLIP_SOURCE permission");
            synchronized (this) {
                final int intendingUid = getIntendingUid(callingPackage, userId);
                final int intendingUserId = UserHandle.getUserId(intendingUid);
                if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
                        intendingUid, intendingUserId, false)
                        || isDeviceLocked(intendingUserId)) {
                    return null;
                }
                PerUserClipboard clipboard = getClipboard(intendingUserId);
                if (clipboard.primaryClip != null) {
                    return clipboard.mPrimaryClipPackage;
                }
                return null;
            }
        }
    };

    private PerUserClipboard getClipboard(@UserIdInt int userId) {