Merge TQ2A.230305.008
Bug: 264720040 Merged-In: I56c2fc14f906cdad80181ab577e2ebc276c151c1 Change-Id: Ib6d03795b3c3419f549d5f834cfdb6b409eb9e28
This commit is contained in:
@@ -16,9 +16,7 @@
|
||||
|
||||
package android.preference;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.app.NotificationManager;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -37,7 +35,6 @@ import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Message;
|
||||
import android.preference.VolumePreference.VolumeStore;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.Global;
|
||||
import android.provider.Settings.System;
|
||||
@@ -47,7 +44,6 @@ import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -119,6 +115,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
|
||||
private final int mMaxStreamVolume;
|
||||
private boolean mAffectedByRingerMode;
|
||||
private boolean mNotificationOrRing;
|
||||
private final boolean mNotifAliasRing;
|
||||
private final Receiver mReceiver = new Receiver();
|
||||
|
||||
private Handler mHandler;
|
||||
@@ -161,7 +158,6 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
|
||||
this(context, streamType, defaultUri, callback, true /* playSample */);
|
||||
}
|
||||
|
||||
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
|
||||
public SeekBarVolumizer(
|
||||
Context context,
|
||||
int streamType,
|
||||
@@ -184,6 +180,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
|
||||
if (mNotificationOrRing) {
|
||||
mRingerMode = mAudioManager.getRingerModeInternal();
|
||||
}
|
||||
mNotifAliasRing = mContext.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_alias_ring_notif_stream_types);
|
||||
mZenMode = mNotificationManager.getZenMode();
|
||||
|
||||
if (hasAudioProductStrategies()) {
|
||||
@@ -290,9 +288,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
|
||||
* so that when user attempts to slide the notification seekbar out of vibrate the
|
||||
* seekbar doesn't wrongly snap back to 0 when the streams aren't aliased
|
||||
*/
|
||||
if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
|
||||
|| mStreamType == AudioManager.STREAM_RING
|
||||
if (mNotifAliasRing || mStreamType == AudioManager.STREAM_RING
|
||||
|| (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
|
||||
mSeekBar.setProgress(0, true);
|
||||
}
|
||||
@@ -369,9 +365,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
|
||||
// set the time of stop volume
|
||||
if ((mStreamType == AudioManager.STREAM_VOICE_CALL
|
||||
|| mStreamType == AudioManager.STREAM_RING
|
||||
|| (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
|
||||
&& mStreamType == AudioManager.STREAM_NOTIFICATION)
|
||||
|| (!mNotifAliasRing && mStreamType == AudioManager.STREAM_NOTIFICATION)
|
||||
|| mStreamType == AudioManager.STREAM_ALARM)) {
|
||||
sStopVolumeTime = java.lang.System.currentTimeMillis();
|
||||
}
|
||||
@@ -650,10 +644,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
|
||||
}
|
||||
|
||||
private void updateVolumeSlider(int streamType, int streamValue) {
|
||||
final boolean streamMatch = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
|
||||
&& mNotificationOrRing ? isNotificationOrRing(streamType) :
|
||||
streamType == mStreamType;
|
||||
final boolean streamMatch = mNotifAliasRing && mNotificationOrRing
|
||||
? isNotificationOrRing(streamType) : streamType == mStreamType;
|
||||
if (mSeekBar != null && streamMatch && streamValue != -1) {
|
||||
final boolean muted = mAudioManager.isStreamMute(mStreamType)
|
||||
|| streamValue == 0;
|
||||
|
||||
@@ -561,11 +561,6 @@ public final class SystemUiDeviceConfigFlags {
|
||||
public static final String TASK_MANAGER_SHOW_USER_VISIBLE_JOBS =
|
||||
"task_manager_show_user_visible_jobs";
|
||||
|
||||
/**
|
||||
* (boolean) Whether to show notification volume control slider separate from ring.
|
||||
*/
|
||||
public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
|
||||
|
||||
/**
|
||||
* (boolean) Whether the clipboard overlay is enabled.
|
||||
*/
|
||||
|
||||
@@ -2018,6 +2018,10 @@
|
||||
STREAM_MUSIC as if it's on TV platform. -->
|
||||
<bool name="config_single_volume">false</bool>
|
||||
|
||||
<!-- Flag indicating whether notification and ringtone volumes
|
||||
are controlled together (aliasing is true) or not. -->
|
||||
<bool name="config_alias_ring_notif_stream_types">true</bool>
|
||||
|
||||
<!-- The number of volume steps for the notification stream -->
|
||||
<integer name="config_audio_notif_vol_steps">7</integer>
|
||||
|
||||
|
||||
@@ -278,6 +278,7 @@
|
||||
<java-symbol type="attr" name="autofillSaveCustomSubtitleMaxHeight"/>
|
||||
<java-symbol type="bool" name="action_bar_embed_tabs" />
|
||||
<java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
|
||||
<java-symbol type="bool" name="config_alias_ring_notif_stream_types" />
|
||||
<java-symbol type="integer" name="config_audio_notif_vol_default" />
|
||||
<java-symbol type="integer" name="config_audio_notif_vol_steps" />
|
||||
<java-symbol type="integer" name="config_audio_ring_vol_default" />
|
||||
|
||||
@@ -791,13 +791,13 @@ private class AnimatedDialog(
|
||||
// Move the drawing of the source in the overlay of this dialog, then animate. We trigger a
|
||||
// one-off synchronization to make sure that this is done in sync between the two different
|
||||
// windows.
|
||||
controller.startDrawingInOverlayOf(decorView)
|
||||
synchronizeNextDraw(
|
||||
then = {
|
||||
isSourceDrawnInDialog = true
|
||||
maybeStartLaunchAnimation()
|
||||
}
|
||||
)
|
||||
controller.startDrawingInOverlayOf(decorView)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -195,14 +195,16 @@ open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
|
||||
backgroundDrawable = WrappedDrawable(background)
|
||||
backgroundView?.background = backgroundDrawable
|
||||
|
||||
// Delay the calls to `ghostedView.setVisibility()` during the animation. This must be
|
||||
// called before `GhostView.addGhost()` is called because the latter will change the
|
||||
// *transition* visibility, which won't be blocked and will affect the normal View
|
||||
// visibility that is saved by `setShouldBlockVisibilityChanges()` for a later restoration.
|
||||
(ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
|
||||
|
||||
// Create a ghost of the view that will be moving and fading out. This allows to fade out
|
||||
// the content before fading out the background.
|
||||
ghostView = GhostView.addGhost(ghostedView, launchContainer)
|
||||
|
||||
// The ghost was just created, so ghostedView is currently invisible. We need to make sure
|
||||
// that it stays invisible as long as we are animating.
|
||||
(ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
|
||||
|
||||
val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
|
||||
matrix.getValues(initialGhostViewMatrixValues)
|
||||
|
||||
@@ -297,14 +299,19 @@ open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
|
||||
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
|
||||
|
||||
GhostView.removeGhost(ghostedView)
|
||||
(ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
|
||||
launchContainerOverlay.remove(backgroundView)
|
||||
|
||||
// Make sure that the view is considered VISIBLE by accessibility by first making it
|
||||
// INVISIBLE then VISIBLE (see b/204944038#comment17 for more info).
|
||||
ghostedView.visibility = View.INVISIBLE
|
||||
ghostedView.visibility = View.VISIBLE
|
||||
ghostedView.invalidate()
|
||||
if (ghostedView is LaunchableView) {
|
||||
// Restore the ghosted view visibility.
|
||||
ghostedView.setShouldBlockVisibilityChanges(false)
|
||||
} else {
|
||||
// Make the ghosted view visible. We ensure that the view is considered VISIBLE by
|
||||
// accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
|
||||
// for more info).
|
||||
ghostedView.visibility = View.INVISIBLE
|
||||
ghostedView.visibility = View.VISIBLE
|
||||
ghostedView.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -21,15 +21,19 @@ import android.view.View
|
||||
/** A view that can expand/launch into an app or a dialog. */
|
||||
interface LaunchableView {
|
||||
/**
|
||||
* Set whether this view should block/postpone all visibility changes. This ensures that this
|
||||
* view:
|
||||
* Set whether this view should block/postpone all calls to [View.setVisibility]. This ensures
|
||||
* that this view:
|
||||
* - remains invisible during the launch animation given that it is ghosted and already drawn
|
||||
* somewhere else.
|
||||
* - remains invisible as long as a dialog expanded from it is shown.
|
||||
* - restores its expected visibility once the dialog expanded from it is dismissed.
|
||||
*
|
||||
* Note that when this is set to true, both the [normal][android.view.View.setVisibility] and
|
||||
* [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked.
|
||||
* When `setShouldBlockVisibilityChanges(false)` is called, then visibility of the View should
|
||||
* be restored to its expected value, i.e. it should have the visibility of the last call to
|
||||
* `View.setVisibility()` that was made after `setShouldBlockVisibilityChanges(true)`, if any,
|
||||
* or the original view visibility otherwise.
|
||||
*
|
||||
* Note that calls to [View.setTransitionVisibility] shouldn't be blocked.
|
||||
*
|
||||
* @param block whether we should block/postpone all calls to `setVisibility` and
|
||||
* `setTransitionVisibility`.
|
||||
@@ -46,27 +50,31 @@ class LaunchableViewDelegate(
|
||||
* super.setVisibility(visibility).
|
||||
*/
|
||||
private val superSetVisibility: (Int) -> Unit,
|
||||
|
||||
/**
|
||||
* The lambda that should set the actual transition visibility of [view], usually by calling
|
||||
* super.setTransitionVisibility(visibility).
|
||||
*/
|
||||
private val superSetTransitionVisibility: (Int) -> Unit,
|
||||
) {
|
||||
) : LaunchableView {
|
||||
private var blockVisibilityChanges = false
|
||||
private var lastVisibility = view.visibility
|
||||
|
||||
/** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */
|
||||
fun setShouldBlockVisibilityChanges(block: Boolean) {
|
||||
override fun setShouldBlockVisibilityChanges(block: Boolean) {
|
||||
if (block == blockVisibilityChanges) {
|
||||
return
|
||||
}
|
||||
|
||||
blockVisibilityChanges = block
|
||||
if (block) {
|
||||
// Save the current visibility for later.
|
||||
lastVisibility = view.visibility
|
||||
} else {
|
||||
superSetVisibility(lastVisibility)
|
||||
// Restore the visibility. To avoid accessibility issues, we change the visibility twice
|
||||
// which makes sure that we trigger a visibility flag change (see b/204944038#comment17
|
||||
// for more info).
|
||||
if (lastVisibility == View.VISIBLE) {
|
||||
superSetVisibility(View.INVISIBLE)
|
||||
superSetVisibility(View.VISIBLE)
|
||||
} else {
|
||||
superSetVisibility(View.VISIBLE)
|
||||
superSetVisibility(lastVisibility)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,16 +87,4 @@ class LaunchableViewDelegate(
|
||||
|
||||
superSetVisibility(visibility)
|
||||
}
|
||||
|
||||
/** Call this when [View.setTransitionVisibility] is called. */
|
||||
fun setTransitionVisibility(visibility: Int) {
|
||||
if (blockVisibilityChanges) {
|
||||
// View.setTransitionVisibility just sets the visibility flag, so we don't have to save
|
||||
// the transition visibility separately from the normal visibility.
|
||||
lastVisibility = visibility
|
||||
return
|
||||
}
|
||||
|
||||
superSetTransitionVisibility(visibility)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,23 +34,29 @@ internal constructor(
|
||||
override val sourceIdentity: Any = source
|
||||
|
||||
override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
|
||||
// Delay the calls to `source.setVisibility()` during the animation. This must be called
|
||||
// before `GhostView.addGhost()` is called because the latter will change the *transition*
|
||||
// visibility, which won't be blocked and will affect the normal View visibility that is
|
||||
// saved by `setShouldBlockVisibilityChanges()` for a later restoration.
|
||||
(source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
|
||||
|
||||
// Create a temporary ghost of the source (which will make it invisible) and add it
|
||||
// to the host dialog.
|
||||
GhostView.addGhost(source, viewGroup)
|
||||
|
||||
// The ghost of the source was just created, so the source is currently invisible.
|
||||
// We need to make sure that it stays invisible as long as the dialog is shown or
|
||||
// animating.
|
||||
(source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
|
||||
}
|
||||
|
||||
override fun stopDrawingInOverlay() {
|
||||
// Note: here we should remove the ghost from the overlay, but in practice this is
|
||||
// already done by the launch controllers created below.
|
||||
// already done by the launch controller created below.
|
||||
|
||||
// Make sure we allow the source to change its visibility again.
|
||||
(source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
|
||||
source.visibility = View.VISIBLE
|
||||
if (source is LaunchableView) {
|
||||
// Make sure we allow the source to change its visibility again and restore its previous
|
||||
// value.
|
||||
source.setShouldBlockVisibilityChanges(false)
|
||||
} else {
|
||||
// We made the source invisible earlier, so let's make it visible again.
|
||||
source.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun createLaunchController(): LaunchAnimator.Controller {
|
||||
@@ -67,10 +73,14 @@ internal constructor(
|
||||
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
|
||||
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
|
||||
|
||||
// We hide the source when the dialog is showing. We will make this view
|
||||
// visible again when dismissing the dialog. This does nothing if the source
|
||||
// implements [LaunchableView], as it's already INVISIBLE in that case.
|
||||
source.visibility = View.INVISIBLE
|
||||
// At this point the view visibility is restored by the delegate, so we delay the
|
||||
// visibility changes again and make it invisible while the dialog is shown.
|
||||
if (source is LaunchableView) {
|
||||
source.setShouldBlockVisibilityChanges(true)
|
||||
source.setTransitionVisibility(View.INVISIBLE)
|
||||
} else {
|
||||
source.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,13 +100,15 @@ internal constructor(
|
||||
}
|
||||
|
||||
override fun onExitAnimationCancelled() {
|
||||
// Make sure we allow the source to change its visibility again.
|
||||
(source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
|
||||
|
||||
// If the view is invisible it's probably because of us, so we make it visible
|
||||
// again.
|
||||
if (source.visibility == View.INVISIBLE) {
|
||||
source.visibility = View.VISIBLE
|
||||
if (source is LaunchableView) {
|
||||
// Make sure we allow the source to change its visibility again.
|
||||
source.setShouldBlockVisibilityChanges(false)
|
||||
} else {
|
||||
// If the view is invisible it's probably because of us, so we make it visible
|
||||
// again.
|
||||
if (source.visibility == View.INVISIBLE) {
|
||||
source.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2022 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:pathData="M11,7V2H13V7ZM17.6,9.85 L16.2,8.4 19.75,4.85 21.15,6.3ZM6.4,9.85 L2.85,6.3 4.25,4.85 7.8,8.4ZM12,12Q14.95,12 17.812,13.188Q20.675,14.375 22.9,16.75Q23.2,17.05 23.2,17.45Q23.2,17.85 22.9,18.15L20.6,20.4Q20.325,20.675 19.963,20.7Q19.6,20.725 19.3,20.5L16.4,18.3Q16.2,18.15 16.1,17.95Q16,17.75 16,17.5V14.65Q15.05,14.35 14.05,14.175Q13.05,14 12,14Q10.95,14 9.95,14.175Q8.95,14.35 8,14.65V17.5Q8,17.75 7.9,17.95Q7.8,18.15 7.6,18.3L4.7,20.5Q4.4,20.725 4.038,20.7Q3.675,20.675 3.4,20.4L1.1,18.15Q0.8,17.85 0.8,17.45Q0.8,17.05 1.1,16.75Q3.3,14.375 6.175,13.188Q9.05,12 12,12ZM6,15.35Q5.275,15.725 4.6,16.212Q3.925,16.7 3.2,17.3L4.2,18.3L6,16.9ZM18,15.4V16.9L19.8,18.3L20.8,17.35Q20.075,16.7 19.4,16.225Q18.725,15.75 18,15.4ZM6,15.35Q6,15.35 6,15.35Q6,15.35 6,15.35ZM18,15.4Q18,15.4 18,15.4Q18,15.4 18,15.4Z"
|
||||
android:fillColor="?android:attr/colorPrimary"/>
|
||||
|
||||
</vector>
|
||||
@@ -1,34 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2022 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:pathData="M0.8,4.2l8.1,8.1c-2.2,0.5 -5.2,1.6 -7.8,4.4c-0.4,0.4 -0.4,1 0,1.4l2.3,2.3c0.3,0.3 0.9,0.4 1.3,0.1l2.9,-2.2C7.8,18.1 8,17.8 8,17.5v-2.9c0.9,-0.3 1.7,-0.5 2.7,-0.6l8.5,8.5l1.4,-1.4L2.2,2.8L0.8,4.2z"
|
||||
android:fillColor="?android:attr/colorPrimary"/>
|
||||
<path
|
||||
android:pathData="M11,2h2v5h-2z"
|
||||
android:fillColor="?android:attr/colorPrimary"/>
|
||||
<path
|
||||
android:pathData="M21.2,6.3l-1.4,-1.4l-3.6,3.6l1.4,1.4C17.6,9.8 21,6.3 21.2,6.3z"
|
||||
android:fillColor="?android:attr/colorPrimary"/>
|
||||
<path
|
||||
android:pathData="M22.9,16.7c-2.8,-3 -6.2,-4.1 -8.4,-4.5l7.2,7.2l1.3,-1.3C23.3,17.7 23.3,17.1 22.9,16.7z"
|
||||
android:fillColor="?android:attr/colorPrimary"/>
|
||||
</vector>
|
||||
@@ -1,25 +0,0 @@
|
||||
<!--
|
||||
~ Copyright (C) 2022 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:autoMirrored="true">
|
||||
<path android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M19.8,22.6 L16.775,19.575Q16.15,19.975 15.45,20.263Q14.75,20.55 14,20.725V18.675Q14.35,18.55 14.688,18.425Q15.025,18.3 15.325,18.125L12,14.8V20L7,15H3V9H6.2L1.4,4.2L2.8,2.8L21.2,21.2ZM19.6,16.8 L18.15,15.35Q18.575,14.575 18.788,13.725Q19,12.875 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,13.3 20.638,14.525Q20.275,15.75 19.6,16.8ZM16.25,13.45 L14,11.2V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,12.375 16.438,12.738Q16.375,13.1 16.25,13.45ZM12,9.2 L9.4,6.6 12,4ZM10,15.15V12.8L8.2,11H5V13H7.85ZM9.1,11.9Z"/>
|
||||
</vector>
|
||||
@@ -1,25 +0,0 @@
|
||||
<!--
|
||||
~ Copyright (C) 2022 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:autoMirrored="true">
|
||||
<path android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,13.275 15.838,14.362Q15.175,15.45 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
|
||||
</vector>
|
||||
@@ -148,9 +148,4 @@
|
||||
<include layout="@layout/ongoing_privacy_chip"/>
|
||||
</FrameLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/space"
|
||||
/>
|
||||
</com.android.systemui.util.NoRemeasureMotionLayout>
|
||||
@@ -56,13 +56,9 @@
|
||||
<Layout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/space"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/carrier_group"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
@@ -87,39 +83,27 @@
|
||||
<Constraint
|
||||
android:id="@+id/statusIcons">
|
||||
<Layout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
|
||||
app:layout_constraintStart_toEndOf="@id/space"
|
||||
app:layout_constraintWidth_default="wrap"
|
||||
app:layout_constraintStart_toEndOf="@id/date"
|
||||
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
|
||||
app:layout_constraintTop_toTopOf="@id/date"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintBottom_toBottomOf="@id/date"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/batteryRemainingIcon">
|
||||
<Layout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
|
||||
app:layout_constraintWidth_default="wrap"
|
||||
app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
|
||||
app:layout_constraintStart_toEndOf="@id/statusIcons"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/date"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintBottom_toBottomOf="@id/date"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
|
||||
<Constraint
|
||||
android:id="@id/space">
|
||||
<Layout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toEndOf="@id/date"
|
||||
app:layout_constraintEnd_toStartOf="@id/statusIcons"
|
||||
/>
|
||||
</Constraint>
|
||||
</ConstraintSet>
|
||||
@@ -28,7 +28,6 @@ class LaunchableImageView : ImageView, LaunchableView {
|
||||
LaunchableViewDelegate(
|
||||
this,
|
||||
superSetVisibility = { super.setVisibility(it) },
|
||||
superSetTransitionVisibility = { super.setTransitionVisibility(it) },
|
||||
)
|
||||
|
||||
constructor(context: Context?) : super(context)
|
||||
@@ -53,8 +52,4 @@ class LaunchableImageView : ImageView, LaunchableView {
|
||||
override fun setVisibility(visibility: Int) {
|
||||
delegate.setVisibility(visibility)
|
||||
}
|
||||
|
||||
override fun setTransitionVisibility(visibility: Int) {
|
||||
delegate.setTransitionVisibility(visibility)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ class LaunchableLinearLayout : LinearLayout, LaunchableView {
|
||||
LaunchableViewDelegate(
|
||||
this,
|
||||
superSetVisibility = { super.setVisibility(it) },
|
||||
superSetTransitionVisibility = { super.setTransitionVisibility(it) },
|
||||
)
|
||||
|
||||
constructor(context: Context?) : super(context)
|
||||
@@ -53,8 +52,4 @@ class LaunchableLinearLayout : LinearLayout, LaunchableView {
|
||||
override fun setVisibility(visibility: Int) {
|
||||
delegate.setVisibility(visibility)
|
||||
}
|
||||
|
||||
override fun setTransitionVisibility(visibility: Int) {
|
||||
delegate.setTransitionVisibility(visibility)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ public interface QSHost {
|
||||
void removeCallback(Callback callback);
|
||||
void removeTile(String tileSpec);
|
||||
void removeTiles(Collection<String> specs);
|
||||
void unmarkTileAsAutoAdded(String tileSpec);
|
||||
|
||||
int indexOf(String tileSpec);
|
||||
|
||||
|
||||
@@ -427,11 +427,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
|
||||
mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unmarkTileAsAutoAdded(String spec) {
|
||||
if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tile to the end
|
||||
*
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toolbar;
|
||||
@@ -74,8 +75,8 @@ public class QSCustomizer extends LinearLayout {
|
||||
toolbar.setNavigationIcon(
|
||||
getResources().getDrawable(value.resourceId, mContext.getTheme()));
|
||||
|
||||
toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
|
||||
mContext.getString(com.android.internal.R.string.reset));
|
||||
toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
toolbar.setTitle(R.string.qs_edit);
|
||||
mRecyclerView = findViewById(android.R.id.list);
|
||||
mTransparentView = findViewById(R.id.customizer_transparent_view);
|
||||
|
||||
@@ -132,7 +132,7 @@ public class TileServices extends IQSService.Stub {
|
||||
final String slot = tile.getComponent().getClassName();
|
||||
// TileServices doesn't know how to add more than 1 icon per slot, so remove all
|
||||
mMainHandler.post(() -> mHost.getIconController()
|
||||
.removeAllIconsForSlot(slot));
|
||||
.removeAllIconsForExternalSlot(slot));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -219,9 +219,9 @@ object FooterActionsViewBinder {
|
||||
// Small button with the number only.
|
||||
foregroundServicesWithTextView.isVisible = false
|
||||
|
||||
foregroundServicesWithNumberView.visibility = View.VISIBLE
|
||||
foregroundServicesWithNumberView.isVisible = true
|
||||
foregroundServicesWithNumberView.setOnClickListener {
|
||||
foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
|
||||
foregroundServices.onClick(Expandable.fromView(foregroundServicesWithNumberView))
|
||||
}
|
||||
foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString()
|
||||
foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text
|
||||
|
||||
@@ -145,7 +145,6 @@ open class QSTileViewImpl @JvmOverloads constructor(
|
||||
private val launchableViewDelegate = LaunchableViewDelegate(
|
||||
this,
|
||||
superSetVisibility = { super.setVisibility(it) },
|
||||
superSetTransitionVisibility = { super.setTransitionVisibility(it) },
|
||||
)
|
||||
private var lastDisabledByPolicy = false
|
||||
|
||||
@@ -362,10 +361,6 @@ open class QSTileViewImpl @JvmOverloads constructor(
|
||||
launchableViewDelegate.setVisibility(visibility)
|
||||
}
|
||||
|
||||
override fun setTransitionVisibility(visibility: Int) {
|
||||
launchableViewDelegate.setTransitionVisibility(visibility)
|
||||
}
|
||||
|
||||
// Accessibility
|
||||
|
||||
override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) {
|
||||
|
||||
@@ -101,7 +101,6 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements
|
||||
@MainThread
|
||||
public void onManagedProfileRemoved() {
|
||||
mHost.removeTile(getTileSpec());
|
||||
mHost.unmarkTileAsAutoAdded(getTileSpec());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -112,7 +112,7 @@ class UserTrackerImpl internal constructor(
|
||||
// These get called when a managed profile goes in or out of quiet mode.
|
||||
addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
|
||||
addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
|
||||
|
||||
addAction(Intent.ACTION_MANAGED_PROFILE_ADDED)
|
||||
addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)
|
||||
addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
|
||||
}
|
||||
@@ -129,6 +129,7 @@ class UserTrackerImpl internal constructor(
|
||||
Intent.ACTION_USER_INFO_CHANGED,
|
||||
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
|
||||
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
|
||||
Intent.ACTION_MANAGED_PROFILE_ADDED,
|
||||
Intent.ACTION_MANAGED_PROFILE_REMOVED,
|
||||
Intent.ACTION_MANAGED_PROFILE_UNLOCKED -> {
|
||||
handleProfilesChanged()
|
||||
|
||||
@@ -175,9 +175,10 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
*/
|
||||
var shadeExpandedFraction = -1f
|
||||
set(value) {
|
||||
if (visible && field != value) {
|
||||
if (field != value) {
|
||||
header.alpha = ShadeInterpolation.getContentAlpha(value)
|
||||
field = value
|
||||
updateVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +332,9 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
.setDuration(duration)
|
||||
.alpha(if (show) 0f else 1f)
|
||||
.setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN)
|
||||
.setUpdateListener {
|
||||
updateVisibility()
|
||||
}
|
||||
.start()
|
||||
}
|
||||
|
||||
@@ -414,7 +418,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
private fun updateVisibility() {
|
||||
val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) {
|
||||
View.GONE
|
||||
} else if (qsVisible) {
|
||||
} else if (qsVisible && header.alpha > 0f) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.INVISIBLE
|
||||
|
||||
@@ -36,10 +36,6 @@ public class AlphaOptimizedFrameLayout extends FrameLayout implements Launchable
|
||||
visibility -> {
|
||||
super.setVisibility(visibility);
|
||||
return Unit.INSTANCE;
|
||||
},
|
||||
visibility -> {
|
||||
super.setTransitionVisibility(visibility);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
|
||||
public AlphaOptimizedFrameLayout(Context context) {
|
||||
@@ -73,9 +69,4 @@ public class AlphaOptimizedFrameLayout extends FrameLayout implements Launchable
|
||||
public void setVisibility(int visibility) {
|
||||
mLaunchableViewDelegate.setVisibility(visibility);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransitionVisibility(int visibility) {
|
||||
mLaunchableViewDelegate.setTransitionVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1431,6 +1431,22 @@ public class NotificationChildrenContainer extends ViewGroup
|
||||
@Override
|
||||
public void applyRoundnessAndInvalidate() {
|
||||
boolean last = true;
|
||||
if (mUseRoundnessSourceTypes) {
|
||||
if (mNotificationHeaderWrapper != null) {
|
||||
mNotificationHeaderWrapper.requestTopRoundness(
|
||||
/* value = */ getTopRoundness(),
|
||||
/* sourceType = */ FROM_PARENT,
|
||||
/* animate = */ false
|
||||
);
|
||||
}
|
||||
if (mNotificationHeaderWrapperLowPriority != null) {
|
||||
mNotificationHeaderWrapperLowPriority.requestTopRoundness(
|
||||
/* value = */ getTopRoundness(),
|
||||
/* sourceType = */ FROM_PARENT,
|
||||
/* animate = */ false
|
||||
);
|
||||
}
|
||||
}
|
||||
for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
|
||||
ExpandableNotificationRow child = mAttachedChildren.get(i);
|
||||
if (child.getVisibility() == View.GONE) {
|
||||
|
||||
@@ -5193,7 +5193,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
println(pw, "intrinsicPadding", mIntrinsicPadding);
|
||||
println(pw, "topPadding", mTopPadding);
|
||||
println(pw, "bottomPadding", mBottomPadding);
|
||||
mNotificationStackSizeCalculator.dump(pw, args);
|
||||
});
|
||||
pw.println();
|
||||
pw.println("Contents:");
|
||||
|
||||
@@ -30,7 +30,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableView
|
||||
import com.android.systemui.util.Compile
|
||||
import com.android.systemui.util.children
|
||||
import java.io.PrintWriter
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
@@ -54,8 +53,6 @@ constructor(
|
||||
@Main private val resources: Resources
|
||||
) {
|
||||
|
||||
private lateinit var lastComputeHeightLog : String
|
||||
|
||||
/**
|
||||
* Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
|
||||
* If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
|
||||
@@ -117,9 +114,7 @@ constructor(
|
||||
shelfIntrinsicHeight: Float
|
||||
): Int {
|
||||
log { "\n" }
|
||||
|
||||
val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
|
||||
/* computeHeight= */ false)
|
||||
val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
|
||||
|
||||
var maxNotifications =
|
||||
stackHeightSequence.lastIndexWhile { heightResult ->
|
||||
@@ -162,21 +157,18 @@ constructor(
|
||||
shelfIntrinsicHeight: Float
|
||||
): Float {
|
||||
log { "\n" }
|
||||
lastComputeHeightLog = ""
|
||||
val heightPerMaxNotifications =
|
||||
computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
|
||||
/* computeHeight= */ true)
|
||||
computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
|
||||
|
||||
val (notificationsHeight, shelfHeightWithSpaceBefore) =
|
||||
heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
|
||||
heightPerMaxNotifications.last() // Height with all notifications visible.
|
||||
}
|
||||
lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
|
||||
log {
|
||||
"computeHeight(maxNotifications=$maxNotifications," +
|
||||
"shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
|
||||
"${notificationsHeight + shelfHeightWithSpaceBefore}" +
|
||||
" = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
|
||||
log {
|
||||
lastComputeHeightLog
|
||||
}
|
||||
return notificationsHeight + shelfHeightWithSpaceBefore
|
||||
}
|
||||
@@ -192,8 +184,7 @@ constructor(
|
||||
|
||||
private fun computeHeightPerNotificationLimit(
|
||||
stack: NotificationStackScrollLayout,
|
||||
shelfHeight: Float,
|
||||
computeHeight: Boolean
|
||||
shelfHeight: Float
|
||||
): Sequence<StackHeight> = sequence {
|
||||
log { "computeHeightPerNotificationLimit" }
|
||||
|
||||
@@ -222,14 +213,9 @@ constructor(
|
||||
currentIndex = firstViewInShelfIndex)
|
||||
spaceBeforeShelf + shelfHeight
|
||||
}
|
||||
|
||||
val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
|
||||
"shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
|
||||
if (computeHeight) {
|
||||
lastComputeHeightLog += "\n" + currentLog
|
||||
}
|
||||
log {
|
||||
currentLog
|
||||
"i=$i notificationsHeight=$notifications " +
|
||||
"shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
|
||||
}
|
||||
yield(
|
||||
StackHeight(
|
||||
@@ -274,10 +260,6 @@ constructor(
|
||||
return size
|
||||
}
|
||||
|
||||
fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||
pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
|
||||
}
|
||||
|
||||
private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
|
||||
if (visibility == GONE || hasNoContentHeight()) return false
|
||||
if (onLockscreen) {
|
||||
|
||||
@@ -154,9 +154,7 @@ public class AutoTileManager implements UserAwareController {
|
||||
if (!mAutoTracker.isAdded(SAVER)) {
|
||||
mDataSaverController.addCallback(mDataSaverListener);
|
||||
}
|
||||
if (!mAutoTracker.isAdded(WORK)) {
|
||||
mManagedProfileController.addCallback(mProfileCallback);
|
||||
}
|
||||
mManagedProfileController.addCallback(mProfileCallback);
|
||||
if (!mAutoTracker.isAdded(NIGHT)
|
||||
&& ColorDisplayManager.isNightDisplayAvailable(mContext)) {
|
||||
mNightDisplayListener.setCallback(mNightDisplayCallback);
|
||||
@@ -275,18 +273,18 @@ public class AutoTileManager implements UserAwareController {
|
||||
return mCurrentUser.getIdentifier();
|
||||
}
|
||||
|
||||
public void unmarkTileAsAutoAdded(String tabSpec) {
|
||||
mAutoTracker.setTileRemoved(tabSpec);
|
||||
}
|
||||
|
||||
private final ManagedProfileController.Callback mProfileCallback =
|
||||
new ManagedProfileController.Callback() {
|
||||
@Override
|
||||
public void onManagedProfileChanged() {
|
||||
if (mAutoTracker.isAdded(WORK)) return;
|
||||
if (mManagedProfileController.hasActiveProfile()) {
|
||||
if (mAutoTracker.isAdded(WORK)) return;
|
||||
mHost.addTile(WORK);
|
||||
mAutoTracker.setTileAdded(WORK);
|
||||
} else {
|
||||
if (!mAutoTracker.isAdded(WORK)) return;
|
||||
mHost.removeTile(WORK);
|
||||
mAutoTracker.setTileRemoved(WORK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +427,7 @@ public class AutoTileManager implements UserAwareController {
|
||||
initSafetyTile();
|
||||
} else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) {
|
||||
mHost.removeTile(mSafetySpec);
|
||||
mHost.unmarkTileAsAutoAdded(mSafetySpec);
|
||||
mAutoTracker.setTileRemoved(mSafetySpec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,12 +79,30 @@ public interface StatusBarIconController {
|
||||
|
||||
/** Refresh the state of an IconManager by recreating the views */
|
||||
void refreshIconGroup(IconManager iconManager);
|
||||
/** */
|
||||
|
||||
/**
|
||||
* Adds or updates an icon for a given slot for a **tile service icon**.
|
||||
*
|
||||
* TODO(b/265307726): Merge with {@link #setIcon(String, StatusBarIcon)} or make this method
|
||||
* much more clearly distinct from that method.
|
||||
*/
|
||||
void setExternalIcon(String slot);
|
||||
/** */
|
||||
|
||||
/**
|
||||
* Adds or updates an icon for the given slot for **internal system icons**.
|
||||
*
|
||||
* TODO(b/265307726): Rename to `setInternalIcon`, or merge this appropriately with the
|
||||
* {@link #setIcon(String, StatusBarIcon)} method.
|
||||
*/
|
||||
void setIcon(String slot, int resourceId, CharSequence contentDescription);
|
||||
/** */
|
||||
|
||||
/**
|
||||
* Adds or updates an icon for the given slot for an **externally-provided icon**.
|
||||
*
|
||||
* TODO(b/265307726): Rename to `setExternalIcon` or something similar.
|
||||
*/
|
||||
void setIcon(String slot, StatusBarIcon icon);
|
||||
|
||||
/** */
|
||||
void setWifiIcon(String slot, WifiIconState state);
|
||||
|
||||
@@ -133,9 +151,17 @@ public interface StatusBarIconController {
|
||||
* TAG_PRIMARY to refer to the first icon at a given slot.
|
||||
*/
|
||||
void removeIcon(String slot, int tag);
|
||||
|
||||
/** */
|
||||
void removeAllIconsForSlot(String slot);
|
||||
|
||||
/**
|
||||
* Removes all the icons for the given slot.
|
||||
*
|
||||
* Only use this for icons that have come from **an external process**.
|
||||
*/
|
||||
void removeAllIconsForExternalSlot(String slot);
|
||||
|
||||
// TODO: See if we can rename this tunable name.
|
||||
String ICON_HIDE_LIST = "icon_blacklist";
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.internal.statusbar.StatusBarIcon;
|
||||
import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.R;
|
||||
@@ -63,6 +65,10 @@ public class StatusBarIconControllerImpl implements Tunable,
|
||||
ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode {
|
||||
|
||||
private static final String TAG = "StatusBarIconController";
|
||||
// Use this suffix to prevent external icon slot names from unintentionally overriding our
|
||||
// internal, system-level slot names. See b/255428281.
|
||||
@VisibleForTesting
|
||||
protected static final String EXTERNAL_SLOT_SUFFIX = "__external";
|
||||
|
||||
private final StatusBarIconList mStatusBarIconList;
|
||||
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
|
||||
@@ -346,21 +352,26 @@ public class StatusBarIconControllerImpl implements Tunable,
|
||||
|
||||
@Override
|
||||
public void setExternalIcon(String slot) {
|
||||
int viewIndex = mStatusBarIconList.getViewIndex(slot, 0);
|
||||
String slotName = createExternalSlotName(slot);
|
||||
int viewIndex = mStatusBarIconList.getViewIndex(slotName, 0);
|
||||
int height = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.status_bar_icon_drawing_size);
|
||||
mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height));
|
||||
}
|
||||
|
||||
//TODO: remove this (used in command queue and for 3rd party tiles?)
|
||||
// Override for *both* CommandQueue.Callbacks AND StatusBarIconController.
|
||||
// TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to
|
||||
// differentiate between those callback methods and StatusBarIconController methods.
|
||||
@Override
|
||||
public void setIcon(String slot, StatusBarIcon icon) {
|
||||
String slotName = createExternalSlotName(slot);
|
||||
if (icon == null) {
|
||||
removeAllIconsForSlot(slot);
|
||||
removeAllIconsForSlot(slotName);
|
||||
return;
|
||||
}
|
||||
|
||||
StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon);
|
||||
setIcon(slot, holder);
|
||||
setIcon(slotName, holder);
|
||||
}
|
||||
|
||||
private void setIcon(String slot, @NonNull StatusBarIconHolder holder) {
|
||||
@@ -406,10 +417,12 @@ public class StatusBarIconControllerImpl implements Tunable,
|
||||
}
|
||||
}
|
||||
|
||||
/** */
|
||||
// CommandQueue.Callbacks override
|
||||
// TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to
|
||||
// differentiate between those callback methods and StatusBarIconController methods.
|
||||
@Override
|
||||
public void removeIcon(String slot) {
|
||||
removeAllIconsForSlot(slot);
|
||||
removeAllIconsForExternalSlot(slot);
|
||||
}
|
||||
|
||||
/** */
|
||||
@@ -423,6 +436,11 @@ public class StatusBarIconControllerImpl implements Tunable,
|
||||
mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllIconsForExternalSlot(String slotName) {
|
||||
removeAllIconsForSlot(createExternalSlotName(slotName));
|
||||
}
|
||||
|
||||
/** */
|
||||
@Override
|
||||
public void removeAllIconsForSlot(String slotName) {
|
||||
@@ -506,4 +524,12 @@ public class StatusBarIconControllerImpl implements Tunable,
|
||||
public void onDensityOrFontScaleChanged() {
|
||||
refreshIconGroups();
|
||||
}
|
||||
|
||||
private String createExternalSlotName(String slot) {
|
||||
if (slot.endsWith(EXTERNAL_SLOT_SUFFIX)) {
|
||||
return slot;
|
||||
} else {
|
||||
return slot + EXTERNAL_SLOT_SUFFIX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
import android.os.Trace;
|
||||
import android.os.VibrationEffect;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.Global;
|
||||
import android.text.InputFilter;
|
||||
@@ -109,8 +108,6 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||
import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
|
||||
import com.android.internal.jank.InteractionJankMonitor;
|
||||
import com.android.internal.view.RotationPolicy;
|
||||
@@ -130,15 +127,11 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
|
||||
import com.android.systemui.util.AlphaTintDrawableWrapper;
|
||||
import com.android.systemui.util.DeviceConfigProxy;
|
||||
import com.android.systemui.util.RoundedCornerProgressDrawable;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -195,9 +188,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
private ViewGroup mDialogRowsView;
|
||||
private ViewGroup mRinger;
|
||||
|
||||
private DeviceConfigProxy mDeviceConfigProxy;
|
||||
private Executor mExecutor;
|
||||
|
||||
/**
|
||||
* Container for the top part of the dialog, which contains the ringer, the ringer drawer, the
|
||||
* volume rows, and the ellipsis button. This does not include the live caption button.
|
||||
@@ -286,13 +276,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
private BackgroundBlurDrawable mDialogRowsViewBackground;
|
||||
private final InteractionJankMonitor mInteractionJankMonitor;
|
||||
|
||||
private boolean mSeparateNotification;
|
||||
|
||||
@VisibleForTesting
|
||||
int mVolumeRingerIconDrawableId;
|
||||
@VisibleForTesting
|
||||
int mVolumeRingerMuteIconDrawableId;
|
||||
|
||||
public VolumeDialogImpl(
|
||||
Context context,
|
||||
VolumeDialogController volumeDialogController,
|
||||
@@ -303,8 +286,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
VolumePanelFactory volumePanelFactory,
|
||||
ActivityStarter activityStarter,
|
||||
InteractionJankMonitor interactionJankMonitor,
|
||||
DeviceConfigProxy deviceConfigProxy,
|
||||
Executor executor,
|
||||
DumpManager dumpManager) {
|
||||
mContext =
|
||||
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
|
||||
@@ -347,50 +328,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
}
|
||||
|
||||
initDimens();
|
||||
|
||||
mDeviceConfigProxy = deviceConfigProxy;
|
||||
mExecutor = executor;
|
||||
mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
|
||||
updateRingerModeIconSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* If ringer and notification are the same stream (T and earlier), use notification-like bell
|
||||
* icon set.
|
||||
* If ringer and notification are separated, then use generic speaker icons.
|
||||
*/
|
||||
private void updateRingerModeIconSet() {
|
||||
if (mSeparateNotification) {
|
||||
mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on;
|
||||
mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute;
|
||||
} else {
|
||||
mVolumeRingerIconDrawableId = R.drawable.ic_volume_ringer;
|
||||
mVolumeRingerMuteIconDrawableId = R.drawable.ic_volume_ringer_mute;
|
||||
}
|
||||
|
||||
if (mRingerDrawerMuteIcon != null) {
|
||||
mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
|
||||
}
|
||||
if (mRingerDrawerNormalIcon != null) {
|
||||
mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change icon for ring stream (not ringer mode icon)
|
||||
*/
|
||||
private void updateRingRowIcon() {
|
||||
Optional<VolumeRow> volumeRow = mRows.stream().filter(row -> row.stream == STREAM_RING)
|
||||
.findFirst();
|
||||
if (volumeRow.isPresent()) {
|
||||
VolumeRow volRow = volumeRow.get();
|
||||
volRow.iconRes = mSeparateNotification ? R.drawable.ic_ring_volume
|
||||
: R.drawable.ic_volume_ringer;
|
||||
volRow.iconMuteRes = mSeparateNotification ? R.drawable.ic_ring_volume_off
|
||||
: R.drawable.ic_volume_ringer_mute;
|
||||
volRow.setIcon(volRow.iconRes, mContext.getTheme());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -407,9 +344,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
mController.getState();
|
||||
|
||||
mConfigurationController.addCallback(this);
|
||||
|
||||
mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
mExecutor, this::onDeviceConfigChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -417,24 +351,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
mController.removeCallback(mControllerCallbackH);
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
mConfigurationController.removeCallback(this);
|
||||
mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update ringer mode icon based on the config
|
||||
*/
|
||||
private void onDeviceConfigChange(DeviceConfig.Properties properties) {
|
||||
Set<String> changeSet = properties.getKeyset();
|
||||
if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
|
||||
boolean newVal = properties.getBoolean(
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
|
||||
if (newVal != mSeparateNotification) {
|
||||
mSeparateNotification = newVal;
|
||||
updateRingerModeIconSet();
|
||||
updateRingRowIcon();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -643,8 +559,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon);
|
||||
mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background);
|
||||
|
||||
updateRingerModeIconSet();
|
||||
|
||||
setupRingerDrawer();
|
||||
|
||||
mODICaptionsView = mDialog.findViewById(R.id.odi_captions);
|
||||
@@ -668,14 +582,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
addRow(AudioManager.STREAM_MUSIC,
|
||||
R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
|
||||
if (!AudioSystem.isSingleVolume(mContext)) {
|
||||
if (mSeparateNotification) {
|
||||
addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
|
||||
R.drawable.ic_ring_volume_off, true, false);
|
||||
} else {
|
||||
addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer,
|
||||
R.drawable.ic_volume_ringer, true, false);
|
||||
}
|
||||
|
||||
addRow(AudioManager.STREAM_RING,
|
||||
R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false);
|
||||
addRow(STREAM_ALARM,
|
||||
R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false);
|
||||
addRow(AudioManager.STREAM_VOICE_CALL,
|
||||
@@ -1634,8 +1542,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
|
||||
break;
|
||||
case AudioManager.RINGER_MODE_SILENT:
|
||||
mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
|
||||
mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
|
||||
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
|
||||
mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
|
||||
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
|
||||
addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT,
|
||||
mContext.getString(R.string.volume_ringer_hint_unmute));
|
||||
@@ -1644,14 +1552,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
default:
|
||||
boolean muted = (mAutomute && ss.level == 0) || ss.muted;
|
||||
if (!isZenMuted && muted) {
|
||||
mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
|
||||
mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
|
||||
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
|
||||
mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
|
||||
addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
|
||||
mContext.getString(R.string.volume_ringer_hint_unmute));
|
||||
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
|
||||
} else {
|
||||
mRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
|
||||
mSelectedRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
|
||||
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
|
||||
mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
|
||||
if (mController.hasVibrator()) {
|
||||
addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
|
||||
mContext.getString(R.string.volume_ringer_hint_vibrate));
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
|
||||
import com.android.internal.jank.InteractionJankMonitor;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
@@ -29,14 +28,11 @@ import com.android.systemui.plugins.VolumeDialogController;
|
||||
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
|
||||
import com.android.systemui.util.DeviceConfigProxy;
|
||||
import com.android.systemui.volume.VolumeComponent;
|
||||
import com.android.systemui.volume.VolumeDialogComponent;
|
||||
import com.android.systemui.volume.VolumeDialogImpl;
|
||||
import com.android.systemui.volume.VolumePanelFactory;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -61,8 +57,6 @@ public interface VolumeModule {
|
||||
VolumePanelFactory volumePanelFactory,
|
||||
ActivityStarter activityStarter,
|
||||
InteractionJankMonitor interactionJankMonitor,
|
||||
DeviceConfigProxy deviceConfigProxy,
|
||||
@Main Executor executor,
|
||||
DumpManager dumpManager) {
|
||||
VolumeDialogImpl impl = new VolumeDialogImpl(
|
||||
context,
|
||||
@@ -74,8 +68,6 @@ public interface VolumeModule {
|
||||
volumePanelFactory,
|
||||
activityStarter,
|
||||
interactionJankMonitor,
|
||||
deviceConfigProxy,
|
||||
executor,
|
||||
dumpManager);
|
||||
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
|
||||
impl.setAutomute(true);
|
||||
|
||||
@@ -12,11 +12,13 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.jank.InteractionJankMonitor
|
||||
import com.android.internal.policy.DecorView
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import junit.framework.Assert.assertEquals
|
||||
import junit.framework.Assert.assertFalse
|
||||
import junit.framework.Assert.assertNotNull
|
||||
@@ -205,25 +207,74 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
verify(interactionJankMonitor).end(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAnimationDoesNotChangeLaunchableViewVisibility_viewVisible() {
|
||||
val touchSurface = createTouchSurface()
|
||||
|
||||
// View is VISIBLE when starting the animation.
|
||||
runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.VISIBLE }
|
||||
|
||||
// View is invisible while the dialog is shown.
|
||||
val dialog = showDialogFromView(touchSurface)
|
||||
assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
|
||||
|
||||
// View is visible again when the dialog is dismissed.
|
||||
runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
|
||||
assertThat(touchSurface.visibility).isEqualTo(View.VISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAnimationDoesNotChangeLaunchableViewVisibility_viewInvisible() {
|
||||
val touchSurface = createTouchSurface()
|
||||
|
||||
// View is INVISIBLE when starting the animation.
|
||||
runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.INVISIBLE }
|
||||
|
||||
// View is INVISIBLE while the dialog is shown.
|
||||
val dialog = showDialogFromView(touchSurface)
|
||||
assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
|
||||
|
||||
// View is invisible like it was before showing the dialog.
|
||||
runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
|
||||
assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAnimationDoesNotChangeLaunchableViewVisibility_viewVisibleThenGone() {
|
||||
val touchSurface = createTouchSurface()
|
||||
|
||||
// View is VISIBLE when starting the animation.
|
||||
runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.VISIBLE }
|
||||
|
||||
// View is INVISIBLE while the dialog is shown.
|
||||
val dialog = showDialogFromView(touchSurface)
|
||||
assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
|
||||
|
||||
// Some external call makes the View GONE. It remains INVISIBLE while the dialog is shown,
|
||||
// as all visibility changes should be blocked.
|
||||
runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.GONE }
|
||||
assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
|
||||
|
||||
// View is restored to GONE once the dialog is dismissed.
|
||||
runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
|
||||
assertThat(touchSurface.visibility).isEqualTo(View.GONE)
|
||||
}
|
||||
|
||||
private fun createAndShowDialog(
|
||||
animator: DialogLaunchAnimator = dialogLaunchAnimator,
|
||||
): TestDialog {
|
||||
val touchSurface = createTouchSurface()
|
||||
return runOnMainThreadAndWaitForIdleSync {
|
||||
val dialog = TestDialog(context)
|
||||
animator.showFromView(dialog, touchSurface)
|
||||
dialog
|
||||
}
|
||||
return showDialogFromView(touchSurface, animator)
|
||||
}
|
||||
|
||||
private fun createTouchSurface(): View {
|
||||
return runOnMainThreadAndWaitForIdleSync {
|
||||
val touchSurfaceRoot = LinearLayout(context)
|
||||
val touchSurface = View(context)
|
||||
val touchSurface = TouchSurfaceView(context)
|
||||
touchSurfaceRoot.addView(touchSurface)
|
||||
|
||||
// We need to attach the root to the window manager otherwise the exit animation will
|
||||
// be skipped
|
||||
// be skipped.
|
||||
ViewUtils.attachView(touchSurfaceRoot)
|
||||
attachedViews.add(touchSurfaceRoot)
|
||||
|
||||
@@ -231,6 +282,17 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDialogFromView(
|
||||
touchSurface: View,
|
||||
animator: DialogLaunchAnimator = dialogLaunchAnimator,
|
||||
): TestDialog {
|
||||
return runOnMainThreadAndWaitForIdleSync {
|
||||
val dialog = TestDialog(context)
|
||||
animator.showFromView(dialog, touchSurface)
|
||||
dialog
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDialogAndShowFromDialog(animateFrom: Dialog): TestDialog {
|
||||
return runOnMainThreadAndWaitForIdleSync {
|
||||
val dialog = TestDialog(context)
|
||||
@@ -248,6 +310,22 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
return result
|
||||
}
|
||||
|
||||
private class TouchSurfaceView(context: Context) : FrameLayout(context), LaunchableView {
|
||||
private val delegate =
|
||||
LaunchableViewDelegate(
|
||||
this,
|
||||
superSetVisibility = { super.setVisibility(it) },
|
||||
)
|
||||
|
||||
override fun setShouldBlockVisibilityChanges(block: Boolean) {
|
||||
delegate.setShouldBlockVisibilityChanges(block)
|
||||
}
|
||||
|
||||
override fun setVisibility(visibility: Int) {
|
||||
delegate.setVisibility(visibility)
|
||||
}
|
||||
}
|
||||
|
||||
private class TestDialog(context: Context) : Dialog(context) {
|
||||
companion object {
|
||||
const val DIALOG_WIDTH = 100
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.android.systemui.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.UserInfo
|
||||
import android.os.Handler
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import androidx.concurrent.futures.DirectExecutor
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.util.concurrent.Executor
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(Parameterized::class)
|
||||
class UserTrackerImplReceiveTest : SysuiTestCase() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters
|
||||
fun data(): Iterable<String> =
|
||||
listOf(
|
||||
Intent.ACTION_USER_INFO_CHANGED,
|
||||
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
|
||||
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
|
||||
Intent.ACTION_MANAGED_PROFILE_ADDED,
|
||||
Intent.ACTION_MANAGED_PROFILE_REMOVED,
|
||||
Intent.ACTION_MANAGED_PROFILE_UNLOCKED
|
||||
)
|
||||
}
|
||||
|
||||
private val executor: Executor = DirectExecutor.INSTANCE
|
||||
|
||||
@Mock private lateinit var context: Context
|
||||
@Mock private lateinit var userManager: UserManager
|
||||
@Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
|
||||
@Mock(stubOnly = true) private lateinit var handler: Handler
|
||||
|
||||
@Parameterized.Parameter lateinit var intentAction: String
|
||||
@Mock private lateinit var callback: UserTracker.Callback
|
||||
@Captor private lateinit var captor: ArgumentCaptor<List<UserInfo>>
|
||||
|
||||
private lateinit var tracker: UserTrackerImpl
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
`when`(context.user).thenReturn(UserHandle.SYSTEM)
|
||||
`when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context)
|
||||
|
||||
tracker = UserTrackerImpl(context, userManager, dumpManager, handler)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calls callback and updates profiles when an intent received`() {
|
||||
tracker.initialize(0)
|
||||
tracker.addCallback(callback, executor)
|
||||
val profileID = tracker.userId + 10
|
||||
|
||||
`when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
|
||||
val id = invocation.getArgument<Int>(0)
|
||||
val info = UserInfo(id, "", UserInfo.FLAG_FULL)
|
||||
val infoProfile =
|
||||
UserInfo(
|
||||
id + 10,
|
||||
"",
|
||||
"",
|
||||
UserInfo.FLAG_MANAGED_PROFILE,
|
||||
UserManager.USER_TYPE_PROFILE_MANAGED
|
||||
)
|
||||
infoProfile.profileGroupId = id
|
||||
listOf(info, infoProfile)
|
||||
}
|
||||
|
||||
tracker.onReceive(context, Intent(intentAction))
|
||||
|
||||
verify(callback, times(0)).onUserChanged(anyInt(), any())
|
||||
verify(callback, times(1)).onProfilesChanged(capture(captor))
|
||||
assertThat(captor.value.map { it.id }).containsExactly(0, profileID)
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,16 @@ class UserTrackerImplTest : SysuiTestCase() {
|
||||
|
||||
verify(context).registerReceiverForAllUsers(
|
||||
eq(tracker), capture(captor), isNull(), eq(handler))
|
||||
with(captor.value) {
|
||||
assertThat(countActions()).isEqualTo(7)
|
||||
assertThat(hasAction(Intent.ACTION_USER_SWITCHED)).isTrue()
|
||||
assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
|
||||
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
|
||||
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
|
||||
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
|
||||
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
|
||||
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -279,37 +289,6 @@ class UserTrackerImplTest : SysuiTestCase() {
|
||||
assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(newID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCallbackCalledOnProfileChanged() {
|
||||
tracker.initialize(0)
|
||||
val callback = TestCallback()
|
||||
tracker.addCallback(callback, executor)
|
||||
val profileID = tracker.userId + 10
|
||||
|
||||
`when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
|
||||
val id = invocation.getArgument<Int>(0)
|
||||
val info = UserInfo(id, "", UserInfo.FLAG_FULL)
|
||||
val infoProfile = UserInfo(
|
||||
id + 10,
|
||||
"",
|
||||
"",
|
||||
UserInfo.FLAG_MANAGED_PROFILE,
|
||||
UserManager.USER_TYPE_PROFILE_MANAGED
|
||||
)
|
||||
infoProfile.profileGroupId = id
|
||||
listOf(info, infoProfile)
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
|
||||
.putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
|
||||
|
||||
tracker.onReceive(context, intent)
|
||||
|
||||
assertThat(callback.calledOnUserChanged).isEqualTo(0)
|
||||
assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
|
||||
assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCallbackCalledOnUserInfoChanged() {
|
||||
tracker.initialize(0)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
|
||||
import androidx.constraintlayout.widget.ConstraintSet.START
|
||||
@@ -92,12 +93,12 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
|
||||
assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
|
||||
|
||||
assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID)
|
||||
assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0f)
|
||||
assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0.5f)
|
||||
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
|
||||
.isEqualTo(PARENT_ID)
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
|
||||
.isEqualTo(1f)
|
||||
.isEqualTo(0.5f)
|
||||
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
|
||||
.isEqualTo(R.id.end_guide)
|
||||
@@ -331,10 +332,8 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
|
||||
val views = mapOf(
|
||||
R.id.clock to "clock",
|
||||
R.id.date to "date",
|
||||
R.id.statusIcons to "icons",
|
||||
R.id.privacy_container to "privacy",
|
||||
R.id.carrier_group to "carriers",
|
||||
R.id.batteryRemainingIcon to "battery",
|
||||
)
|
||||
views.forEach { (id, name) ->
|
||||
assertWithMessage("$name has 0 height in qqs")
|
||||
@@ -352,11 +351,8 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
|
||||
fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
|
||||
val views = mapOf(
|
||||
R.id.clock to "clock",
|
||||
R.id.date to "date",
|
||||
R.id.statusIcons to "icons",
|
||||
R.id.privacy_container to "privacy",
|
||||
R.id.carrier_group to "carriers",
|
||||
R.id.batteryRemainingIcon to "battery",
|
||||
)
|
||||
views.forEach { (id, name) ->
|
||||
expect.withMessage("$name changes height")
|
||||
@@ -369,8 +365,8 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
private fun Int.fromConstraint() = when (this) {
|
||||
-1 -> "MATCH_PARENT"
|
||||
-2 -> "WRAP_CONTENT"
|
||||
ViewGroup.LayoutParams.MATCH_PARENT -> "MATCH_PARENT"
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT -> "WRAP_CONTENT"
|
||||
else -> toString()
|
||||
}
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
null
|
||||
}
|
||||
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
|
||||
whenever(view.alpha).thenReturn(1f)
|
||||
|
||||
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.StatusBarManager
|
||||
import android.content.Context
|
||||
import android.testing.AndroidTestingRunner
|
||||
@@ -30,6 +31,7 @@ import com.android.systemui.statusbar.policy.VariableDateViewController
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@@ -37,6 +39,7 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Answers
|
||||
import org.mockito.ArgumentMatchers.anyFloat
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.mock
|
||||
@@ -75,6 +78,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
|
||||
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
|
||||
var viewVisibility = View.GONE
|
||||
var viewAlpha = 1f
|
||||
|
||||
private lateinit var mLargeScreenShadeHeaderController: LargeScreenShadeHeaderController
|
||||
private lateinit var carrierIconSlots: List<String>
|
||||
@@ -101,6 +105,13 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
null
|
||||
}
|
||||
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
|
||||
|
||||
whenever(view.setAlpha(anyFloat())).then {
|
||||
viewAlpha = it.arguments[0] as Float
|
||||
null
|
||||
}
|
||||
whenever(view.alpha).thenAnswer { _ -> viewAlpha }
|
||||
|
||||
whenever(variableDateViewControllerFactory.create(any()))
|
||||
.thenReturn(variableDateViewController)
|
||||
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
|
||||
@@ -154,6 +165,16 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun alphaChangesUpdateVisibility() {
|
||||
makeShadeVisible()
|
||||
mLargeScreenShadeHeaderController.shadeExpandedFraction = 0f
|
||||
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
|
||||
|
||||
mLargeScreenShadeHeaderController.shadeExpandedFraction = 1f
|
||||
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleCarrier_enablesCarrierIconsInStatusIcons() {
|
||||
whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
|
||||
@@ -238,6 +259,39 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
verify(animator).start()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShadeExpanded_true_alpha_zero_invisible() {
|
||||
view.alpha = 0f
|
||||
mLargeScreenShadeHeaderController.largeScreenActive = true
|
||||
mLargeScreenShadeHeaderController.qsVisible = true
|
||||
|
||||
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animatorCallsUpdateVisibilityOnUpdate() {
|
||||
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, 0L)
|
||||
|
||||
val updateCaptor = argumentCaptor<ValueAnimator.AnimatorUpdateListener>()
|
||||
verify(animator).setUpdateListener(capture(updateCaptor))
|
||||
|
||||
mLargeScreenShadeHeaderController.largeScreenActive = true
|
||||
mLargeScreenShadeHeaderController.qsVisible = true
|
||||
|
||||
view.alpha = 1f
|
||||
updateCaptor.value.onAnimationUpdate(mock())
|
||||
|
||||
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
|
||||
|
||||
view.alpha = 0f
|
||||
updateCaptor.value.onAnimationUpdate(mock())
|
||||
|
||||
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun demoMode_attachDemoMode() {
|
||||
val cb = argumentCaptor<DemoMode>()
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.notification.LegacySourceType;
|
||||
import com.android.systemui.statusbar.notification.SourceType;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
|
||||
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
@@ -216,4 +217,29 @@ public class NotificationChildrenContainerTest extends SysuiTestCase {
|
||||
Assert.assertEquals(1f, mChildrenContainer.getBottomRoundness(), 0.001f);
|
||||
Assert.assertEquals(1f, notificationRow.getBottomRoundness(), 0.001f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_header() {
|
||||
mChildrenContainer.useRoundnessSourceTypes(true);
|
||||
|
||||
NotificationHeaderViewWrapper header = mChildrenContainer.getNotificationHeaderWrapper();
|
||||
Assert.assertEquals(0f, header.getTopRoundness(), 0.001f);
|
||||
|
||||
mChildrenContainer.requestTopRoundness(1f, SourceType.from(""), false);
|
||||
|
||||
Assert.assertEquals(1f, header.getTopRoundness(), 0.001f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_headerLowPriority() {
|
||||
mChildrenContainer.useRoundnessSourceTypes(true);
|
||||
mChildrenContainer.setIsLowPriority(true);
|
||||
|
||||
NotificationHeaderViewWrapper header = mChildrenContainer.getNotificationHeaderWrapper();
|
||||
Assert.assertEquals(0f, header.getTopRoundness(), 0.001f);
|
||||
|
||||
mChildrenContainer.requestTopRoundness(1f, SourceType.from(""), false);
|
||||
|
||||
Assert.assertEquals(1f, header.getTopRoundness(), 0.001f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNotNull;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
@@ -74,6 +75,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -115,8 +117,10 @@ public class AutoTileManagerTest extends SysuiTestCase {
|
||||
@Spy private PackageManager mPackageManager;
|
||||
private final boolean mIsReduceBrightColorsAvailable = true;
|
||||
|
||||
private AutoTileManager mAutoTileManager;
|
||||
private AutoTileManager mAutoTileManager; // under test
|
||||
|
||||
private SecureSettings mSecureSettings;
|
||||
private ManagedProfileController.Callback mManagedProfileCallback;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -303,7 +307,7 @@ public class AutoTileManagerTest extends SysuiTestCase {
|
||||
|
||||
InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
|
||||
inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
|
||||
inOrderManagedProfile.verify(mManagedProfileController, never()).addCallback(any());
|
||||
inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
|
||||
|
||||
if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
|
||||
InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
|
||||
@@ -503,6 +507,40 @@ public class AutoTileManagerTest extends SysuiTestCase {
|
||||
verify(mQsTileHost, times(2)).addTile(safetyComponent, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managedProfileAdded_tileAdded() {
|
||||
when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false);
|
||||
mAutoTileManager = createAutoTileManager(mContext);
|
||||
Mockito.doAnswer((Answer<Object>) invocation -> {
|
||||
mManagedProfileCallback = invocation.getArgument(0);
|
||||
return null;
|
||||
}).when(mManagedProfileController).addCallback(any());
|
||||
mAutoTileManager.init();
|
||||
when(mManagedProfileController.hasActiveProfile()).thenReturn(true);
|
||||
|
||||
mManagedProfileCallback.onManagedProfileChanged();
|
||||
|
||||
verify(mQsTileHost, times(1)).addTile(eq("work"));
|
||||
verify(mAutoAddTracker, times(1)).setTileAdded(eq("work"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managedProfileRemoved_tileRemoved() {
|
||||
when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(true);
|
||||
mAutoTileManager = createAutoTileManager(mContext);
|
||||
Mockito.doAnswer((Answer<Object>) invocation -> {
|
||||
mManagedProfileCallback = invocation.getArgument(0);
|
||||
return null;
|
||||
}).when(mManagedProfileController).addCallback(any());
|
||||
mAutoTileManager.init();
|
||||
when(mManagedProfileController.hasActiveProfile()).thenReturn(false);
|
||||
|
||||
mManagedProfileCallback.onManagedProfileChanged();
|
||||
|
||||
verify(mQsTileHost, times(1)).removeTile(eq("work"));
|
||||
verify(mAutoAddTracker, times(1)).setTileRemoved(eq("work"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyArray_doesNotCrash() {
|
||||
mContext.getOrCreateTestableResources().addOverride(
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.phone
|
||||
|
||||
import android.os.UserHandle
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.statusbar.StatusBarIcon
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl.EXTERNAL_SLOT_SUFFIX
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.verify
|
||||
|
||||
@SmallTest
|
||||
class StatusBarIconControllerImplTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var underTest: StatusBarIconControllerImpl
|
||||
|
||||
private lateinit var iconList: StatusBarIconList
|
||||
private val iconGroup: StatusBarIconController.IconManager = mock()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
iconList = StatusBarIconList(arrayOf())
|
||||
underTest =
|
||||
StatusBarIconControllerImpl(
|
||||
context,
|
||||
mock(),
|
||||
mock(),
|
||||
mock(),
|
||||
mock(),
|
||||
mock(),
|
||||
iconList,
|
||||
mock(),
|
||||
)
|
||||
underTest.addIconGroup(iconGroup)
|
||||
}
|
||||
|
||||
/** Regression test for b/255428281. */
|
||||
@Test
|
||||
fun internalAndExternalIconWithSameName_bothDisplayed() {
|
||||
val slotName = "mute"
|
||||
|
||||
// Internal
|
||||
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
|
||||
|
||||
// External
|
||||
val externalIcon =
|
||||
StatusBarIcon(
|
||||
"external.package",
|
||||
UserHandle.ALL,
|
||||
/* iconId= */ 2,
|
||||
/* iconLevel= */ 0,
|
||||
/* number= */ 0,
|
||||
"contentDescription",
|
||||
)
|
||||
underTest.setIcon(slotName, externalIcon)
|
||||
|
||||
assertThat(iconList.slots).hasSize(2)
|
||||
// Whichever was added last comes first
|
||||
assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
|
||||
assertThat(iconList.slots[1].name).isEqualTo(slotName)
|
||||
assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
|
||||
assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
|
||||
}
|
||||
|
||||
/** Regression test for b/255428281. */
|
||||
@Test
|
||||
fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveIcon_internalStays() {
|
||||
val slotName = "mute"
|
||||
|
||||
// Internal
|
||||
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
|
||||
|
||||
// External
|
||||
underTest.setIcon(slotName, createExternalIcon())
|
||||
|
||||
// WHEN the external icon is removed via #removeIcon
|
||||
underTest.removeIcon(slotName)
|
||||
|
||||
// THEN the external icon is removed but the internal icon remains
|
||||
// Note: [StatusBarIconList] never removes slots from its list, it just sets the holder for
|
||||
// the slot to null when an icon is removed.
|
||||
assertThat(iconList.slots).hasSize(2)
|
||||
assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
|
||||
assertThat(iconList.slots[1].name).isEqualTo(slotName)
|
||||
assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal
|
||||
assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
|
||||
|
||||
verify(iconGroup).onRemoveIcon(0)
|
||||
}
|
||||
|
||||
/** Regression test for b/255428281. */
|
||||
@Test
|
||||
fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveAll_internalStays() {
|
||||
val slotName = "mute"
|
||||
|
||||
// Internal
|
||||
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
|
||||
|
||||
// External
|
||||
underTest.setIcon(slotName, createExternalIcon())
|
||||
|
||||
// WHEN the external icon is removed via #removeAllIconsForExternalSlot
|
||||
underTest.removeAllIconsForExternalSlot(slotName)
|
||||
|
||||
// THEN the external icon is removed but the internal icon remains
|
||||
assertThat(iconList.slots).hasSize(2)
|
||||
assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
|
||||
assertThat(iconList.slots[1].name).isEqualTo(slotName)
|
||||
assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal
|
||||
assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
|
||||
|
||||
verify(iconGroup).onRemoveIcon(0)
|
||||
}
|
||||
|
||||
/** Regression test for b/255428281. */
|
||||
@Test
|
||||
fun internalAndExternalIconWithSameName_externalRemoved_viaSetNull_internalStays() {
|
||||
val slotName = "mute"
|
||||
|
||||
// Internal
|
||||
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
|
||||
|
||||
// External
|
||||
underTest.setIcon(slotName, createExternalIcon())
|
||||
|
||||
// WHEN the external icon is removed via a #setIcon(null)
|
||||
underTest.setIcon(slotName, /* icon= */ null)
|
||||
|
||||
// THEN the external icon is removed but the internal icon remains
|
||||
assertThat(iconList.slots).hasSize(2)
|
||||
assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
|
||||
assertThat(iconList.slots[1].name).isEqualTo(slotName)
|
||||
assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal
|
||||
assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
|
||||
|
||||
verify(iconGroup).onRemoveIcon(0)
|
||||
}
|
||||
|
||||
/** Regression test for b/255428281. */
|
||||
@Test
|
||||
fun internalAndExternalIconWithSameName_internalRemoved_viaRemove_externalStays() {
|
||||
val slotName = "mute"
|
||||
|
||||
// Internal
|
||||
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
|
||||
|
||||
// External
|
||||
underTest.setIcon(slotName, createExternalIcon())
|
||||
|
||||
// WHEN the internal icon is removed via #removeIcon
|
||||
underTest.removeIcon(slotName, /* tag= */ 0)
|
||||
|
||||
// THEN the external icon is removed but the internal icon remains
|
||||
assertThat(iconList.slots).hasSize(2)
|
||||
assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
|
||||
assertThat(iconList.slots[1].name).isEqualTo(slotName)
|
||||
assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
|
||||
assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal
|
||||
|
||||
verify(iconGroup).onRemoveIcon(1)
|
||||
}
|
||||
|
||||
/** Regression test for b/255428281. */
|
||||
@Test
|
||||
fun internalAndExternalIconWithSameName_internalRemoved_viaRemoveAll_externalStays() {
|
||||
val slotName = "mute"
|
||||
|
||||
// Internal
|
||||
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
|
||||
|
||||
// External
|
||||
underTest.setIcon(slotName, createExternalIcon())
|
||||
|
||||
// WHEN the internal icon is removed via #removeAllIconsForSlot
|
||||
underTest.removeAllIconsForSlot(slotName)
|
||||
|
||||
// THEN the external icon is removed but the internal icon remains
|
||||
assertThat(iconList.slots).hasSize(2)
|
||||
assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
|
||||
assertThat(iconList.slots[1].name).isEqualTo(slotName)
|
||||
assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
|
||||
assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal
|
||||
|
||||
verify(iconGroup).onRemoveIcon(1)
|
||||
}
|
||||
|
||||
/** Regression test for b/255428281. */
|
||||
@Test
|
||||
fun internalAndExternalIconWithSameName_internalUpdatedIndependently() {
|
||||
val slotName = "mute"
|
||||
|
||||
// Internal
|
||||
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
|
||||
|
||||
// External
|
||||
val startingExternalIcon =
|
||||
StatusBarIcon(
|
||||
"external.package",
|
||||
UserHandle.ALL,
|
||||
/* iconId= */ 20,
|
||||
/* iconLevel= */ 0,
|
||||
/* number= */ 0,
|
||||
"externalDescription",
|
||||
)
|
||||
underTest.setIcon(slotName, startingExternalIcon)
|
||||
|
||||
// WHEN the internal icon is updated
|
||||
underTest.setIcon(slotName, /* resourceId= */ 11, "newContentDescription")
|
||||
|
||||
// THEN only the internal slot gets the updates
|
||||
val internalSlot = iconList.slots[1]
|
||||
val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!!
|
||||
assertThat(internalSlot.name).isEqualTo(slotName)
|
||||
assertThat(internalHolder.icon!!.contentDescription).isEqualTo("newContentDescription")
|
||||
assertThat(internalHolder.icon!!.icon.resId).isEqualTo(11)
|
||||
|
||||
// And the external slot has its own values
|
||||
val externalSlot = iconList.slots[0]
|
||||
val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!!
|
||||
assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
|
||||
assertThat(externalHolder.icon!!.contentDescription).isEqualTo("externalDescription")
|
||||
assertThat(externalHolder.icon!!.icon.resId).isEqualTo(20)
|
||||
}
|
||||
|
||||
/** Regression test for b/255428281. */
|
||||
@Test
|
||||
fun internalAndExternalIconWithSameName_externalUpdatedIndependently() {
|
||||
val slotName = "mute"
|
||||
|
||||
// Internal
|
||||
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
|
||||
|
||||
// External
|
||||
val startingExternalIcon =
|
||||
StatusBarIcon(
|
||||
"external.package",
|
||||
UserHandle.ALL,
|
||||
/* iconId= */ 20,
|
||||
/* iconLevel= */ 0,
|
||||
/* number= */ 0,
|
||||
"externalDescription",
|
||||
)
|
||||
underTest.setIcon(slotName, startingExternalIcon)
|
||||
|
||||
// WHEN the external icon is updated
|
||||
val newExternalIcon =
|
||||
StatusBarIcon(
|
||||
"external.package",
|
||||
UserHandle.ALL,
|
||||
/* iconId= */ 21,
|
||||
/* iconLevel= */ 0,
|
||||
/* number= */ 0,
|
||||
"newExternalDescription",
|
||||
)
|
||||
underTest.setIcon(slotName, newExternalIcon)
|
||||
|
||||
// THEN only the external slot gets the updates
|
||||
val externalSlot = iconList.slots[0]
|
||||
val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!!
|
||||
assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
|
||||
assertThat(externalHolder.icon!!.contentDescription).isEqualTo("newExternalDescription")
|
||||
assertThat(externalHolder.icon!!.icon.resId).isEqualTo(21)
|
||||
|
||||
// And the internal slot has its own values
|
||||
val internalSlot = iconList.slots[1]
|
||||
val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!!
|
||||
assertThat(internalSlot.name).isEqualTo(slotName)
|
||||
assertThat(internalHolder.icon!!.contentDescription).isEqualTo("contentDescription")
|
||||
assertThat(internalHolder.icon!!.icon.resId).isEqualTo(10)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun externalSlot_alreadyEndsWithSuffix_suffixNotAddedTwice() {
|
||||
underTest.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon())
|
||||
|
||||
assertThat(iconList.slots).hasSize(1)
|
||||
assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX")
|
||||
}
|
||||
|
||||
private fun createExternalIcon(): StatusBarIcon {
|
||||
return StatusBarIcon(
|
||||
"external.package",
|
||||
UserHandle.ALL,
|
||||
/* iconId= */ 2,
|
||||
/* iconLevel= */ 0,
|
||||
/* number= */ 0,
|
||||
"contentDescription",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package com.android.systemui.volume;
|
||||
|
||||
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -29,7 +28,6 @@ import static org.mockito.Mockito.verify;
|
||||
import android.app.KeyguardManager;
|
||||
import android.media.AudioManager;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.InputDevice;
|
||||
@@ -40,7 +38,6 @@ import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||
import com.android.internal.jank.InteractionJankMonitor;
|
||||
import com.android.systemui.Prefs;
|
||||
import com.android.systemui.R;
|
||||
@@ -53,9 +50,6 @@ import com.android.systemui.plugins.VolumeDialogController.State;
|
||||
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
|
||||
import com.android.systemui.util.DeviceConfigProxyFake;
|
||||
import com.android.systemui.util.concurrency.FakeExecutor;
|
||||
import com.android.systemui.util.time.FakeSystemClock;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -78,8 +72,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
View mDrawerVibrate;
|
||||
View mDrawerMute;
|
||||
View mDrawerNormal;
|
||||
private DeviceConfigProxyFake mDeviceConfigProxy;
|
||||
private FakeExecutor mExecutor;
|
||||
|
||||
@Mock
|
||||
VolumeDialogController mVolumeDialogController;
|
||||
@@ -108,9 +100,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
|
||||
getContext().addMockSystemService(KeyguardManager.class, mKeyguard);
|
||||
|
||||
mDeviceConfigProxy = new DeviceConfigProxyFake();
|
||||
mExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
|
||||
mDialog = new VolumeDialogImpl(
|
||||
getContext(),
|
||||
mVolumeDialogController,
|
||||
@@ -121,8 +110,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
mVolumePanelFactory,
|
||||
mActivityStarter,
|
||||
mInteractionJankMonitor,
|
||||
mDeviceConfigProxy,
|
||||
mExecutor,
|
||||
mDumpManager
|
||||
);
|
||||
mDialog.init(0, null);
|
||||
@@ -141,9 +128,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1);
|
||||
|
||||
Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
|
||||
|
||||
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
|
||||
}
|
||||
|
||||
private State createShellState() {
|
||||
@@ -313,35 +297,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
AudioManager.RINGER_MODE_NORMAL, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ideally we would look at the ringer ImageView and check its assigned drawable id, but that
|
||||
* API does not exist. So we do the next best thing; we check the cached icon id.
|
||||
*/
|
||||
@Test
|
||||
public void notificationVolumeSeparated_theRingerIconChanges() {
|
||||
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
|
||||
|
||||
mExecutor.runAllReady(); // for the config change to take effect
|
||||
|
||||
// assert icon is new based on res id
|
||||
assertEquals(mDialog.mVolumeRingerIconDrawableId,
|
||||
R.drawable.ic_speaker_on);
|
||||
assertEquals(mDialog.mVolumeRingerMuteIconDrawableId,
|
||||
R.drawable.ic_speaker_mute);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notificationVolumeNotSeparated_theRingerIconRemainsTheSame() {
|
||||
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
|
||||
|
||||
mExecutor.runAllReady();
|
||||
|
||||
assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_volume_ringer);
|
||||
assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute);
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
public void testContentDescriptions() {
|
||||
|
||||
@@ -97,6 +97,10 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager>
|
||||
public void removeAllIconsForSlot(String slot) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllIconsForExternalSlot(String slot) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIconAccessibilityLiveRegion(String slot, int mode) {
|
||||
}
|
||||
|
||||
@@ -8197,15 +8197,13 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
t.traceEnd();
|
||||
}
|
||||
|
||||
boolean isBootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
|
||||
|
||||
// Some systems - like automotive - will explicitly unlock system user then switch
|
||||
// to a secondary user. Hence, we don't want to send duplicate broadcasts for
|
||||
// the system user here.
|
||||
// to a secondary user.
|
||||
// TODO(b/242195409): this workaround shouldn't be necessary once we move
|
||||
// the headless-user start logic to UserManager-land.
|
||||
final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM)
|
||||
&& !UserManager.isHeadlessSystemUserMode();
|
||||
|
||||
if (isBootingSystemUser) {
|
||||
if (isBootingSystemUser && !UserManager.isHeadlessSystemUserMode()) {
|
||||
t.traceBegin("startHomeOnAllDisplays");
|
||||
mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
|
||||
t.traceEnd();
|
||||
@@ -8217,6 +8215,10 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
|
||||
|
||||
if (isBootingSystemUser) {
|
||||
// Need to send the broadcasts for the system user here because
|
||||
// UserController#startUserInternal will not send them for the system user starting,
|
||||
// It checks if the user state already exists, which is always the case for the
|
||||
// system user.
|
||||
t.traceBegin("sendUserStartBroadcast");
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final int callingPid = Binder.getCallingPid();
|
||||
|
||||
@@ -379,16 +379,11 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
|
||||
resolvedType = key.requestResolvedType;
|
||||
}
|
||||
|
||||
// Apply any launch flags from the ActivityOptions. This is used only by SystemUI
|
||||
// to ensure that we can launch the pending intent with a consistent launch mode even
|
||||
// if the provided PendingIntent is immutable (ie. to force an activity to launch into
|
||||
// a new task, or to launch multiple instances if supported by the app)
|
||||
// Apply any launch flags from the ActivityOptions. This is to ensure that the caller
|
||||
// can specify a consistent launch mode even if the PendingIntent is immutable
|
||||
final ActivityOptions opts = ActivityOptions.fromBundle(options);
|
||||
if (opts != null) {
|
||||
// TODO(b/254490217): Move this check into SafeActivityOptions
|
||||
if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) {
|
||||
finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
|
||||
}
|
||||
finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
|
||||
}
|
||||
|
||||
// Extract options before clearing calling identity
|
||||
|
||||
@@ -41,7 +41,6 @@ import android.annotation.SuppressLint;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.AppOpsManager;
|
||||
@@ -156,7 +155,6 @@ import android.os.VibrationAttributes;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.os.VibratorManager;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.System;
|
||||
import android.service.notification.ZenModeConfig;
|
||||
@@ -178,7 +176,6 @@ import android.widget.Toast;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.server.EventLogTags;
|
||||
@@ -237,7 +234,6 @@ public class AudioService extends IAudioService.Stub
|
||||
AudioSystemAdapter.OnVolRangeInitRequestListener {
|
||||
|
||||
private static final String TAG = "AS.AudioService";
|
||||
private static final boolean CONFIG_DEFAULT_VAL = false;
|
||||
|
||||
private final AudioSystemAdapter mAudioSystem;
|
||||
private final SystemServerAdapter mSystemServer;
|
||||
@@ -992,7 +988,6 @@ public class AudioService extends IAudioService.Stub
|
||||
* @param looper Looper to use for the service's message handler. If this is null, an
|
||||
* {@link AudioSystemThread} is created as the messaging thread instead.
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
|
||||
public AudioService(Context context, AudioSystemAdapter audioSystem,
|
||||
SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper,
|
||||
AppOpsManager appOps) {
|
||||
@@ -1032,12 +1027,8 @@ public class AudioService extends IAudioService.Stub
|
||||
mUseVolumeGroupAliases = mContext.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups);
|
||||
|
||||
mNotifAliasRing = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
|
||||
|
||||
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
ActivityThread.currentApplication().getMainExecutor(),
|
||||
this::onDeviceConfigChange);
|
||||
mNotifAliasRing = mContext.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_alias_ring_notif_stream_types);
|
||||
|
||||
// Initialize volume
|
||||
// Priority 1 - Android Property
|
||||
@@ -1255,22 +1246,6 @@ public class AudioService extends IAudioService.Stub
|
||||
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Separating notification volume from ring is NOT of aliasing the corresponding streams
|
||||
* @param properties
|
||||
*/
|
||||
private void onDeviceConfigChange(DeviceConfig.Properties properties) {
|
||||
Set<String> changeSet = properties.getKeyset();
|
||||
if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
|
||||
boolean newNotifAliasRing = !properties.getBoolean(
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
|
||||
if (mNotifAliasRing != newNotifAliasRing) {
|
||||
mNotifAliasRing = newNotifAliasRing;
|
||||
updateStreamVolumeAlias(true, TAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by handling of MSG_INIT_STREAMS_VOLUMES
|
||||
*/
|
||||
|
||||
@@ -817,9 +817,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
|
||||
mDisplayDeviceConfig = config;
|
||||
loadFromDisplayDeviceConfig(token, info);
|
||||
|
||||
// Since the underlying display-device changed, we really don't know the
|
||||
// last command that was sent to change it's state. Lets assume it is off and we
|
||||
// trigger a change immediately.
|
||||
/// Since the underlying display-device changed, we really don't know the
|
||||
// last command that was sent to change it's state. Lets assume it is unknown so
|
||||
// that we trigger a change immediately.
|
||||
mPowerState.resetScreenState();
|
||||
}
|
||||
if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
|
||||
|
||||
@@ -340,20 +340,12 @@ final class DisplayPowerState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the screen state to {@link Display#STATE_OFF}. Even though we do not know the last
|
||||
* state that was sent to the underlying display-device, we assume it is off.
|
||||
*
|
||||
* We do not set the screen state to {@link Display#STATE_UNKNOWN} to avoid getting in the state
|
||||
* where PhotonicModulator holds onto the lock. This happens because we currently try to keep
|
||||
* the mScreenState and mPendingState in sync, however if the screenState is set to
|
||||
* {@link Display#STATE_UNKNOWN} here, mPendingState will get progressed to this, which will
|
||||
* force the PhotonicModulator thread to wait onto the lock to take it out of that state.
|
||||
* b/262294651 for more info.
|
||||
* Resets the screen state to unknown. Useful when the underlying display-device changes for the
|
||||
* LogicalDisplay and we do not know the last state that was sent to it.
|
||||
*/
|
||||
void resetScreenState() {
|
||||
mScreenState = Display.STATE_OFF;
|
||||
mScreenState = Display.STATE_UNKNOWN;
|
||||
mScreenReady = false;
|
||||
scheduleScreenUpdate();
|
||||
}
|
||||
|
||||
private void scheduleScreenUpdate() {
|
||||
@@ -514,6 +506,8 @@ final class DisplayPowerState {
|
||||
boolean valid = state != Display.STATE_UNKNOWN && !Float.isNaN(brightnessState);
|
||||
boolean changed = stateChanged || backlightChanged;
|
||||
if (!valid || !changed) {
|
||||
mStateChangeInProgress = false;
|
||||
mBacklightChangeInProgress = false;
|
||||
try {
|
||||
mLock.wait();
|
||||
} catch (InterruptedException ex) {
|
||||
|
||||
@@ -95,6 +95,7 @@ import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.EventLog;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.IntArray;
|
||||
import android.util.Slog;
|
||||
@@ -4973,6 +4974,13 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
public void setApplicationRestrictions(String packageName, Bundle restrictions,
|
||||
@UserIdInt int userId) {
|
||||
checkSystemOrRoot("set application restrictions");
|
||||
String validationResult = validateName(packageName);
|
||||
if (validationResult != null) {
|
||||
if (packageName.contains("../")) {
|
||||
EventLog.writeEvent(0x534e4554, "239701237", -1, "");
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid package name: " + validationResult);
|
||||
}
|
||||
if (restrictions != null) {
|
||||
restrictions.setDefusable(true);
|
||||
}
|
||||
@@ -4999,6 +5007,39 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given name is valid.
|
||||
*
|
||||
* Note: the logic is taken from FrameworkParsingPackageUtils in master, edited to remove
|
||||
* unnecessary parts. Copied here for a security fix.
|
||||
*
|
||||
* @param name The name to check.
|
||||
* @return null if it's valid, error message if not
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String validateName(String name) {
|
||||
final int n = name.length();
|
||||
boolean front = true;
|
||||
for (int i = 0; i < n; i++) {
|
||||
final char c = name.charAt(i);
|
||||
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
|
||||
front = false;
|
||||
continue;
|
||||
}
|
||||
if (!front) {
|
||||
if ((c >= '0' && c <= '9') || c == '_') {
|
||||
continue;
|
||||
}
|
||||
if (c == '.') {
|
||||
front = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return "bad character '" + c + "'";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getUidForPackage(String packageName) {
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user