| /* |
| * 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.annotation.MainThread; |
| import android.annotation.NonNull; |
| import android.app.ICompatCameraControlCallback; |
| import android.content.Context; |
| import android.content.res.Configuration; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.ViewRootImpl; |
| import android.view.ViewTreeObserver; |
| import android.view.Window; |
| import android.view.WindowInsets; |
| import android.view.WindowManager; |
| |
| import com.android.internal.policy.PhoneWindow; |
| import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext; |
| import com.android.systemui.screenshot.FloatingWindowUtil; |
| |
| import java.util.function.BiConsumer; |
| |
| import javax.inject.Inject; |
| |
| /** |
| * Handles attaching the window and the window insets for the clipboard overlay. |
| */ |
| public class ClipboardOverlayWindow extends PhoneWindow |
| implements ViewRootImpl.ActivityConfigCallback { |
| private static final String TAG = "ClipboardOverlayWindow"; |
| |
| private final Context mContext; |
| private final WindowManager mWindowManager; |
| private final WindowManager.LayoutParams mWindowLayoutParams; |
| |
| private boolean mKeyboardVisible; |
| private final int mOrientation; |
| private BiConsumer<WindowInsets, Integer> mOnKeyboardChangeListener; |
| private Runnable mOnOrientationChangeListener; |
| |
| @Inject |
| ClipboardOverlayWindow(@OverlayWindowContext Context context) { |
| super(context); |
| mContext = context; |
| mOrientation = mContext.getResources().getConfiguration().orientation; |
| |
| // Setup the window that we are going to use |
| requestFeature(Window.FEATURE_NO_TITLE); |
| requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); |
| setBackgroundDrawableResource(android.R.color.transparent); |
| mWindowManager = mContext.getSystemService(WindowManager.class); |
| mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams(); |
| mWindowLayoutParams.setTitle("ClipboardOverlay"); |
| setWindowManager(mWindowManager, null, null); |
| setWindowFocusable(false); |
| } |
| |
| /** |
| * Set callbacks for keyboard state change and orientation change and attach the window |
| * |
| * @param onKeyboardChangeListener callback for IME visibility changes |
| * @param onOrientationChangeListener callback for device orientation changes |
| */ |
| public void init(@NonNull BiConsumer<WindowInsets, Integer> onKeyboardChangeListener, |
| @NonNull Runnable onOrientationChangeListener) { |
| mOnKeyboardChangeListener = onKeyboardChangeListener; |
| mOnOrientationChangeListener = onOrientationChangeListener; |
| |
| attach(); |
| withWindowAttached(() -> { |
| WindowInsets currentInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets(); |
| mKeyboardVisible = currentInsets.isVisible(WindowInsets.Type.ime()); |
| peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(() -> { |
| WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets(); |
| boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime()); |
| if (keyboardVisible != mKeyboardVisible) { |
| mKeyboardVisible = keyboardVisible; |
| mOnKeyboardChangeListener.accept(insets, mOrientation); |
| } |
| }); |
| peekDecorView().getViewRootImpl().setActivityConfigCallback(this); |
| }); |
| } |
| |
| @Override // ViewRootImpl.ActivityConfigCallback |
| public void onConfigurationChanged(Configuration overrideConfig, int newDisplayId) { |
| if (mContext.getResources().getConfiguration().orientation != mOrientation) { |
| mOnOrientationChangeListener.run(); |
| } |
| } |
| |
| @Override // ViewRootImpl.ActivityConfigCallback |
| public void requestCompatCameraControl(boolean showControl, boolean transformationApplied, |
| ICompatCameraControlCallback callback) { |
| Log.w(TAG, "unexpected requestCompatCameraControl call"); |
| } |
| |
| void remove() { |
| final View decorView = peekDecorView(); |
| if (decorView != null && decorView.isAttachedToWindow()) { |
| mWindowManager.removeViewImmediate(decorView); |
| } |
| } |
| |
| WindowInsets getWindowInsets() { |
| return mWindowManager.getCurrentWindowMetrics().getWindowInsets(); |
| } |
| |
| void withWindowAttached(Runnable action) { |
| View decorView = getDecorView(); |
| if (decorView.isAttachedToWindow()) { |
| action.run(); |
| } else { |
| decorView.getViewTreeObserver().addOnWindowAttachListener( |
| new ViewTreeObserver.OnWindowAttachListener() { |
| @Override |
| public void onWindowAttached() { |
| decorView.getViewTreeObserver().removeOnWindowAttachListener(this); |
| action.run(); |
| } |
| |
| @Override |
| public void onWindowDetached() { |
| } |
| }); |
| } |
| } |
| |
| @MainThread |
| private void attach() { |
| View decorView = getDecorView(); |
| if (decorView.isAttachedToWindow()) { |
| return; |
| } |
| mWindowManager.addView(decorView, mWindowLayoutParams); |
| decorView.requestApplyInsets(); |
| } |
| |
| /** |
| * 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 = peekDecorView(); |
| if (decorView != null && decorView.isAttachedToWindow()) { |
| mWindowManager.updateViewLayout(decorView, mWindowLayoutParams); |
| } |
| } |
| } |