blob: feac5da38138cdfb90ed34cb17d9d3e0508ba3e3 [file] [log] [blame]
/*
* Copyright (C) 2019 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.statusbar.phone;
import android.app.StatusBarManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.media.AudioManager;
import android.media.session.MediaSessionLegacyHelper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.view.GestureDetector;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.ExpandHelper;
import com.android.systemui.R;
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.InjectionInflationController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Lazy;
/**
* Controller for {@link StatusBarWindowView}.
*/
@Singleton
public class StatusBarWindowViewController {
private final InjectionInflationController mInjectionInflationController;
private final NotificationWakeUpCoordinator mCoordinator;
private final PulseExpansionHandler mPulseExpansionHandler;
private final DynamicPrivacyController mDynamicPrivacyController;
private final KeyguardBypassController mBypassController;
private final PluginManager mPluginManager;
private final FalsingManager mFalsingManager;
private final TunerService mTunerService;
private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
private final NotificationEntryManager mNotificationEntryManager;
private final KeyguardStateController mKeyguardStateController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final DozeLog mDozeLog;
private final DozeParameters mDozeParameters;
private final CommandQueue mCommandQueue;
private final StatusBarWindowView mView;
private final Lazy<ShadeController> mShadeControllerLazy;
private GestureDetector mGestureDetector;
private View mBrightnessMirror;
private boolean mTouchActive;
private boolean mTouchCancelled;
private boolean mExpandAnimationPending;
private boolean mExpandAnimationRunning;
private NotificationStackScrollLayout mStackScrollLayout;
private PhoneStatusBarView mStatusBarView;
private StatusBar mService;
private DragDownHelper mDragDownHelper;
private boolean mDoubleTapEnabled;
private boolean mSingleTapEnabled;
private boolean mExpandingBelowNotch;
private final DockManager mDockManager;
@Inject
public StatusBarWindowViewController(
InjectionInflationController injectionInflationController,
NotificationWakeUpCoordinator coordinator,
PulseExpansionHandler pulseExpansionHandler,
DynamicPrivacyController dynamicPrivacyController,
KeyguardBypassController bypassController,
FalsingManager falsingManager,
PluginManager pluginManager,
TunerService tunerService,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationEntryManager notificationEntryManager,
KeyguardStateController keyguardStateController,
SysuiStatusBarStateController statusBarStateController,
DozeLog dozeLog,
DozeParameters dozeParameters,
CommandQueue commandQueue,
SuperStatusBarViewFactory superStatusBarViewFactory,
Lazy<ShadeController> shadeControllerLazy,
DockManager dockManager) {
mInjectionInflationController = injectionInflationController;
mCoordinator = coordinator;
mPulseExpansionHandler = pulseExpansionHandler;
mDynamicPrivacyController = dynamicPrivacyController;
mBypassController = bypassController;
mFalsingManager = falsingManager;
mPluginManager = pluginManager;
mTunerService = tunerService;
mNotificationLockscreenUserManager = notificationLockscreenUserManager;
mNotificationEntryManager = notificationEntryManager;
mKeyguardStateController = keyguardStateController;
mStatusBarStateController = statusBarStateController;
mDozeLog = dozeLog;
mDozeParameters = dozeParameters;
mCommandQueue = commandQueue;
mView = superStatusBarViewFactory.getStatusBarWindowView();
mShadeControllerLazy = shadeControllerLazy;
mDockManager = dockManager;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror);
}
/** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
public void setupExpandedStatusBar() {
// TODO: create controller for NotificationPanelView
NotificationPanelView notificationPanelView = new NotificationPanelView(
mView.getContext(),
null,
mInjectionInflationController,
mCoordinator,
mPulseExpansionHandler,
mDynamicPrivacyController,
mBypassController,
mFalsingManager,
mPluginManager,
mShadeControllerLazy.get(),
mNotificationLockscreenUserManager,
mNotificationEntryManager,
mKeyguardStateController,
mStatusBarStateController,
mDozeLog,
mDozeParameters,
mCommandQueue);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
notificationPanelView.setVisibility(View.INVISIBLE);
notificationPanelView.setId(R.id.notification_panel);
LayoutInflater li = mInjectionInflationController.injectable(
LayoutInflater.from(mView.getContext()));
li.inflate(R.layout.status_bar_expanded, notificationPanelView);
notificationPanelView.onChildrenAttached();
ViewStub statusBarExpanded = mView.findViewById(R.id.status_bar_expanded);
mView.addView(notificationPanelView, mView.indexOfChild(statusBarExpanded), lp);
mView.removeView(statusBarExpanded);
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
TunerService.Tunable tunable = (key, newValue) -> {
AmbientDisplayConfiguration configuration =
new AmbientDisplayConfiguration(mView.getContext());
switch (key) {
case Settings.Secure.DOZE_DOUBLE_TAP_GESTURE:
mDoubleTapEnabled = configuration.doubleTapGestureEnabled(
UserHandle.USER_CURRENT);
break;
case Settings.Secure.DOZE_TAP_SCREEN_GESTURE:
mSingleTapEnabled = configuration.tapGestureEnabled(UserHandle.USER_CURRENT);
}
};
mTunerService.addTunable(tunable,
Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
Settings.Secure.DOZE_TAP_SCREEN_GESTURE);
GestureDetector.SimpleOnGestureListener gestureListener =
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (mSingleTapEnabled && !mDockManager.isDocked()) {
mService.wakeUpIfDozing(
SystemClock.uptimeMillis(), mView, "SINGLE_TAP");
return true;
}
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (mDoubleTapEnabled || mSingleTapEnabled) {
mService.wakeUpIfDozing(
SystemClock.uptimeMillis(), mView, "DOUBLE_TAP");
return true;
}
return false;
}
};
mGestureDetector = new GestureDetector(mView.getContext(), gestureListener);
mView.setInteractionEventHandler(new StatusBarWindowView.InteractionEventHandler() {
@Override
public Boolean handleDispatchTouchEvent(MotionEvent ev) {
boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL;
boolean expandingBelowNotch = mExpandingBelowNotch;
if (isUp || isCancel) {
mExpandingBelowNotch = false;
}
// Reset manual touch dispatch state here but make sure the UP/CANCEL event still
// gets
// delivered.
if (!isCancel && mService.shouldIgnoreTouch()) {
return false;
}
if (isDown && notificationPanelView.isFullyCollapsed()) {
notificationPanelView.startExpandLatencyTracking();
}
if (isDown) {
setTouchActive(true);
mTouchCancelled = false;
} else if (ev.getActionMasked() == MotionEvent.ACTION_UP
|| ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
setTouchActive(false);
}
if (mTouchCancelled || mExpandAnimationRunning || mExpandAnimationPending) {
return false;
}
mFalsingManager.onTouchEvent(ev, mView.getWidth(), mView.getHeight());
mGestureDetector.onTouchEvent(ev);
if (mBrightnessMirror != null
&& mBrightnessMirror.getVisibility() == View.VISIBLE) {
// Disallow new pointers while the brightness mirror is visible. This is so that
// you can't touch anything other than the brightness slider while the mirror is
// showing and the rest of the panel is transparent.
if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
return false;
}
}
if (isDown) {
mStackScrollLayout.closeControlsIfOutsideTouch(ev);
}
if (mService.isDozing()) {
mService.mDozeScrimController.extendPulse();
}
// In case we start outside of the view bounds (below the status bar), we need to
// dispatch
// the touch manually as the view system can't accommodate for touches outside of
// the
// regular view bounds.
if (isDown && ev.getY() >= mView.getBottom()) {
mExpandingBelowNotch = true;
expandingBelowNotch = true;
}
if (expandingBelowNotch) {
return mStatusBarView.dispatchTouchEvent(ev);
}
return null;
}
@Override
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
if (mService.isDozing() && !mService.isPulsing() && !mDockManager.isDocked()) {
// Capture all touch events in always-on.
return true;
}
boolean intercept = false;
if (notificationPanelView.isFullyExpanded()
&& mDragDownHelper.isDragDownEnabled()
&& !mService.isBouncerShowing()
&& !mService.isDozing()) {
intercept = mDragDownHelper.onInterceptTouchEvent(ev);
}
return intercept;
}
@Override
public void didIntercept(MotionEvent ev) {
MotionEvent cancellation = MotionEvent.obtain(ev);
cancellation.setAction(MotionEvent.ACTION_CANCEL);
mStackScrollLayout.onInterceptTouchEvent(cancellation);
notificationPanelView.onInterceptTouchEvent(cancellation);
cancellation.recycle();
}
@Override
public boolean handleTouchEvent(MotionEvent ev) {
boolean handled = false;
if (mService.isDozing()) {
handled = !mService.isPulsing();
}
if ((mDragDownHelper.isDragDownEnabled() && !handled)
|| mDragDownHelper.isDraggingDown()) {
// we still want to finish our drag down gesture when locking the screen
handled = mDragDownHelper.onTouchEvent(ev);
}
return handled;
}
@Override
public void didNotHandleTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
}
}
@Override
public boolean interceptMediaKey(KeyEvent event) {
return mService.interceptMediaKey(event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_BACK:
if (!down) {
mService.onBackPressed();
}
return true;
case KeyEvent.KEYCODE_MENU:
if (!down) {
return mService.onMenuPressed();
}
break;
case KeyEvent.KEYCODE_SPACE:
if (!down) {
return mService.onSpacePressed();
}
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
if (mService.isDozing()) {
MediaSessionLegacyHelper.getHelper(mView.getContext())
.sendVolumeKeyEvent(
event, AudioManager.USE_DEFAULT_STREAM_TYPE, true);
return true;
}
break;
}
return false;
}
});
mView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(View parent, View child) {
if (child.getId() == R.id.brightness_mirror) {
mBrightnessMirror = child;
}
}
@Override
public void onChildViewRemoved(View parent, View child) {
}
});
ExpandHelper.Callback expandHelperCallback = mStackScrollLayout.getExpandHelperCallback();
DragDownHelper.DragDownCallback dragDownCallback = mStackScrollLayout.getDragDownCallback();
setDragDownHelper(
new DragDownHelper(
mView.getContext(), mView, expandHelperCallback,
dragDownCallback, mFalsingManager));
}
public StatusBarWindowView getView() {
return mView;
}
public void setTouchActive(boolean touchActive) {
mTouchActive = touchActive;
}
public void cancelCurrentTouch() {
if (mTouchActive) {
final long now = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mView.dispatchTouchEvent(event);
event.recycle();
mTouchCancelled = true;
}
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print(" mExpandAnimationPending=");
pw.println(mExpandAnimationPending);
pw.print(" mExpandAnimationRunning=");
pw.println(mExpandAnimationRunning);
pw.print(" mTouchCancelled=");
pw.println(mTouchCancelled);
pw.print(" mTouchActive=");
pw.println(mTouchActive);
}
public void setExpandAnimationPending(boolean pending) {
mExpandAnimationPending = pending;
}
public void setExpandAnimationRunning(boolean running) {
mExpandAnimationRunning = running;
}
public void cancelExpandHelper() {
if (mStackScrollLayout != null) {
mStackScrollLayout.cancelExpandHelper();
}
}
public void setStatusBarView(PhoneStatusBarView statusBarView) {
mStatusBarView = statusBarView;
}
public void setService(StatusBar statusBar) {
mService = statusBar;
}
@VisibleForTesting
void setDragDownHelper(DragDownHelper dragDownHelper) {
mDragDownHelper = dragDownHelper;
}
}