Android12去掉剪贴板复制成功的Toast

Android12去掉剪贴板复制成功的Toast

1.前言:

最近在rom定制化开发时,测试提了一个bug,在浏览器或者文本里面使用剪贴板复制成功后会有一个Toast提示,这种体验不是很好,因为每次复制成功都有一个提示,感觉很突兀,修改很简单,去掉toast即可,所以这里记录一下过程.

2.问题截图如下:

3.问题日志分析:

通过logcat日志分析发现在剪切板服务类中有打印toast提示的内容,问题复选了,于是根据提示找到剪切板服务类.

4.源码路径:

frameworks/base/services/core/com/android/server/ClipboardService

scss 复制代码
package com.android.server.clipboard;

import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IUriGrantsManager;
import android.app.KeyguardManager;
import android.app.UriGrantsManager;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IClipboard;
import android.content.IOnPrimaryClipChangedListener;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IUserManager;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.autofill.AutofillManagerInternal;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextClassifierEvent;
import android.view.textclassifier.TextLinks;
import android.widget.Toast;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.WindowManagerInternal;

import java.util.HashSet;
import java.util.List;
import java.util.function.Consumer;

/**
 * Implementation of the clipboard for copy and paste.
 * <p>
 * Caution: exception for clipboard data and isInternalSysWindowAppWithWindowFocus, any of data
 * is accessed by userId or uid should be in * the try segment between
 * Binder.clearCallingIdentity and Binder.restoreCallingIdentity.
 * </p>
 */
public class ClipboardService extends SystemService {

    private static final String TAG = "ClipboardService";
    private static final boolean IS_EMULATOR =
            SystemProperties.getBoolean("ro.boot.qemu", false);

    // DeviceConfig properties
    private static final String PROPERTY_MAX_CLASSIFICATION_LENGTH = "max_classification_length";
    private static final int DEFAULT_MAX_CLASSIFICATION_LENGTH = 400;

    private final ActivityManagerInternal mAmInternal;
    private final IUriGrantsManager mUgm;
    private final UriGrantsManagerInternal mUgmInternal;
    private final WindowManagerInternal mWm;
    private final IUserManager mUm;
    private final PackageManager mPm;
    private final AppOpsManager mAppOps;
    private final ContentCaptureManagerInternal mContentCaptureInternal;
    private final AutofillManagerInternal mAutofillInternal;
    private final IBinder mPermissionOwner;
    private final Consumer<ClipData> mEmulatorClipboardMonitor;
    private final Handler mWorkerHandler;

    @GuardedBy("mLock")
    private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();

    @GuardedBy("mLock")
    private boolean mShowAccessNotifications =
            ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS;

    @GuardedBy("mLock")
    private int mMaxClassificationLength = DEFAULT_MAX_CLASSIFICATION_LENGTH;

    private final Object mLock = new Object();

    /**
     * Instantiates the clipboard.
     */
    public ClipboardService(Context context) {
        super(context);

        mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
        mUgm = UriGrantsManager.getService();
        mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
        mWm = LocalServices.getService(WindowManagerInternal.class);
        mPm = getContext().getPackageManager();
        mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
        mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
        mContentCaptureInternal = LocalServices.getService(ContentCaptureManagerInternal.class);
        mAutofillInternal = LocalServices.getService(AutofillManagerInternal.class);
        final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
        mPermissionOwner = permOwner;
        if (IS_EMULATOR) {
            mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
                synchronized (mLock) {
                    setPrimaryClipInternalLocked(getClipboardLocked(0), clip,
                            android.os.Process.SYSTEM_UID, null);
                }
            });
        } else {
            mEmulatorClipboardMonitor = (clip) -> {};
        }

        updateConfig();
        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CLIPBOARD,
                getContext().getMainExecutor(), properties -> updateConfig());

        HandlerThread workerThread = new HandlerThread(TAG);
        workerThread.start();
        mWorkerHandler = workerThread.getThreadHandler();
    }

    @Override
    public void onStart() {
        publishBinderService(Context.CLIPBOARD_SERVICE, new ClipboardImpl());
    }

    @Override
    public void onUserStopped(@NonNull TargetUser user) {
        synchronized (mLock) {
            mClipboards.remove(user.getUserIdentifier());
        }
    }

    private void updateConfig() {
        synchronized (mLock) {
            mShowAccessNotifications = DeviceConfig.getBoolean(
                    DeviceConfig.NAMESPACE_CLIPBOARD,
                    ClipboardManager.DEVICE_CONFIG_SHOW_ACCESS_NOTIFICATIONS,
                    ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS);
            mMaxClassificationLength = DeviceConfig.getInt(DeviceConfig.NAMESPACE_CLIPBOARD,
                    PROPERTY_MAX_CLASSIFICATION_LENGTH, DEFAULT_MAX_CLASSIFICATION_LENGTH);
        }
    }

    private class ListenerInfo {
        final int mUid;
        final String mPackageName;
        ListenerInfo(int uid, String packageName) {
            mUid = uid;
            mPackageName = packageName;
        }
    }

    private class PerUserClipboard {
        final int userId;

        final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
                = new RemoteCallbackList<IOnPrimaryClipChangedListener>();

        /** Current primary clip. */
        ClipData primaryClip;
        /** UID that set {@link #primaryClip}. */
        int primaryClipUid = android.os.Process.NOBODY_UID;
        /** Package of the app that set {@link #primaryClip}. */
        String mPrimaryClipPackage;

        /** Uids that have already triggered a toast notification for {@link #primaryClip} */
        final SparseBooleanArray mNotifiedUids = new SparseBooleanArray();

        /**
         * Uids that have already triggered a notification to text classifier for
         * {@link #primaryClip}.
         */
        final SparseBooleanArray mNotifiedTextClassifierUids = new SparseBooleanArray();

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

        /** The text classifier session that is used to annotate the text in the primary clip. */
        TextClassifier mTextClassifier;

        PerUserClipboard(int userId) {
            this.userId = userId;
        }
    }

    /**
     * To check if the application has granted the INTERNAL_SYSTEM_WINDOW permission and window
     * focus.
     * <p>
     * All of applications granted INTERNAL_SYSTEM_WINDOW has the risk to leak clip information to
     * the other user because INTERNAL_SYSTEM_WINDOW is signature level. i.e. platform key. Because
     * some of applications have both of INTERNAL_SYSTEM_WINDOW and INTERACT_ACROSS_USERS_FULL at
     * the same time, that means they show the same window to all of users.
     * </p><p>
     * Unfortunately, all of applications with INTERNAL_SYSTEM_WINDOW starts very early and then
     * the real window show is belong to user 0 rather user X. The result of
     * WindowManager.isUidFocused checking user X window is false.
     * </p>
     * @return true if the app granted INTERNAL_SYSTEM_WINDOW permission.
     */
    private boolean isInternalSysWindowAppWithWindowFocus(String callingPackage) {
        // Shell can access the clipboard for testing purposes.
        if (mPm.checkPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW,
                    callingPackage) == PackageManager.PERMISSION_GRANTED) {
            if (mWm.isUidFocused(Binder.getCallingUid())) {
                return true;
            }
        }

        return false;
    }

    /**
     * To get the validate current userId.
     * <p>
     * The intending userId needs to be validated by ActivityManagerInternal.handleIncomingUser.
     * To check if the uid of the process have the permission to run as the userId.
     * e.x. INTERACT_ACROSS_USERS_FULL or INTERACT_ACROSS_USERS permission granted.
     * </p>
     * <p>
     * The application with the granted INTERNAL_SYSTEM_WINDOW permission should run as the output
     * of ActivityManagerInternal.handleIncomingUser rather the userId of Binder.getCAllingUid().
     * To use the userId of Binder.getCallingUid() is the root cause that leaks the information
     * comes from user 0 to user X.
     * </p>
     *
     * @param packageName the package name of the calling side
     * @param userId the userId passed by the calling side
     * @return return the intending userId that has been validated by ActivityManagerInternal.
     */
    @UserIdInt
    private int getIntendingUserId(String packageName, @UserIdInt int userId) {
        final int callingUid = Binder.getCallingUid();
        final int callingUserId = UserHandle.getUserId(callingUid);
        if (!UserManager.supportsMultipleUsers() || callingUserId == userId) {
            return callingUserId;
        }

        int intendingUserId = callingUserId;
        intendingUserId = mAmInternal.handleIncomingUser(Binder.getCallingPid(),
                Binder.getCallingUid(), userId, false /* allow all */, ALLOW_FULL_ONLY,
                "checkClipboardServiceCallingUser", packageName);

        return intendingUserId;
    }

    /**
     * To get the current running uid who is intend to run as.
     * In ording to distinguish the nameing and reducing the confusing names, the client client
     * side pass userId that is intend to run as,
     * @return return IntentingUid = validated intenting userId +
     *         UserHandle.getAppId(Binder.getCallingUid())
     */
    private int getIntendingUid(String packageName, @UserIdInt int userId) {
        return UserHandle.getUid(getIntendingUserId(packageName, userId),
                UserHandle.getAppId(Binder.getCallingUid()));
    }

    /**
     * To handle the difference between userId and intendingUserId, uid and intendingUid.
     *
     * userId means that comes from the calling side and should be validated by
     * ActivityManagerInternal.handleIncomingUser.
     * After validation of ActivityManagerInternal.handleIncomingUser, the userId is called
     * 'intendingUserId' and the uid is called 'intendingUid'.
     */
    private class ClipboardImpl extends IClipboard.Stub {
        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                throws RemoteException {
            try {
                return super.onTransact(code, data, reply, flags);
            } catch (RuntimeException e) {
                if (!(e instanceof SecurityException)) {
                    Slog.wtf("clipboard", "Exception: ", e);
                }
                throw e;
            }

        }

        @Override
        public void setPrimaryClip(ClipData clip, String callingPackage, @UserIdInt int userId) {
            checkAndSetPrimaryClip(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");
            checkAndSetPrimaryClip(clip, callingPackage, userId, sourcePackage);
        }

        private void checkAndSetPrimaryClip(
                ClipData clip, String callingPackage, @UserIdInt int userId, String sourcePackage) {
            if (clip == null || clip.getItemCount() <= 0) {
                throw new IllegalArgumentException("No items");
            }
            final int intendingUid = getIntendingUid(callingPackage, userId);
            final int intendingUserId = UserHandle.getUserId(intendingUid);
            if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
                    intendingUid, intendingUserId)) {
                return;
            }
            checkDataOwner(clip, intendingUid);
            synchronized (mLock) {
                setPrimaryClipInternalLocked(clip, intendingUid, sourcePackage);
            }
        }

        @Override
        public void clearPrimaryClip(String callingPackage, @UserIdInt int userId) {
            final int intendingUid = getIntendingUid(callingPackage, userId);
            final int intendingUserId = UserHandle.getUserId(intendingUid);
            if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
                    intendingUid, intendingUserId)) {
                return;
            }
            synchronized (mLock) {
                setPrimaryClipInternalLocked(null, intendingUid, callingPackage);
            }
        }

        @Override
        public ClipData getPrimaryClip(String pkg, @UserIdInt int userId) {
            final int intendingUid = getIntendingUid(pkg, userId);
            final int intendingUserId = UserHandle.getUserId(intendingUid);
            if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, pkg,
                    intendingUid, intendingUserId)
                    || isDeviceLocked(intendingUserId)) {
                return null;
            }
            synchronized (mLock) {
                try {
                    addActiveOwnerLocked(intendingUid, pkg);
                } catch (SecurityException e) {
                    // Permission could not be granted - URI may be invalid
                    Slog.i(TAG, "Could not grant permission to primary clip. Clearing clipboard.");
                    setPrimaryClipInternalLocked(null, intendingUid, pkg);
                    return null;
                }

                PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
                showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
                notifyTextClassifierLocked(clipboard, pkg, intendingUid);
                return clipboard.primaryClip;
            }
        }

        @Override
        public ClipDescription getPrimaryClipDescription(String callingPackage,
                @UserIdInt int userId) {
            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;
            }
            synchronized (mLock) {
                PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
                return clipboard.primaryClip != null
                        ? clipboard.primaryClip.getDescription() : null;
            }
        }

        @Override
        public boolean hasPrimaryClip(String callingPackage, @UserIdInt int userId) {
            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 false;
            }
            synchronized (mLock) {
                return getClipboardLocked(intendingUserId).primaryClip != null;
            }
        }

        @Override
        public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
                String callingPackage, @UserIdInt int userId) {
            final int intendingUid = getIntendingUid(callingPackage, userId);
            final int intendingUserId = UserHandle.getUserId(intendingUid);
            synchronized (mLock) {
                getClipboardLocked(intendingUserId).primaryClipListeners.register(listener,
                        new ListenerInfo(intendingUid, callingPackage));
            }
        }

        @Override
        public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
                String callingPackage, @UserIdInt int userId) {
            final int intendingUserId = getIntendingUserId(callingPackage, userId);
            synchronized (mLock) {
                getClipboardLocked(intendingUserId).primaryClipListeners.unregister(listener);
            }
        }

        @Override
        public boolean hasClipboardText(String callingPackage, int userId) {
            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 false;
            }
            synchronized (mLock) {
                PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
                if (clipboard.primaryClip != null) {
                    CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
                    return text != null && text.length() > 0;
                }
                return false;
            }
        }

        @Override
        public String getPrimaryClipSource(String callingPackage, int userId) {
            getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
                    "Requires SET_CLIP_SOURCE permission");
            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;
            }
            synchronized (mLock) {
                PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
                if (clipboard.primaryClip != null) {
                    return clipboard.mPrimaryClipPackage;
                }
                return null;
            }
        }
    };

    @GuardedBy("mLock")
    private PerUserClipboard getClipboardLocked(@UserIdInt int userId) {
        PerUserClipboard puc = mClipboards.get(userId);
        if (puc == null) {
            puc = new PerUserClipboard(userId);
            mClipboards.put(userId, puc);
        }
        return puc;
    }

    List<UserInfo> getRelatedProfiles(@UserIdInt int userId) {
        final List<UserInfo> related;
        final long origId = Binder.clearCallingIdentity();
        try {
            related = mUm.getProfiles(userId, true);
        } catch (RemoteException e) {
            Slog.e(TAG, "Remote Exception calling UserManager: " + e);
            return null;
        } finally{
            Binder.restoreCallingIdentity(origId);
        }
        return related;
    }

    /** Check if the user has the given restriction set. Default to true if error occured during
     * calling UserManager, so it fails safe.
     */
    private boolean hasRestriction(String restriction, int userId) {
        try {
            return mUm.hasUserRestriction(restriction, userId);
        } catch (RemoteException e) {
            Slog.e(TAG, "Remote Exception calling UserManager.getUserRestrictions: ", e);
            // Fails safe
            return true;
        }
    }

    void setPrimaryClipInternal(@Nullable ClipData clip, int uid) {
        synchronized (mLock) {
            setPrimaryClipInternalLocked(clip, uid, null);
        }
    }

    @GuardedBy("mLock")
    private void setPrimaryClipInternalLocked(
            @Nullable ClipData clip, int uid, @Nullable String sourcePackage) {
        mEmulatorClipboardMonitor.accept(clip);

        final int userId = UserHandle.getUserId(uid);
        if (clip != null) {
            startClassificationLocked(clip, userId);
        }

        // Update this user
        setPrimaryClipInternalLocked(getClipboardLocked(userId), clip, uid, sourcePackage);

        // 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.
                final boolean canCopy = !hasRestriction(
                        UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, userId);
                // 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 if (clip == null) {
                    // do nothing for canCopy == true and clip == null case
                    // To prevent from NPE happen in 'new ClipData(clip)' when run
                    // android.content.cts.ClipboardManagerTest#testClearPrimaryClip
                } 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 memory.
                    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) {
                        final boolean canCopyIntoProfile = !hasRestriction(
                                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
                        if (canCopyIntoProfile) {
                            setPrimaryClipInternalLocked(
                                    getClipboardLocked(id), clip, uid, sourcePackage);
                        }
                    }
                }
            }
        }
    }

    void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip,
            int uid) {
        synchronized ("mLock") {
            setPrimaryClipInternalLocked(clipboard, clip, uid, null);
        }
    }

    @GuardedBy("mLock")
    private void setPrimaryClipInternalLocked(PerUserClipboard clipboard, @Nullable ClipData clip,
            int uid, @Nullable String sourcePackage) {
        revokeUris(clipboard);
        clipboard.activePermissionOwners.clear();
        if (clip == null && clipboard.primaryClip == null) {
            return;
        }
        clipboard.primaryClip = clip;
        clipboard.mNotifiedUids.clear();
        clipboard.mNotifiedTextClassifierUids.clear();
        if (clip != null) {
            clipboard.primaryClipUid = uid;
            clipboard.mPrimaryClipPackage = sourcePackage;
        } else {
            clipboard.primaryClipUid = android.os.Process.NOBODY_UID;
            clipboard.mPrimaryClipPackage = null;
        }
        if (clip != null) {
            final ClipDescription description = clip.getDescription();
            if (description != null) {
                description.setTimestamp(System.currentTimeMillis());
            }
        }
        sendClipChangedBroadcast(clipboard);
    }

    private void sendClipChangedBroadcast(PerUserClipboard clipboard) {
        final long ident = Binder.clearCallingIdentity();
        final int n = clipboard.primaryClipListeners.beginBroadcast();
        try {
            for (int i = 0; i < n; i++) {
                try {
                    ListenerInfo li = (ListenerInfo)
                            clipboard.primaryClipListeners.getBroadcastCookie(i);

                    if (clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, li.mPackageName,
                            li.mUid, UserHandle.getUserId(li.mUid))) {
                        clipboard.primaryClipListeners.getBroadcastItem(i)
                                .dispatchPrimaryClipChanged();
                    }
                } catch (RemoteException e) {
                    // The RemoteCallbackList will take care of removing
                    // the dead object for us.
                }
            }
        } finally {
            clipboard.primaryClipListeners.finishBroadcast();
            Binder.restoreCallingIdentity(ident);
        }
    }

    @GuardedBy("mLock")
    private void startClassificationLocked(@NonNull ClipData clip, @UserIdInt int userId) {
        CharSequence text = (clip.getItemCount() == 0) ? null : clip.getItemAt(0).getText();
        if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength) {
            clip.getDescription().setClassificationStatus(
                    ClipDescription.CLASSIFICATION_NOT_PERFORMED);
            return;
        }
        TextClassifier classifier;
        final long ident = Binder.clearCallingIdentity();
        try {
            classifier = createTextClassificationManagerAsUser(userId)
                    .createTextClassificationSession(
                            new TextClassificationContext.Builder(
                                    getContext().getPackageName(),
                                    TextClassifier.WIDGET_TYPE_CLIPBOARD
                            ).build()
                    );
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
        if (text.length() > classifier.getMaxGenerateLinksTextLength()) {
            clip.getDescription().setClassificationStatus(
                    ClipDescription.CLASSIFICATION_NOT_PERFORMED);
            return;
        }
        mWorkerHandler.post(() -> doClassification(text, clip, classifier, userId));
    }

    @WorkerThread
    private void doClassification(
            CharSequence text, ClipData clip, TextClassifier classifier, @UserIdInt int userId) {
        TextLinks.Request request = new TextLinks.Request.Builder(text).build();
        TextLinks links = classifier.generateLinks(request);

        // Find the highest confidence for each entity in the text.
        ArrayMap<String, Float> confidences = new ArrayMap<>();
        for (TextLinks.TextLink link : links.getLinks()) {
            for (int i = 0; i < link.getEntityCount(); i++) {
                String entity = link.getEntity(i);
                float conf = link.getConfidenceScore(entity);
                if (conf > confidences.getOrDefault(entity, 0f)) {
                    confidences.put(entity, conf);
                }
            }
        }

        synchronized (mLock) {
            PerUserClipboard clipboard = getClipboardLocked(userId);
            if (clipboard.primaryClip == clip) {
                applyClassificationAndSendBroadcastLocked(
                        clipboard, confidences, links, classifier);

                // Also apply to related profiles if needed
                List<UserInfo> related = getRelatedProfiles(userId);
                if (related != null) {
                    int size = related.size();
                    for (int i = 0; i < size; i++) {
                        int id = related.get(i).id;
                        if (id != userId) {
                            final boolean canCopyIntoProfile = !hasRestriction(
                                    UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
                            if (canCopyIntoProfile) {
                                PerUserClipboard relatedClipboard = getClipboardLocked(id);
                                if (hasTextLocked(relatedClipboard, text)) {
                                    applyClassificationAndSendBroadcastLocked(
                                            relatedClipboard, confidences, links, classifier);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    @GuardedBy("mLock")
    private void applyClassificationAndSendBroadcastLocked(
            PerUserClipboard clipboard, ArrayMap<String, Float> confidences, TextLinks links,
            TextClassifier classifier) {
        clipboard.mTextClassifier = classifier;
        clipboard.primaryClip.getDescription().setConfidenceScores(confidences);
        if (!links.getLinks().isEmpty()) {
            clipboard.primaryClip.getItemAt(0).setTextLinks(links);
        }
        sendClipChangedBroadcast(clipboard);
    }

    @GuardedBy("mLock")
    private boolean hasTextLocked(PerUserClipboard clipboard, @NonNull CharSequence text) {
        return clipboard.primaryClip != null
                && clipboard.primaryClip.getItemCount() > 0
                && text.equals(clipboard.primaryClip.getItemAt(0).getText());
    }

    private boolean isDeviceLocked(@UserIdInt int userId) {
        final long token = Binder.clearCallingIdentity();
        try {
            final KeyguardManager keyguardManager = getContext().getSystemService(
                    KeyguardManager.class);
            return keyguardManager != null && keyguardManager.isDeviceLocked(userId);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    private void checkUriOwner(Uri uri, int sourceUid) {
        if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;

        final long ident = Binder.clearCallingIdentity();
        try {
            // This will throw SecurityException if caller can't grant
            mUgmInternal.checkGrantUriPermission(sourceUid, null,
                    ContentProvider.getUriWithoutUserId(uri),
                    Intent.FLAG_GRANT_READ_URI_PERMISSION,
                    ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    private void checkItemOwner(ClipData.Item item, int uid) {
        if (item.getUri() != null) {
            checkUriOwner(item.getUri(), uid);
        }
        Intent intent = item.getIntent();
        if (intent != null && intent.getData() != null) {
            checkUriOwner(intent.getData(), uid);
        }
    }

    private void checkDataOwner(ClipData data, int uid) {
        final int N = data.getItemCount();
        for (int i=0; i<N; i++) {
            checkItemOwner(data.getItemAt(i), uid);
        }
    }

    private void grantUriPermission(Uri uri, int sourceUid, String targetPkg,
            int targetUserId) {
        if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;

        final long ident = Binder.clearCallingIdentity();
        try {
            mUgm.grantUriPermissionFromOwner(mPermissionOwner, sourceUid, targetPkg,
                    ContentProvider.getUriWithoutUserId(uri),
                    Intent.FLAG_GRANT_READ_URI_PERMISSION,
                    ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)),
                    targetUserId);
        } catch (RemoteException ignored) {
            // Ignored because we're in same process
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    private void grantItemPermission(ClipData.Item item, int sourceUid, String targetPkg,
            int targetUserId) {
        if (item.getUri() != null) {
            grantUriPermission(item.getUri(), sourceUid, targetPkg, targetUserId);
        }
        Intent intent = item.getIntent();
        if (intent != null && intent.getData() != null) {
            grantUriPermission(intent.getData(), sourceUid, targetPkg, targetUserId);
        }
    }

    @GuardedBy("mLock")
    private void addActiveOwnerLocked(int uid, String pkg) {
        final IPackageManager pm = AppGlobals.getPackageManager();
        final int targetUserHandle = UserHandle.getCallingUserId();
        final long oldIdentity = Binder.clearCallingIdentity();
        try {
            PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
            if (pi == null) {
                throw new IllegalArgumentException("Unknown package " + pkg);
            }
            if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
                throw new SecurityException("Calling uid " + uid
                        + " does not own package " + pkg);
            }
        } catch (RemoteException e) {
            // Can't happen; the package manager is in the same process
        } finally {
            Binder.restoreCallingIdentity(oldIdentity);
        }
        PerUserClipboard clipboard = getClipboardLocked(UserHandle.getUserId(uid));
        if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
            final int N = clipboard.primaryClip.getItemCount();
            for (int i=0; i<N; i++) {
                grantItemPermission(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid,
                        pkg, UserHandle.getUserId(uid));
            }
            clipboard.activePermissionOwners.add(pkg);
        }
    }

    private void revokeUriPermission(Uri uri, int sourceUid) {
        if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;

        final long ident = Binder.clearCallingIdentity();
        try {
            mUgmInternal.revokeUriPermissionFromOwner(mPermissionOwner,
                    ContentProvider.getUriWithoutUserId(uri),
                    Intent.FLAG_GRANT_READ_URI_PERMISSION,
                    ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    private void revokeItemPermission(ClipData.Item item, int sourceUid) {
        if (item.getUri() != null) {
            revokeUriPermission(item.getUri(), sourceUid);
        }
        Intent intent = item.getIntent();
        if (intent != null && intent.getData() != null) {
            revokeUriPermission(intent.getData(), sourceUid);
        }
    }

    private void revokeUris(PerUserClipboard clipboard) {
        if (clipboard.primaryClip == null) {
            return;
        }
        final int N = clipboard.primaryClip.getItemCount();
        for (int i=0; i<N; i++) {
            revokeItemPermission(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid);
        }
    }

    private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
            @UserIdInt int userId) {
        return clipboardAccessAllowed(op, callingPackage, uid, userId, true);
    }

    private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
            @UserIdInt int userId, boolean shouldNoteOp) {

        boolean allowed;

        // First, verify package ownership to ensure use below is safe.
        mAppOps.checkPackage(uid, callingPackage);

        // Shell can access the clipboard for testing purposes.
        if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND,
                    callingPackage) == PackageManager.PERMISSION_GRANTED) {
            allowed = true;
        } else {
            // The default IME is always allowed to access the clipboard.
            allowed = isDefaultIme(userId, callingPackage);
        }

        switch (op) {
            case AppOpsManager.OP_READ_CLIPBOARD:
                // Clipboard can only be read by applications with focus..
                // or the application have the INTERNAL_SYSTEM_WINDOW and INTERACT_ACROSS_USERS_FULL
                // at the same time. e.x. SystemUI. It needs to check the window focus of
                // Binder.getCallingUid(). Without checking, the user X can't copy any thing from
                // INTERNAL_SYSTEM_WINDOW to the other applications.
                if (!allowed) {
                    allowed = mWm.isUidFocused(uid)
                            || isInternalSysWindowAppWithWindowFocus(callingPackage);
                }
                if (!allowed && mContentCaptureInternal != null) {
                    // ...or the Content Capture Service
                    // The uid parameter of mContentCaptureInternal.isContentCaptureServiceForUser
                    // is used to check if the uid has the permission BIND_CONTENT_CAPTURE_SERVICE.
                    // if the application has the permission, let it to access user's clipboard.
                    // To passed synthesized uid user#10_app#systemui may not tell the real uid.
                    // userId must pass intending userId. i.e. user#10.
                    allowed = mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId);
                }
                if (!allowed && mAutofillInternal != null) {
                    // ...or the Augmented Autofill Service
                    // The uid parameter of mAutofillInternal.isAugmentedAutofillServiceForUser
                    // is used to check if the uid has the permission BIND_AUTOFILL_SERVICE.
                    // if the application has the permission, let it to access user's clipboard.
                    // To passed synthesized uid user#10_app#systemui may not tell the real uid.
                    // userId must pass intending userId. i.e. user#10.
                    allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId);
                }
                break;
            case AppOpsManager.OP_WRITE_CLIPBOARD:
                // Writing is allowed without focus.
                allowed = true;
                break;
            default:
                throw new IllegalArgumentException("Unknown clipboard appop " + op);
        }
        if (!allowed) {
            Slog.e(TAG, "Denying clipboard access to " + callingPackage
                    + ", application is not in focus nor is it a system service for "
                    + "user " + userId);
            return false;
        }
        // Finally, check the app op.
        int appOpsResult;
        if (shouldNoteOp) {
            appOpsResult = mAppOps.noteOp(op, uid, callingPackage);
        } else {
            appOpsResult = mAppOps.checkOp(op, uid, callingPackage);
        }

        return appOpsResult == AppOpsManager.MODE_ALLOWED;
    }

    private boolean isDefaultIme(int userId, String packageName) {
        String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
                Settings.Secure.DEFAULT_INPUT_METHOD, userId);
        if (!TextUtils.isEmpty(defaultIme)) {
            final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName();
            return imePkg.equals(packageName);
        }
        return false;
    }

    /**
     * Shows a toast to inform the user that an app has accessed the clipboard. This is only done if
     * the setting is enabled, and if the accessing app is not the source of the data and is not the
     * IME, the content capture service, or the autofill service. The notification is also only
     * shown once per clip for each app.
     */
    @GuardedBy("mLock")
    private void showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId,
            PerUserClipboard clipboard) {
        if (clipboard.primaryClip == null) {
            return;
        }
        if (Settings.Secure.getInt(getContext().getContentResolver(),
                Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
                (mShowAccessNotifications ? 1 : 0)) == 0) {
            return;
        }
        // Don't notify if the app accessing the clipboard is the same as the current owner.
        if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) {
            return;
        }
        // Exclude special cases: IME, ContentCapture, Autofill.
        if (isDefaultIme(userId, callingPackage)) {
            return;
        }
        if (mContentCaptureInternal != null
                && mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId)) {
            return;
        }
        if (mAutofillInternal != null
                && mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) {
            return;
        }
        // Don't notify if already notified for this uid and clip.
        if (clipboard.mNotifiedUids.get(uid)) {
            return;
        }
        clipboard.mNotifiedUids.put(uid, true);

        Binder.withCleanCallingIdentity(() -> {
            try {
                CharSequence callingAppLabel = mPm.getApplicationLabel(
                        mPm.getApplicationInfoAsUser(callingPackage, 0, userId));
                String message =
                        getContext().getString(R.string.pasted_from_clipboard, callingAppLabel);
                Slog.i(TAG, message);
                 Toast.makeText(
                        getContext(), UiThread.get().getLooper(), message, Toast.LENGTH_SHORT)
                        .show(); 
            } catch (PackageManager.NameNotFoundException e) {
                // do nothing
            }
        });
    }

    /**
     * Returns true if the provided {@link ClipData} represents a single piece of text. That is, if
     * there is only on {@link ClipData.Item}, and that item contains a non-empty piece of text and
     * no URI or Intent. Note that HTML may be provided along with text so the presence of
     * HtmlText in the clip does not prevent this method returning true.
     */
    private static boolean isText(@NonNull ClipData data) {
        if (data.getItemCount() > 1) {
            return false;
        }
        ClipData.Item item = data.getItemAt(0);

        return !TextUtils.isEmpty(item.getText()) && item.getUri() == null
                && item.getIntent() == null;
    }

    /** Potentially notifies the text classifier that an app is accessing a text clip. */
    @GuardedBy("mLock")
    private void notifyTextClassifierLocked(
            PerUserClipboard clipboard, String callingPackage, int callingUid) {
        if (clipboard.primaryClip == null) {
            return;
        }
        ClipData.Item item = clipboard.primaryClip.getItemAt(0);
        if (item == null) {
            return;
        }
        if (!isText(clipboard.primaryClip)) {
            return;
        }
        TextClassifier textClassifier = clipboard.mTextClassifier;
        // Don't notify text classifier if we haven't used it to annotate the text in the clip.
        if (textClassifier == null) {
            return;
        }
        // Don't notify text classifier if the app reading the clipboard does not have the focus.
        if (!mWm.isUidFocused(callingUid)) {
            return;
        }
        // Don't notify text classifier again if already notified for this uid and clip.
        if (clipboard.mNotifiedTextClassifierUids.get(callingUid)) {
            return;
        }
        clipboard.mNotifiedTextClassifierUids.put(callingUid, true);
        Binder.withCleanCallingIdentity(() -> {
            TextClassifierEvent.TextLinkifyEvent pasteEvent =
                    new TextClassifierEvent.TextLinkifyEvent.Builder(
                            TextClassifierEvent.TYPE_READ_CLIPBOARD)
                            .setEventContext(new TextClassificationContext.Builder(
                                    callingPackage, TextClassifier.WIDGET_TYPE_CLIPBOARD)
                                    .build())
                            .setExtras(
                                    Bundle.forPair("source_package", clipboard.mPrimaryClipPackage))
                            .build();
            textClassifier.onTextClassifierEvent(pasteEvent);
        });
    }

    private TextClassificationManager createTextClassificationManagerAsUser(@UserIdInt int userId) {
        Context context = getContext().createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
        return context.getSystemService(TextClassificationManager.class);
    }
}

5.解决方法:

关键修改点:屏蔽或者删除toast提示

ini 复制代码
  Binder.withCleanCallingIdentity(() -> {
        try {
            CharSequence callingAppLabel = mPm.getApplicationLabel(
                    mPm.getApplicationInfoAsUser(callingPackage, 0, userId));
            String message =
                    getContext().getString(R.string.pasted_from_clipboard, callingAppLabel);
            Slog.i(TAG, message);
            /* Toast.makeText(
                    getContext(), UiThread.get().getLooper(), message, Toast.LENGTH_SHORT)
                    .show(); */
        } catch (PackageManager.NameNotFoundException e) {
            // do nothing
        }
    });
scss 复制代码
package com.android.server.clipboard;

import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IUriGrantsManager;
import android.app.KeyguardManager;
import android.app.UriGrantsManager;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IClipboard;
import android.content.IOnPrimaryClipChangedListener;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IUserManager;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.autofill.AutofillManagerInternal;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextClassifierEvent;
import android.view.textclassifier.TextLinks;
import android.widget.Toast;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.WindowManagerInternal;

import java.util.HashSet;
import java.util.List;
import java.util.function.Consumer;

public class ClipboardService extends SystemService {

    private static final String TAG = "ClipboardService";
    private static final boolean IS_EMULATOR =
            SystemProperties.getBoolean("ro.boot.qemu", false);

    // DeviceConfig properties
    private static final String PROPERTY_MAX_CLASSIFICATION_LENGTH = "max_classification_length";
    private static final int DEFAULT_MAX_CLASSIFICATION_LENGTH = 400;

    private final ActivityManagerInternal mAmInternal;
    private final IUriGrantsManager mUgm;
    private final UriGrantsManagerInternal mUgmInternal;
    private final WindowManagerInternal mWm;
    private final IUserManager mUm;
    private final PackageManager mPm;
    private final AppOpsManager mAppOps;
    private final ContentCaptureManagerInternal mContentCaptureInternal;
    private final AutofillManagerInternal mAutofillInternal;
    private final IBinder mPermissionOwner;
    private final Consumer<ClipData> mEmulatorClipboardMonitor;
    private final Handler mWorkerHandler;

    @GuardedBy("mLock")
    private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();

    @GuardedBy("mLock")
    private boolean mShowAccessNotifications =
            ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS;

    @GuardedBy("mLock")
    private int mMaxClassificationLength = DEFAULT_MAX_CLASSIFICATION_LENGTH;

    private final Object mLock = new Object();

    /**
     * Instantiates the clipboard.
     */
    public ClipboardService(Context context) {
        super(context);

        mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
        mUgm = UriGrantsManager.getService();
        mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
        mWm = LocalServices.getService(WindowManagerInternal.class);
        mPm = getContext().getPackageManager();
        mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
        mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
        mContentCaptureInternal = LocalServices.getService(ContentCaptureManagerInternal.class);
        mAutofillInternal = LocalServices.getService(AutofillManagerInternal.class);
        final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
        mPermissionOwner = permOwner;
        if (IS_EMULATOR) {
            mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
                synchronized (mLock) {
                    setPrimaryClipInternalLocked(getClipboardLocked(0), clip,
                            android.os.Process.SYSTEM_UID, null);
                }
            });
        } else {
            mEmulatorClipboardMonitor = (clip) -> {};
        }

        updateConfig();
        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CLIPBOARD,
                getContext().getMainExecutor(), properties -> updateConfig());

        HandlerThread workerThread = new HandlerThread(TAG);
        workerThread.start();
        mWorkerHandler = workerThread.getThreadHandler();
    }

    @Override
    public void onStart() {
        publishBinderService(Context.CLIPBOARD_SERVICE, new ClipboardImpl());
    }

    @Override
    public void onUserStopped(@NonNull TargetUser user) {
        synchronized (mLock) {
            mClipboards.remove(user.getUserIdentifier());
        }
    }

    private void updateConfig() {
        synchronized (mLock) {
            mShowAccessNotifications = DeviceConfig.getBoolean(
                    DeviceConfig.NAMESPACE_CLIPBOARD,
                    ClipboardManager.DEVICE_CONFIG_SHOW_ACCESS_NOTIFICATIONS,
                    ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS);
            mMaxClassificationLength = DeviceConfig.getInt(DeviceConfig.NAMESPACE_CLIPBOARD,
                    PROPERTY_MAX_CLASSIFICATION_LENGTH, DEFAULT_MAX_CLASSIFICATION_LENGTH);
        }
    }

    private class ListenerInfo {
        final int mUid;
        final String mPackageName;
        ListenerInfo(int uid, String packageName) {
            mUid = uid;
            mPackageName = packageName;
        }
    }

    private class PerUserClipboard {
        final int userId;

        final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
                = new RemoteCallbackList<IOnPrimaryClipChangedListener>();

        /** Current primary clip. */
        ClipData primaryClip;
        /** UID that set {@link #primaryClip}. */
        int primaryClipUid = android.os.Process.NOBODY_UID;
        /** Package of the app that set {@link #primaryClip}. */
        String mPrimaryClipPackage;

        /** Uids that have already triggered a toast notification for {@link #primaryClip} */
        final SparseBooleanArray mNotifiedUids = new SparseBooleanArray();

        /**
         * Uids that have already triggered a notification to text classifier for
         * {@link #primaryClip}.
         */
        final SparseBooleanArray mNotifiedTextClassifierUids = new SparseBooleanArray();

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

        /** The text classifier session that is used to annotate the text in the primary clip. */
        TextClassifier mTextClassifier;

        PerUserClipboard(int userId) {
            this.userId = userId;
        }
    }


    private boolean isInternalSysWindowAppWithWindowFocus(String callingPackage) {
        // Shell can access the clipboard for testing purposes.
        if (mPm.checkPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW,
                    callingPackage) == PackageManager.PERMISSION_GRANTED) {
            if (mWm.isUidFocused(Binder.getCallingUid())) {
                return true;
            }
        }

        return false;
    }


    @UserIdInt
    private int getIntendingUserId(String packageName, @UserIdInt int userId) {
        final int callingUid = Binder.getCallingUid();
        final int callingUserId = UserHandle.getUserId(callingUid);
        if (!UserManager.supportsMultipleUsers() || callingUserId == userId) {
            return callingUserId;
        }

        int intendingUserId = callingUserId;
        intendingUserId = mAmInternal.handleIncomingUser(Binder.getCallingPid(),
                Binder.getCallingUid(), userId, false /* allow all */, ALLOW_FULL_ONLY,
                "checkClipboardServiceCallingUser", packageName);

        return intendingUserId;
    }


    private int getIntendingUid(String packageName, @UserIdInt int userId) {
        return UserHandle.getUid(getIntendingUserId(packageName, userId),
                UserHandle.getAppId(Binder.getCallingUid()));
    }


    private class ClipboardImpl extends IClipboard.Stub {
        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                throws RemoteException {
            try {
                return super.onTransact(code, data, reply, flags);
            } catch (RuntimeException e) {
                if (!(e instanceof SecurityException)) {
                    Slog.wtf("clipboard", "Exception: ", e);
                }
                throw e;
            }

        }

        @Override
        public void setPrimaryClip(ClipData clip, String callingPackage, @UserIdInt int userId) {
            checkAndSetPrimaryClip(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");
            checkAndSetPrimaryClip(clip, callingPackage, userId, sourcePackage);
        }

        private void checkAndSetPrimaryClip(
                ClipData clip, String callingPackage, @UserIdInt int userId, String sourcePackage) {
            if (clip == null || clip.getItemCount() <= 0) {
                throw new IllegalArgumentException("No items");
            }
            final int intendingUid = getIntendingUid(callingPackage, userId);
            final int intendingUserId = UserHandle.getUserId(intendingUid);
            if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
                    intendingUid, intendingUserId)) {
                return;
            }
            checkDataOwner(clip, intendingUid);
            synchronized (mLock) {
                setPrimaryClipInternalLocked(clip, intendingUid, sourcePackage);
            }
        }

        @Override
        public void clearPrimaryClip(String callingPackage, @UserIdInt int userId) {
            final int intendingUid = getIntendingUid(callingPackage, userId);
            final int intendingUserId = UserHandle.getUserId(intendingUid);
            if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
                    intendingUid, intendingUserId)) {
                return;
            }
            synchronized (mLock) {
                setPrimaryClipInternalLocked(null, intendingUid, callingPackage);
            }
        }

        @Override
        public ClipData getPrimaryClip(String pkg, @UserIdInt int userId) {
            final int intendingUid = getIntendingUid(pkg, userId);
            final int intendingUserId = UserHandle.getUserId(intendingUid);
            if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, pkg,
                    intendingUid, intendingUserId)
                    || isDeviceLocked(intendingUserId)) {
                return null;
            }
            synchronized (mLock) {
                try {
                    addActiveOwnerLocked(intendingUid, pkg);
                } catch (SecurityException e) {
                    // Permission could not be granted - URI may be invalid
                    Slog.i(TAG, "Could not grant permission to primary clip. Clearing clipboard.");
                    setPrimaryClipInternalLocked(null, intendingUid, pkg);
                    return null;
                }

                PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
                showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
                notifyTextClassifierLocked(clipboard, pkg, intendingUid);
                return clipboard.primaryClip;
            }
        }

        @Override
        public ClipDescription getPrimaryClipDescription(String callingPackage,
                @UserIdInt int userId) {
            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;
            }
            synchronized (mLock) {
                PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
                return clipboard.primaryClip != null
                        ? clipboard.primaryClip.getDescription() : null;
            }
        }

        @Override
        public boolean hasPrimaryClip(String callingPackage, @UserIdInt int userId) {
            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 false;
            }
            synchronized (mLock) {
                return getClipboardLocked(intendingUserId).primaryClip != null;
            }
        }

        @Override
        public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
                String callingPackage, @UserIdInt int userId) {
            final int intendingUid = getIntendingUid(callingPackage, userId);
            final int intendingUserId = UserHandle.getUserId(intendingUid);
            synchronized (mLock) {
                getClipboardLocked(intendingUserId).primaryClipListeners.register(listener,
                        new ListenerInfo(intendingUid, callingPackage));
            }
        }

        @Override
        public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
                String callingPackage, @UserIdInt int userId) {
            final int intendingUserId = getIntendingUserId(callingPackage, userId);
            synchronized (mLock) {
                getClipboardLocked(intendingUserId).primaryClipListeners.unregister(listener);
            }
        }

        @Override
        public boolean hasClipboardText(String callingPackage, int userId) {
            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 false;
            }
            synchronized (mLock) {
                PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
                if (clipboard.primaryClip != null) {
                    CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
                    return text != null && text.length() > 0;
                }
                return false;
            }
        }

        @Override
        public String getPrimaryClipSource(String callingPackage, int userId) {
            getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
                    "Requires SET_CLIP_SOURCE permission");
            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;
            }
            synchronized (mLock) {
                PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
                if (clipboard.primaryClip != null) {
                    return clipboard.mPrimaryClipPackage;
                }
                return null;
            }
        }
    };

    @GuardedBy("mLock")
    private PerUserClipboard getClipboardLocked(@UserIdInt int userId) {
        PerUserClipboard puc = mClipboards.get(userId);
        if (puc == null) {
            puc = new PerUserClipboard(userId);
            mClipboards.put(userId, puc);
        }
        return puc;
    }

    List<UserInfo> getRelatedProfiles(@UserIdInt int userId) {
        final List<UserInfo> related;
        final long origId = Binder.clearCallingIdentity();
        try {
            related = mUm.getProfiles(userId, true);
        } catch (RemoteException e) {
            Slog.e(TAG, "Remote Exception calling UserManager: " + e);
            return null;
        } finally{
            Binder.restoreCallingIdentity(origId);
        }
        return related;
    }

    /** Check if the user has the given restriction set. Default to true if error occured during
     * calling UserManager, so it fails safe.
     */
    private boolean hasRestriction(String restriction, int userId) {
        try {
            return mUm.hasUserRestriction(restriction, userId);
        } catch (RemoteException e) {
            Slog.e(TAG, "Remote Exception calling UserManager.getUserRestrictions: ", e);
            // Fails safe
            return true;
        }
    }

    void setPrimaryClipInternal(@Nullable ClipData clip, int uid) {
        synchronized (mLock) {
            setPrimaryClipInternalLocked(clip, uid, null);
        }
    }

    @GuardedBy("mLock")
    private void setPrimaryClipInternalLocked(
            @Nullable ClipData clip, int uid, @Nullable String sourcePackage) {
        mEmulatorClipboardMonitor.accept(clip);

        final int userId = UserHandle.getUserId(uid);
        if (clip != null) {
            startClassificationLocked(clip, userId);
        }

        // Update this user
        setPrimaryClipInternalLocked(getClipboardLocked(userId), clip, uid, sourcePackage);

        // 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.
                final boolean canCopy = !hasRestriction(
                        UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, userId);
                // 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 if (clip == null) {
                    // do nothing for canCopy == true and clip == null case
                    // To prevent from NPE happen in 'new ClipData(clip)' when run
                    // android.content.cts.ClipboardManagerTest#testClearPrimaryClip
                } 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 memory.
                    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) {
                        final boolean canCopyIntoProfile = !hasRestriction(
                                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
                        if (canCopyIntoProfile) {
                            setPrimaryClipInternalLocked(
                                    getClipboardLocked(id), clip, uid, sourcePackage);
                        }
                    }
                }
            }
        }
    }

    void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip,
            int uid) {
        synchronized ("mLock") {
            setPrimaryClipInternalLocked(clipboard, clip, uid, null);
        }
    }

    @GuardedBy("mLock")
    private void setPrimaryClipInternalLocked(PerUserClipboard clipboard, @Nullable ClipData clip,
            int uid, @Nullable String sourcePackage) {
        revokeUris(clipboard);
        clipboard.activePermissionOwners.clear();
        if (clip == null && clipboard.primaryClip == null) {
            return;
        }
        clipboard.primaryClip = clip;
        clipboard.mNotifiedUids.clear();
        clipboard.mNotifiedTextClassifierUids.clear();
        if (clip != null) {
            clipboard.primaryClipUid = uid;
            clipboard.mPrimaryClipPackage = sourcePackage;
        } else {
            clipboard.primaryClipUid = android.os.Process.NOBODY_UID;
            clipboard.mPrimaryClipPackage = null;
        }
        if (clip != null) {
            final ClipDescription description = clip.getDescription();
            if (description != null) {
                description.setTimestamp(System.currentTimeMillis());
            }
        }
        sendClipChangedBroadcast(clipboard);
    }

    private void sendClipChangedBroadcast(PerUserClipboard clipboard) {
        final long ident = Binder.clearCallingIdentity();
        final int n = clipboard.primaryClipListeners.beginBroadcast();
        try {
            for (int i = 0; i < n; i++) {
                try {
                    ListenerInfo li = (ListenerInfo)
                            clipboard.primaryClipListeners.getBroadcastCookie(i);

                    if (clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, li.mPackageName,
                            li.mUid, UserHandle.getUserId(li.mUid))) {
                        clipboard.primaryClipListeners.getBroadcastItem(i)
                                .dispatchPrimaryClipChanged();
                    }
                } catch (RemoteException e) {
                    // The RemoteCallbackList will take care of removing
                    // the dead object for us.
                }
            }
        } finally {
            clipboard.primaryClipListeners.finishBroadcast();
            Binder.restoreCallingIdentity(ident);
        }
    }

    @GuardedBy("mLock")
    private void startClassificationLocked(@NonNull ClipData clip, @UserIdInt int userId) {
        CharSequence text = (clip.getItemCount() == 0) ? null : clip.getItemAt(0).getText();
        if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength) {
            clip.getDescription().setClassificationStatus(
                    ClipDescription.CLASSIFICATION_NOT_PERFORMED);
            return;
        }
        TextClassifier classifier;
        final long ident = Binder.clearCallingIdentity();
        try {
            classifier = createTextClassificationManagerAsUser(userId)
                    .createTextClassificationSession(
                            new TextClassificationContext.Builder(
                                    getContext().getPackageName(),
                                    TextClassifier.WIDGET_TYPE_CLIPBOARD
                            ).build()
                    );
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
        if (text.length() > classifier.getMaxGenerateLinksTextLength()) {
            clip.getDescription().setClassificationStatus(
                    ClipDescription.CLASSIFICATION_NOT_PERFORMED);
            return;
        }
        mWorkerHandler.post(() -> doClassification(text, clip, classifier, userId));
    }

    @WorkerThread
    private void doClassification(
            CharSequence text, ClipData clip, TextClassifier classifier, @UserIdInt int userId) {
        TextLinks.Request request = new TextLinks.Request.Builder(text).build();
        TextLinks links = classifier.generateLinks(request);

        // Find the highest confidence for each entity in the text.
        ArrayMap<String, Float> confidences = new ArrayMap<>();
        for (TextLinks.TextLink link : links.getLinks()) {
            for (int i = 0; i < link.getEntityCount(); i++) {
                String entity = link.getEntity(i);
                float conf = link.getConfidenceScore(entity);
                if (conf > confidences.getOrDefault(entity, 0f)) {
                    confidences.put(entity, conf);
                }
            }
        }

        synchronized (mLock) {
            PerUserClipboard clipboard = getClipboardLocked(userId);
            if (clipboard.primaryClip == clip) {
                applyClassificationAndSendBroadcastLocked(
                        clipboard, confidences, links, classifier);

                // Also apply to related profiles if needed
                List<UserInfo> related = getRelatedProfiles(userId);
                if (related != null) {
                    int size = related.size();
                    for (int i = 0; i < size; i++) {
                        int id = related.get(i).id;
                        if (id != userId) {
                            final boolean canCopyIntoProfile = !hasRestriction(
                                    UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
                            if (canCopyIntoProfile) {
                                PerUserClipboard relatedClipboard = getClipboardLocked(id);
                                if (hasTextLocked(relatedClipboard, text)) {
                                    applyClassificationAndSendBroadcastLocked(
                                            relatedClipboard, confidences, links, classifier);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    @GuardedBy("mLock")
    private void applyClassificationAndSendBroadcastLocked(
            PerUserClipboard clipboard, ArrayMap<String, Float> confidences, TextLinks links,
            TextClassifier classifier) {
        clipboard.mTextClassifier = classifier;
        clipboard.primaryClip.getDescription().setConfidenceScores(confidences);
        if (!links.getLinks().isEmpty()) {
            clipboard.primaryClip.getItemAt(0).setTextLinks(links);
        }
        sendClipChangedBroadcast(clipboard);
    }

    @GuardedBy("mLock")
    private boolean hasTextLocked(PerUserClipboard clipboard, @NonNull CharSequence text) {
        return clipboard.primaryClip != null
                && clipboard.primaryClip.getItemCount() > 0
                && text.equals(clipboard.primaryClip.getItemAt(0).getText());
    }

    private boolean isDeviceLocked(@UserIdInt int userId) {
        final long token = Binder.clearCallingIdentity();
        try {
            final KeyguardManager keyguardManager = getContext().getSystemService(
                    KeyguardManager.class);
            return keyguardManager != null && keyguardManager.isDeviceLocked(userId);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    private void checkUriOwner(Uri uri, int sourceUid) {
        if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;

        final long ident = Binder.clearCallingIdentity();
        try {
            // This will throw SecurityException if caller can't grant
            mUgmInternal.checkGrantUriPermission(sourceUid, null,
                    ContentProvider.getUriWithoutUserId(uri),
                    Intent.FLAG_GRANT_READ_URI_PERMISSION,
                    ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    private void checkItemOwner(ClipData.Item item, int uid) {
        if (item.getUri() != null) {
            checkUriOwner(item.getUri(), uid);
        }
        Intent intent = item.getIntent();
        if (intent != null && intent.getData() != null) {
            checkUriOwner(intent.getData(), uid);
        }
    }

    private void checkDataOwner(ClipData data, int uid) {
        final int N = data.getItemCount();
        for (int i=0; i<N; i++) {
            checkItemOwner(data.getItemAt(i), uid);
        }
    }

    private void grantUriPermission(Uri uri, int sourceUid, String targetPkg,
            int targetUserId) {
        if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;

        final long ident = Binder.clearCallingIdentity();
        try {
            mUgm.grantUriPermissionFromOwner(mPermissionOwner, sourceUid, targetPkg,
                    ContentProvider.getUriWithoutUserId(uri),
                    Intent.FLAG_GRANT_READ_URI_PERMISSION,
                    ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)),
                    targetUserId);
        } catch (RemoteException ignored) {
            // Ignored because we're in same process
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    private void grantItemPermission(ClipData.Item item, int sourceUid, String targetPkg,
            int targetUserId) {
        if (item.getUri() != null) {
            grantUriPermission(item.getUri(), sourceUid, targetPkg, targetUserId);
        }
        Intent intent = item.getIntent();
        if (intent != null && intent.getData() != null) {
            grantUriPermission(intent.getData(), sourceUid, targetPkg, targetUserId);
        }
    }

    @GuardedBy("mLock")
    private void addActiveOwnerLocked(int uid, String pkg) {
        final IPackageManager pm = AppGlobals.getPackageManager();
        final int targetUserHandle = UserHandle.getCallingUserId();
        final long oldIdentity = Binder.clearCallingIdentity();
        try {
            PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
            if (pi == null) {
                throw new IllegalArgumentException("Unknown package " + pkg);
            }
            if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
                throw new SecurityException("Calling uid " + uid
                        + " does not own package " + pkg);
            }
        } catch (RemoteException e) {
            // Can't happen; the package manager is in the same process
        } finally {
            Binder.restoreCallingIdentity(oldIdentity);
        }
        PerUserClipboard clipboard = getClipboardLocked(UserHandle.getUserId(uid));
        if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
            final int N = clipboard.primaryClip.getItemCount();
            for (int i=0; i<N; i++) {
                grantItemPermission(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid,
                        pkg, UserHandle.getUserId(uid));
            }
            clipboard.activePermissionOwners.add(pkg);
        }
    }

    private void revokeUriPermission(Uri uri, int sourceUid) {
        if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;

        final long ident = Binder.clearCallingIdentity();
        try {
            mUgmInternal.revokeUriPermissionFromOwner(mPermissionOwner,
                    ContentProvider.getUriWithoutUserId(uri),
                    Intent.FLAG_GRANT_READ_URI_PERMISSION,
                    ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    private void revokeItemPermission(ClipData.Item item, int sourceUid) {
        if (item.getUri() != null) {
            revokeUriPermission(item.getUri(), sourceUid);
        }
        Intent intent = item.getIntent();
        if (intent != null && intent.getData() != null) {
            revokeUriPermission(intent.getData(), sourceUid);
        }
    }

    private void revokeUris(PerUserClipboard clipboard) {
        if (clipboard.primaryClip == null) {
            return;
        }
        final int N = clipboard.primaryClip.getItemCount();
        for (int i=0; i<N; i++) {
            revokeItemPermission(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid);
        }
    }

    private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
            @UserIdInt int userId) {
        return clipboardAccessAllowed(op, callingPackage, uid, userId, true);
    }

    private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
            @UserIdInt int userId, boolean shouldNoteOp) {

        boolean allowed;

        // First, verify package ownership to ensure use below is safe.
        mAppOps.checkPackage(uid, callingPackage);

        // Shell can access the clipboard for testing purposes.
        if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND,
                    callingPackage) == PackageManager.PERMISSION_GRANTED) {
            allowed = true;
        } else {
            // The default IME is always allowed to access the clipboard.
            allowed = isDefaultIme(userId, callingPackage);
        }

        switch (op) {
            case AppOpsManager.OP_READ_CLIPBOARD:

                if (!allowed) {
                    allowed = mWm.isUidFocused(uid)
                            || isInternalSysWindowAppWithWindowFocus(callingPackage);
                }
                if (!allowed && mContentCaptureInternal != null) {

                    allowed = mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId);
                }
                if (!allowed && mAutofillInternal != null) {

                    allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId);
                }
                break;
            case AppOpsManager.OP_WRITE_CLIPBOARD:
                // Writing is allowed without focus.
                allowed = true;
                break;
            default:
                throw new IllegalArgumentException("Unknown clipboard appop " + op);
        }
        if (!allowed) {
            Slog.e(TAG, "Denying clipboard access to " + callingPackage
                    + ", application is not in focus nor is it a system service for "
                    + "user " + userId);
            return false;
        }
        // Finally, check the app op.
        int appOpsResult;
        if (shouldNoteOp) {
            appOpsResult = mAppOps.noteOp(op, uid, callingPackage);
        } else {
            appOpsResult = mAppOps.checkOp(op, uid, callingPackage);
        }

        return appOpsResult == AppOpsManager.MODE_ALLOWED;
    }

    private boolean isDefaultIme(int userId, String packageName) {
        String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
                Settings.Secure.DEFAULT_INPUT_METHOD, userId);
        if (!TextUtils.isEmpty(defaultIme)) {
            final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName();
            return imePkg.equals(packageName);
        }
        return false;
    }

    /**
     * Shows a toast to inform the user that an app has accessed the clipboard. This is only done if
     * the setting is enabled, and if the accessing app is not the source of the data and is not the
     * IME, the content capture service, or the autofill service. The notification is also only
     * shown once per clip for each app.
     */
    @GuardedBy("mLock")
    private void showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId,
            PerUserClipboard clipboard) {
        if (clipboard.primaryClip == null) {
            return;
        }
        if (Settings.Secure.getInt(getContext().getContentResolver(),
                Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
                (mShowAccessNotifications ? 1 : 0)) == 0) {
            return;
        }
        // Don't notify if the app accessing the clipboard is the same as the current owner.
        if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) {
            return;
        }
        // Exclude special cases: IME, ContentCapture, Autofill.
        if (isDefaultIme(userId, callingPackage)) {
            return;
        }
        if (mContentCaptureInternal != null
                && mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId)) {
            return;
        }
        if (mAutofillInternal != null
                && mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) {
            return;
        }
        // Don't notify if already notified for this uid and clip.
        if (clipboard.mNotifiedUids.get(uid)) {
            return;
        }
        clipboard.mNotifiedUids.put(uid, true);

        Binder.withCleanCallingIdentity(() -> {
            try {
                CharSequence callingAppLabel = mPm.getApplicationLabel(
                        mPm.getApplicationInfoAsUser(callingPackage, 0, userId));
                String message =
                        getContext().getString(R.string.pasted_from_clipboard, callingAppLabel);
                Slog.i(TAG, message);
                /* Toast.makeText(
                        getContext(), UiThread.get().getLooper(), message, Toast.LENGTH_SHORT)
                        .show(); */
            } catch (PackageManager.NameNotFoundException e) {
                // do nothing
            }
        });
    }

    /**
     * Returns true if the provided {@link ClipData} represents a single piece of text. That is, if
     * there is only on {@link ClipData.Item}, and that item contains a non-empty piece of text and
     * no URI or Intent. Note that HTML may be provided along with text so the presence of
     * HtmlText in the clip does not prevent this method returning true.
     */
    private static boolean isText(@NonNull ClipData data) {
        if (data.getItemCount() > 1) {
            return false;
        }
        ClipData.Item item = data.getItemAt(0);

        return !TextUtils.isEmpty(item.getText()) && item.getUri() == null
                && item.getIntent() == null;
    }

    /** Potentially notifies the text classifier that an app is accessing a text clip. */
    @GuardedBy("mLock")
    private void notifyTextClassifierLocked(
            PerUserClipboard clipboard, String callingPackage, int callingUid) {
        if (clipboard.primaryClip == null) {
            return;
        }
        ClipData.Item item = clipboard.primaryClip.getItemAt(0);
        if (item == null) {
            return;
        }
        if (!isText(clipboard.primaryClip)) {
            return;
        }
        TextClassifier textClassifier = clipboard.mTextClassifier;
        // Don't notify text classifier if we haven't used it to annotate the text in the clip.
        if (textClassifier == null) {
            return;
        }
        // Don't notify text classifier if the app reading the clipboard does not have the focus.
        if (!mWm.isUidFocused(callingUid)) {
            return;
        }
        // Don't notify text classifier again if already notified for this uid and clip.
        if (clipboard.mNotifiedTextClassifierUids.get(callingUid)) {
            return;
        }
        clipboard.mNotifiedTextClassifierUids.put(callingUid, true);
        Binder.withCleanCallingIdentity(() -> {
            TextClassifierEvent.TextLinkifyEvent pasteEvent =
                    new TextClassifierEvent.TextLinkifyEvent.Builder(
                            TextClassifierEvent.TYPE_READ_CLIPBOARD)
                            .setEventContext(new TextClassificationContext.Builder(
                                    callingPackage, TextClassifier.WIDGET_TYPE_CLIPBOARD)
                                    .build())
                            .setExtras(
                                    Bundle.forPair("source_package", clipboard.mPrimaryClipPackage))
                            .build();
            textClassifier.onTextClassifierEvent(pasteEvent);
        });
    }

    private TextClassificationManager createTextClassificationManagerAsUser(@UserIdInt int userId) {
        Context context = getContext().createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
        return context.getSystemService(TextClassificationManager.class);
    }
}

6.解决后效果如下:

再次到浏览器使用剪切板复制文本内容,发现没有出现toast,此bug已解决

7.总结:

今天遇到的问题就是一个toast提示,需要根据日志排查定位到问题原因,解决也很简单,删除或者注释toast即可。

相关推荐
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android