Skip to content
This repository has been archived by the owner on Oct 29, 2024. It is now read-only.

Commit

Permalink
add per-app clipboard setting
Browse files Browse the repository at this point in the history
  • Loading branch information
octocorvus committed Aug 14, 2024
1 parent 652113a commit 1bb59de
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 4 deletions.
48 changes: 48 additions & 0 deletions core/java/android/ext/settings/app/AswClipboardRead.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package android.ext.settings.app;

import static android.ext.settings.ExtSettings.CLIPBOARD_READ_ACCESS;

import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.GosPackageState;
import android.content.pm.GosPackageStateBase;
import android.ext.settings.ClipboardReadSetting;

/** @hide */
public class AswClipboardRead extends AppSwitch {
public static final AswClipboardRead I = new AswClipboardRead();

private AswClipboardRead() {
gosPsFlag = GosPackageState.FLAG_CLIPBOARD_READ;
gosPsFlagNonDefault = GosPackageState.FLAG_CLIPBOARD_READ_NON_DEFAULT;
gosPsFlagSuppressNotif = GosPackageState.FLAG_CLIPBOARD_READ_SUPPRESS_NOTIF;
}

@Override
public Boolean getImmutableValue(Context ctx, int userId, ApplicationInfo appInfo,
@Nullable GosPackageStateBase ps, StateInfo si) {
if (appInfo.isSystemApp()) {
si.immutabilityReason = IR_IS_SYSTEM_APP;
return true;
}

return null;
}

@Override
protected boolean getDefaultValueInner(Context ctx, int userId, ApplicationInfo appInfo,
@Nullable GosPackageStateBase ps, StateInfo si) {
si.defaultValueReason = DVR_DEFAULT_SETTING;
return CLIPBOARD_READ_ACCESS.get(ctx, userId) == ClipboardReadSetting.ALLOWED;
}

public boolean isNotificationSuppressed(Context ctx, int userId,
@Nullable GosPackageStateBase ps) {
if (isUsingDefaultValue(ps)) {
return CLIPBOARD_READ_ACCESS.get(ctx, userId) == ClipboardReadSetting.BLOCKED;
}

return isNotificationSuppressed(ps);
}
}
7 changes: 7 additions & 0 deletions core/res/res/values/string_ext.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
<java-symbol type="string" name="notif_text_tap_to_see_details" />
<string name="notif_action_more_info">More info</string>
<java-symbol type="string" name="notif_action_more_info" />
<string name="notif_action_allow_once">Allow once</string>
<java-symbol type="string" name="notif_action_allow_once" />

<string name="notif_native_debug_title">%1$s tried to use native code debugging</string>
<java-symbol type="string" name="notif_native_debug_title" />
Expand All @@ -46,4 +48,9 @@
<string name="notif_hmalloc_crash_title">hardened_malloc detected an error in %1$s</string>
<java-symbol type="string" name="notif_hmalloc_crash_title" />

<string name="notif_clipboard_read_title">Allow %1$s to read the clipboard?</string>
<java-symbol type="string" name="notif_clipboard_read_title" />
<string name="notif_clipboard_read_content_text">Clipboard holds data from %1$s. Tap to allow in settings.</string>
<java-symbol type="string" name="notif_clipboard_read_content_text" />

</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package android.content.clipboard;

/**
* @hide
*/
public abstract class ClipboardManagerInternal {
public abstract void setAllowOneTimeAccess(String pkg, int userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.android.server.clipboard;

import android.content.ClipData;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
import android.ext.settings.app.AswClipboardRead;
import android.os.Process;

import com.android.server.LocalServices;
import com.android.server.ext.ClipboardReadNotification;
import com.android.server.pm.pkg.GosPackageStatePm;

class ClipboardHelper {
static final ClipData dummyClip = ClipData.newPlainText(null, "");

static int getPackageUid(String pkgName, int userId) {
var pmi = LocalServices.getService(PackageManagerInternal.class);
return pmi.getPackageUid(pkgName, 0, userId);
}

static boolean isReadAllowedForPackage(Context ctx, String pkgName, int userId) {
var pmi = LocalServices.getService(PackageManagerInternal.class);
ApplicationInfo appInfo = pmi.getApplicationInfo(pkgName, 0, Process.SYSTEM_UID, userId);
if (appInfo == null) {
return false;
}
GosPackageStatePm ps = pmi.getGosPackageState(pkgName, userId);
return AswClipboardRead.I.get(ctx, userId, appInfo, ps);
}

static boolean isNotificationSuppressed(Context ctx, String pkgName, int userId) {
var pmi = LocalServices.getService(PackageManagerInternal.class);
ApplicationInfo appInfo = pmi.getApplicationInfo(pkgName, 0, Process.SYSTEM_UID, userId);
if (appInfo == null) {
return true;
}
GosPackageStatePm ps = pmi.getGosPackageState(pkgName, userId);
return AswClipboardRead.I.isNotificationSuppressed(ctx, userId, ps);
}

static void maybeNotifyAccessDenied(Context ctx, int deviceId, String pkgName, int pkgUid,
String primaryClipPackage, int primaryClipUid) {
var n = ClipboardReadNotification.maybeCreate(ctx, deviceId, pkgName, pkgUid,
primaryClipPackage, primaryClipUid);
if (n != null) {
n.maybeShow();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import android.content.IOnPrimaryClipChangedListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.clipboard.ClipboardManagerInternal;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
Expand Down Expand Up @@ -98,6 +99,7 @@
import com.android.server.UiThread;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.ext.ClipboardReadNotification;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.WindowManagerInternal;

Expand Down Expand Up @@ -212,6 +214,8 @@ public ClipboardService(Context context) {
HandlerThread workerThread = new HandlerThread(TAG);
workerThread.start();
mWorkerHandler = workerThread.getThreadHandler();

LocalServices.addService(ClipboardManagerInternal.class, new ClipboardManagerInternalImpl());
}

@Override
Expand Down Expand Up @@ -322,6 +326,9 @@ private static class Clipboard {
*/
final SparseBooleanArray mNotifiedTextClassifierUids = new SparseBooleanArray();

/** Uids that have access to current clip. */
final SparseBooleanArray mCurrentClipAllowedUids = new SparseBooleanArray();

final HashSet<String> activePermissionOwners
= new HashSet<String>();

Expand Down Expand Up @@ -682,6 +689,10 @@ public ClipData getPrimaryClip(
if (clipboard == null) {
return null;
}
if (!isReadAllowedForPkg(pkg, intendingUid, clipboard)) {
maybeNotifyAccessDenied(pkg, intendingUid, intendingDeviceId, clipboard);
return clipboard.primaryClip != null ? ClipboardHelper.dummyClip : null;
}
showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
notifyTextClassifierLocked(clipboard, pkg, intendingUid);
if (clipboard.primaryClip != null) {
Expand Down Expand Up @@ -710,8 +721,13 @@ public ClipDescription getPrimaryClipDescription(
}
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
return (clipboard != null && clipboard.primaryClip != null)
? clipboard.primaryClip.getDescription() : null;
ClipDescription clipDesc = null;
if (clipboard != null && clipboard.primaryClip != null) {
clipDesc = isReadAllowedForPkg(callingPackage, intendingUid, clipboard)
? clipboard.primaryClip.getDescription()
: ClipboardHelper.dummyClip.getDescription();
}
return clipDesc;
}
}

Expand Down Expand Up @@ -809,7 +825,8 @@ public boolean hasClipboardText(
}
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
if (clipboard != null && clipboard.primaryClip != null) {
if (clipboard != null && clipboard.primaryClip != null
&& isReadAllowedForPkg(callingPackage, intendingUid, clipboard)) {
CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
return text != null && text.length() > 0;
}
Expand Down Expand Up @@ -838,7 +855,8 @@ public String getPrimaryClipSource(
}
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
if (clipboard != null && clipboard.primaryClip != null) {
if (clipboard != null && clipboard.primaryClip != null
&& isReadAllowedForPkg(callingPackage, intendingUid, clipboard)) {
return clipboard.mPrimaryClipPackage;
}
return null;
Expand Down Expand Up @@ -876,6 +894,23 @@ public void handleMessage(@NonNull Message msg) {
}
};

private class ClipboardManagerInternalImpl extends ClipboardManagerInternal {
@Override
public void setAllowOneTimeAccess(String pkg, int userId) {
synchronized (mLock) {
int pkgUid = ClipboardHelper.getPackageUid(pkg, userId);
if (pkgUid < 0) {
return;
}
Clipboard clipboard = getClipboardLocked(userId, DEVICE_ID_DEFAULT);
if (clipboard == null) {
return;
}
clipboard.mCurrentClipAllowedUids.put(pkgUid, true);
}
}
}

@GuardedBy("mLock")
private @Nullable Clipboard getClipboardLocked(@UserIdInt int userId, int deviceId) {
Clipboard clipboard = mClipboards.get(userId, deviceId);
Expand Down Expand Up @@ -1022,6 +1057,8 @@ private void setPrimaryClipInternalNoClassifyLocked(Clipboard clipboard,
clipboard.primaryClip = clip;
clipboard.mNotifiedUids.clear();
clipboard.mNotifiedTextClassifierUids.clear();
clipboard.mCurrentClipAllowedUids.clear();
Binder.withCleanCallingIdentity(() -> ClipboardReadNotification.cancelAll(getContext()));
if (clip != null) {
clipboard.primaryClipUid = uid;
clipboard.mPrimaryClipPackage = sourcePackage;
Expand All @@ -1047,6 +1084,10 @@ private void sendClipChangedBroadcast(Clipboard clipboard) {
ListenerInfo li = (ListenerInfo)
clipboard.primaryClipListeners.getBroadcastCookie(i);

if (!isReadAllowedForPkg(li.mPackageName, li.mUid, clipboard)) {
continue;
}

if (clipboardAccessAllowed(
AppOpsManager.OP_READ_CLIPBOARD,
li.mPackageName,
Expand Down Expand Up @@ -1619,4 +1660,27 @@ private TextClassificationManager createTextClassificationManagerAsUser(@UserIdI
Context context = getContext().createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
return context.getSystemService(TextClassificationManager.class);
}

private boolean isReadAllowedForPkg(String pkg, int pkgUid, Clipboard clipboard) {
int userId = UserHandle.getUserId(pkgUid);
return pkgUid == clipboard.primaryClipUid
|| clipboard.mCurrentClipAllowedUids.get(pkgUid)
|| ClipboardHelper.isReadAllowedForPackage(getContext(), pkg, userId);
}

private void maybeNotifyAccessDenied(String pkg, int uid, int deviceId, Clipboard clipboard) {
if (clipboard.primaryClip == null) {
return;
}

Slog.d(TAG, "clipboard read blocked for: " + pkg);

int userId = UserHandle.getUserId(uid);
if (ClipboardHelper.isNotificationSuppressed(getContext(), pkg, userId)) {
return;
}
Binder.withCleanCallingIdentity(
() -> ClipboardHelper.maybeNotifyAccessDenied(getContext(), deviceId, pkg, uid,
clipboard.mPrimaryClipPackage, clipboard.primaryClipUid));
}
}
Loading

0 comments on commit 1bb59de

Please sign in to comment.