Merge "Privacy chip (3/many)" into sc-dev am: cfa1f3d130

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14442084

Change-Id: Icd34f328a4be4012e93ca5188c2a7382254fed6d
This commit is contained in:
Evan Laird
2021-05-07 18:25:38 +00:00
committed by Automerger Merge Worker
7 changed files with 253 additions and 125 deletions

View File

@@ -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) {

View File

@@ -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) {
return synchronized(lock) {
if (rot == nextViewState.rotation) {
return
}
} }
// A rotation has started, hide the views to avoid flicker // If we rotated, hide all dotes until the next state resolves
setCornerVisibilities(View.INVISIBLE) setCornerVisibilities(View.INVISIBLE)
if (hasMultipleHeights && (rotation % 2) != (rot % 2)) { val newCorner = selectDesignatedCorner(rot)
// we've changed from vertical to horizontal; update status bar height val index = newCorner.cornerIndex()
needsHeightUpdate = true
}
rotation = rot val h = when (rot) {
needsRotationUpdate = true 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,23 +231,17 @@ 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()
.alpha(1.0f)
@UiThread .setDuration(300)
private fun fadeInDot() { .start()
designatedCorner?.let { dot -> }
dot.visibility = View.VISIBLE
dot.alpha = 0f
dot.animate()
.alpha(1.0f)
.setDuration(300)
.start()
} }
} }
@@ -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) {
synchronized(lock) {
nextViewState = nextViewState.copy(hideDotForQuickSettings = expanded)
}
}
if (rot) { private fun scheduleUpdate() {
needsRotationUpdate = false dlog("scheduleUpdate: ")
updateRotations()
newDesignatedCorner = selectDesignatedCorner() cancelRunnable?.run()
cancelRunnable = uiExecutor?.executeDelayed({
processNextViewState()
}, 100)
}
@UiThread
private fun processNextViewState() {
dlog("processNextViewState: ")
val newState: ViewState
synchronized(lock) {
newState = nextViewState.copy()
} }
if (height) { resolveState(newState)
needsHeightUpdate = false }
updateHeights(rotation)
@UiThread
private fun resolveState(state: ViewState) {
dlog("resolveState $state")
if (state == currentViewState) {
dlog("resolveState: skipping")
return
} }
if (width) { if (state.rotation != currentViewState.rotation) {
needsMarginUpdate = false // A rotation has started, hide the views to avoid flicker
updateCornerSizes() updateRotations(state.rotation)
} }
if (newDesignatedCorner != null && newDesignatedCorner != designatedCorner) { if (state.height != currentViewState.height) {
updateDesignatedCorner(newDesignatedCorner) 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
}
}

View File

@@ -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 }
} }

View File

@@ -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);
} }

View File

@@ -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);
}
} }

View File

@@ -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();

View File

@@ -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;
}
} }