blob: 9dac9b393d6094044c661516dbeb4dad192cf719 [file] [log] [blame]
/*
* 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);
}
}
}