Merge "Privacy chip (3/many)" into sc-dev
This commit is contained in:
@@ -91,6 +91,7 @@ import com.android.systemui.settings.UserTracker;
|
|||||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||||
import com.android.systemui.tuner.TunerService;
|
import com.android.systemui.tuner.TunerService;
|
||||||
import com.android.systemui.tuner.TunerService.Tunable;
|
import com.android.systemui.tuner.TunerService.Tunable;
|
||||||
|
import com.android.systemui.util.concurrency.ThreadFactory;
|
||||||
import com.android.systemui.util.settings.SecureSettings;
|
import com.android.systemui.util.settings.SecureSettings;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -128,6 +129,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
|||||||
private CameraAvailabilityListener mCameraListener;
|
private CameraAvailabilityListener mCameraListener;
|
||||||
private final UserTracker mUserTracker;
|
private final UserTracker mUserTracker;
|
||||||
private final PrivacyDotViewController mDotViewController;
|
private final PrivacyDotViewController mDotViewController;
|
||||||
|
private final ThreadFactory mThreadFactory;
|
||||||
|
|
||||||
//TODO: These are piecemeal being updated to Points for now to support non-square rounded
|
//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
|
// corners. for now it is only supposed when reading the intrinsic size from the drawables with
|
||||||
@@ -215,7 +217,8 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
|||||||
BroadcastDispatcher broadcastDispatcher,
|
BroadcastDispatcher broadcastDispatcher,
|
||||||
TunerService tunerService,
|
TunerService tunerService,
|
||||||
UserTracker userTracker,
|
UserTracker userTracker,
|
||||||
PrivacyDotViewController dotViewController) {
|
PrivacyDotViewController dotViewController,
|
||||||
|
ThreadFactory threadFactory) {
|
||||||
super(context);
|
super(context);
|
||||||
mMainHandler = handler;
|
mMainHandler = handler;
|
||||||
mSecureSettings = secureSettings;
|
mSecureSettings = secureSettings;
|
||||||
@@ -223,6 +226,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
|||||||
mTunerService = tunerService;
|
mTunerService = tunerService;
|
||||||
mUserTracker = userTracker;
|
mUserTracker = userTracker;
|
||||||
mDotViewController = dotViewController;
|
mDotViewController = dotViewController;
|
||||||
|
mThreadFactory = threadFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -233,7 +237,8 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
|||||||
}
|
}
|
||||||
mHandler = startHandlerThread();
|
mHandler = startHandlerThread();
|
||||||
mHandler.post(this::startOnScreenDecorationsThread);
|
mHandler.post(this::startOnScreenDecorationsThread);
|
||||||
mDotViewController.setUiExecutor(mHandler::post);
|
mDotViewController.setUiExecutor(
|
||||||
|
mThreadFactory.buildDelayableExecutorOnLooper(mHandler.getLooper()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -643,7 +648,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
|||||||
|
|
||||||
int newRotation = mContext.getDisplay().getRotation();
|
int newRotation = mContext.getDisplay().getRotation();
|
||||||
if (mRotation != newRotation) {
|
if (mRotation != newRotation) {
|
||||||
mDotViewController.updateRotation(newRotation);
|
mDotViewController.setNewRotation(newRotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mPendingRotationChange) {
|
if (mPendingRotationChange) {
|
||||||
|
|||||||
@@ -17,20 +17,25 @@
|
|||||||
package com.android.systemui.statusbar.events
|
package com.android.systemui.statusbar.events
|
||||||
|
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
|
||||||
import android.animation.ObjectAnimator
|
|
||||||
import android.annotation.UiThread
|
import android.annotation.UiThread
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy
|
||||||
import com.android.systemui.animation.Interpolators
|
import com.android.systemui.animation.Interpolators
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
|
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||||
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher
|
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher
|
||||||
import com.android.systemui.statusbar.phone.StatusBarMarginUpdatedListener
|
import com.android.systemui.statusbar.phone.StatusBarMarginUpdatedListener
|
||||||
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
|
import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
|
||||||
|
import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
|
||||||
|
import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
|
||||||
|
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
|
||||||
|
|
||||||
import java.lang.IllegalStateException
|
import java.lang.IllegalStateException
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
@@ -54,31 +59,33 @@ import javax.inject.Inject
|
|||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class PrivacyDotViewController @Inject constructor(
|
class PrivacyDotViewController @Inject constructor(
|
||||||
@Main private val mainExecutor: Executor,
|
@Main private val mainExecutor: Executor,
|
||||||
|
private val stateController: StatusBarStateController,
|
||||||
private val locationPublisher: StatusBarLocationPublisher,
|
private val locationPublisher: StatusBarLocationPublisher,
|
||||||
private val animationScheduler: SystemStatusAnimationScheduler
|
private val animationScheduler: SystemStatusAnimationScheduler
|
||||||
) {
|
) {
|
||||||
private var rotation = 0
|
|
||||||
private var leftSize = 0
|
|
||||||
private var rightSize = 0
|
|
||||||
|
|
||||||
private var sbHeightPortrait = 0
|
private var sbHeightPortrait = 0
|
||||||
private var sbHeightLandscape = 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 tl: View
|
||||||
private lateinit var tr: View
|
private lateinit var tr: View
|
||||||
private lateinit var bl: View
|
private lateinit var bl: View
|
||||||
private lateinit var br: View
|
private lateinit var br: View
|
||||||
|
|
||||||
// Track which corner is active (based on orientation + RTL)
|
// Only can be modified on @UiThread
|
||||||
private var designatedCorner: View? = null
|
private var currentViewState: ViewState = ViewState()
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private var nextViewState: ViewState = currentViewState.copy()
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
scheduleUpdate()
|
||||||
|
}
|
||||||
|
private val lock = Object()
|
||||||
|
private var cancelRunnable: Runnable? = null
|
||||||
|
|
||||||
// Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread
|
// Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread
|
||||||
private var uiExecutor: Executor? = null
|
private var uiExecutor: DelayableExecutor? = null
|
||||||
|
private var e: DelayableExecutor? = null
|
||||||
|
|
||||||
private val views: Sequence<View>
|
private val views: Sequence<View>
|
||||||
get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
|
get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
|
||||||
@@ -89,29 +96,78 @@ class PrivacyDotViewController @Inject constructor(
|
|||||||
setStatusBarMargins(marginLeft, marginRight)
|
setStatusBarMargins(marginLeft, marginRight)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
stateController.addCallback(object : StatusBarStateController.StateListener {
|
||||||
|
override fun onExpandedChanged(isExpanded: Boolean) {
|
||||||
|
setStatusBarExpanded(isExpanded)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setUiExecutor(e: Executor) {
|
fun setUiExecutor(e: DelayableExecutor) {
|
||||||
uiExecutor = e
|
uiExecutor = e
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun updateRotation(rot: Int) {
|
fun setNewRotation(rot: Int) {
|
||||||
dlog("updateRotation: ")
|
dlog("updateRotation: $rot")
|
||||||
if (rot == rotation) {
|
|
||||||
|
synchronized(lock) {
|
||||||
|
if (rot == nextViewState.rotation) {
|
||||||
return
|
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
|
// If we rotated, hide all dotes until the next state resolves
|
||||||
needsRotationUpdate = true
|
setCornerVisibilities(View.INVISIBLE)
|
||||||
|
|
||||||
|
val newCorner = selectDesignatedCorner(rot)
|
||||||
|
val index = newCorner.cornerIndex()
|
||||||
|
|
||||||
|
val h = when (rot) {
|
||||||
|
ROTATION_NONE, ROTATION_UPSIDE_DOWN -> sbHeightPortrait
|
||||||
|
ROTATION_LANDSCAPE, ROTATION_SEASCAPE -> sbHeightLandscape
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
synchronized(lock) {
|
||||||
|
nextViewState = nextViewState.copy(
|
||||||
|
rotation = rot,
|
||||||
|
height = h,
|
||||||
|
designatedCorner = newCorner,
|
||||||
|
cornerIndex = index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private fun hideDotView(dot: View, animate: Boolean) {
|
||||||
|
dot.clearAnimation()
|
||||||
|
if (animate) {
|
||||||
|
dot.animate()
|
||||||
|
.setDuration(DURATION)
|
||||||
|
.setInterpolator(Interpolators.ALPHA_OUT)
|
||||||
|
.alpha(0f)
|
||||||
|
.withEndAction { dot.visibility = View.INVISIBLE }
|
||||||
|
.start()
|
||||||
|
} else {
|
||||||
|
dot.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private fun showDotView(dot: View, animate: Boolean) {
|
||||||
|
dot.clearAnimation()
|
||||||
|
if (animate) {
|
||||||
|
dot.visibility = View.VISIBLE
|
||||||
|
dot.alpha = 0f
|
||||||
|
dot.animate()
|
||||||
|
.alpha(1f)
|
||||||
|
.setDuration(DURATION)
|
||||||
|
.setInterpolator(Interpolators.ALPHA_IN)
|
||||||
|
.start()
|
||||||
|
} else {
|
||||||
|
dot.visibility = View.VISIBLE
|
||||||
|
dot.alpha = 1f
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@@ -127,14 +183,14 @@ class PrivacyDotViewController @Inject constructor(
|
|||||||
|
|
||||||
// Update the gravity and margins of the privacy views
|
// Update the gravity and margins of the privacy views
|
||||||
@UiThread
|
@UiThread
|
||||||
private fun updateRotations() {
|
private fun updateRotations(rotation: Int) {
|
||||||
// To keep a view in the corner, its gravity is always the description of its current corner
|
// 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
|
// 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
|
// like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
|
||||||
// rotating the device counter-clockwise increments rotation by 1
|
// rotating the device counter-clockwise increments rotation by 1
|
||||||
|
|
||||||
views.forEach { corner ->
|
views.forEach { corner ->
|
||||||
val rotatedCorner = rotatedCorner(cornerForView(corner))
|
val rotatedCorner = rotatedCorner(cornerForView(corner), rotation)
|
||||||
(corner.layoutParams as FrameLayout.LayoutParams).apply {
|
(corner.layoutParams as FrameLayout.LayoutParams).apply {
|
||||||
gravity = rotatedCorner.toGravity()
|
gravity = rotatedCorner.toGravity()
|
||||||
}
|
}
|
||||||
@@ -147,26 +203,24 @@ class PrivacyDotViewController @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private fun updateCornerSizes() {
|
private fun updateCornerSizes(l: Int, r: Int, rotation: Int) {
|
||||||
views.forEach { corner ->
|
views.forEach { corner ->
|
||||||
val rotatedCorner = rotatedCorner(cornerForView(corner))
|
val rotatedCorner = rotatedCorner(cornerForView(corner), rotation)
|
||||||
val w = widthForCorner(rotatedCorner)
|
val w = widthForCorner(rotatedCorner, l, r)
|
||||||
Log.d(TAG, "updateCornerSizes: setting (${cornerForView(corner)}) to $w")
|
|
||||||
(corner.layoutParams as FrameLayout.LayoutParams).width = w
|
(corner.layoutParams as FrameLayout.LayoutParams).width = w
|
||||||
corner.requestLayout()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Designated view will be the one at statusbar's view.END
|
// Designated view will be the one at statusbar's view.END
|
||||||
@UiThread
|
@UiThread
|
||||||
private fun selectDesignatedCorner(): View? {
|
private fun selectDesignatedCorner(r: Int): View? {
|
||||||
if (!this::tl.isInitialized) {
|
if (!this::tl.isInitialized) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val isRtl = tl.isLayoutRtl
|
val isRtl = tl.isLayoutRtl
|
||||||
|
|
||||||
return when (rotation) {
|
return when (r) {
|
||||||
0 -> if (isRtl) tl else tr
|
0 -> if (isRtl) tl else tr
|
||||||
1 -> if (isRtl) tr else br
|
1 -> if (isRtl) tr else br
|
||||||
2 -> if (isRtl) br else bl
|
2 -> if (isRtl) br else bl
|
||||||
@@ -177,25 +231,19 @@ class PrivacyDotViewController @Inject constructor(
|
|||||||
|
|
||||||
// Track the current designated corner and maybe animate to a new rotation
|
// Track the current designated corner and maybe animate to a new rotation
|
||||||
@UiThread
|
@UiThread
|
||||||
private fun updateDesignatedCorner(newCorner: View) {
|
private fun updateDesignatedCorner(newCorner: View?, shouldShowDot: Boolean) {
|
||||||
designatedCorner = newCorner
|
if (shouldShowDot) {
|
||||||
|
newCorner?.apply {
|
||||||
if (animationScheduler.hasPersistentDot) {
|
clearAnimation()
|
||||||
fadeInDot()
|
visibility = View.VISIBLE
|
||||||
}
|
alpha = 0f
|
||||||
}
|
animate()
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private fun fadeInDot() {
|
|
||||||
designatedCorner?.let { dot ->
|
|
||||||
dot.visibility = View.VISIBLE
|
|
||||||
dot.alpha = 0f
|
|
||||||
dot.animate()
|
|
||||||
.alpha(1.0f)
|
.alpha(1.0f)
|
||||||
.setDuration(300)
|
.setDuration(300)
|
||||||
.start()
|
.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private fun setCornerVisibilities(vis: Int) {
|
private fun setCornerVisibilities(vis: Int) {
|
||||||
@@ -214,7 +262,7 @@ class PrivacyDotViewController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rotatedCorner(corner: Int): Int {
|
private fun rotatedCorner(corner: Int, rotation: Int): Int {
|
||||||
var modded = corner - rotation
|
var modded = corner - rotation
|
||||||
if (modded < 0) {
|
if (modded < 0) {
|
||||||
modded += 4
|
modded += 4
|
||||||
@@ -223,10 +271,10 @@ class PrivacyDotViewController @Inject constructor(
|
|||||||
return modded
|
return modded
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun widthForCorner(corner: Int): Int {
|
private fun widthForCorner(corner: Int, left: Int, right: Int): Int {
|
||||||
return when (corner) {
|
return when (corner) {
|
||||||
TOP_LEFT, BOTTOM_LEFT -> leftSize
|
TOP_LEFT, BOTTOM_LEFT -> left
|
||||||
TOP_RIGHT, BOTTOM_RIGHT -> rightSize
|
TOP_RIGHT, BOTTOM_RIGHT -> right
|
||||||
else -> throw IllegalArgumentException("Unknown corner")
|
else -> throw IllegalArgumentException("Unknown corner")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,10 +292,16 @@ class PrivacyDotViewController @Inject constructor(
|
|||||||
bl = bottomLeft
|
bl = bottomLeft
|
||||||
br = bottomRight
|
br = bottomRight
|
||||||
|
|
||||||
designatedCorner = selectDesignatedCorner()
|
val dc = selectDesignatedCorner(0)
|
||||||
|
val index = dc.cornerIndex()
|
||||||
|
|
||||||
mainExecutor.execute {
|
mainExecutor.execute {
|
||||||
animationScheduler.addCallback(systemStatusAnimationCallback)
|
animationScheduler.addCallback(systemStatusAnimationCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized(lock) {
|
||||||
|
nextViewState = nextViewState.copy(designatedCorner = dc, cornerIndex = index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -257,8 +311,6 @@ class PrivacyDotViewController @Inject constructor(
|
|||||||
fun setStatusBarHeights(portrait: Int, landscape: Int) {
|
fun setStatusBarHeights(portrait: Int, landscape: Int) {
|
||||||
sbHeightPortrait = portrait
|
sbHeightPortrait = portrait
|
||||||
sbHeightLandscape = landscape
|
sbHeightLandscape = landscape
|
||||||
|
|
||||||
hasMultipleHeights = portrait != landscape
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,85 +320,109 @@ class PrivacyDotViewController @Inject constructor(
|
|||||||
* @param right space between the status bar contents and the right side of the screen
|
* @param right space between the status bar contents and the right side of the screen
|
||||||
*/
|
*/
|
||||||
private fun setStatusBarMargins(left: Int, right: Int) {
|
private fun setStatusBarMargins(left: Int, right: Int) {
|
||||||
leftSize = left
|
dlog("setStatusBarMargins l=$left r=$right")
|
||||||
rightSize = right
|
synchronized(lock) {
|
||||||
|
nextViewState = nextViewState.copy(marginLeft = left, marginRight = 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) {
|
/**
|
||||||
dlog("doUpdates: ")
|
* We won't show the dot when quick settings is showing
|
||||||
var newDesignatedCorner: View? = null
|
*/
|
||||||
|
private fun setStatusBarExpanded(expanded: Boolean) {
|
||||||
if (rot) {
|
synchronized(lock) {
|
||||||
needsRotationUpdate = false
|
nextViewState = nextViewState.copy(hideDotForQuickSettings = expanded)
|
||||||
updateRotations()
|
}
|
||||||
newDesignatedCorner = selectDesignatedCorner()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (height) {
|
private fun scheduleUpdate() {
|
||||||
needsHeightUpdate = false
|
dlog("scheduleUpdate: ")
|
||||||
updateHeights(rotation)
|
|
||||||
|
cancelRunnable?.run()
|
||||||
|
cancelRunnable = uiExecutor?.executeDelayed({
|
||||||
|
processNextViewState()
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (width) {
|
@UiThread
|
||||||
needsMarginUpdate = false
|
private fun processNextViewState() {
|
||||||
updateCornerSizes()
|
dlog("processNextViewState: ")
|
||||||
|
|
||||||
|
val newState: ViewState
|
||||||
|
synchronized(lock) {
|
||||||
|
newState = nextViewState.copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newDesignatedCorner != null && newDesignatedCorner != designatedCorner) {
|
resolveState(newState)
|
||||||
updateDesignatedCorner(newDesignatedCorner)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private fun resolveState(state: ViewState) {
|
||||||
|
dlog("resolveState $state")
|
||||||
|
if (state == currentViewState) {
|
||||||
|
dlog("resolveState: skipping")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.rotation != currentViewState.rotation) {
|
||||||
|
// A rotation has started, hide the views to avoid flicker
|
||||||
|
updateRotations(state.rotation)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.height != currentViewState.height) {
|
||||||
|
updateHeights(state.rotation)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.marginLeft != currentViewState.marginLeft ||
|
||||||
|
state.marginRight != currentViewState.marginRight) {
|
||||||
|
updateCornerSizes(state.marginLeft, state.marginRight, state.rotation)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.designatedCorner != currentViewState.designatedCorner) {
|
||||||
|
updateDesignatedCorner(state.designatedCorner, state.shouldShowDot())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.needsLayout(currentViewState)) {
|
||||||
|
views.forEach { it.requestLayout() }
|
||||||
|
}
|
||||||
|
|
||||||
|
val shouldShow = state.shouldShowDot()
|
||||||
|
if (shouldShow != currentViewState.shouldShowDot()) {
|
||||||
|
if (shouldShow && state.designatedCorner != null) {
|
||||||
|
showDotView(state.designatedCorner, true)
|
||||||
|
} else if (!shouldShow && state.designatedCorner != null) {
|
||||||
|
hideDotView(state.designatedCorner, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentViewState = state
|
||||||
}
|
}
|
||||||
|
|
||||||
private val systemStatusAnimationCallback: SystemStatusAnimationCallback =
|
private val systemStatusAnimationCallback: SystemStatusAnimationCallback =
|
||||||
object : SystemStatusAnimationCallback {
|
object : SystemStatusAnimationCallback {
|
||||||
override fun onSystemStatusAnimationTransitionToPersistentDot(
|
override fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? {
|
||||||
showAnimation: Boolean
|
synchronized(lock) {
|
||||||
): Animator? {
|
nextViewState = nextViewState.copy(systemPrivacyEventIsActive = true)
|
||||||
if (designatedCorner == null) {
|
|
||||||
return null
|
|
||||||
} else if (!showAnimation) {
|
|
||||||
uiExecutor?.execute { fadeInDot() }
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val alpha = ObjectAnimator.ofFloat(
|
return null
|
||||||
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? {
|
override fun onHidePersistentDot(): Animator? {
|
||||||
if (designatedCorner == null) {
|
synchronized(lock) {
|
||||||
return null
|
nextViewState = nextViewState.copy(systemPrivacyEventIsActive = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun View?.cornerIndex(): Int {
|
||||||
|
if (this != null) {
|
||||||
|
return cornerForView(this)
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dlog(s: String) {
|
private fun dlog(s: String) {
|
||||||
@@ -382,3 +458,26 @@ private fun Int.innerGravity(): Int {
|
|||||||
else -> throw IllegalArgumentException("Not a corner")
|
else -> throw IllegalArgumentException("Not a corner")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class ViewState(
|
||||||
|
// don't @ me with names
|
||||||
|
val systemPrivacyEventIsActive: Boolean = false,
|
||||||
|
val hideDotForQuickSettings: Boolean = false,
|
||||||
|
val statusBarExpanded: Boolean = false,
|
||||||
|
val rotation: Int = 0,
|
||||||
|
val height: Int = 0,
|
||||||
|
val marginLeft: Int = 0,
|
||||||
|
val marginRight: Int = 0,
|
||||||
|
val cornerIndex: Int = -1,
|
||||||
|
val designatedCorner: View? = null
|
||||||
|
) {
|
||||||
|
fun shouldShowDot(): Boolean {
|
||||||
|
return systemPrivacyEventIsActive && !hideDotForQuickSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
fun needsLayout(other: ViewState): Boolean {
|
||||||
|
return rotation != other.rotation ||
|
||||||
|
marginRight != other.marginRight ||
|
||||||
|
height != other.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class SystemStatusAnimationScheduler @Inject constructor(
|
|||||||
scheduleEvent(event)
|
scheduleEvent(event)
|
||||||
} else if (event.forceVisible) {
|
} else if (event.forceVisible) {
|
||||||
hasPersistentDot = true
|
hasPersistentDot = true
|
||||||
notifyTransitionToPersistentDot(showAnimation = false)
|
notifyTransitionToPersistentDot()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -202,7 +202,7 @@ class SystemStatusAnimationScheduler @Inject constructor(
|
|||||||
|
|
||||||
aSet2.play(chipAnimator).before(systemAnimator)
|
aSet2.play(chipAnimator).before(systemAnimator)
|
||||||
if (hasPersistentDot) {
|
if (hasPersistentDot) {
|
||||||
val dotAnim = notifyTransitionToPersistentDot(showAnimation = true)
|
val dotAnim = notifyTransitionToPersistentDot()
|
||||||
if (dotAnim != null) aSet2.playTogether(systemAnimator, dotAnim)
|
if (dotAnim != null) aSet2.playTogether(systemAnimator, dotAnim)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,9 +214,9 @@ class SystemStatusAnimationScheduler @Inject constructor(
|
|||||||
}, DELAY)
|
}, DELAY)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyTransitionToPersistentDot(showAnimation: Boolean): Animator? {
|
private fun notifyTransitionToPersistentDot(): Animator? {
|
||||||
val anims: List<Animator> = listeners.mapNotNull {
|
val anims: List<Animator> = listeners.mapNotNull {
|
||||||
it.onSystemStatusAnimationTransitionToPersistentDot(showAnimation)
|
it.onSystemStatusAnimationTransitionToPersistentDot()
|
||||||
}
|
}
|
||||||
if (anims.isNotEmpty()) {
|
if (anims.isNotEmpty()) {
|
||||||
val aSet = AnimatorSet()
|
val aSet = AnimatorSet()
|
||||||
@@ -326,9 +326,7 @@ interface SystemStatusAnimationCallback {
|
|||||||
@JvmDefault fun onSystemChromeAnimationEnd() {}
|
@JvmDefault fun onSystemChromeAnimationEnd() {}
|
||||||
|
|
||||||
// Best method name, change my mind
|
// Best method name, change my mind
|
||||||
@JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(
|
@JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { return null }
|
||||||
showAnimation: Boolean
|
|
||||||
): Animator? { return null }
|
|
||||||
@JvmDefault fun onHidePersistentDot(): Animator? { return null }
|
@JvmDefault fun onHidePersistentDot(): Animator? { return null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,15 @@
|
|||||||
|
|
||||||
package com.android.systemui.util.concurrency;
|
package com.android.systemui.util.concurrency;
|
||||||
|
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for building Executors running on a unique named thread.
|
* Factory for building Executors running on a unique named thread.
|
||||||
*
|
*
|
||||||
* Use this when our generally available @Main, @Background, @UiBackground, @LongRunning, or
|
* Use this when our generally available @Main, @Background, @UiBackground, @LongRunning, or
|
||||||
* similar global qualifiers don't quite cut it. Note that the methods here create entirely new
|
* similar global qualifiers don't quite cut it. Note that the methods here can create entirely new
|
||||||
* threads; there are no singletons here. Use responsibly.
|
* threads; there are no singletons here. Use responsibly.
|
||||||
*/
|
*/
|
||||||
public interface ThreadFactory {
|
public interface ThreadFactory {
|
||||||
@@ -41,4 +43,9 @@ public interface ThreadFactory {
|
|||||||
* implementation. Assume this is the case and use responsibly.
|
* implementation. Assume this is the case and use responsibly.
|
||||||
**/
|
**/
|
||||||
DelayableExecutor buildDelayableExecutorOnNewThread(String threadName);
|
DelayableExecutor buildDelayableExecutorOnNewThread(String threadName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an {@link DelayableExecutor} running the given Looper
|
||||||
|
**/
|
||||||
|
DelayableExecutor buildDelayableExecutorOnLooper(Looper looper);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.systemui.util.concurrency;
|
package com.android.systemui.util.concurrency;
|
||||||
|
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
@@ -35,4 +36,8 @@ class ThreadFactoryImpl implements ThreadFactory {
|
|||||||
handlerThread.start();
|
handlerThread.start();
|
||||||
return new ExecutorImpl(handlerThread.getLooper());
|
return new ExecutorImpl(handlerThread.getLooper());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DelayableExecutor buildDelayableExecutorOnLooper(Looper looper) {
|
||||||
|
return new ExecutorImpl(looper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,8 +65,12 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
|
|||||||
import com.android.systemui.settings.UserTracker;
|
import com.android.systemui.settings.UserTracker;
|
||||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||||
import com.android.systemui.tuner.TunerService;
|
import com.android.systemui.tuner.TunerService;
|
||||||
|
import com.android.systemui.util.concurrency.FakeExecutor;
|
||||||
|
import com.android.systemui.util.concurrency.FakeThreadFactory;
|
||||||
|
import com.android.systemui.util.concurrency.ThreadFactory;
|
||||||
import com.android.systemui.util.settings.FakeSettings;
|
import com.android.systemui.util.settings.FakeSettings;
|
||||||
import com.android.systemui.util.settings.SecureSettings;
|
import com.android.systemui.util.settings.SecureSettings;
|
||||||
|
import com.android.systemui.util.time.FakeSystemClock;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -89,6 +93,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
|||||||
private DisplayManager mDisplayManager;
|
private DisplayManager mDisplayManager;
|
||||||
private SecureSettings mSecureSettings;
|
private SecureSettings mSecureSettings;
|
||||||
private Handler mMainHandler;
|
private Handler mMainHandler;
|
||||||
|
private ThreadFactory mThreadFactory;
|
||||||
@Mock
|
@Mock
|
||||||
private TunerService mTunerService;
|
private TunerService mTunerService;
|
||||||
@Mock
|
@Mock
|
||||||
@@ -105,6 +110,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
|||||||
mTestableLooper = TestableLooper.get(this);
|
mTestableLooper = TestableLooper.get(this);
|
||||||
mMainHandler = new Handler(mTestableLooper.getLooper());
|
mMainHandler = new Handler(mTestableLooper.getLooper());
|
||||||
mSecureSettings = new FakeSettings();
|
mSecureSettings = new FakeSettings();
|
||||||
|
mThreadFactory = new FakeThreadFactory(new FakeExecutor(new FakeSystemClock()));
|
||||||
|
|
||||||
mWindowManager = mock(WindowManager.class);
|
mWindowManager = mock(WindowManager.class);
|
||||||
WindowMetrics metrics = mContext.getSystemService(WindowManager.class)
|
WindowMetrics metrics = mContext.getSystemService(WindowManager.class)
|
||||||
@@ -119,7 +125,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
|||||||
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
|
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
|
||||||
|
|
||||||
mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler, mSecureSettings,
|
mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler, mSecureSettings,
|
||||||
mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController) {
|
mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,
|
||||||
|
mThreadFactory) {
|
||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.systemui.util.concurrency;
|
package com.android.systemui.util.concurrency;
|
||||||
|
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,4 +39,9 @@ public class FakeThreadFactory implements ThreadFactory {
|
|||||||
public DelayableExecutor buildDelayableExecutorOnNewThread(String threadName) {
|
public DelayableExecutor buildDelayableExecutorOnNewThread(String threadName) {
|
||||||
return mFakeExecutor;
|
return mFakeExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DelayableExecutor buildDelayableExecutorOnLooper(Looper looper) {
|
||||||
|
return mFakeExecutor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user