Merge "Phase 1 of status bar system event animations" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
e438034aee
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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.
|
||||
-->
|
||||
|
||||
<!-- dot drawable for ongoing system privacy events -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid
|
||||
android:color="@color/privacy_circle"/>
|
||||
<size
|
||||
android:width="6dp"
|
||||
android:height="6dp"
|
||||
/>
|
||||
</shape>
|
||||
@@ -14,6 +14,8 @@
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- TODO: remove this in favor of requiring top and bottom layouts -->
|
||||
<com.android.systemui.RegionInterceptingFrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/rounded_corners_default"
|
||||
@@ -26,6 +28,25 @@
|
||||
android:layout_gravity="left|top"
|
||||
android:tint="#ff000000"
|
||||
android:src="@drawable/rounded"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_dot_left_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginTop="@dimen/status_bar_padding_top"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_gravity="left|top"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot_left"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/right"
|
||||
android:layout_width="12dp"
|
||||
@@ -33,4 +54,22 @@
|
||||
android:tint="#ff000000"
|
||||
android:layout_gravity="right|bottom"
|
||||
android:src="@drawable/rounded"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_dot_right_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginTop="@dimen/status_bar_padding_top"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_gravity="right|top"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot_right"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</com.android.systemui.RegionInterceptingFrameLayout>
|
||||
|
||||
@@ -26,6 +26,24 @@
|
||||
android:layout_gravity="left|bottom"
|
||||
android:tint="#ff000000"
|
||||
android:src="@drawable/rounded_corner_bottom"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_dot_left_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginTop="@dimen/status_bar_padding_top"
|
||||
android:layout_marginLeft="0dp"
|
||||
android:layout_gravity="left|bottom"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/right"
|
||||
android:layout_width="12dp"
|
||||
@@ -33,4 +51,21 @@
|
||||
android:tint="#ff000000"
|
||||
android:layout_gravity="right|bottom"
|
||||
android:src="@drawable/rounded_corner_bottom"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_dot_right_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginTop="@dimen/status_bar_padding_top"
|
||||
android:layout_marginRight="0dp"
|
||||
android:layout_gravity="right|bottom"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|left"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
</com.android.systemui.RegionInterceptingFrameLayout>
|
||||
|
||||
@@ -26,6 +26,24 @@
|
||||
android:layout_gravity="left|top"
|
||||
android:tint="#ff000000"
|
||||
android:src="@drawable/rounded_corner_top"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_dot_left_container"
|
||||
android:layout_height="@*android:dimen/status_bar_height_portrait"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginTop="@dimen/status_bar_padding_top"
|
||||
android:layout_marginLeft="0dp"
|
||||
android:layout_gravity="left|top"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/right"
|
||||
android:layout_width="12dp"
|
||||
@@ -33,4 +51,24 @@
|
||||
android:tint="#ff000000"
|
||||
android:layout_gravity="right|top"
|
||||
android:src="@drawable/rounded_corner_top"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_dot_right_container"
|
||||
android:layout_height="@*android:dimen/status_bar_height_portrait"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginTop="@dimen/status_bar_padding_top"
|
||||
android:layout_marginRight="0dp"
|
||||
android:layout_gravity="right|top"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|left"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</com.android.systemui.RegionInterceptingFrameLayout>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:paddingTop="@dimen/status_bar_padding_top"
|
||||
android:paddingEnd="8dp"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/dot_view"
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="invisible"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -79,6 +79,8 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
|
||||
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
|
||||
import com.android.systemui.statusbar.SmartReplyController;
|
||||
import com.android.systemui.statusbar.VibratorHelper;
|
||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
|
||||
import com.android.systemui.statusbar.notification.NotificationFilter;
|
||||
@@ -352,6 +354,8 @@ public class Dependency {
|
||||
@Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
|
||||
@Inject Lazy<NavigationBarOverlayController> mNavbarButtonsControllerLazy;
|
||||
@Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager;
|
||||
@Inject Lazy<SystemStatusAnimationScheduler> mSystemStatusAnimationSchedulerLazy;
|
||||
@Inject Lazy<PrivacyDotViewController> mPrivacyDotViewControllerLazy;
|
||||
|
||||
@Inject
|
||||
public Dependency() {
|
||||
@@ -561,6 +565,10 @@ public class Dependency {
|
||||
|
||||
mProviders.put(NavigationBarOverlayController.class, mNavbarButtonsControllerLazy::get);
|
||||
|
||||
mProviders.put(SystemStatusAnimationScheduler.class,
|
||||
mSystemStatusAnimationSchedulerLazy::get);
|
||||
mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get);
|
||||
|
||||
Dependency.setInstance(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.qs.SecureSetting;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||
import com.android.systemui.tuner.TunerService;
|
||||
import com.android.systemui.tuner.TunerService.Tunable;
|
||||
import com.android.systemui.util.settings.SecureSettings;
|
||||
@@ -126,6 +127,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
private DisplayManager.DisplayListener mDisplayListener;
|
||||
private CameraAvailabilityListener mCameraListener;
|
||||
private final UserTracker mUserTracker;
|
||||
private final PrivacyDotViewController mDotViewController;
|
||||
|
||||
//TODO: These are piecemeal being updated to Points for now to support non-square rounded
|
||||
// corners. for now it is only supposed when reading the intrinsic size from the drawables with
|
||||
@@ -140,6 +142,11 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
protected View[] mOverlays;
|
||||
@Nullable
|
||||
private DisplayCutoutView[] mCutoutViews;
|
||||
//TODO:
|
||||
View mTopLeftDot;
|
||||
View mTopRightDot;
|
||||
View mBottomLeftDot;
|
||||
View mBottomRightDot;
|
||||
private float mDensity;
|
||||
private WindowManager mWindowManager;
|
||||
private int mRotation;
|
||||
@@ -147,6 +154,8 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
private Handler mHandler;
|
||||
private boolean mPendingRotationChange;
|
||||
private boolean mIsRoundedCornerMultipleRadius;
|
||||
private int mStatusBarHeightPortrait;
|
||||
private int mStatusBarHeightLandscape;
|
||||
|
||||
private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
|
||||
new CameraAvailabilityListener.CameraTransitionCallback() {
|
||||
@@ -205,13 +214,15 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
SecureSettings secureSettings,
|
||||
BroadcastDispatcher broadcastDispatcher,
|
||||
TunerService tunerService,
|
||||
UserTracker userTracker) {
|
||||
UserTracker userTracker,
|
||||
PrivacyDotViewController dotViewController) {
|
||||
super(context);
|
||||
mMainHandler = handler;
|
||||
mSecureSettings = secureSettings;
|
||||
mBroadcastDispatcher = broadcastDispatcher;
|
||||
mTunerService = tunerService;
|
||||
mUserTracker = userTracker;
|
||||
mDotViewController = dotViewController;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -222,6 +233,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
}
|
||||
mHandler = startHandlerThread();
|
||||
mHandler.post(this::startOnScreenDecorationsThread);
|
||||
mDotViewController.setUiExecutor(mHandler::post);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -286,6 +298,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
|
||||
private void setupDecorations() {
|
||||
if (hasRoundedCorners() || shouldDrawCutout()) {
|
||||
updateStatusBarHeight();
|
||||
final DisplayCutout cutout = getCutout();
|
||||
final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll();
|
||||
int rotatedPos;
|
||||
@@ -298,6 +311,10 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
removeOverlay(i);
|
||||
}
|
||||
}
|
||||
// Overlays have been created, send the dots to the controller
|
||||
//TODO: need a better way to do this
|
||||
mDotViewController.initialize(
|
||||
mTopLeftDot, mTopRightDot, mBottomLeftDot, mBottomRightDot);
|
||||
} else {
|
||||
removeAllOverlays();
|
||||
}
|
||||
@@ -431,14 +448,21 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
private View overlayForPosition(@BoundsPosition int pos) {
|
||||
switch (pos) {
|
||||
case BOUNDS_POSITION_TOP:
|
||||
return LayoutInflater.from(mContext)
|
||||
case BOUNDS_POSITION_LEFT:
|
||||
View top = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.rounded_corners_top, null);
|
||||
mTopLeftDot = top.findViewById(R.id.privacy_dot_left_container);
|
||||
mTopRightDot = top.findViewById(R.id.privacy_dot_right_container);
|
||||
return top;
|
||||
case BOUNDS_POSITION_BOTTOM:
|
||||
return LayoutInflater.from(mContext)
|
||||
case BOUNDS_POSITION_RIGHT:
|
||||
View bottom = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.rounded_corners_bottom, null);
|
||||
mBottomLeftDot = bottom.findViewById(R.id.privacy_dot_left_container);
|
||||
mBottomRightDot = bottom.findViewById(R.id.privacy_dot_right_container);
|
||||
return bottom;
|
||||
default:
|
||||
return LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.rounded_corners, null);
|
||||
throw new IllegalArgumentException("Unknown bounds position");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,6 +599,11 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
View child;
|
||||
for (int j = 0; j < size; j++) {
|
||||
child = ((ViewGroup) mOverlays[i]).getChildAt(j);
|
||||
if (child.getId() == R.id.privacy_dot_left_container
|
||||
|| child.getId() == R.id.privacy_dot_right_container) {
|
||||
// Exclude privacy dot from color inversion (for now?)
|
||||
continue;
|
||||
}
|
||||
if (child instanceof ImageView) {
|
||||
((ImageView) child).setImageTintList(tintList);
|
||||
} else if (child instanceof DisplayCutoutView) {
|
||||
@@ -611,10 +640,15 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
|
||||
"must call on " + mHandler.getLooper().getThread()
|
||||
+ ", but was " + Thread.currentThread());
|
||||
|
||||
int newRotation = mContext.getDisplay().getRotation();
|
||||
if (mRotation != newRotation) {
|
||||
mDotViewController.updateRotation(newRotation);
|
||||
}
|
||||
|
||||
if (mPendingRotationChange) {
|
||||
return;
|
||||
}
|
||||
int newRotation = mContext.getDisplay().getRotation();
|
||||
if (newRotation != mRotation) {
|
||||
mRotation = newRotation;
|
||||
|
||||
@@ -630,6 +664,14 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStatusBarHeight() {
|
||||
mStatusBarHeightLandscape = mContext.getResources().getDimensionPixelSize(
|
||||
com.android.internal.R.dimen.status_bar_height_landscape);
|
||||
mStatusBarHeightPortrait = mContext.getResources().getDimensionPixelSize(
|
||||
com.android.internal.R.dimen.status_bar_height_portrait);
|
||||
mDotViewController.setStatusBarHeights(mStatusBarHeightPortrait, mStatusBarHeightLandscape);
|
||||
}
|
||||
|
||||
private void updateRoundedCornerRadii() {
|
||||
// We should eventually move to just using the intrinsic size of the drawables since
|
||||
// they should be sized to the exact pixels they want to cover. Therefore I'm purposely not
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* 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.statusbar.events
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.UiThread
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
|
||||
import com.android.systemui.animation.Interpolators
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
|
||||
import java.lang.IllegalStateException
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Understands how to keep the persistent privacy dot in the corner of the screen in
|
||||
* ScreenDecorations, which does not rotate with the device.
|
||||
*
|
||||
* The basic principle here is that each dot will sit in a box that is equal to the margins of the
|
||||
* status bar (specifically the status_bar_contents view in PhoneStatusBarView). Each dot container
|
||||
* will have its gravity set towards the corner (i.e., top-right corner gets top|right gravity), and
|
||||
* the contained ImageView will be set to center_vertical and away from the corner horizontally. The
|
||||
* Views will match the status bar top padding and status bar height so that the dot can appear to
|
||||
* reside directly after the status bar system contents (basically to the right of the battery).
|
||||
*
|
||||
* NOTE: any operation that modifies views directly must run on the provided executor, because
|
||||
* these views are owned by ScreenDecorations and it runs in its own thread
|
||||
*/
|
||||
|
||||
@SysUISingleton
|
||||
class PrivacyDotViewController @Inject constructor(
|
||||
@Main val mainExecutor: Executor,
|
||||
val animationScheduler: SystemStatusAnimationScheduler
|
||||
) {
|
||||
private var rotation = 0
|
||||
private var leftSize = 0
|
||||
private var rightSize = 0
|
||||
|
||||
private var sbHeightPortrait = 0
|
||||
private var sbHeightLandscape = 0
|
||||
|
||||
private var hasMultipleHeights = false
|
||||
private var needsHeightUpdate = false
|
||||
private var needsRotationUpdate = false
|
||||
private var needsMarginUpdate = false
|
||||
|
||||
private lateinit var tl: View
|
||||
private lateinit var tr: View
|
||||
private lateinit var bl: View
|
||||
private lateinit var br: View
|
||||
|
||||
// Track which corner is active (based on orientation + RTL)
|
||||
private var designatedCorner: View? = null
|
||||
|
||||
// Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread
|
||||
private var uiExecutor: Executor? = null
|
||||
|
||||
private val views: Sequence<View>
|
||||
get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
|
||||
|
||||
fun setUiExecutor(e: Executor) {
|
||||
uiExecutor = e
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun updateRotation(rot: Int) {
|
||||
if (rot == rotation) {
|
||||
return
|
||||
}
|
||||
|
||||
// A rotation has started, hide the views to avoid flicker
|
||||
setCornerVisibilities(View.INVISIBLE)
|
||||
|
||||
if (hasMultipleHeights && (rotation % 2) != (rot % 2)) {
|
||||
// we've changed from vertical to horizontal; update status bar height
|
||||
needsHeightUpdate = true
|
||||
}
|
||||
|
||||
rotation = rot
|
||||
needsRotationUpdate = true
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun updateHeights(rot: Int) {
|
||||
val height = when (rot) {
|
||||
0, 2 -> sbHeightPortrait
|
||||
1, 3 -> sbHeightLandscape
|
||||
else -> 0
|
||||
}
|
||||
|
||||
views.forEach { it.layoutParams.height = height }
|
||||
}
|
||||
|
||||
// Update the gravity and margins of the privacy views
|
||||
@UiThread
|
||||
private fun updateRotations() {
|
||||
// To keep a view in the corner, its gravity is always the description of its current corner
|
||||
// Therefore, just figure out which view is in which corner. This turns out to be something
|
||||
// like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
|
||||
// rotating the device counter-clockwise increments rotation by 1
|
||||
|
||||
views.forEach { corner ->
|
||||
val rotatedCorner = rotatedCorner(cornerForView(corner))
|
||||
(corner.layoutParams as FrameLayout.LayoutParams).apply {
|
||||
gravity = rotatedCorner.toGravity()
|
||||
}
|
||||
|
||||
// Set the dot's view gravity to hug the status bar
|
||||
(corner.findViewById<View>(R.id.privacy_dot)
|
||||
.layoutParams as FrameLayout.LayoutParams)
|
||||
.gravity = rotatedCorner.innerGravity()
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun updateCornerSizes() {
|
||||
views.forEach { corner ->
|
||||
val rotatedCorner = rotatedCorner(cornerForView(corner))
|
||||
val w = widthForCorner(rotatedCorner)
|
||||
Log.d(TAG, "updateCornerSizes: setting (${cornerForView(corner)}) to $w")
|
||||
(corner.layoutParams as FrameLayout.LayoutParams).width = w
|
||||
corner.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
// Designated view will be the one at statusbar's view.END
|
||||
@UiThread
|
||||
private fun selectDesignatedCorner(): View? {
|
||||
if (!this::tl.isInitialized) {
|
||||
return null
|
||||
}
|
||||
|
||||
val isRtl = tl.isLayoutRtl
|
||||
|
||||
return when (rotation) {
|
||||
0 -> if (isRtl) tl else tr
|
||||
1 -> if (isRtl) tr else br
|
||||
2 -> if (isRtl) br else bl
|
||||
3 -> if (isRtl) bl else tl
|
||||
else -> throw IllegalStateException("unknown rotation")
|
||||
}
|
||||
}
|
||||
|
||||
// Track the current designated corner and maybe animate to a new rotation
|
||||
@UiThread
|
||||
private fun updateDesignatedCorner(newCorner: View) {
|
||||
designatedCorner = newCorner
|
||||
|
||||
if (animationScheduler.hasPersistentDot) {
|
||||
designatedCorner!!.visibility = View.VISIBLE
|
||||
designatedCorner!!.alpha = 0f
|
||||
designatedCorner!!.animate()
|
||||
.alpha(1.0f)
|
||||
.setDuration(300)
|
||||
.start()
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun setCornerVisibilities(vis: Int) {
|
||||
views.forEach { corner ->
|
||||
corner.visibility = vis
|
||||
}
|
||||
}
|
||||
|
||||
private fun cornerForView(v: View): Int {
|
||||
return when (v) {
|
||||
tl -> TOP_LEFT
|
||||
tr -> TOP_RIGHT
|
||||
bl -> BOTTOM_LEFT
|
||||
br -> BOTTOM_RIGHT
|
||||
else -> throw IllegalArgumentException("not a corner view")
|
||||
}
|
||||
}
|
||||
|
||||
private fun rotatedCorner(corner: Int): Int {
|
||||
var modded = corner - rotation
|
||||
if (modded < 0) {
|
||||
modded += 4
|
||||
}
|
||||
|
||||
return modded
|
||||
}
|
||||
|
||||
private fun widthForCorner(corner: Int): Int {
|
||||
return when (corner) {
|
||||
TOP_LEFT, BOTTOM_LEFT -> leftSize
|
||||
TOP_RIGHT, BOTTOM_RIGHT -> rightSize
|
||||
else -> throw IllegalArgumentException("Unknown corner")
|
||||
}
|
||||
}
|
||||
|
||||
fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
|
||||
if (this::tl.isInitialized && this::tr.isInitialized &&
|
||||
this::bl.isInitialized && this::br.isInitialized) {
|
||||
if (tl == topLeft && tr == topRight && bl == bottomLeft && br == bottomRight) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tl = topLeft
|
||||
tr = topRight
|
||||
bl = bottomLeft
|
||||
br = bottomRight
|
||||
|
||||
designatedCorner = selectDesignatedCorner()
|
||||
mainExecutor.execute {
|
||||
animationScheduler.addCallback(systemStatusAnimationCallback)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status bar height in portrait and landscape, in pixels. If they are the same you can
|
||||
* pass the same value twice
|
||||
*/
|
||||
fun setStatusBarHeights(portrait: Int, landscape: Int) {
|
||||
sbHeightPortrait = portrait
|
||||
sbHeightLandscape = landscape
|
||||
|
||||
hasMultipleHeights = portrait != landscape
|
||||
}
|
||||
|
||||
/**
|
||||
* The dot view containers will fill the margin in order to position the dots correctly
|
||||
*
|
||||
* @param left the space between the status bar contents and the left side of the screen
|
||||
* @param right space between the status bar contents and the right side of the screen
|
||||
*/
|
||||
fun setStatusBarMargins(left: Int, right: Int) {
|
||||
leftSize = left
|
||||
rightSize = right
|
||||
|
||||
needsMarginUpdate = true
|
||||
|
||||
// Margins come after PhoneStatusBarView does a layout pass, and so will always happen
|
||||
// after rotation changes. It is safe to execute the updates from here
|
||||
uiExecutor?.execute {
|
||||
doUpdates(needsRotationUpdate, needsHeightUpdate, needsMarginUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doUpdates(rot: Boolean, height: Boolean, width: Boolean) {
|
||||
var newDesignatedCorner: View? = null
|
||||
|
||||
if (rot) {
|
||||
needsRotationUpdate = false
|
||||
updateRotations()
|
||||
newDesignatedCorner = selectDesignatedCorner()
|
||||
}
|
||||
|
||||
if (height) {
|
||||
needsHeightUpdate = false
|
||||
updateHeights(rotation)
|
||||
}
|
||||
|
||||
if (width) {
|
||||
needsMarginUpdate = false
|
||||
updateCornerSizes()
|
||||
}
|
||||
|
||||
if (newDesignatedCorner != null && newDesignatedCorner != designatedCorner) {
|
||||
updateDesignatedCorner(newDesignatedCorner)
|
||||
}
|
||||
}
|
||||
|
||||
private val systemStatusAnimationCallback: SystemStatusAnimationCallback =
|
||||
object : SystemStatusAnimationCallback {
|
||||
override fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? {
|
||||
if (designatedCorner == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val alpha = ObjectAnimator.ofFloat(
|
||||
designatedCorner, "alpha", 0f, 1f)
|
||||
alpha.duration = DURATION
|
||||
alpha.interpolator = Interpolators.ALPHA_OUT
|
||||
alpha.addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationStart(animator: Animator) {
|
||||
uiExecutor?.execute { designatedCorner?.visibility = View.VISIBLE }
|
||||
}
|
||||
})
|
||||
return alpha
|
||||
}
|
||||
|
||||
override fun onHidePersistentDot(): Animator? {
|
||||
if (designatedCorner == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val alpha = ObjectAnimator.ofFloat(
|
||||
designatedCorner, "alpha", 1f, 0f)
|
||||
alpha.duration = DURATION
|
||||
alpha.interpolator = Interpolators.ALPHA_OUT
|
||||
alpha.addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animator: Animator) {
|
||||
uiExecutor?.execute { designatedCorner?.visibility = View.INVISIBLE }
|
||||
}
|
||||
})
|
||||
alpha.start()
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const val TOP_LEFT = 0
|
||||
const val TOP_RIGHT = 1
|
||||
const val BOTTOM_RIGHT = 2
|
||||
const val BOTTOM_LEFT = 3
|
||||
private const val DURATION = 160L
|
||||
private const val TAG = "PrivacyDotViewController"
|
||||
|
||||
private fun Int.toGravity(): Int {
|
||||
return when (this) {
|
||||
TOP_LEFT -> Gravity.TOP or Gravity.LEFT
|
||||
TOP_RIGHT -> Gravity.TOP or Gravity.RIGHT
|
||||
BOTTOM_LEFT -> Gravity.BOTTOM or Gravity.LEFT
|
||||
BOTTOM_RIGHT -> Gravity.BOTTOM or Gravity.RIGHT
|
||||
else -> throw IllegalArgumentException("Not a corner")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.innerGravity(): Int {
|
||||
return when (this) {
|
||||
TOP_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT
|
||||
TOP_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT
|
||||
BOTTOM_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT
|
||||
BOTTOM_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT
|
||||
else -> throw IllegalArgumentException("Not a corner")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.statusbar.events
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import com.android.settingslib.graph.ThemedBatteryDrawable
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.privacy.OngoingPrivacyChip
|
||||
import com.android.systemui.privacy.PrivacyItem
|
||||
|
||||
interface StatusEvent {
|
||||
val priority: Int
|
||||
// Whether or not to force the status bar open and show a dot
|
||||
val forceVisible: Boolean
|
||||
val viewCreator: (context: Context) -> View
|
||||
}
|
||||
|
||||
class BatteryEvent : StatusEvent {
|
||||
override val priority = 50
|
||||
override val forceVisible = false
|
||||
|
||||
override val viewCreator: (context: Context) -> View = { context ->
|
||||
val iv = ImageView(context)
|
||||
iv.setImageDrawable(ThemedBatteryDrawable(context, Color.WHITE))
|
||||
iv.setBackgroundDrawable(ColorDrawable(Color.GREEN))
|
||||
iv
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return javaClass.simpleName
|
||||
}
|
||||
}
|
||||
class PrivacyEvent : StatusEvent {
|
||||
override val priority = 100
|
||||
override val forceVisible = true
|
||||
var privacyItems: List<PrivacyItem> = listOf()
|
||||
|
||||
override val viewCreator: (context: Context) -> View = { context ->
|
||||
val v = LayoutInflater.from(context)
|
||||
.inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip
|
||||
v.privacyList = privacyItems
|
||||
v
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return javaClass.simpleName
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.statusbar.events
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.widget.FrameLayout
|
||||
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.statusbar.SuperStatusBarViewFactory
|
||||
import com.android.systemui.statusbar.phone.StatusBarWindowController
|
||||
import com.android.systemui.statusbar.phone.StatusBarWindowView
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* //TODO: this _probably_ doesn't control a window anymore
|
||||
* Controls the window for system event animations.
|
||||
*/
|
||||
class SystemEventChipAnimationController @Inject constructor(
|
||||
private val context: Context,
|
||||
private val statusBarViewFactory: SuperStatusBarViewFactory,
|
||||
private val statusBarWindowController: StatusBarWindowController
|
||||
) : SystemStatusChipAnimationCallback {
|
||||
var showPersistentDot = false
|
||||
set(value) {
|
||||
field = value
|
||||
statusBarWindowController.setForceStatusBarVisible(value)
|
||||
maybeUpdateShowDot()
|
||||
}
|
||||
|
||||
private lateinit var animationWindowView: FrameLayout
|
||||
private lateinit var animationDotView: View
|
||||
private lateinit var statusBarWindowView: StatusBarWindowView
|
||||
private var currentAnimatedView: View? = null
|
||||
|
||||
// TODO: move to dagger
|
||||
private var initialized = false
|
||||
|
||||
override fun onChipAnimationStart(
|
||||
viewCreator: (context: Context) -> View,
|
||||
@SystemAnimationState state: Int
|
||||
) {
|
||||
if (!initialized) init()
|
||||
|
||||
if (state == ANIMATING_IN) {
|
||||
currentAnimatedView = viewCreator(context)
|
||||
animationWindowView.addView(currentAnimatedView, layoutParamsDefault)
|
||||
|
||||
// We are animating IN; chip comes in from View.END
|
||||
currentAnimatedView?.apply {
|
||||
translationX = width.toFloat()
|
||||
alpha = 0f
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
// We are animating away
|
||||
currentAnimatedView?.apply {
|
||||
translationX = 0f
|
||||
alpha = 1f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChipAnimationEnd(@SystemAnimationState state: Int) {
|
||||
if (state == ANIMATING_IN) {
|
||||
// Finished animating in
|
||||
currentAnimatedView?.apply {
|
||||
translationX = 0f
|
||||
alpha = 1f
|
||||
}
|
||||
} else {
|
||||
// Finished animating away
|
||||
currentAnimatedView?.apply {
|
||||
visibility = View.INVISIBLE
|
||||
}
|
||||
animationWindowView.removeView(currentAnimatedView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChipAnimationUpdate(
|
||||
animator: ValueAnimator,
|
||||
@SystemAnimationState state: Int
|
||||
) {
|
||||
// Alpha is parameterized 0,1, and translation from (width, 0)
|
||||
currentAnimatedView?.apply {
|
||||
val amt = animator.animatedValue as Float
|
||||
|
||||
alpha = amt
|
||||
|
||||
val w = width
|
||||
val translation = (1 - amt) * w
|
||||
translationX = translation
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeUpdateShowDot() {
|
||||
if (!initialized) return
|
||||
if (!showPersistentDot && currentAnimatedView == null) {
|
||||
animationDotView.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun init() {
|
||||
initialized = true
|
||||
statusBarWindowView = statusBarViewFactory.statusBarWindowView
|
||||
animationWindowView = LayoutInflater.from(context)
|
||||
.inflate(R.layout.system_event_animation_window, null) as FrameLayout
|
||||
animationDotView = animationWindowView.findViewById(R.id.dot_view)
|
||||
val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||
lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
||||
statusBarWindowView.addView(animationWindowView, lp)
|
||||
}
|
||||
|
||||
private val layoutParamsDefault = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also {
|
||||
it.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.statusbar.events
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.privacy.PrivacyItem
|
||||
import com.android.systemui.privacy.PrivacyItemController
|
||||
import com.android.systemui.statusbar.policy.BatteryController
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Listens for system events (battery, privacy, connectivity) and allows listeners
|
||||
* to show status bar animations when they happen
|
||||
*/
|
||||
@SysUISingleton
|
||||
class SystemEventCoordinator @Inject constructor(
|
||||
private val batteryController: BatteryController,
|
||||
private val privacyController: PrivacyItemController
|
||||
) {
|
||||
private lateinit var scheduler: SystemStatusAnimationScheduler
|
||||
|
||||
fun startObserving() {
|
||||
/* currently unused
|
||||
batteryController.addCallback(batteryStateListener)
|
||||
*/
|
||||
privacyController.addCallback(privacyStateListener)
|
||||
}
|
||||
|
||||
fun stopObserving() {
|
||||
/* currently unused
|
||||
batteryController.removeCallback(batteryStateListener)
|
||||
*/
|
||||
privacyController.removeCallback(privacyStateListener)
|
||||
}
|
||||
|
||||
fun attachScheduler(s: SystemStatusAnimationScheduler) {
|
||||
this.scheduler = s
|
||||
}
|
||||
|
||||
fun notifyPluggedIn() {
|
||||
scheduler.onStatusEvent(BatteryEvent())
|
||||
}
|
||||
|
||||
fun notifyPrivacyItemsEmpty() {
|
||||
scheduler.setShouldShowPersistentPrivacyIndicator(false)
|
||||
}
|
||||
|
||||
fun notifyPrivacyItemsChanged() {
|
||||
val event = PrivacyEvent()
|
||||
event.privacyItems = privacyStateListener.currentPrivacyItems
|
||||
scheduler.onStatusEvent(event)
|
||||
}
|
||||
|
||||
private val batteryStateListener = object : BatteryController.BatteryStateChangeCallback {
|
||||
var plugged = false
|
||||
var stateKnown = false
|
||||
override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
|
||||
if (!stateKnown) {
|
||||
stateKnown = true
|
||||
plugged = pluggedIn
|
||||
notifyListeners()
|
||||
return
|
||||
}
|
||||
|
||||
if (plugged != pluggedIn) {
|
||||
plugged = pluggedIn
|
||||
notifyListeners()
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyListeners() {
|
||||
// We only care about the plugged in status
|
||||
if (plugged) notifyPluggedIn()
|
||||
}
|
||||
}
|
||||
|
||||
private val privacyStateListener = object : PrivacyItemController.Callback {
|
||||
var currentPrivacyItems = listOf<PrivacyItem>()
|
||||
|
||||
override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) {
|
||||
if (privacyItems.isNotEmpty() && currentPrivacyItems.containsAll(privacyItems)) {
|
||||
return
|
||||
}
|
||||
currentPrivacyItems = privacyItems
|
||||
notifyListeners()
|
||||
}
|
||||
|
||||
private fun notifyListeners() {
|
||||
if (currentPrivacyItems.isEmpty()) {
|
||||
notifyPrivacyItemsEmpty()
|
||||
} else {
|
||||
notifyPrivacyItemsChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val TAG = "SystemEventCoordinator"
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* 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.statusbar.events
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.IntDef
|
||||
import android.content.Context
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.statusbar.phone.StatusBarWindowController
|
||||
import com.android.systemui.statusbar.policy.CallbackController
|
||||
import com.android.systemui.util.Assert
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Dead-simple scheduler for system status events. Obeys the following principles (all values TBD):
|
||||
* - Avoiding log spam by only allowing 12 events per minute (1event/5s)
|
||||
* - Waits 100ms to schedule any event for debouncing/prioritization
|
||||
* - Simple prioritization: Privacy > Battery > connectivity (encoded in StatusEvent)
|
||||
* - Only schedules a single event, and throws away lowest priority events
|
||||
*
|
||||
* There are 4 basic stages of animation at play here:
|
||||
* 1. System chrome animation OUT
|
||||
* 2. Chip animation IN
|
||||
* 3. Chip animation OUT; potentially into a dot
|
||||
* 4. System chrome animation IN
|
||||
*
|
||||
* Thus we can keep all animations synchronized with two separate ValueAnimators, one for system
|
||||
* chrome and the other for the chip. These can animate from 0,1 and listeners can parameterize
|
||||
* their respective views based on the progress of the animator. Interpolation differences TBD
|
||||
*/
|
||||
@SysUISingleton
|
||||
class SystemStatusAnimationScheduler @Inject constructor(
|
||||
private val coordinator: SystemEventCoordinator,
|
||||
private val chipAnimationController: SystemEventChipAnimationController,
|
||||
private val statusBarWindowController: StatusBarWindowController,
|
||||
private val systemClock: SystemClock,
|
||||
@Main private val executor: DelayableExecutor
|
||||
) : CallbackController<SystemStatusAnimationCallback> {
|
||||
|
||||
/** True from the time a scheduled event starts until it's animation finishes */
|
||||
var isActive = false
|
||||
private set
|
||||
|
||||
@SystemAnimationState var animationState: Int = IDLE
|
||||
private set
|
||||
|
||||
/** True if the persistent privacy dot should be active */
|
||||
var hasPersistentDot = false
|
||||
private set
|
||||
|
||||
private var scheduledEvent: StatusEvent? = null
|
||||
private var cancelExecutionRunnable: Runnable? = null
|
||||
private val listeners = mutableSetOf<SystemStatusAnimationCallback>()
|
||||
|
||||
init {
|
||||
coordinator.attachScheduler(this)
|
||||
}
|
||||
|
||||
fun onStatusEvent(event: StatusEvent) {
|
||||
// Ignore any updates until the system is up and running
|
||||
if (isTooEarly()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't deal with threading for now (no need let's be honest)
|
||||
Assert.isMainThread()
|
||||
if (event.priority > scheduledEvent?.priority ?: -1) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "scheduling event $event")
|
||||
}
|
||||
scheduleEvent(event)
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "ignoring event $event")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearDotIfVisible() {
|
||||
notifyHidePersistentDot()
|
||||
}
|
||||
|
||||
fun setShouldShowPersistentPrivacyIndicator(should: Boolean) {
|
||||
if (hasPersistentDot == should) {
|
||||
return
|
||||
}
|
||||
|
||||
hasPersistentDot = should
|
||||
|
||||
if (!hasPersistentDot) {
|
||||
clearDotIfVisible()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTooEarly(): Boolean {
|
||||
Log.d(TAG, "time=> ${systemClock.uptimeMillis() - Process.getStartUptimeMillis()}")
|
||||
return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the scheduled event (if any) and schedule a new one
|
||||
*/
|
||||
private fun scheduleEvent(event: StatusEvent) {
|
||||
scheduledEvent = event
|
||||
if (scheduledEvent!!.forceVisible) {
|
||||
hasPersistentDot = true
|
||||
}
|
||||
|
||||
// Schedule the animation to start after a debounce period
|
||||
cancelExecutionRunnable = executor.executeDelayed({
|
||||
cancelExecutionRunnable = null
|
||||
animationState = ANIMATING_IN
|
||||
statusBarWindowController.setForceStatusBarVisible(true)
|
||||
|
||||
val entranceAnimator = ValueAnimator.ofFloat(1f, 0f)
|
||||
entranceAnimator.duration = ENTRANCE_ANIM_LENGTH
|
||||
entranceAnimator.addListener(systemAnimatorAdapter)
|
||||
entranceAnimator.addUpdateListener(systemUpdateListener)
|
||||
|
||||
val chipAnimator = ValueAnimator.ofFloat(0f, 1f)
|
||||
chipAnimator.duration = CHIP_ANIM_LENGTH
|
||||
chipAnimator.addListener(
|
||||
ChipAnimatorAdapter(RUNNING_CHIP_ANIM, scheduledEvent!!.viewCreator))
|
||||
chipAnimator.addUpdateListener(chipUpdateListener)
|
||||
|
||||
val aSet2 = AnimatorSet()
|
||||
aSet2.playSequentially(entranceAnimator, chipAnimator)
|
||||
aSet2.start()
|
||||
|
||||
executor.executeDelayed({
|
||||
animationState = ANIMATING_OUT
|
||||
|
||||
val systemAnimator = ValueAnimator.ofFloat(0f, 1f)
|
||||
systemAnimator.duration = ENTRANCE_ANIM_LENGTH
|
||||
systemAnimator.addListener(systemAnimatorAdapter)
|
||||
systemAnimator.addUpdateListener(systemUpdateListener)
|
||||
|
||||
val chipAnimator = ValueAnimator.ofFloat(1f, 0f)
|
||||
chipAnimator.duration = CHIP_ANIM_LENGTH
|
||||
chipAnimator.addListener(ChipAnimatorAdapter(IDLE, scheduledEvent!!.viewCreator))
|
||||
chipAnimator.addUpdateListener(chipUpdateListener)
|
||||
|
||||
val aSet2 = AnimatorSet()
|
||||
|
||||
aSet2.play(chipAnimator).before(systemAnimator)
|
||||
if (hasPersistentDot) {
|
||||
val dotAnim = notifyTransitionToPersistentDot()
|
||||
if (dotAnim != null) aSet2.playTogether(systemAnimator, dotAnim)
|
||||
}
|
||||
|
||||
aSet2.start()
|
||||
|
||||
statusBarWindowController.setForceStatusBarVisible(false)
|
||||
scheduledEvent = null
|
||||
}, DISPLAY_LENGTH)
|
||||
}, DELAY)
|
||||
}
|
||||
|
||||
private fun notifyTransitionToPersistentDot(): Animator? {
|
||||
val anims: List<Animator> = listeners.mapNotNull {
|
||||
it.onSystemStatusAnimationTransitionToPersistentDot()
|
||||
}
|
||||
if (anims.isNotEmpty()) {
|
||||
val aSet = AnimatorSet()
|
||||
aSet.playTogether(anims)
|
||||
return aSet
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun notifyHidePersistentDot(): Animator? {
|
||||
val anims: List<Animator> = listeners.mapNotNull {
|
||||
it.onHidePersistentDot()
|
||||
}
|
||||
|
||||
if (anims.isNotEmpty()) {
|
||||
val aSet = AnimatorSet()
|
||||
aSet.playTogether(anims)
|
||||
return aSet
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun notifySystemStart() {
|
||||
listeners.forEach { it.onSystemChromeAnimationStart() }
|
||||
}
|
||||
|
||||
private fun notifySystemFinish() {
|
||||
listeners.forEach { it.onSystemChromeAnimationEnd() }
|
||||
}
|
||||
|
||||
private fun notifySystemAnimationUpdate(anim: ValueAnimator) {
|
||||
listeners.forEach { it.onSystemChromeAnimationUpdate(anim) }
|
||||
}
|
||||
|
||||
override fun addCallback(listener: SystemStatusAnimationCallback) {
|
||||
Assert.isMainThread()
|
||||
|
||||
if (listeners.isEmpty()) {
|
||||
coordinator.startObserving()
|
||||
}
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
override fun removeCallback(listener: SystemStatusAnimationCallback) {
|
||||
Assert.isMainThread()
|
||||
|
||||
listeners.remove(listener)
|
||||
if (listeners.isEmpty()) {
|
||||
coordinator.stopObserving()
|
||||
}
|
||||
}
|
||||
|
||||
private val systemUpdateListener = ValueAnimator.AnimatorUpdateListener {
|
||||
anim -> notifySystemAnimationUpdate(anim)
|
||||
}
|
||||
|
||||
private val systemAnimatorAdapter = object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(p0: Animator?) {
|
||||
notifySystemFinish()
|
||||
}
|
||||
|
||||
override fun onAnimationStart(p0: Animator?) {
|
||||
notifySystemStart()
|
||||
}
|
||||
}
|
||||
|
||||
private val chipUpdateListener = ValueAnimator.AnimatorUpdateListener {
|
||||
anim -> chipAnimationController.onChipAnimationUpdate(anim, animationState)
|
||||
}
|
||||
|
||||
inner class ChipAnimatorAdapter(
|
||||
@SystemAnimationState val endState: Int,
|
||||
val viewCreator: (context: Context) -> View
|
||||
) : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(p0: Animator?) {
|
||||
chipAnimationController.onChipAnimationEnd(animationState)
|
||||
animationState = endState
|
||||
}
|
||||
|
||||
override fun onAnimationStart(p0: Animator?) {
|
||||
chipAnimationController.onChipAnimationStart(viewCreator, animationState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The general idea here is that this scheduler will run two value animators, and provide
|
||||
* animator-like callbacks for each kind of animation. The SystemChrome animation is expected to
|
||||
* create space for the chip animation to display. This means hiding the system elements in the
|
||||
* status bar and keyguard.
|
||||
*
|
||||
* TODO: the chip animation really only has one client, we can probably remove it from this
|
||||
* interface
|
||||
*
|
||||
* The value animators themselves are simple animators from 0.0 to 1.0. Listeners can apply any
|
||||
* interpolation they choose but realistically these are most likely to be simple alpha transitions
|
||||
*/
|
||||
interface SystemStatusAnimationCallback {
|
||||
@JvmDefault fun onSystemChromeAnimationUpdate(animator: ValueAnimator) {}
|
||||
@JvmDefault fun onSystemChromeAnimationStart() {}
|
||||
@JvmDefault fun onSystemChromeAnimationEnd() {}
|
||||
|
||||
// Best method name, change my mind
|
||||
@JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { return null }
|
||||
@JvmDefault fun onHidePersistentDot(): Animator? { return null }
|
||||
}
|
||||
|
||||
interface SystemStatusChipAnimationCallback {
|
||||
fun onChipAnimationUpdate(animator: ValueAnimator, @SystemAnimationState state: Int) {}
|
||||
|
||||
fun onChipAnimationStart(
|
||||
viewCreator: (context: Context) -> View,
|
||||
@SystemAnimationState state: Int
|
||||
) {}
|
||||
|
||||
fun onChipAnimationEnd(@SystemAnimationState state: Int) {}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@IntDef(
|
||||
value = [
|
||||
IDLE, ANIMATING_IN, RUNNING_CHIP_ANIM, ANIMATING_OUT
|
||||
]
|
||||
)
|
||||
annotation class SystemAnimationState
|
||||
|
||||
/** No animation is in progress */
|
||||
const val IDLE = 0
|
||||
/** System is animating out, and chip is animating in */
|
||||
const val ANIMATING_IN = 1
|
||||
/** Chip has animated in and is awaiting exit animation, and optionally playing its own animation */
|
||||
const val RUNNING_CHIP_ANIM = 2
|
||||
/** Chip is animating away and system is animating back */
|
||||
const val ANIMATING_OUT = 3
|
||||
|
||||
private const val TAG = "SystemStatusAnimationScheduler"
|
||||
private const val DELAY: Long = 100
|
||||
private const val DISPLAY_LENGTH = 5000L
|
||||
private const val ENTRANCE_ANIM_LENGTH = 500L
|
||||
private const val CHIP_ANIM_LENGTH = 500L
|
||||
private const val MIN_UPTIME: Long = 5 * 1000
|
||||
|
||||
private const val DEBUG = false
|
||||
@@ -19,10 +19,16 @@ import static android.app.StatusBarManager.DISABLE_CLOCK;
|
||||
import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
|
||||
import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
|
||||
|
||||
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
|
||||
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
|
||||
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Fragment;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -36,6 +42,9 @@ import com.android.systemui.animation.Interpolators;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.CommandQueue;
|
||||
import com.android.systemui.statusbar.StatusBarState;
|
||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
|
||||
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
|
||||
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
|
||||
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
|
||||
@@ -44,6 +53,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
import com.android.systemui.statusbar.policy.NetworkController;
|
||||
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -55,7 +66,8 @@ import javax.inject.Inject;
|
||||
* updated by the StatusBarIconController and DarkIconManager while it is attached.
|
||||
*/
|
||||
public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks,
|
||||
StatusBarStateController.StateListener {
|
||||
StatusBarStateController.StateListener,
|
||||
SystemStatusAnimationCallback {
|
||||
|
||||
public static final String TAG = "CollapsedStatusBarFragment";
|
||||
private static final String EXTRA_PANEL_STATE = "panel_state";
|
||||
@@ -78,6 +90,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
|
||||
private View mOperatorNameFrame;
|
||||
private CommandQueue mCommandQueue;
|
||||
private OngoingCallController mOngoingCallController;
|
||||
private final SystemStatusAnimationScheduler mAnimationScheduler;
|
||||
private final PrivacyDotViewController mDotViewController;
|
||||
|
||||
private List<String> mBlockedIcons = new ArrayList<>();
|
||||
|
||||
@@ -103,8 +117,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
|
||||
};
|
||||
|
||||
@Inject
|
||||
public CollapsedStatusBarFragment(OngoingCallController ongoingCallController) {
|
||||
public CollapsedStatusBarFragment(
|
||||
OngoingCallController ongoingCallController,
|
||||
SystemStatusAnimationScheduler animationScheduler,
|
||||
PrivacyDotViewController dotViewController
|
||||
) {
|
||||
mOngoingCallController = ongoingCallController;
|
||||
mAnimationScheduler = animationScheduler;
|
||||
mDotViewController = dotViewController;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,6 +147,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
mStatusBar = (PhoneStatusBarView) view;
|
||||
View contents = mStatusBar.findViewById(R.id.status_bar_contents);
|
||||
contents.addOnLayoutChangeListener(mStatusBarLayoutListener);
|
||||
updateStatusBarLocation(contents.getLeft(), contents.getRight());
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
|
||||
mStatusBar.restoreHierarchyState(
|
||||
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
|
||||
@@ -143,6 +166,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
|
||||
showClock(false);
|
||||
initEmergencyCryptkeeperText();
|
||||
initOperatorName();
|
||||
mAnimationScheduler.addCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -208,6 +232,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
|
||||
if (displayId != getContext().getDisplayId()) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "disable: ");
|
||||
state1 = adjustDisableFlags(state1);
|
||||
final int old1 = mDisabled1;
|
||||
final int diff1 = state1 ^ old1;
|
||||
@@ -292,19 +317,22 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
|
||||
return false;
|
||||
}
|
||||
|
||||
public void hideSystemIconArea(boolean animate) {
|
||||
private void hideSystemIconArea(boolean animate) {
|
||||
animateHide(mSystemIconArea, animate);
|
||||
}
|
||||
|
||||
public void showSystemIconArea(boolean animate) {
|
||||
animateShow(mSystemIconArea, animate);
|
||||
private void showSystemIconArea(boolean animate) {
|
||||
// Only show the system icon area if we are not currently animating
|
||||
if (mAnimationScheduler.getAnimationState() == IDLE) {
|
||||
animateShow(mSystemIconArea, animate);
|
||||
}
|
||||
}
|
||||
|
||||
public void hideClock(boolean animate) {
|
||||
private void hideClock(boolean animate) {
|
||||
animateHiddenState(mClockView, clockHiddenMode(), animate);
|
||||
}
|
||||
|
||||
public void showClock(boolean animate) {
|
||||
private void showClock(boolean animate) {
|
||||
animateShow(mClockView, animate);
|
||||
}
|
||||
|
||||
@@ -425,12 +453,60 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(int newState) {
|
||||
|
||||
}
|
||||
public void onStateChanged(int newState) { }
|
||||
|
||||
@Override
|
||||
public void onDozingChanged(boolean isDozing) {
|
||||
disable(getContext().getDisplayId(), mDisabled1, mDisabled1, false /* animate */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemChromeAnimationStart() {
|
||||
if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT
|
||||
&& !isSystemIconAreaDisabled()) {
|
||||
mSystemIconArea.setVisibility(View.VISIBLE);
|
||||
mSystemIconArea.setAlpha(0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemChromeAnimationEnd() {
|
||||
// Make sure the system icons are out of the way
|
||||
if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) {
|
||||
mSystemIconArea.setVisibility(View.INVISIBLE);
|
||||
mSystemIconArea.setAlpha(0f);
|
||||
} else {
|
||||
if (isSystemIconAreaDisabled()) {
|
||||
// don't unhide
|
||||
return;
|
||||
}
|
||||
|
||||
mSystemIconArea.setAlpha(1f);
|
||||
mSystemIconArea.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemChromeAnimationUpdate(@NotNull ValueAnimator animator) {
|
||||
mSystemIconArea.setAlpha((float) animator.getAnimatedValue());
|
||||
}
|
||||
|
||||
private boolean isSystemIconAreaDisabled() {
|
||||
return (mDisabled1 & DISABLE_SYSTEM_INFO) != 0 || (mDisabled2 & DISABLE2_SYSTEM_ICONS) != 0;
|
||||
}
|
||||
|
||||
private void updateStatusBarLocation(int left, int right) {
|
||||
int leftMargin = left - mStatusBar.getLeft();
|
||||
int rightMargin = mStatusBar.getRight() - right;
|
||||
|
||||
mDotViewController.setStatusBarMargins(leftMargin, rightMargin);
|
||||
}
|
||||
|
||||
// Listen for view end changes of PhoneStatusBarView and publish that to the privacy dot
|
||||
private View.OnLayoutChangeListener mStatusBarLayoutListener =
|
||||
(view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||
if (left != oldLeft || right != oldRight) {
|
||||
updateStatusBarLocation(left, right);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ package com.android.systemui.statusbar.phone;
|
||||
|
||||
import static com.android.systemui.DejankUtils.whitelistIpcs;
|
||||
import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
|
||||
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
|
||||
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.ColorInt;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
@@ -47,6 +50,8 @@ import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.animation.Interpolators;
|
||||
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
|
||||
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
|
||||
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
|
||||
import com.android.systemui.statusbar.policy.BatteryController;
|
||||
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
|
||||
@@ -64,8 +69,11 @@ import java.util.List;
|
||||
/**
|
||||
* The header group on Keyguard.
|
||||
*/
|
||||
public class KeyguardStatusBarView extends RelativeLayout
|
||||
implements BatteryStateChangeCallback, OnUserInfoChangedListener, ConfigurationListener {
|
||||
public class KeyguardStatusBarView extends RelativeLayout implements
|
||||
BatteryStateChangeCallback,
|
||||
OnUserInfoChangedListener,
|
||||
ConfigurationListener,
|
||||
SystemStatusAnimationCallback {
|
||||
|
||||
private static final int LAYOUT_NONE = 0;
|
||||
private static final int LAYOUT_CUTOUT = 1;
|
||||
@@ -96,6 +104,8 @@ public class KeyguardStatusBarView extends RelativeLayout
|
||||
private ViewGroup mStatusIconArea;
|
||||
private int mLayoutState = LAYOUT_NONE;
|
||||
|
||||
private SystemStatusAnimationScheduler mAnimationScheduler;
|
||||
|
||||
/**
|
||||
* Draw this many pixels into the left/right side of the cutout to optimally use the space
|
||||
*/
|
||||
@@ -125,6 +135,7 @@ public class KeyguardStatusBarView extends RelativeLayout
|
||||
loadDimens();
|
||||
loadBlockList();
|
||||
mBatteryController = Dependency.get(BatteryController.class);
|
||||
mAnimationScheduler = Dependency.get(SystemStatusAnimationScheduler.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -349,6 +360,7 @@ public class KeyguardStatusBarView extends RelativeLayout
|
||||
mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
|
||||
mIconManager.setBlockList(mBlockedIcons);
|
||||
Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
|
||||
mAnimationScheduler.addCallback(this);
|
||||
onThemeChanged();
|
||||
}
|
||||
|
||||
@@ -358,6 +370,7 @@ public class KeyguardStatusBarView extends RelativeLayout
|
||||
Dependency.get(UserInfoController.class).removeCallback(this);
|
||||
Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
|
||||
Dependency.get(ConfigurationController.class).removeCallback(this);
|
||||
mAnimationScheduler.removeCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -509,4 +522,30 @@ public class KeyguardStatusBarView extends RelativeLayout
|
||||
mBatteryView.dump(fd, pw, args);
|
||||
}
|
||||
}
|
||||
|
||||
/** SystemStatusAnimationCallback */
|
||||
@Override
|
||||
public void onSystemChromeAnimationStart() {
|
||||
if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT) {
|
||||
mSystemIconsContainer.setVisibility(View.VISIBLE);
|
||||
mSystemIconsContainer.setAlpha(0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemChromeAnimationEnd() {
|
||||
// Make sure the system icons are out of the way
|
||||
if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) {
|
||||
mSystemIconsContainer.setVisibility(View.INVISIBLE);
|
||||
mSystemIconsContainer.setAlpha(0f);
|
||||
} else {
|
||||
mSystemIconsContainer.setAlpha(1f);
|
||||
mSystemIconsContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemChromeAnimationUpdate(ValueAnimator anim) {
|
||||
mSystemIconsContainer.setAlpha((float) anim.getAnimatedValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +208,8 @@ import com.android.systemui.statusbar.SuperStatusBarViewFactory;
|
||||
import com.android.systemui.statusbar.SysuiStatusBarStateController;
|
||||
import com.android.systemui.statusbar.VibratorHelper;
|
||||
import com.android.systemui.statusbar.charging.WiredChargingRippleController;
|
||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
|
||||
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
||||
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
|
||||
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
|
||||
@@ -426,6 +428,8 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
private final DemoModeController mDemoModeController;
|
||||
private NotificationsController mNotificationsController;
|
||||
private final OngoingCallController mOngoingCallController;
|
||||
private final SystemStatusAnimationScheduler mAnimationScheduler;
|
||||
private final PrivacyDotViewController mDotViewController;
|
||||
|
||||
// expanded notifications
|
||||
// the sliding/resizing panel within the notification window
|
||||
@@ -794,6 +798,8 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
BrightnessSlider.Factory brightnessSliderFactory,
|
||||
WiredChargingRippleController chargingRippleAnimationController,
|
||||
OngoingCallController ongoingCallController,
|
||||
SystemStatusAnimationScheduler animationScheduler,
|
||||
PrivacyDotViewController dotViewController,
|
||||
TunerService tunerService,
|
||||
FeatureFlags featureFlags) {
|
||||
super(context);
|
||||
@@ -875,6 +881,8 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
mBrightnessSliderFactory = brightnessSliderFactory;
|
||||
mChargingRippleAnimationController = chargingRippleAnimationController;
|
||||
mOngoingCallController = ongoingCallController;
|
||||
mAnimationScheduler = animationScheduler;
|
||||
mDotViewController = dotViewController;
|
||||
mFeatureFlags = featureFlags;
|
||||
|
||||
tunerService.addTunable(
|
||||
@@ -1171,7 +1179,10 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
}).getFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.status_bar_container,
|
||||
new CollapsedStatusBarFragment(mOngoingCallController),
|
||||
new CollapsedStatusBarFragment(
|
||||
mOngoingCallController,
|
||||
mAnimationScheduler,
|
||||
mDotViewController),
|
||||
CollapsedStatusBarFragment.TAG)
|
||||
.commit();
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ import com.android.systemui.statusbar.SuperStatusBarViewFactory;
|
||||
import com.android.systemui.statusbar.SysuiStatusBarStateController;
|
||||
import com.android.systemui.statusbar.VibratorHelper;
|
||||
import com.android.systemui.statusbar.charging.WiredChargingRippleController;
|
||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
|
||||
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
||||
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
|
||||
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
|
||||
@@ -209,6 +211,8 @@ public interface StatusBarPhoneModule {
|
||||
BrightnessSlider.Factory brightnessSliderFactory,
|
||||
WiredChargingRippleController chargingRippleAnimationController,
|
||||
OngoingCallController ongoingCallController,
|
||||
SystemStatusAnimationScheduler animationScheduler,
|
||||
PrivacyDotViewController dotViewController,
|
||||
TunerService tunerService,
|
||||
FeatureFlags featureFlags) {
|
||||
return new StatusBar(
|
||||
@@ -293,6 +297,8 @@ public interface StatusBarPhoneModule {
|
||||
brightnessSliderFactory,
|
||||
chargingRippleAnimationController,
|
||||
ongoingCallController,
|
||||
animationScheduler,
|
||||
dotViewController,
|
||||
tunerService,
|
||||
featureFlags);
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ import androidx.test.filters.SmallTest;
|
||||
import com.android.systemui.R.dimen;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||
import com.android.systemui.tuner.TunerService;
|
||||
import com.android.systemui.util.settings.FakeSettings;
|
||||
import com.android.systemui.util.settings.SecureSettings;
|
||||
@@ -94,6 +95,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
private BroadcastDispatcher mBroadcastDispatcher;
|
||||
@Mock
|
||||
private UserTracker mUserTracker;
|
||||
@Mock
|
||||
private PrivacyDotViewController mDotViewController;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
@@ -116,7 +119,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
|
||||
|
||||
mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler, mSecureSettings,
|
||||
mBroadcastDispatcher, mTunerService, mUserTracker) {
|
||||
mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController) {
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
|
||||
@@ -37,6 +37,8 @@ import androidx.test.filters.SmallTest;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiBaseFragmentTest;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
|
||||
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
|
||||
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
|
||||
|
||||
@@ -58,6 +60,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
|
||||
private View mCenteredNotificationAreaView;
|
||||
private StatusBarStateController mStatusBarStateController;
|
||||
private OngoingCallController mOngoingCallController;
|
||||
private SystemStatusAnimationScheduler mAnimationScheduler;
|
||||
private PrivacyDotViewController mDotViewController;
|
||||
|
||||
public CollapsedStatusBarFragmentTest() {
|
||||
super(CollapsedStatusBarFragment.class);
|
||||
@@ -212,6 +216,11 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
|
||||
@Override
|
||||
protected Fragment instantiate(Context context, String className, Bundle arguments) {
|
||||
mOngoingCallController = mock(OngoingCallController.class);
|
||||
return new CollapsedStatusBarFragment(mOngoingCallController);
|
||||
mAnimationScheduler = mock(SystemStatusAnimationScheduler.class);
|
||||
mDotViewController = mock(PrivacyDotViewController.class);
|
||||
return new CollapsedStatusBarFragment(
|
||||
mOngoingCallController,
|
||||
mAnimationScheduler,
|
||||
mDotViewController);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl;
|
||||
import com.android.systemui.statusbar.SuperStatusBarViewFactory;
|
||||
import com.android.systemui.statusbar.VibratorHelper;
|
||||
import com.android.systemui.statusbar.charging.WiredChargingRippleController;
|
||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
|
||||
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
@@ -263,6 +265,8 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
@Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
|
||||
@Mock private WiredChargingRippleController mWiredChargingRippleController;
|
||||
@Mock private OngoingCallController mOngoingCallController;
|
||||
@Mock private SystemStatusAnimationScheduler mAnimationScheduler;
|
||||
@Mock private PrivacyDotViewController mDotViewController;
|
||||
@Mock private TunerService mTunerService;
|
||||
@Mock private FeatureFlags mFeatureFlags;
|
||||
private ShadeController mShadeController;
|
||||
@@ -429,6 +433,8 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
mBrightnessSliderFactory,
|
||||
mWiredChargingRippleController,
|
||||
mOngoingCallController,
|
||||
mAnimationScheduler,
|
||||
mDotViewController,
|
||||
mTunerService,
|
||||
mFeatureFlags);
|
||||
when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
|
||||
|
||||
Reference in New Issue
Block a user