blob: 63b4288ce055ae7d7e4ee9c68df0d477cda88a26 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.clipboardoverlay;
import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import javax.inject.Inject;
import javax.inject.Provider;
/**
* ClipboardListener brings up a clipboard overlay when something is copied to the clipboard.
*/
@SysUISingleton
public class ClipboardListener implements
CoreStartable, ClipboardManager.OnPrimaryClipChangedListener {
private static final String TAG = "ClipboardListener";
@VisibleForTesting
static final String SHELL_PACKAGE = "com.android.shell";
@VisibleForTesting
static final String EXTRA_SUPPRESS_OVERLAY =
"com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY";
private final Context mContext;
private final Provider<ClipboardOverlayController> mOverlayProvider;
private final ClipboardToast mClipboardToast;
private final ClipboardManager mClipboardManager;
private final UiEventLogger mUiEventLogger;
private ClipboardOverlay mClipboardOverlay;
@Inject
public ClipboardListener(Context context,
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardToast clipboardToast,
ClipboardManager clipboardManager,
UiEventLogger uiEventLogger) {
mContext = context;
mOverlayProvider = clipboardOverlayControllerProvider;
mClipboardToast = clipboardToast;
mClipboardManager = clipboardManager;
mUiEventLogger = uiEventLogger;
}
@Override
public void start() {
mClipboardManager.addPrimaryClipChangedListener(this);
}
@Override
public void onPrimaryClipChanged() {
if (!mClipboardManager.hasPrimaryClip()) {
return;
}
String clipSource = mClipboardManager.getPrimaryClipSource();
ClipData clipData = mClipboardManager.getPrimaryClip();
if (shouldSuppressOverlay(clipData, clipSource, isEmulator())) {
Log.i(TAG, "Clipboard overlay suppressed.");
return;
}
if (!isUserSetupComplete() // user should not access intents from this state
|| clipData == null // shouldn't happen, but just in case
|| clipData.getItemCount() == 0) {
if (shouldShowToast(clipData)) {
mUiEventLogger.log(CLIPBOARD_TOAST_SHOWN, 0, clipSource);
mClipboardToast.showCopiedToast();
}
return;
}
if (mClipboardOverlay == null) {
mClipboardOverlay = mOverlayProvider.get();
mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource);
} else {
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
}
mClipboardOverlay.setClipData(clipData, clipSource);
mClipboardOverlay.setOnSessionCompleteListener(() -> {
// Session is complete, free memory until it's needed again.
mClipboardOverlay = null;
});
}
// The overlay is suppressed if EXTRA_SUPPRESS_OVERLAY is true and the device is an emulator or
// the source package is SHELL_PACKAGE. This is meant to suppress the overlay when the emulator
// or a mirrored device is syncing the clipboard.
@VisibleForTesting
static boolean shouldSuppressOverlay(ClipData clipData, String clipSource,
boolean isEmulator) {
if (!(isEmulator || SHELL_PACKAGE.equals(clipSource))) {
return false;
}
if (clipData == null || clipData.getDescription().getExtras() == null) {
return false;
}
return clipData.getDescription().getExtras().getBoolean(EXTRA_SUPPRESS_OVERLAY, false);
}
boolean shouldShowToast(ClipData clipData) {
if (clipData == null) {
return false;
} else if (clipData.getDescription().getClassificationStatus() == CLASSIFICATION_COMPLETE) {
// only show for classification complete if we aren't already showing a toast, to ignore
// the duplicate ClipData with classification
return !mClipboardToast.isShowing();
}
return true;
}
private static boolean isEmulator() {
return SystemProperties.getBoolean("ro.boot.qemu", false);
}
private boolean isUserSetupComplete() {
return Settings.Secure.getInt(mContext.getContentResolver(),
SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
}
interface ClipboardOverlay {
void setClipData(ClipData clipData, String clipSource);
void setOnSessionCompleteListener(Runnable runnable);
}
}