Merge "Remove clipboard refactor flag" into tm-qpr-dev

This commit is contained in:
Miranda Kephart
2023-01-26 17:03:33 +00:00
committed by Android (Google) Code Review
7 changed files with 8 additions and 1302 deletions

View File

@@ -566,11 +566,6 @@ public final class SystemUiDeviceConfigFlags {
*/ */
public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification"; public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
/**
* (boolean) Whether the clipboard overlay is enabled.
*/
public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
/** /**
* (boolean) Whether widget provider info would be saved to / loaded from system persistence * (boolean) Whether widget provider info would be saved to / loaded from system persistence
* layer as opposed to individual manifests in respective apps. * layer as opposed to individual manifests in respective apps.

View File

@@ -1,160 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<com.android.systemui.screenshot.DraggableConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/clipboard_ui"
android:theme="@style/FloatingOverlay"
android:alpha="0"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/clipboard_overlay_window_name">
<ImageView
android:id="@+id/actions_container_background"
android:visibility="gone"
android:layout_height="0dp"
android:layout_width="0dp"
android:elevation="4dp"
android:background="@drawable/action_chip_container_background"
android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
app:layout_constraintBottom_toBottomOf="@+id/actions_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/actions_container"
app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
<HorizontalScrollView
android:id="@+id/actions_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
android:paddingEnd="@dimen/overlay_action_container_padding_right"
android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="4dp"
android:scrollbars="none"
android:layout_marginBottom="4dp"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_percent="1.0"
app:layout_constraintWidth_max="wrap"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/preview_border"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:id="@+id/actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<include layout="@layout/overlay_action_chip"
android:id="@+id/share_chip"/>
<include layout="@layout/overlay_action_chip"
android:id="@+id/remote_copy_chip"/>
<include layout="@layout/overlay_action_chip"
android:id="@+id/edit_chip"/>
</LinearLayout>
</HorizontalScrollView>
<View
android:id="@+id/preview_border"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="@dimen/overlay_offset_x"
android:layout_marginBottom="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:elevation="7dp"
app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
android:background="@drawable/overlay_border"/>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/clipboard_preview_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierMargin="@dimen/overlay_border_width"
app:barrierDirection="end"
app:constraint_referenced_ids="clipboard_preview"/>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/clipboard_preview_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top"
app:barrierMargin="@dimen/overlay_border_width_neg"
app:constraint_referenced_ids="clipboard_preview"/>
<FrameLayout
android:id="@+id/clipboard_preview"
android:elevation="7dp"
android:background="@drawable/overlay_preview_background"
android:clipChildren="true"
android:clipToOutline="true"
android:clipToPadding="true"
android:layout_width="@dimen/clipboard_preview_size"
android:layout_margin="@dimen/overlay_border_width"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:layout_constraintBottom_toBottomOf="@id/preview_border"
app:layout_constraintStart_toStartOf="@id/preview_border"
app:layout_constraintEnd_toEndOf="@id/preview_border"
app:layout_constraintTop_toTopOf="@id/preview_border">
<TextView android:id="@+id/text_preview"
android:textFontWeight="500"
android:padding="8dp"
android:gravity="center|start"
android:ellipsize="end"
android:autoSizeTextType="uniform"
android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
android:textColor="?attr/overlayButtonTextColor"
android:textColorLink="?attr/overlayButtonTextColor"
android:background="?androidprv:attr/colorAccentSecondary"
android:layout_width="@dimen/clipboard_preview_size"
android:layout_height="@dimen/clipboard_preview_size"/>
<ImageView
android:id="@+id/image_preview"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:contentDescription="@string/clipboard_image_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/hidden_preview"
android:visibility="gone"
android:textFontWeight="500"
android:padding="8dp"
android:gravity="center"
android:textSize="14sp"
android:textColor="?attr/overlayButtonTextColor"
android:background="?androidprv:attr/colorAccentSecondary"
android:layout_width="@dimen/clipboard_preview_size"
android:layout_height="@dimen/clipboard_preview_size"/>
</FrameLayout>
<FrameLayout
android:id="@+id/dismiss_button"
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
android:elevation="10dp"
android:visibility="gone"
android:alpha="0"
app:layout_constraintStart_toEndOf="@id/clipboard_preview"
app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
app:layout_constraintTop_toTopOf="@id/clipboard_preview"
app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
android:contentDescription="@string/clipboard_dismiss_description">
<ImageView
android:id="@+id/dismiss_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/overlay_dismiss_button_margin"
android:src="@drawable/overlay_cancel"/>
</FrameLayout>
</com.android.systemui.screenshot.DraggableConstraintLayout>

View File

@@ -18,7 +18,6 @@ package com.android.systemui.clipboardoverlay;
import static android.content.ClipDescription.CLASSIFICATION_COMPLETE; import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED; 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_OVERLAY_UPDATED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
@@ -29,7 +28,6 @@ import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.os.SystemProperties; import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
@@ -37,9 +35,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable; import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.util.DeviceConfigProxy;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider; import javax.inject.Provider;
@@ -59,42 +54,28 @@ public class ClipboardListener implements
"com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY"; "com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY";
private final Context mContext; private final Context mContext;
private final DeviceConfigProxy mDeviceConfig;
private final Provider<ClipboardOverlayController> mOverlayProvider; private final Provider<ClipboardOverlayController> mOverlayProvider;
private final ClipboardOverlayControllerLegacyFactory mOverlayFactory;
private final ClipboardToast mClipboardToast; private final ClipboardToast mClipboardToast;
private final ClipboardManager mClipboardManager; private final ClipboardManager mClipboardManager;
private final UiEventLogger mUiEventLogger; private final UiEventLogger mUiEventLogger;
private final FeatureFlags mFeatureFlags;
private boolean mUsingNewOverlay;
private ClipboardOverlay mClipboardOverlay; private ClipboardOverlay mClipboardOverlay;
@Inject @Inject
public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy, public ClipboardListener(Context context,
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider, Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardOverlayControllerLegacyFactory overlayFactory,
ClipboardToast clipboardToast, ClipboardToast clipboardToast,
ClipboardManager clipboardManager, ClipboardManager clipboardManager,
UiEventLogger uiEventLogger, UiEventLogger uiEventLogger) {
FeatureFlags featureFlags) {
mContext = context; mContext = context;
mDeviceConfig = deviceConfigProxy;
mOverlayProvider = clipboardOverlayControllerProvider; mOverlayProvider = clipboardOverlayControllerProvider;
mOverlayFactory = overlayFactory;
mClipboardToast = clipboardToast; mClipboardToast = clipboardToast;
mClipboardManager = clipboardManager; mClipboardManager = clipboardManager;
mUiEventLogger = uiEventLogger; mUiEventLogger = uiEventLogger;
mFeatureFlags = featureFlags;
mUsingNewOverlay = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
} }
@Override @Override
public void start() { public void start() {
if (mDeviceConfig.getBoolean( mClipboardManager.addPrimaryClipChangedListener(this);
DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, true)) {
mClipboardManager.addPrimaryClipChangedListener(this);
}
} }
@Override @Override
@@ -120,14 +101,8 @@ public class ClipboardListener implements
return; return;
} }
boolean enabled = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR); if (mClipboardOverlay == null) {
if (mClipboardOverlay == null || enabled != mUsingNewOverlay) { mClipboardOverlay = mOverlayProvider.get();
mUsingNewOverlay = enabled;
if (enabled) {
mClipboardOverlay = mOverlayProvider.get();
} else {
mClipboardOverlay = mOverlayFactory.create(mContext);
}
mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource); mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource);
} else { } else {
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource); mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);

View File

@@ -1,963 +0,0 @@
/*
* 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.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EDIT_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
import static java.util.Objects.requireNonNull;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.MainThread;
import android.app.ICompatCameraControlCallback;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Looper;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
import android.util.TypedValue;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLinks;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.screenshot.DraggableConstraintLayout;
import com.android.systemui.screenshot.FloatingWindowUtil;
import com.android.systemui.screenshot.OverlayActionChip;
import com.android.systemui.screenshot.TimeoutHandler;
import java.io.IOException;
import java.util.ArrayList;
/**
* Controls state and UI for the overlay that appears when something is added to the clipboard
*/
public class ClipboardOverlayControllerLegacy implements ClipboardListener.ClipboardOverlay {
private static final String TAG = "ClipboardOverlayCtrlr";
private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
/** Constants for screenshot/copy deconflicting */
public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT";
public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF";
public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
private static final int FONT_SEARCH_STEP_PX = 4;
private final Context mContext;
private final ClipboardLogger mClipboardLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DisplayManager mDisplayManager;
private final DisplayMetrics mDisplayMetrics;
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mWindowLayoutParams;
private final PhoneWindow mWindow;
private final TimeoutHandler mTimeoutHandler;
private final AccessibilityManager mAccessibilityManager;
private final TextClassifier mTextClassifier;
private final DraggableConstraintLayout mView;
private final View mClipboardPreview;
private final ImageView mImagePreview;
private final TextView mTextPreview;
private final TextView mHiddenPreview;
private final View mPreviewBorder;
private final OverlayActionChip mEditChip;
private final OverlayActionChip mShareChip;
private final OverlayActionChip mRemoteCopyChip;
private final View mActionContainerBackground;
private final View mDismissButton;
private final LinearLayout mActionContainer;
private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
private Runnable mOnSessionCompleteListener;
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
private BroadcastReceiver mCloseDialogsReceiver;
private BroadcastReceiver mScreenshotReceiver;
private boolean mBlockAttach = false;
private Animator mExitAnimator;
private Animator mEnterAnimator;
private final int mOrientation;
private boolean mKeyboardVisible;
public ClipboardOverlayControllerLegacy(Context context,
BroadcastDispatcher broadcastDispatcher,
BroadcastSender broadcastSender,
TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
mBroadcastDispatcher = broadcastDispatcher;
mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
final Context displayContext = context.createDisplayContext(getDefaultDisplay());
mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mClipboardLogger = new ClipboardLogger(uiEventLogger);
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
.getTextClassifier();
mWindowManager = mContext.getSystemService(WindowManager.class);
mDisplayMetrics = new DisplayMetrics();
mContext.getDisplay().getRealMetrics(mDisplayMetrics);
mTimeoutHandler = timeoutHandler;
mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
// Setup the window that we are going to use
mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
mWindowLayoutParams.setTitle("ClipboardOverlay");
mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
mWindow.setWindowManager(mWindowManager, null, null);
setWindowFocusable(false);
mView = (DraggableConstraintLayout)
LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay_legacy, null);
mActionContainerBackground =
requireNonNull(mView.findViewById(R.id.actions_container_background));
mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview));
mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
mShareChip = requireNonNull(mView.findViewById(R.id.share_chip));
mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
mEditChip.setAlpha(1);
mShareChip.setAlpha(1);
mRemoteCopyChip.setAlpha(1);
mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
@Override
public void onInteraction() {
mTimeoutHandler.resetTimeout();
}
@Override
public void onSwipeDismissInitiated(Animator animator) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
mExitAnimator = animator;
}
@Override
public void onDismissComplete() {
hideImmediate();
}
});
mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
int availableHeight = mTextPreview.getHeight()
- (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
return true;
});
mDismissButton.setOnClickListener(view -> {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
animateOut();
});
mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
mRemoteCopyChip.setIcon(
Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
mOrientation = mContext.getResources().getConfiguration().orientation;
attachWindow();
withWindowAttached(() -> {
mWindow.setContentView(mView);
WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
updateInsets(insets);
mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
WindowInsets insets =
mWindowManager.getCurrentWindowMetrics().getWindowInsets();
boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
if (keyboardVisible != mKeyboardVisible) {
mKeyboardVisible = keyboardVisible;
updateInsets(insets);
}
}
});
mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
new ViewRootImpl.ActivityConfigCallback() {
@Override
public void onConfigurationChanged(Configuration overrideConfig,
int newDisplayId) {
if (mContext.getResources().getConfiguration().orientation
!= mOrientation) {
mClipboardLogger.logSessionComplete(
CLIPBOARD_OVERLAY_DISMISSED_OTHER);
hideImmediate();
}
}
@Override
public void requestCompatCameraControl(
boolean showControl, boolean transformationApplied,
ICompatCameraControlCallback callback) {
Log.w(TAG, "unexpected requestCompatCameraControl call");
}
});
});
mTimeoutHandler.setOnTimeoutRunnable(() -> {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
animateOut();
});
mCloseDialogsReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
animateOut();
}
}
};
mBroadcastDispatcher.registerReceiver(mCloseDialogsReceiver,
new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
mScreenshotReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (SCREENSHOT_ACTION.equals(intent.getAction())) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
animateOut();
}
}
};
mBroadcastDispatcher.registerReceiver(mScreenshotReceiver,
new IntentFilter(SCREENSHOT_ACTION), null, null, Context.RECEIVER_EXPORTED,
SELF_PERMISSION);
monitorOutsideTouches();
Intent copyIntent = new Intent(COPY_OVERLAY_ACTION);
// Set package name so the system knows it's safe
copyIntent.setPackage(mContext.getPackageName());
broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
}
@Override // ClipboardListener.ClipboardOverlay
public void setClipData(ClipData clipData, String clipSource) {
if (mExitAnimator != null && mExitAnimator.isRunning()) {
mExitAnimator.cancel();
}
reset();
String accessibilityAnnouncement;
boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
&& clipData.getDescription().getExtras()
.getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
if (clipData == null || clipData.getItemCount() == 0) {
showTextPreview(
mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
mTextPreview);
accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
} else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
ClipData.Item item = clipData.getItemAt(0);
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
if (item.getTextLinks() != null) {
AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
}
}
if (isSensitive) {
showEditableText(
mContext.getResources().getString(R.string.clipboard_asterisks), true);
} else {
showEditableText(item.getText(), false);
}
showShareChip(clipData);
accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
} else if (clipData.getItemAt(0).getUri() != null) {
if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
showShareChip(clipData);
accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
} else {
accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
}
} else {
showTextPreview(
mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
mTextPreview);
accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
}
Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
// Only show remote copy if it's available.
PackageManager packageManager = mContext.getPackageManager();
if (packageManager.resolveActivity(
remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
mRemoteCopyChip.setContentDescription(
mContext.getString(R.string.clipboard_send_nearby_description));
mRemoteCopyChip.setVisibility(View.VISIBLE);
mRemoteCopyChip.setOnClickListener((v) -> {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
mContext.startActivity(remoteCopyIntent);
animateOut();
});
mActionContainerBackground.setVisibility(View.VISIBLE);
} else {
mRemoteCopyChip.setVisibility(View.GONE);
}
withWindowAttached(() -> {
if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
mView.post(this::animateIn);
}
mView.announceForAccessibility(accessibilityAnnouncement);
});
mTimeoutHandler.resetTimeout();
}
@Override // ClipboardListener.ClipboardOverlay
public void setOnSessionCompleteListener(Runnable runnable) {
mOnSessionCompleteListener = runnable;
}
private void classifyText(ClipData.Item item, String source) {
ArrayList<RemoteAction> actions = new ArrayList<>();
for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
TextClassification classification = mTextClassifier.classifyText(
item.getText(), link.getStart(), link.getEnd(), null);
actions.addAll(classification.getActions());
}
mView.post(() -> {
resetActionChips();
if (actions.size() > 0) {
mActionContainerBackground.setVisibility(View.VISIBLE);
for (RemoteAction action : actions) {
Intent targetIntent = action.getActionIntent().getIntent();
ComponentName component = targetIntent.getComponent();
if (component != null && !TextUtils.equals(source,
component.getPackageName())) {
OverlayActionChip chip = constructActionChip(action);
mActionContainer.addView(chip);
mActionChips.add(chip);
break; // only show at most one action chip
}
}
}
});
}
private void showShareChip(ClipData clip) {
mShareChip.setVisibility(View.VISIBLE);
mActionContainerBackground.setVisibility(View.VISIBLE);
mShareChip.setOnClickListener((v) -> shareContent(clip));
}
private OverlayActionChip constructActionChip(RemoteAction action) {
OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
R.layout.overlay_action_chip, mActionContainer, false);
chip.setText(action.getTitle());
chip.setContentDescription(action.getTitle());
chip.setIcon(action.getIcon(), false);
chip.setPendingIntent(action.getActionIntent(), () -> {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
animateOut();
});
chip.setAlpha(1);
return chip;
}
private void monitorOutsideTouches() {
InputManager inputManager = mContext.getSystemService(InputManager.class);
mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
Looper.getMainLooper()) {
@Override
public void onInputEvent(InputEvent event) {
if (event instanceof MotionEvent) {
MotionEvent motionEvent = (MotionEvent) event;
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
Region touchRegion = new Region();
final Rect tmpRect = new Rect();
mPreviewBorder.getBoundsOnScreen(tmpRect);
tmpRect.inset(
(int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
(int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
-SWIPE_PADDING_DP));
touchRegion.op(tmpRect, Region.Op.UNION);
mActionContainerBackground.getBoundsOnScreen(tmpRect);
tmpRect.inset(
(int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
(int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
-SWIPE_PADDING_DP));
touchRegion.op(tmpRect, Region.Op.UNION);
mDismissButton.getBoundsOnScreen(tmpRect);
touchRegion.op(tmpRect, Region.Op.UNION);
if (!touchRegion.contains(
(int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
animateOut();
}
}
}
finishInputEvent(event, true /* handled */);
}
};
}
private void editImage(Uri uri) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext));
animateOut();
}
private void editText() {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
mContext.startActivity(IntentCreator.getTextEditorIntent(mContext));
animateOut();
}
private void shareContent(ClipData clip) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED);
mContext.startActivity(IntentCreator.getShareIntent(clip, mContext));
animateOut();
}
private void showSinglePreview(View v) {
mTextPreview.setVisibility(View.GONE);
mImagePreview.setVisibility(View.GONE);
mHiddenPreview.setVisibility(View.GONE);
v.setVisibility(View.VISIBLE);
}
private void showTextPreview(CharSequence text, TextView textView) {
showSinglePreview(textView);
final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length()));
textView.setText(truncatedText);
updateTextSize(truncatedText, textView);
textView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (right - left != oldRight - oldLeft) {
updateTextSize(truncatedText, textView);
}
});
mEditChip.setVisibility(View.GONE);
}
private void updateTextSize(CharSequence text, TextView textView) {
Paint paint = new Paint(textView.getPaint());
Resources res = textView.getResources();
float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
// If the text is a single word and would fit within the TextView at the min font size,
// find the biggest font size that will fit.
float fontSizePx = minFontSize;
while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
&& fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
fontSizePx += FONT_SEARCH_STEP_PX;
}
// Need to turn off autosizing, otherwise setTextSize is a no-op.
textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
// It's possible to hit the max font size and not fill the width, so centering
// horizontally looks better in this case.
textView.setGravity(Gravity.CENTER);
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
} else {
// Otherwise just stick with autosize.
textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
(int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
}
}
private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
float fontSizePx) {
paint.setTextSize(fontSizePx);
float size = paint.measureText(text.toString());
float availableWidth = textView.getWidth() - textView.getPaddingLeft()
- textView.getPaddingRight();
return size < availableWidth;
}
private static boolean isOneWord(CharSequence text) {
return text.toString().split("\\s+", 2).length == 1;
}
private void showEditableText(CharSequence text, boolean hidden) {
TextView textView = hidden ? mHiddenPreview : mTextPreview;
showTextPreview(text, textView);
View.OnClickListener listener = v -> editText();
setAccessibilityActionToEdit(textView);
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
mEditChip.setVisibility(View.VISIBLE);
mActionContainerBackground.setVisibility(View.VISIBLE);
mEditChip.setContentDescription(
mContext.getString(R.string.clipboard_edit_text_description));
mEditChip.setOnClickListener(listener);
}
textView.setOnClickListener(listener);
}
private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
View.OnClickListener listener = v -> editImage(uri);
ContentResolver resolver = mContext.getContentResolver();
String mimeType = resolver.getType(uri);
boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
if (isSensitive) {
mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
showSinglePreview(mHiddenPreview);
if (isEditableImage) {
mHiddenPreview.setOnClickListener(listener);
setAccessibilityActionToEdit(mHiddenPreview);
}
} else if (isEditableImage) { // if the MIMEtype is image, try to load
try {
int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
// The width of the view is capped, height maintains aspect ratio, so allow it to be
// taller if needed.
Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
showSinglePreview(mImagePreview);
mImagePreview.setImageBitmap(thumbnail);
mImagePreview.setOnClickListener(listener);
setAccessibilityActionToEdit(mImagePreview);
} catch (IOException e) {
Log.e(TAG, "Thumbnail loading failed", e);
showTextPreview(
mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
mTextPreview);
isEditableImage = false;
}
} else {
showTextPreview(
mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
mTextPreview);
}
if (isEditableImage && DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
mEditChip.setVisibility(View.VISIBLE);
mActionContainerBackground.setVisibility(View.VISIBLE);
mEditChip.setOnClickListener(listener);
mEditChip.setContentDescription(
mContext.getString(R.string.clipboard_edit_image_description));
}
return isEditableImage;
}
private void setAccessibilityActionToEdit(View view) {
ViewCompat.replaceAccessibilityAction(view,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
mContext.getString(R.string.clipboard_edit), null);
}
private void animateIn() {
if (mAccessibilityManager.isEnabled()) {
mDismissButton.setVisibility(View.VISIBLE);
}
mEnterAnimator = getEnterAnimation();
mEnterAnimator.start();
}
private void animateOut() {
if (mExitAnimator != null && mExitAnimator.isRunning()) {
return;
}
Animator anim = getExitAnimation();
anim.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
mCancelled = true;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (!mCancelled) {
hideImmediate();
}
}
});
mExitAnimator = anim;
anim.start();
}
private Animator getEnterAnimation() {
TimeInterpolator linearInterpolator = new LinearInterpolator();
TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
AnimatorSet enterAnim = new AnimatorSet();
ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
rootAnim.setInterpolator(linearInterpolator);
rootAnim.setDuration(66);
rootAnim.addUpdateListener(animation -> {
mView.setAlpha(animation.getAnimatedFraction());
});
ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
scaleAnim.setInterpolator(scaleInterpolator);
scaleAnim.setDuration(333);
scaleAnim.addUpdateListener(animation -> {
float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
mClipboardPreview.setScaleX(previewScale);
mClipboardPreview.setScaleY(previewScale);
mPreviewBorder.setScaleX(previewScale);
mPreviewBorder.setScaleY(previewScale);
float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
mActionContainer.setScaleX(actionsScaleX);
mActionContainer.setScaleY(actionsScaleY);
mActionContainerBackground.setScaleX(actionsScaleX);
mActionContainerBackground.setScaleY(actionsScaleY);
});
ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
alphaAnim.setInterpolator(linearInterpolator);
alphaAnim.setDuration(283);
alphaAnim.addUpdateListener(animation -> {
float alpha = animation.getAnimatedFraction();
mClipboardPreview.setAlpha(alpha);
mPreviewBorder.setAlpha(alpha);
mDismissButton.setAlpha(alpha);
mActionContainer.setAlpha(alpha);
});
mActionContainer.setAlpha(0);
mPreviewBorder.setAlpha(0);
mClipboardPreview.setAlpha(0);
enterAnim.play(rootAnim).with(scaleAnim);
enterAnim.play(alphaAnim).after(50).after(rootAnim);
enterAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mView.setAlpha(1);
mTimeoutHandler.resetTimeout();
}
});
return enterAnim;
}
private Animator getExitAnimation() {
TimeInterpolator linearInterpolator = new LinearInterpolator();
TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
AnimatorSet exitAnim = new AnimatorSet();
ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
rootAnim.setInterpolator(linearInterpolator);
rootAnim.setDuration(100);
rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
scaleAnim.setInterpolator(scaleInterpolator);
scaleAnim.setDuration(250);
scaleAnim.addUpdateListener(animation -> {
float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
mClipboardPreview.setScaleX(previewScale);
mClipboardPreview.setScaleY(previewScale);
mPreviewBorder.setScaleX(previewScale);
mPreviewBorder.setScaleY(previewScale);
float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
mActionContainer.setScaleX(actionScaleX);
mActionContainer.setScaleY(actionScaleY);
mActionContainerBackground.setScaleX(actionScaleX);
mActionContainerBackground.setScaleY(actionScaleY);
});
ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
alphaAnim.setInterpolator(linearInterpolator);
alphaAnim.setDuration(166);
alphaAnim.addUpdateListener(animation -> {
float alpha = 1 - animation.getAnimatedFraction();
mClipboardPreview.setAlpha(alpha);
mPreviewBorder.setAlpha(alpha);
mDismissButton.setAlpha(alpha);
mActionContainer.setAlpha(alpha);
});
exitAnim.play(alphaAnim).with(scaleAnim);
exitAnim.play(rootAnim).after(150).after(alphaAnim);
return exitAnim;
}
private void hideImmediate() {
// Note this may be called multiple times if multiple dismissal events happen at the same
// time.
mTimeoutHandler.cancelTimeout();
final View decorView = mWindow.peekDecorView();
if (decorView != null && decorView.isAttachedToWindow()) {
mWindowManager.removeViewImmediate(decorView);
}
if (mCloseDialogsReceiver != null) {
mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
mCloseDialogsReceiver = null;
}
if (mScreenshotReceiver != null) {
mBroadcastDispatcher.unregisterReceiver(mScreenshotReceiver);
mScreenshotReceiver = null;
}
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
if (mInputMonitor != null) {
mInputMonitor.dispose();
mInputMonitor = null;
}
if (mOnSessionCompleteListener != null) {
mOnSessionCompleteListener.run();
}
}
private void resetActionChips() {
for (OverlayActionChip chip : mActionChips) {
mActionContainer.removeView(chip);
}
mActionChips.clear();
}
private void reset() {
mView.setTranslationX(0);
mView.setAlpha(0);
mActionContainerBackground.setVisibility(View.GONE);
mShareChip.setVisibility(View.GONE);
mEditChip.setVisibility(View.GONE);
mRemoteCopyChip.setVisibility(View.GONE);
resetActionChips();
mTimeoutHandler.cancelTimeout();
mClipboardLogger.reset();
}
@MainThread
private void attachWindow() {
View decorView = mWindow.getDecorView();
if (decorView.isAttachedToWindow() || mBlockAttach) {
return;
}
mBlockAttach = true;
mWindowManager.addView(decorView, mWindowLayoutParams);
decorView.requestApplyInsets();
mView.requestApplyInsets();
decorView.getViewTreeObserver().addOnWindowAttachListener(
new ViewTreeObserver.OnWindowAttachListener() {
@Override
public void onWindowAttached() {
mBlockAttach = false;
}
@Override
public void onWindowDetached() {
}
}
);
}
private void withWindowAttached(Runnable action) {
View decorView = mWindow.getDecorView();
if (decorView.isAttachedToWindow()) {
action.run();
} else {
decorView.getViewTreeObserver().addOnWindowAttachListener(
new ViewTreeObserver.OnWindowAttachListener() {
@Override
public void onWindowAttached() {
mBlockAttach = false;
decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
action.run();
}
@Override
public void onWindowDetached() {
}
});
}
}
private void updateInsets(WindowInsets insets) {
int orientation = mContext.getResources().getConfiguration().orientation;
FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
if (p == null) {
return;
}
DisplayCutout cutout = insets.getDisplayCutout();
Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
if (cutout == null) {
p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
} else {
Insets waterfall = cutout.getWaterfallInsets();
if (orientation == ORIENTATION_PORTRAIT) {
p.setMargins(
waterfall.left,
Math.max(cutout.getSafeInsetTop(), waterfall.top),
waterfall.right,
Math.max(imeInsets.bottom,
Math.max(cutout.getSafeInsetBottom(),
Math.max(navBarInsets.bottom, waterfall.bottom))));
} else {
p.setMargins(
waterfall.left,
waterfall.top,
waterfall.right,
Math.max(imeInsets.bottom,
Math.max(navBarInsets.bottom, waterfall.bottom)));
}
}
mView.setLayoutParams(p);
mView.requestLayout();
}
private Display getDefaultDisplay() {
return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
}
/**
* Updates the window focusability. If the window is already showing, then it updates the
* window immediately, otherwise the layout params will be applied when the window is next
* shown.
*/
private void setWindowFocusable(boolean focusable) {
int flags = mWindowLayoutParams.flags;
if (focusable) {
mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
} else {
mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
}
if (mWindowLayoutParams.flags == flags) {
return;
}
final View decorView = mWindow.peekDecorView();
if (decorView != null && decorView.isAttachedToWindow()) {
mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
}
}
static class ClipboardLogger {
private final UiEventLogger mUiEventLogger;
private boolean mGuarded = false;
ClipboardLogger(UiEventLogger uiEventLogger) {
mUiEventLogger = uiEventLogger;
}
void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
if (!mGuarded) {
mGuarded = true;
mUiEventLogger.log(event);
}
}
void reset() {
mGuarded = false;
}
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright (C) 2022 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 android.content.Context;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.screenshot.TimeoutHandler;
import javax.inject.Inject;
/**
* A factory that churns out ClipboardOverlayControllerLegacys on demand.
*/
@SysUISingleton
public class ClipboardOverlayControllerLegacyFactory {
private final UiEventLogger mUiEventLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
private final BroadcastSender mBroadcastSender;
@Inject
public ClipboardOverlayControllerLegacyFactory(BroadcastDispatcher broadcastDispatcher,
BroadcastSender broadcastSender, UiEventLogger uiEventLogger) {
this.mBroadcastDispatcher = broadcastDispatcher;
this.mBroadcastSender = broadcastSender;
this.mUiEventLogger = uiEventLogger;
}
/**
* One new ClipboardOverlayControllerLegacy, coming right up!
*/
public ClipboardOverlayControllerLegacy create(Context context) {
return new ClipboardOverlayControllerLegacy(context, mBroadcastDispatcher, mBroadcastSender,
new TimeoutHandler(context), mUiEventLogger);
}
}

View File

@@ -528,7 +528,6 @@ object Flags {
unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations") unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
// 1700 - clipboard // 1700 - clipboard
@JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior") @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
// 1800 - shade container // 1800 - shade container

View File

@@ -16,8 +16,6 @@
package com.android.systemui.clipboardoverlay; package com.android.systemui.clipboardoverlay;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE; import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -33,7 +31,6 @@ import android.content.ClipData;
import android.content.ClipDescription; import android.content.ClipDescription;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.os.PersistableBundle; import android.os.PersistableBundle;
import android.provider.DeviceConfig;
import android.provider.Settings; import android.provider.Settings;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
@@ -41,9 +38,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.util.DeviceConfigProxyFake;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -63,18 +57,11 @@ public class ClipboardListenerTest extends SysuiTestCase {
@Mock @Mock
private ClipboardManager mClipboardManager; private ClipboardManager mClipboardManager;
@Mock @Mock
private ClipboardOverlayControllerLegacyFactory mClipboardOverlayControllerLegacyFactory;
@Mock
private ClipboardOverlayControllerLegacy mOverlayControllerLegacy;
@Mock
private ClipboardOverlayController mOverlayController; private ClipboardOverlayController mOverlayController;
@Mock @Mock
private ClipboardToast mClipboardToast; private ClipboardToast mClipboardToast;
@Mock @Mock
private UiEventLogger mUiEventLogger; private UiEventLogger mUiEventLogger;
@Mock
private FeatureFlags mFeatureFlags;
private DeviceConfigProxyFake mDeviceConfigProxy;
private ClipData mSampleClipData; private ClipData mSampleClipData;
private String mSampleSource = "Example source"; private String mSampleSource = "Example source";
@@ -97,8 +84,6 @@ public class ClipboardListenerTest extends SysuiTestCase {
mOverlayControllerProvider = () -> mOverlayController; mOverlayControllerProvider = () -> mOverlayController;
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
when(mClipboardOverlayControllerLegacyFactory.create(any()))
.thenReturn(mOverlayControllerLegacy);
when(mClipboardManager.hasPrimaryClip()).thenReturn(true); when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
Settings.Secure.putInt( Settings.Secure.putInt(
mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 1); mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 1);
@@ -108,26 +93,13 @@ public class ClipboardListenerTest extends SysuiTestCase {
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData); when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource); when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
mDeviceConfigProxy = new DeviceConfigProxyFake(); mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
mClipboardToast, mClipboardManager, mUiEventLogger);
mClipboardListener = new ClipboardListener(getContext(), mDeviceConfigProxy,
mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
mClipboardToast, mClipboardManager, mUiEventLogger, mFeatureFlags);
} }
@Test
public void test_disabled() {
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"false", false);
mClipboardListener.start();
verifyZeroInteractions(mClipboardManager);
verifyZeroInteractions(mUiEventLogger);
}
@Test @Test
public void test_enabled() { public void test_initialization() {
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"true", false);
mClipboardListener.start(); mClipboardListener.start();
verify(mClipboardManager).addPrimaryClipChangedListener(any()); verify(mClipboardManager).addPrimaryClipChangedListener(any());
verifyZeroInteractions(mUiEventLogger); verifyZeroInteractions(mUiEventLogger);
@@ -135,45 +107,6 @@ public class ClipboardListenerTest extends SysuiTestCase {
@Test @Test
public void test_consecutiveCopies() { public void test_consecutiveCopies() {
when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"true", false);
mClipboardListener.start();
mClipboardListener.onPrimaryClipChanged();
verify(mClipboardOverlayControllerLegacyFactory).create(any());
verify(mOverlayControllerLegacy).setClipData(
mClipDataCaptor.capture(), mStringCaptor.capture());
assertEquals(mSampleClipData, mClipDataCaptor.getValue());
assertEquals(mSampleSource, mStringCaptor.getValue());
verify(mOverlayControllerLegacy).setOnSessionCompleteListener(mRunnableCaptor.capture());
// Should clear the overlay controller
mRunnableCaptor.getValue().run();
mClipboardListener.onPrimaryClipChanged();
verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
// Not calling the runnable here, just change the clip again and verify that the overlay is
// NOT recreated.
mClipboardListener.onPrimaryClipChanged();
verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
verifyZeroInteractions(mOverlayControllerProvider);
}
@Test
public void test_consecutiveCopies_new() {
when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"true", false);
mClipboardListener.start(); mClipboardListener.start();
mClipboardListener.onPrimaryClipChanged(); mClipboardListener.onPrimaryClipChanged();
@@ -200,7 +133,6 @@ public class ClipboardListenerTest extends SysuiTestCase {
mClipboardListener.onPrimaryClipChanged(); mClipboardListener.onPrimaryClipChanged();
verify(mOverlayControllerProvider, times(2)).get(); verify(mOverlayControllerProvider, times(2)).get();
verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
} }
@Test @Test
@@ -231,23 +163,6 @@ public class ClipboardListenerTest extends SysuiTestCase {
@Test @Test
public void test_logging_enterAndReenter() { public void test_logging_enterAndReenter() {
when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
mClipboardListener.start();
mClipboardListener.onPrimaryClipChanged();
mClipboardListener.onPrimaryClipChanged();
verify(mUiEventLogger, times(1)).log(
ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
verify(mUiEventLogger, times(1)).log(
ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
}
@Test
public void test_logging_enterAndReenter_new() {
when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
mClipboardListener.start(); mClipboardListener.start();
mClipboardListener.onPrimaryClipChanged(); mClipboardListener.onPrimaryClipChanged();
@@ -271,6 +186,5 @@ public class ClipboardListenerTest extends SysuiTestCase {
ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource); ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
verify(mClipboardToast, times(1)).showCopiedToast(); verify(mClipboardToast, times(1)).showCopiedToast();
verifyZeroInteractions(mOverlayControllerProvider); verifyZeroInteractions(mOverlayControllerProvider);
verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
} }
} }