blob: ff00560956b5aa17376e03356fea3c1a42957559 [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.launcher3.taskbar.overlay;
import static android.view.KeyEvent.ACTION_UP;
import static android.view.KeyEvent.KEYCODE_BACK;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import android.content.Context;
import android.graphics.Insets;
import android.media.permission.SafeCloseable;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import com.android.app.viewcapture.SettingsAwareViewCapture;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/** Root drag layer for the Taskbar overlay window. */
public class TaskbarOverlayDragLayer extends
BaseDragLayer<TaskbarOverlayContext> implements
ViewTreeObserver.OnComputeInternalInsetsListener {
private SafeCloseable mViewCaptureCloseable;
private final List<OnClickListener> mOnClickListeners = new CopyOnWriteArrayList<>();
private final TouchController mClickListenerTouchController = new TouchController() {
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
for (OnClickListener listener : mOnClickListeners) {
listener.onClick(TaskbarOverlayDragLayer.this);
}
}
return false;
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
for (int i = 0; i < getChildCount(); i++) {
if (isEventOverView(getChildAt(i), ev)) {
return false;
}
}
return true;
}
};
TaskbarOverlayDragLayer(Context context) {
super(context, null, 1);
setClipChildren(false);
recreateControllers();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
mViewCaptureCloseable = SettingsAwareViewCapture.getInstance(getContext())
.startCapture(getRootView(), ".TaskbarOverlay");
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
mViewCaptureCloseable.close();
}
@Override
public void recreateControllers() {
mControllers = mOnClickListeners.isEmpty()
? new TouchController[]{mActivity.getDragController()}
: new TouchController[] {
mActivity.getDragController(), mClickListenerTouchController};
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
if (topView != null && topView.canHandleBack()) {
topView.onBackInvoked();
return true;
}
}
return super.dispatchKeyEvent(event);
}
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
if (mActivity.isAnySystemDragInProgress()) {
inoutInfo.touchableRegion.setEmpty();
inoutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
}
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
insets = updateInsetsDueToStashing(insets);
setInsets(insets.getInsets(WindowInsets.Type.systemBars()).toRect());
return insets;
}
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
mActivity.getOverlayController().maybeCloseWindow();
}
/**
* Adds the given callback to clicks to this drag layer.
* <p>
* Clicks are only accepted on this drag layer if they fall within this drag layer's bounds and
* outside the bounds of all child views.
* <p>
* If the click falls within the bounds of a child view, then this callback does not run and
* that child can optionally handle it.
*/
private void addOnClickListener(@NonNull OnClickListener listener) {
boolean wasEmpty = mOnClickListeners.isEmpty();
mOnClickListeners.add(listener);
if (wasEmpty) {
recreateControllers();
}
}
/**
* Removes the given on click callback.
* <p>
* No-op if the callback was never added.
*/
private void removeOnClickListener(@NonNull OnClickListener listener) {
boolean wasEmpty = mOnClickListeners.isEmpty();
mOnClickListeners.remove(listener);
if (!wasEmpty && mOnClickListeners.isEmpty()) {
recreateControllers();
}
}
/**
* Queues the given callback on the next click on this drag layer.
* <p>
* Once run, this callback is immediately removed.
*/
public void runOnClickOnce(@NonNull OnClickListener listener) {
addOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
listener.onClick(v);
removeOnClickListener(this);
}
});
}
/**
* Taskbar automatically stashes when opening all apps, but we don't report the insets as
* changing to avoid moving the underlying app. But internally, the apps view should still
* layout according to the stashed insets rather than the unstashed insets. So this method
* does two things:
* 1) Sets navigationBars bottom inset to stashedHeight.
* 2) Sets tappableInsets bottom inset to 0.
*/
private WindowInsets updateInsetsDueToStashing(WindowInsets oldInsets) {
if (!DisplayController.isTransientTaskbar(mActivity)) {
return oldInsets;
}
WindowInsets.Builder updatedInsetsBuilder = new WindowInsets.Builder(oldInsets);
Insets oldNavInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars());
Insets newNavInsets = Insets.of(oldNavInsets.left, oldNavInsets.top, oldNavInsets.right,
mActivity.getStashedTaskbarHeight());
updatedInsetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement());
Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top,
oldTappableInsets.right, 0);
updatedInsetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets);
return updatedInsetsBuilder.build();
}
}