Merge "Fix media control flash when unlock device" into tm-qpr-dev
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 14fe522..3e6283e 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1132,12 +1132,26 @@
* @return the dimensions of system wallpaper
* @hide
*/
+ @Nullable
public Rect peekBitmapDimensions() {
return sGlobals.peekWallpaperDimensions(
mContext, true /* returnDefault */, mContext.getUserId());
}
/**
+ * Peek the dimensions of given wallpaper of the user without decoding it.
+ *
+ * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or
+ * {@link #FLAG_LOCK}.
+ * @return the dimensions of system wallpaper
+ * @hide
+ */
+ @Nullable
+ public Rect peekBitmapDimensions(@SetWallpaperFlags int which) {
+ return peekBitmapDimensions();
+ }
+
+ /**
* Get an open, readable file descriptor to the given wallpaper image file.
* The caller is responsible for closing the file descriptor when done ingesting the file.
*
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml
new file mode 100644
index 0000000..e850d68
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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="?attr/colorControlNormal">
+ <path
+ android:fillColor="#1f1f1f"
+ android:pathData="M8,22V11L6,8V2H18V8L16,11V22ZM12,15.5Q11.375,15.5 10.938,15.062Q10.5,14.625 10.5,14Q10.5,13.375 10.938,12.938Q11.375,12.5 12,12.5Q12.625,12.5 13.062,12.938Q13.5,13.375 13.5,14Q13.5,14.625 13.062,15.062Q12.625,15.5 12,15.5ZM8,5H16V4H8ZM16,7H8V7.4L10,10.4V20H14V10.4L16,7.4ZM12,12Z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml
new file mode 100644
index 0000000..91b9ae5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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="?attr/colorControlNormal">
+ <path
+ android:fillColor="#1f1f1f"
+ android:pathData="M6,5V2H18V5ZM12,15.5Q12.625,15.5 13.062,15.062Q13.5,14.625 13.5,14Q13.5,13.375 13.062,12.938Q12.625,12.5 12,12.5Q11.375,12.5 10.938,12.938Q10.5,13.375 10.5,14Q10.5,14.625 10.938,15.062Q11.375,15.5 12,15.5ZM8,22V11L6,8V7H18V8L16,11V22Z"/>
+</vector>
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index 4dfcd63..66e5d7c4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -30,6 +30,7 @@
import android.service.controls.ControlsProviderService
import androidx.annotation.WorkerThread
import com.android.settingslib.applications.DefaultAppInfo
+import com.android.systemui.R
import java.util.Objects
class ControlsServiceInfo(
@@ -59,7 +60,8 @@
* instead of using the controls rendered by SystemUI.
*
* The activity must be in the same package, exported, enabled and protected by the
- * [Manifest.permission.BIND_CONTROLS] permission.
+ * [Manifest.permission.BIND_CONTROLS] permission. Additionally, only packages declared in
+ * [R.array.config_controlsPreferredPackages] can declare activities for use as a panel.
*/
var panelActivity: ComponentName? = null
private set
@@ -70,6 +72,9 @@
fun resolvePanelActivity() {
if (resolved) return
resolved = true
+ val validPackages = context.resources
+ .getStringArray(R.array.config_controlsPreferredPackages)
+ if (componentName.packageName !in validPackages) return
panelActivity = _panelActivity?.let {
val resolveInfos = mPm.queryIntentActivitiesAsUser(
Intent().setComponent(it),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index f5220b8..73dbeab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -25,6 +25,7 @@
object BuiltInKeyguardQuickAffordanceKeys {
// Please keep alphabetical order of const names to simplify future maintenance.
const val CAMERA = "camera"
+ const val FLASHLIGHT = "flashlight"
const val HOME_CONTROLS = "home"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
new file mode 100644
index 0000000..49527d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.statusbar.policy.FlashlightController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+@SysUISingleton
+class FlashlightQuickAffordanceConfig @Inject constructor(
+ @Application private val context: Context,
+ private val flashlightController: FlashlightController,
+) : KeyguardQuickAffordanceConfig {
+
+ private sealed class FlashlightState {
+
+ abstract fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState
+
+ object On: FlashlightState() {
+ override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.ic_flashlight_on,
+ ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+ ),
+ ActivationState.Active
+ )
+ }
+
+ object OffAvailable: FlashlightState() {
+ override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.ic_flashlight_off,
+ ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+ ),
+ ActivationState.Inactive
+ )
+ }
+
+ object Unavailable: FlashlightState() {
+ override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ }
+
+ override val key: String
+ get() = BuiltInKeyguardQuickAffordanceKeys.FLASHLIGHT
+
+ override val pickerName: String
+ get() = context.getString(R.string.quick_settings_flashlight_label)
+
+ override val pickerIconResourceId: Int
+ get() = if (flashlightController.isEnabled) {
+ R.drawable.ic_flashlight_on
+ } else {
+ R.drawable.ic_flashlight_off
+ }
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ conflatedCallbackFlow {
+ val flashlightCallback = object : FlashlightController.FlashlightListener {
+ override fun onFlashlightChanged(enabled: Boolean) {
+ trySendWithFailureLogging(
+ if (enabled) {
+ FlashlightState.On.toLockScreenState()
+ } else {
+ FlashlightState.OffAvailable.toLockScreenState()
+ },
+ TAG
+ )
+ }
+
+ override fun onFlashlightError() {
+ trySendWithFailureLogging(FlashlightState.OffAvailable.toLockScreenState(), TAG)
+ }
+
+ override fun onFlashlightAvailabilityChanged(available: Boolean) {
+ trySendWithFailureLogging(
+ if (!available) {
+ FlashlightState.Unavailable.toLockScreenState()
+ } else {
+ if (flashlightController.isEnabled) {
+ FlashlightState.On.toLockScreenState()
+ } else {
+ FlashlightState.OffAvailable.toLockScreenState()
+ }
+ },
+ TAG
+ )
+ }
+ }
+
+ flashlightController.addCallback(flashlightCallback)
+
+ awaitClose {
+ flashlightController.removeCallback(flashlightCallback)
+ }
+ }
+
+ override fun onTriggered(expandable: Expandable?):
+ KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ flashlightController
+ .setFlashlight(flashlightController.isAvailable && !flashlightController.isEnabled)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
+ if (flashlightController.isAvailable) {
+ KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+
+ companion object {
+ private const val TAG = "FlashlightQuickAffordanceConfig"
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index f7225a2..3013227c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -26,6 +26,7 @@
@Provides
@ElementsIntoSet
fun quickAffordanceConfigs(
+ flashlight: FlashlightQuickAffordanceConfig,
home: HomeControlsKeyguardQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
@@ -33,6 +34,7 @@
): Set<KeyguardQuickAffordanceConfig> {
return setOf(
camera,
+ flashlight,
home,
quickAccessWallet,
qrCodeScanner,
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index f9e341c..d6e29e0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -16,9 +16,9 @@
package com.android.systemui.log
-import android.app.ActivityManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogcatEchoTracker
@@ -29,15 +29,6 @@
private val dumpManager: DumpManager,
private val logcatEchoTracker: LogcatEchoTracker
) {
- /* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */
- private fun adjustMaxSize(requestedMaxSize: Int): Int {
- return if (ActivityManager.isLowRamDeviceStatic()) {
- minOf(requestedMaxSize, 20) /* low ram max log size*/
- } else {
- requestedMaxSize
- }
- }
-
@JvmOverloads
fun create(
name: String,
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt
new file mode 100644
index 0000000..619eac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.log
+
+import android.app.ActivityManager
+
+class LogBufferHelper {
+ companion object {
+ /** If necessary, returns a limited maximum size for low ram (Go) devices */
+ fun adjustMaxSize(requestedMaxSize: Int): Int {
+ return if (ActivityManager.isLowRamDeviceStatic()) {
+ minOf(requestedMaxSize, 20) /* low ram max log size*/
+ } else {
+ requestedMaxSize
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
new file mode 100644
index 0000000..c27bfa3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.log.table
+
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * An interface that enables logging the difference between values in table format.
+ *
+ * Many objects that we want to log are data-y objects with a collection of fields. When logging
+ * these objects, we want to log each field separately. This allows ABT (Android Bug Tool) to easily
+ * highlight changes in individual fields.
+ *
+ * See [TableLogBuffer].
+ */
+interface Diffable<T> {
+ /**
+ * Finds the differences between [prevVal] and [this] and logs those diffs to [row].
+ *
+ * Each implementer should determine which individual fields have changed between [prevVal] and
+ * [this], and only log the fields that have actually changed. This helps save buffer space.
+ *
+ * For example, if:
+ * - prevVal = Object(val1=100, val2=200, val3=300)
+ * - this = Object(val1=100, val2=200, val3=333)
+ *
+ * Then only the val3 change should be logged.
+ */
+ fun logDiffs(prevVal: T, row: TableRowLogger)
+}
+
+/**
+ * Each time the flow is updated with a new value, logs the differences between the previous value
+ * and the new value to the given [tableLogBuffer].
+ *
+ * The new value's [Diffable.logDiffs] method will be used to log the differences to the table.
+ *
+ * @param columnPrefix a prefix that will be applied to every column name that gets logged.
+ */
+fun <T : Diffable<T>> Flow<T>.logDiffsForTable(
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String,
+ initialValue: T,
+): Flow<T> {
+ return this.pairwiseBy(initialValue) { prevVal, newVal ->
+ tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal)
+ newVal
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
new file mode 100644
index 0000000..68c297f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.log.table
+
+/**
+ * A object used with [TableLogBuffer] to store changes in variables over time. Is recyclable.
+ *
+ * Each message represents a change to exactly 1 type, specified by [DataType].
+ */
+data class TableChange(
+ var timestamp: Long = 0,
+ var columnPrefix: String = "",
+ var columnName: String = "",
+ var type: DataType = DataType.EMPTY,
+ var bool: Boolean = false,
+ var int: Int = 0,
+ var str: String? = null,
+) {
+ /** Resets to default values so that the object can be recycled. */
+ fun reset(timestamp: Long, columnPrefix: String, columnName: String) {
+ this.timestamp = timestamp
+ this.columnPrefix = columnPrefix
+ this.columnName = columnName
+ this.type = DataType.EMPTY
+ this.bool = false
+ this.int = 0
+ this.str = null
+ }
+
+ /** Sets this to store a string change. */
+ fun set(value: String?) {
+ type = DataType.STRING
+ str = value
+ }
+
+ /** Sets this to store a boolean change. */
+ fun set(value: Boolean) {
+ type = DataType.BOOLEAN
+ bool = value
+ }
+
+ /** Sets this to store an int change. */
+ fun set(value: Int) {
+ type = DataType.INT
+ int = value
+ }
+
+ /** Returns true if this object has a change. */
+ fun hasData(): Boolean {
+ return columnName.isNotBlank() && type != DataType.EMPTY
+ }
+
+ fun getName(): String {
+ return if (columnPrefix.isNotBlank()) {
+ "$columnPrefix.$columnName"
+ } else {
+ columnName
+ }
+ }
+
+ fun getVal(): String {
+ return when (type) {
+ DataType.EMPTY -> null
+ DataType.STRING -> str
+ DataType.INT -> int
+ DataType.BOOLEAN -> bool
+ }.toString()
+ }
+
+ enum class DataType {
+ STRING,
+ BOOLEAN,
+ INT,
+ EMPTY,
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
new file mode 100644
index 0000000..429637a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -0,0 +1,264 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.log.table
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.DumpsysTableLogger
+import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import java.text.SimpleDateFormat
+import java.util.Locale
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * A logger that logs changes in table format.
+ *
+ * Some parts of System UI maintain a lot of pieces of state at once.
+ * [com.android.systemui.plugins.log.LogBuffer] allows us to easily log change events:
+ *
+ * - 10-10 10:10:10.456: state2 updated to newVal2
+ * - 10-10 10:11:00.000: stateN updated to StateN(val1=true, val2=1)
+ * - 10-10 10:11:02.123: stateN updated to StateN(val1=true, val2=2)
+ * - 10-10 10:11:05.123: state1 updated to newVal1
+ * - 10-10 10:11:06.000: stateN updated to StateN(val1=false, val2=3)
+ *
+ * However, it can sometimes be more useful to view the state changes in table format:
+ *
+ * - timestamp--------- | state1- | state2- | ... | stateN.val1 | stateN.val2
+ * - -------------------------------------------------------------------------
+ * - 10-10 10:10:10.123 | val1--- | val2--- | ... | false------ | 0-----------
+ * - 10-10 10:10:10.456 | val1--- | newVal2 | ... | false------ | 0-----------
+ * - 10-10 10:11:00.000 | val1--- | newVal2 | ... | true------- | 1-----------
+ * - 10-10 10:11:02.123 | val1--- | newVal2 | ... | true------- | 2-----------
+ * - 10-10 10:11:05.123 | newVal1 | newVal2 | ... | true------- | 2-----------
+ * - 10-10 10:11:06.000 | newVal1 | newVal2 | ... | false------ | 3-----------
+ *
+ * This class enables easy logging of the state changes in both change event format and table
+ * format.
+ *
+ * This class also enables easy logging of states that are a collection of fields. For example,
+ * stateN in the above example consists of two fields -- val1 and val2. It's useful to put each
+ * field into its own column so that ABT (Android Bug Tool) can easily highlight changes to
+ * individual fields.
+ *
+ * How it works:
+ *
+ * 1) Create an instance of this buffer via [TableLogBufferFactory].
+ *
+ * 2) For any states being logged, implement [Diffable]. Implementing [Diffable] allows the state to
+ * only log the fields that have *changed* since the previous update, instead of always logging all
+ * fields.
+ *
+ * 3) Each time a change in a state happens, call [logDiffs]. If your state is emitted using a
+ * [Flow], you should use the [logDiffsForTable] extension function to automatically log diffs any
+ * time your flow emits a new value.
+ *
+ * When a dump occurs, there will be two dumps:
+ *
+ * 1) The change events under the dumpable name "$name-changes".
+ *
+ * 2) This class will coalesce all the diffs into a table format and log them under the dumpable
+ * name "$name-table".
+ *
+ * @param maxSize the maximum size of the buffer. Must be > 0.
+ */
+class TableLogBuffer(
+ maxSize: Int,
+ private val name: String,
+ private val systemClock: SystemClock,
+) {
+ init {
+ if (maxSize <= 0) {
+ throw IllegalArgumentException("maxSize must be > 0")
+ }
+ }
+
+ private val buffer = RingBuffer(maxSize) { TableChange() }
+
+ // A [TableRowLogger] object, re-used each time [logDiffs] is called.
+ // (Re-used to avoid object allocation.)
+ private val tempRow = TableRowLoggerImpl(0, columnPrefix = "", this)
+
+ /**
+ * Log the differences between [prevVal] and [newVal].
+ *
+ * The [newVal] object's method [Diffable.logDiffs] will be used to fetch the diffs.
+ *
+ * @param columnPrefix a prefix that will be applied to every column name that gets logged. This
+ * ensures that all the columns related to the same state object will be grouped together in the
+ * table.
+ */
+ @Synchronized
+ fun <T : Diffable<T>> logDiffs(columnPrefix: String, prevVal: T, newVal: T) {
+ val row = tempRow
+ row.timestamp = systemClock.currentTimeMillis()
+ row.columnPrefix = columnPrefix
+ newVal.logDiffs(prevVal, row)
+ }
+
+ // Keep these individual [logChange] methods private (don't let clients give us their own
+ // timestamps.)
+
+ private fun logChange(timestamp: Long, prefix: String, columnName: String, value: String?) {
+ val change = obtain(timestamp, prefix, columnName)
+ change.set(value)
+ }
+
+ private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Boolean) {
+ val change = obtain(timestamp, prefix, columnName)
+ change.set(value)
+ }
+
+ private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int) {
+ val change = obtain(timestamp, prefix, columnName)
+ change.set(value)
+ }
+
+ // TODO(b/259454430): Add additional change types here.
+
+ @Synchronized
+ private fun obtain(timestamp: Long, prefix: String, columnName: String): TableChange {
+ val tableChange = buffer.advance()
+ tableChange.reset(timestamp, prefix, columnName)
+ return tableChange
+ }
+
+ /**
+ * Registers this buffer as dumpables in [dumpManager]. Must be called for the table to be
+ * dumped.
+ *
+ * This will be automatically called in [TableLogBufferFactory.create].
+ */
+ fun registerDumpables(dumpManager: DumpManager) {
+ dumpManager.registerNormalDumpable("$name-changes", changeDumpable)
+ dumpManager.registerNormalDumpable("$name-table", tableDumpable)
+ }
+
+ private val changeDumpable = Dumpable { pw, _ -> dumpChanges(pw) }
+ private val tableDumpable = Dumpable { pw, _ -> dumpTable(pw) }
+
+ /** Dumps the list of [TableChange] objects. */
+ @Synchronized
+ @VisibleForTesting
+ fun dumpChanges(pw: PrintWriter) {
+ for (i in 0 until buffer.size) {
+ buffer[i].dump(pw)
+ }
+ }
+
+ /** Dumps an individual [TableChange]. */
+ private fun TableChange.dump(pw: PrintWriter) {
+ if (!this.hasData()) {
+ return
+ }
+ val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(timestamp)
+ pw.print(formattedTimestamp)
+ pw.print(" ")
+ pw.print(this.getName())
+ pw.print("=")
+ pw.print(this.getVal())
+ pw.println()
+ }
+
+ /**
+ * Coalesces all the [TableChange] objects into a table of values of time and dumps the table.
+ */
+ // TODO(b/259454430): Since this is an expensive process, it could cause the bug report dump to
+ // fail and/or not dump anything else. We should move this processing to ABT (Android Bug
+ // Tool), where we have unlimited time to process.
+ @Synchronized
+ @VisibleForTesting
+ fun dumpTable(pw: PrintWriter) {
+ val messages = buffer.iterator().asSequence().toList()
+
+ if (messages.isEmpty()) {
+ return
+ }
+
+ // Step 1: Create list of column headers
+ val headerSet = mutableSetOf<String>()
+ messages.forEach { headerSet.add(it.getName()) }
+ val headers: MutableList<String> = headerSet.toList().sorted().toMutableList()
+ headers.add(0, "timestamp")
+
+ // Step 2: Create a list with the current values for each column. Will be updated with each
+ // change.
+ val currentRow: MutableList<String> = MutableList(headers.size) { DEFAULT_COLUMN_VALUE }
+
+ // Step 3: For each message, make the correct update to [currentRow] and save it to [rows].
+ val columnIndices: Map<String, Int> =
+ headers.mapIndexed { index, headerName -> headerName to index }.toMap()
+ val allRows = mutableListOf<List<String>>()
+
+ messages.forEach {
+ if (!it.hasData()) {
+ return@forEach
+ }
+
+ val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(it.timestamp)
+ if (formattedTimestamp != currentRow[0]) {
+ // The timestamp has updated, so save the previous row and continue to the next row
+ allRows.add(currentRow.toList())
+ currentRow[0] = formattedTimestamp
+ }
+ val columnIndex = columnIndices[it.getName()]!!
+ currentRow[columnIndex] = it.getVal()
+ }
+ // Add the last row
+ allRows.add(currentRow.toList())
+
+ // Step 4: Dump the rows
+ DumpsysTableLogger(
+ name,
+ headers,
+ allRows,
+ )
+ .printTableData(pw)
+ }
+
+ /**
+ * A private implementation of [TableRowLogger].
+ *
+ * Used so that external clients can't modify [timestamp].
+ */
+ private class TableRowLoggerImpl(
+ var timestamp: Long,
+ var columnPrefix: String,
+ val tableLogBuffer: TableLogBuffer,
+ ) : TableRowLogger {
+ /** Logs a change to a string value. */
+ override fun logChange(columnName: String, value: String?) {
+ tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+ }
+
+ /** Logs a change to a boolean value. */
+ override fun logChange(columnName: String, value: Boolean) {
+ tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+ }
+
+ /** Logs a change to an int value. */
+ override fun logChange(columnName: String, value: Int) {
+ tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+ }
+ }
+}
+
+val TABLE_LOG_DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+private const val DEFAULT_COLUMN_VALUE = "UNKNOWN"
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
new file mode 100644
index 0000000..f1f906f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.log.table
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+
+@SysUISingleton
+class TableLogBufferFactory
+@Inject
+constructor(
+ private val dumpManager: DumpManager,
+ private val systemClock: SystemClock,
+) {
+ fun create(
+ name: String,
+ maxSize: Int,
+ ): TableLogBuffer {
+ val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock)
+ tableBuffer.registerDumpables(dumpManager)
+ return tableBuffer
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt
new file mode 100644
index 0000000..a7ba13b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.log.table
+
+/**
+ * A class that logs a row to [TableLogBuffer].
+ *
+ * Objects that implement [Diffable] will receive an instance of this class, and can log any changes
+ * to individual fields using the [logChange] methods. All logged changes will be associated with
+ * the same timestamp.
+ */
+interface TableRowLogger {
+ /** Logs a change to a string value. */
+ fun logChange(columnName: String, value: String?)
+
+ /** Logs a change to a boolean value. */
+ fun logChange(columnName: String, value: Boolean)
+
+ /** Logs a change to an int value. */
+ fun logChange(columnName: String, value: Int)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 31e4464..5e47d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -19,6 +19,7 @@
import android.annotation.IdRes
import android.app.StatusBarManager
import android.content.res.Configuration
+import android.os.Bundle
import android.os.Trace
import android.os.Trace.TRACE_TAG_APP
import android.util.Pair
@@ -34,6 +35,8 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -53,6 +56,7 @@
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
+import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.VariableDateView
import com.android.systemui.statusbar.policy.VariableDateViewController
@@ -89,7 +93,8 @@
private val dumpManager: DumpManager,
private val featureFlags: FeatureFlags,
private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
- private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager
+ private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
+ private val demoModeController: DemoModeController
) : ViewController<View>(header), Dumpable {
companion object {
@@ -126,7 +131,7 @@
private lateinit var qsCarrierGroupController: QSCarrierGroupController
private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
- private val clock: TextView = header.findViewById(R.id.clock)
+ private val clock: Clock = header.findViewById(R.id.clock)
private val date: TextView = header.findViewById(R.id.date)
private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
@@ -212,6 +217,14 @@
view.onApplyWindowInsets(insets)
}
+ private val demoModeReceiver = object : DemoMode {
+ override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
+ override fun dispatchDemoCommand(command: String, args: Bundle) =
+ clock.dispatchDemoCommand(command, args)
+ override fun onDemoModeStarted() = clock.onDemoModeStarted()
+ override fun onDemoModeFinished() = clock.onDemoModeFinished()
+ }
+
private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
override fun onChipVisibilityRefreshed(visible: Boolean) {
if (header is MotionLayout) {
@@ -300,6 +313,7 @@
dumpManager.registerDumpable(this)
configurationController.addCallback(configurationControllerListener)
+ demoModeController.addCallback(demoModeReceiver)
updateVisibility()
updateTransition()
@@ -309,6 +323,7 @@
privacyIconsController.chipVisibilityListener = null
dumpManager.unregisterDumpable(this::class.java.simpleName)
configurationController.removeCallback(configurationControllerListener)
+ demoModeController.removeCallback(demoModeReceiver)
}
fun disable(state1: Int, state2: Int, animate: Boolean) {
@@ -521,4 +536,7 @@
updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges)
}
}
+
+ @VisibleForTesting
+ internal fun simulateViewDetached() = this.onViewDetached()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e68182e..629d3d4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2072,6 +2072,13 @@
mInitialTouchX = x;
initVelocityTracker();
trackMovement(event);
+ float qsExpansionFraction = computeQsExpansionFraction();
+ // Intercept the touch if QS is between fully collapsed and fully expanded state
+ if (qsExpansionFraction > 0.0 && qsExpansionFraction < 1.0) {
+ mShadeLog.logMotionEvent(event,
+ "onQsIntercept: down action, QS partially expanded/collapsed");
+ return true;
+ }
if (mKeyguardShowing
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
// Dragging down on the lockscreen statusbar should prohibit other interactions
@@ -2324,6 +2331,13 @@
if (!isFullyCollapsed()) {
handleQsDown(event);
}
+ // defer touches on QQS to shade while shade is collapsing. Added margin for error
+ // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS.
+ if (computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) {
+ mShadeLog.logMotionEvent(event,
+ "handleQsTouch: QQS touched while shade collapsing");
+ mQsTracking = false;
+ }
if (!mQsExpandImmediate && mQsTracking) {
onQsTouch(event);
if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) {
@@ -2564,7 +2578,6 @@
// Reset scroll position and apply that position to the expanded height.
float height = mQsExpansionHeight;
setQsExpansionHeight(height);
- updateExpandedHeightToMaxHeight();
mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
// When expanding QS, let's authenticate the user if possible,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index fcd1b8a..0662fb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.pipeline.dagger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
@@ -32,6 +35,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module
abstract class StatusBarPipelineModule {
@@ -57,4 +61,15 @@
@Binds
abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
+
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ @WifiTableLog
+ fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("WifiTableLog", 100)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
new file mode 100644
index 0000000..ac395a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** Wifi logs in table format. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class WifiTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
index 062c3d1..8436b13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -17,12 +17,30 @@
package com.android.systemui.statusbar.pipeline.wifi.data.model
import androidx.annotation.VisibleForTesting
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.log.table.Diffable
/** Provides information about the current wifi network. */
-sealed class WifiNetworkModel {
+sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
+
/** A model representing that we have no active wifi network. */
object Inactive : WifiNetworkModel() {
override fun toString() = "WifiNetwork.Inactive"
+
+ override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+ if (prevVal is Inactive) {
+ return
+ }
+ row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE)
+
+ if (prevVal is CarrierMerged) {
+ // The only difference between CarrierMerged and Inactive is the type
+ return
+ }
+
+ // When changing from Active to Inactive, we need to log diffs to all the fields.
+ logDiffsFromActiveToNotActive(prevVal as Active, row)
+ }
}
/**
@@ -33,6 +51,21 @@
*/
object CarrierMerged : WifiNetworkModel() {
override fun toString() = "WifiNetwork.CarrierMerged"
+
+ override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+ if (prevVal is CarrierMerged) {
+ return
+ }
+ row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)
+
+ if (prevVal is Inactive) {
+ // The only difference between CarrierMerged and Inactive is the type.
+ return
+ }
+
+ // When changing from Active to CarrierMerged, we need to log diffs to all the fields.
+ logDiffsFromActiveToNotActive(prevVal as Active, row)
+ }
}
/** Provides information about an active wifi network. */
@@ -76,6 +109,41 @@
}
}
+ override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+ if (prevVal !is Active) {
+ row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE)
+ }
+
+ if (prevVal !is Active || prevVal.networkId != networkId) {
+ row.logChange(COL_NETWORK_ID, networkId)
+ }
+ if (prevVal !is Active || prevVal.isValidated != isValidated) {
+ row.logChange(COL_VALIDATED, isValidated)
+ }
+ if (prevVal !is Active || prevVal.level != level) {
+ row.logChange(COL_LEVEL, level ?: LEVEL_DEFAULT)
+ }
+ if (prevVal !is Active || prevVal.ssid != ssid) {
+ row.logChange(COL_SSID, ssid)
+ }
+
+ // TODO(b/238425913): The passpoint-related values are frequently never used, so it
+ // would be great to not log them when they're not used.
+ if (prevVal !is Active || prevVal.isPasspointAccessPoint != isPasspointAccessPoint) {
+ row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
+ }
+ if (prevVal !is Active ||
+ prevVal.isOnlineSignUpForPasspointAccessPoint !=
+ isOnlineSignUpForPasspointAccessPoint) {
+ row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
+ }
+ if (prevVal !is Active ||
+ prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) {
+ row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
+ }
+ }
+
+
override fun toString(): String {
// Only include the passpoint-related values in the string if we have them. (Most
// networks won't have them so they'll be mostly clutter.)
@@ -101,4 +169,37 @@
internal const val MAX_VALID_LEVEL = 4
}
}
+
+ internal fun logDiffsFromActiveToNotActive(prevActive: Active, row: TableRowLogger) {
+ row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
+ row.logChange(COL_VALIDATED, false)
+ row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+ row.logChange(COL_SSID, null)
+
+ if (prevActive.isPasspointAccessPoint) {
+ row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
+ }
+ if (prevActive.isOnlineSignUpForPasspointAccessPoint) {
+ row.logChange(COL_ONLINE_SIGN_UP, false)
+ }
+ if (prevActive.passpointProviderFriendlyName != null) {
+ row.logChange(COL_PASSPOINT_NAME, null)
+ }
+ }
}
+
+const val TYPE_CARRIER_MERGED = "CarrierMerged"
+const val TYPE_INACTIVE = "Inactive"
+const val TYPE_ACTIVE = "Active"
+
+const val COL_NETWORK_TYPE = "type"
+const val COL_NETWORK_ID = "networkId"
+const val COL_VALIDATED = "isValidated"
+const val COL_LEVEL = "level"
+const val COL_SSID = "ssid"
+const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint"
+const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint"
+const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName"
+
+const val LEVEL_DEFAULT = -1
+const val NETWORK_ID_DEFAULT = -1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 93448c1d..a663536 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -36,6 +36,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
@@ -82,6 +85,7 @@
broadcastDispatcher: BroadcastDispatcher,
connectivityManager: ConnectivityManager,
logger: ConnectivityPipelineLogger,
+ @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
@Main mainExecutor: Executor,
@Application scope: CoroutineScope,
wifiManager: WifiManager?,
@@ -199,6 +203,12 @@
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "wifiNetwork",
+ initialValue = WIFI_NETWORK_DEFAULT,
+ )
// There will be multiple wifi icons in different places that will frequently
// subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
// new subscribes will get the latest value immediately upon subscription. Otherwise, the
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index 98ff8d1..c677f19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -31,6 +31,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.settingslib.applications.ServiceListing
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dump.DumpManager
@@ -110,6 +111,12 @@
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
mContext.setMockPackageManager(packageManager)
+ mContext.orCreateTestableResources
+ .addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(componentName.packageName)
+ )
+
// Return true by default, we'll test the false path
`when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true)
@@ -482,6 +489,35 @@
}
@Test
+ fun testPackageNotPreferred_nullPanel() {
+ mContext.orCreateTestableResources
+ .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
+
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
fun testListingsNotModifiedByCallback() {
// This test checks that if the list passed to the callback is modified, it has no effect
// in the resulting services
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..cda7018
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.statusbar.policy.FlashlightController
+import com.android.systemui.utils.leaks.FakeFlashlightController
+import com.android.systemui.utils.leaks.LeakCheckedTest
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
+
+ @Mock private lateinit var context: Context
+ private lateinit var flashlightController: FakeFlashlightController
+ private lateinit var underTest : FlashlightQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ injectLeakCheckedDependency(FlashlightController::class.java)
+ MockitoAnnotations.initMocks(this)
+
+ flashlightController = SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) as FakeFlashlightController
+ underTest = FlashlightQuickAffordanceConfig(context, flashlightController)
+ }
+
+ @Test
+ fun `flashlight is off -- triggered -- icon is on and active`() = runTest {
+ //given
+ flashlightController.isEnabled = false
+ flashlightController.isAvailable = true
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ underTest.onTriggered(null)
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertEquals(R.drawable.ic_flashlight_on,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight is on -- triggered -- icon is off and inactive`() = runTest {
+ //given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = true
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ underTest.onTriggered(null)
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertEquals(R.drawable.ic_flashlight_off,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight is on -- receives error -- icon is off and inactive`() = runTest {
+ //given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ flashlightController.onFlashlightError()
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertEquals(R.drawable.ic_flashlight_off,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight availability now off -- hidden`() = runTest {
+ //given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ flashlightController.onFlashlightAvailabilityChanged(false)
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight availability now on -- flashlight on -- inactive and icon off`() = runTest {
+ //given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ flashlightController.onFlashlightAvailabilityChanged(true)
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Active)
+ assertEquals(R.drawable.ic_flashlight_on, (lastValue.icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight availability now on -- flashlight off -- inactive and icon off`() = runTest {
+ //given
+ flashlightController.isEnabled = false
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ flashlightController.onFlashlightAvailabilityChanged(true)
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Inactive)
+ assertEquals(R.drawable.ic_flashlight_off, (lastValue.icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight available -- picker state default`() = runTest {
+ //given
+ flashlightController.isAvailable = true
+
+ //when
+ val result = underTest.getPickerScreenState()
+
+ //then
+ assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.Default)
+ }
+
+ @Test
+ fun `flashlight not available -- picker state unavailable`() = runTest {
+ //given
+ flashlightController.isAvailable = false
+
+ //when
+ val result = underTest.getPickerScreenState()
+
+ //then
+ assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
new file mode 100644
index 0000000..432764a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.log.table
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class TableChangeTest : SysuiTestCase() {
+
+ @Test
+ fun setString_isString() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set("fakeValue")
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("fakeValue")
+ }
+
+ @Test
+ fun setBoolean_isBoolean() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(true)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("true")
+ }
+
+ @Test
+ fun setInt_isInt() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(8900)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("8900")
+ }
+
+ @Test
+ fun setThenReset_isEmpty() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(8900)
+ underTest.reset(timestamp = 0, columnPrefix = "prefix", columnName = "name")
+
+ assertThat(underTest.hasData()).isFalse()
+ assertThat(underTest.getVal()).isEqualTo("null")
+ }
+
+ @Test
+ fun getName_hasPrefix() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+
+ assertThat(underTest.getName()).contains("fakePrefix")
+ assertThat(underTest.getName()).contains("fakeName")
+ }
+
+ @Test
+ fun getName_noPrefix() {
+ val underTest = TableChange(columnPrefix = "", columnName = "fakeName")
+
+ assertThat(underTest.getName()).contains("fakeName")
+ }
+
+ @Test
+ fun resetThenSet_hasNewValue() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "prefix", columnName = "original")
+ underTest.set("fakeValue")
+ underTest.reset(timestamp = 0, columnPrefix = "", columnName = "updated")
+ underTest.set(8900)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getName()).contains("updated")
+ assertThat(underTest.getName()).doesNotContain("prefix")
+ assertThat(underTest.getName()).doesNotContain("original")
+ assertThat(underTest.getVal()).isEqualTo("8900")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
new file mode 100644
index 0000000..688c66a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -0,0 +1,260 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.log.table
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class TableLogBufferTest : SysuiTestCase() {
+ private lateinit var underTest: TableLogBuffer
+
+ private lateinit var systemClock: FakeSystemClock
+ private lateinit var outputWriter: StringWriter
+
+ @Before
+ fun setup() {
+ systemClock = FakeSystemClock()
+ outputWriter = StringWriter()
+
+ underTest = TableLogBuffer(MAX_SIZE, NAME, systemClock)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun maxSizeZero_throwsException() {
+ TableLogBuffer(maxSize = 0, "name", systemClock)
+ }
+
+ @Test
+ fun dumpChanges_strChange_logsFromNext() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("stringValChange", "prevStringVal")
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("stringValChange", "newStringVal")
+ }
+ }
+
+ underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("prefix")
+ assertThat(dumpedString).contains("stringValChange")
+ assertThat(dumpedString).contains("newStringVal")
+ assertThat(dumpedString).doesNotContain("prevStringVal")
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+ }
+
+ @Test
+ fun dumpChanges_boolChange_logsFromNext() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", false)
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", true)
+ }
+ }
+
+ underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("prefix")
+ assertThat(dumpedString).contains("booleanValChange")
+ assertThat(dumpedString).contains("true")
+ assertThat(dumpedString).doesNotContain("false")
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+ }
+
+ @Test
+ fun dumpChanges_intChange_logsFromNext() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("intValChange", 12345)
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("intValChange", 67890)
+ }
+ }
+
+ underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("prefix")
+ assertThat(dumpedString).contains("intValChange")
+ assertThat(dumpedString).contains("67890")
+ assertThat(dumpedString).doesNotContain("12345")
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+ }
+
+ @Test
+ fun dumpChanges_noPrefix() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", false)
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", true)
+ }
+ }
+
+ // WHEN there's a blank prefix
+ underTest.logDiffs("", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ // THEN the dump still works
+ assertThat(dumpedString).contains("booleanValChange")
+ assertThat(dumpedString).contains("true")
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+ }
+
+ @Test
+ fun dumpChanges_multipleChangesForSameColumn_logs() {
+ lateinit var valToDump: String
+
+ val diffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("valChange", valToDump)
+ }
+ }
+
+ systemClock.setCurrentTimeMillis(12000L)
+ valToDump = "stateValue12"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ systemClock.setCurrentTimeMillis(20000L)
+ valToDump = "stateValue20"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ systemClock.setCurrentTimeMillis(40000L)
+ valToDump = "stateValue40"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ systemClock.setCurrentTimeMillis(45000L)
+ valToDump = "stateValue45"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("valChange")
+ assertThat(dumpedString).contains("stateValue12")
+ assertThat(dumpedString).contains("stateValue20")
+ assertThat(dumpedString).contains("stateValue40")
+ assertThat(dumpedString).contains("stateValue45")
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(12000L))
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(20000L))
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(40000L))
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(45000L))
+ }
+
+ @Test
+ fun dumpChanges_multipleChangesAtOnce_logs() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable = object : TestDiffable() {}
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("status", "in progress")
+ row.logChange("connected", false)
+ }
+ }
+
+ underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("status")
+ assertThat(dumpedString).contains("in progress")
+ assertThat(dumpedString).contains("connected")
+ assertThat(dumpedString).contains("false")
+ }
+
+ @Test
+ fun dumpChanges_rotatesIfBufferIsFull() {
+ lateinit var valToDump: String
+
+ val prevDiffable = object : TestDiffable() {}
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("status", valToDump)
+ }
+ }
+
+ for (i in 0 until MAX_SIZE + 3) {
+ valToDump = "testString[$i]"
+ underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+ }
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).doesNotContain("testString[0]")
+ assertThat(dumpedString).doesNotContain("testString[1]")
+ assertThat(dumpedString).doesNotContain("testString[2]")
+ assertThat(dumpedString).contains("testString[3]")
+ assertThat(dumpedString).contains("testString[${MAX_SIZE + 2}]")
+ }
+
+ private fun dumpChanges(): String {
+ underTest.dumpChanges(PrintWriter(outputWriter))
+ return outputWriter.toString()
+ }
+
+ private abstract class TestDiffable : Diffable<TestDiffable> {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {}
+ }
+}
+
+private const val NAME = "TestTableBuffer"
+private const val MAX_SIZE = 10
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index e1007fa..858d0e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -35,6 +35,8 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -50,10 +52,12 @@
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.VariableDateView
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.eq
import com.android.systemui.util.mockito.mock
@@ -104,7 +108,7 @@
@Mock
private lateinit var featureFlags: FeatureFlags
@Mock
- private lateinit var clock: TextView
+ private lateinit var clock: Clock
@Mock
private lateinit var date: VariableDateView
@Mock
@@ -138,6 +142,7 @@
private lateinit var qsConstraints: ConstraintSet
@Mock
private lateinit var largeScreenConstraints: ConstraintSet
+ @Mock private lateinit var demoModeController: DemoModeController
@JvmField @Rule
val mockitoRule = MockitoJUnit.rule()
@@ -146,10 +151,12 @@
private lateinit var controller: LargeScreenShadeHeaderController
private lateinit var carrierIconSlots: List<String>
private val configurationController = FakeConfigurationController()
+ private lateinit var demoModeControllerCapture: ArgumentCaptor<DemoMode>
@Before
fun setUp() {
- whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+ demoModeControllerCapture = argumentCaptor<DemoMode>()
+ whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
whenever(clock.context).thenReturn(mockedContext)
whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
@@ -195,7 +202,8 @@
dumpManager,
featureFlags,
qsCarrierGroupControllerBuilder,
- combinedShadeHeadersConstraintManager
+ combinedShadeHeadersConstraintManager,
+ demoModeController
)
whenever(view.isAttachedToWindow).thenReturn(true)
controller.init()
@@ -617,6 +625,21 @@
}
@Test
+ fun demoMode_attachDemoMode() {
+ verify(demoModeController).addCallback(capture(demoModeControllerCapture))
+ demoModeControllerCapture.value.onDemoModeStarted()
+ verify(clock).onDemoModeStarted()
+ }
+
+ @Test
+ fun demoMode_detachDemoMode() {
+ controller.simulateViewDetached()
+ verify(demoModeController).removeCallback(capture(demoModeControllerCapture))
+ demoModeControllerCapture.value.onDemoModeFinished()
+ verify(clock).onDemoModeFinished()
+ }
+
+ @Test
fun animateOutOnStartCustomizing() {
val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
val duration = 1000L
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 90ae693..b4c8f98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -13,6 +13,8 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -22,9 +24,12 @@
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.FakeConfigurationController
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.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -52,7 +57,7 @@
@Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
@Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
@Mock private lateinit var featureFlags: FeatureFlags
- @Mock private lateinit var clock: TextView
+ @Mock private lateinit var clock: Clock
@Mock private lateinit var date: TextView
@Mock private lateinit var carrierGroup: QSCarrierGroup
@Mock private lateinit var batteryMeterView: BatteryMeterView
@@ -66,6 +71,7 @@
CombinedShadeHeadersConstraintManager
@Mock private lateinit var mockedContext: Context
+ @Mock private lateinit var demoModeController: DemoModeController
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
var viewVisibility = View.GONE
@@ -76,7 +82,7 @@
@Before
fun setup() {
- whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+ whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
whenever(clock.context).thenReturn(mockedContext)
whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
whenever(date.context).thenReturn(mockedContext)
@@ -111,8 +117,9 @@
dumpManager,
featureFlags,
qsCarrierGroupControllerBuilder,
- combinedShadeHeadersConstraintManager
- )
+ combinedShadeHeadersConstraintManager,
+ demoModeController
+ )
whenever(view.isAttachedToWindow).thenReturn(true)
mLargeScreenShadeHeaderController.init()
carrierIconSlots = listOf(
@@ -230,4 +237,21 @@
verify(animator).setInterpolator(Interpolators.ALPHA_IN)
verify(animator).start()
}
+
+ @Test
+ fun demoMode_attachDemoMode() {
+ val cb = argumentCaptor<DemoMode>()
+ verify(demoModeController).addCallback(capture(cb))
+ cb.value.onDemoModeStarted()
+ verify(clock).onDemoModeStarted()
+ }
+
+ @Test
+ fun demoMode_detachDemoMode() {
+ mLargeScreenShadeHeaderController.simulateViewDetached()
+ val cb = argumentCaptor<DemoMode>()
+ verify(demoModeController).removeCallback(capture(cb))
+ cb.value.onDemoModeFinished()
+ verify(clock).onDemoModeFinished()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
index 3d29d2b..30fd308 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
@@ -18,8 +18,10 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MIN_VALID_LEVEL
+import com.google.common.truth.Truth.assertThat
import org.junit.Test
@SmallTest
@@ -48,6 +50,125 @@
WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1)
}
+ // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
+
+ @Test
+ fun logDiffs_inactiveToActive_logsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+ }
+ @Test
+ fun logDiffs_activeToInactive_resetsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ WifiNetworkModel.Inactive.logDiffs(prevVal = activeNetwork, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+ }
+
+ @Test
+ fun logDiffs_carrierMergedToActive_logsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ activeNetwork.logDiffs(prevVal = WifiNetworkModel.CarrierMerged, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+ }
+ @Test
+ fun logDiffs_activeToCarrierMerged_resetsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ WifiNetworkModel.CarrierMerged.logDiffs(prevVal = activeNetwork, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+ }
+
+ @Test
+ fun logDiffs_activeChangesLevel_onlyLevelLogged() {
+ val logger = TestLogger()
+ val prevActiveNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+ val newActiveNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 2,
+ ssid = "Test SSID"
+ )
+
+ newActiveNetwork.logDiffs(prevActiveNetwork, logger)
+
+ assertThat(logger.changes).isEqualTo(listOf(Pair(COL_LEVEL, "2")))
+ }
+
+ private class TestLogger : TableRowLogger {
+ val changes = mutableListOf<Pair<String, String>>()
+
+ override fun logChange(columnName: String, value: String?) {
+ changes.add(Pair(columnName, value.toString()))
+ }
+
+ override fun logChange(columnName: String, value: Int) {
+ changes.add(Pair(columnName, value.toString()))
+ }
+
+ override fun logChange(columnName: String, value: Boolean) {
+ changes.add(Pair(columnName, value.toString()))
+ }
+ }
+
companion object {
private const val NETWORK_ID = 2
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index a64a4bd..800f3c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -29,6 +29,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
@@ -69,6 +70,7 @@
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogger: TableLogBuffer
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var wifiManager: WifiManager
private lateinit var executor: Executor
@@ -804,6 +806,7 @@
broadcastDispatcher,
connectivityManager,
logger,
+ tableLogger,
executor,
scope,
wifiManagerToUse,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index f6fd2cb..f68baf5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -16,32 +16,71 @@
import android.testing.LeakCheck;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+import java.util.ArrayList;
+import java.util.List;
+
public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener>
implements FlashlightController {
+
+ private final List<FlashlightListener> callbacks = new ArrayList<>();
+
+ @VisibleForTesting
+ public boolean isAvailable;
+ @VisibleForTesting
+ public boolean isEnabled;
+ @VisibleForTesting
+ public boolean hasFlashlight;
+
public FakeFlashlightController(LeakCheck test) {
super(test, "flashlight");
}
+ @VisibleForTesting
+ public void onFlashlightAvailabilityChanged(boolean newValue) {
+ callbacks.forEach(
+ flashlightListener -> flashlightListener.onFlashlightAvailabilityChanged(newValue)
+ );
+ }
+
+ @VisibleForTesting
+ public void onFlashlightError() {
+ callbacks.forEach(FlashlightListener::onFlashlightError);
+ }
+
@Override
public boolean hasFlashlight() {
- return false;
+ return hasFlashlight;
}
@Override
public void setFlashlight(boolean newState) {
-
+ callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState));
}
@Override
public boolean isAvailable() {
- return false;
+ return isAvailable;
}
@Override
public boolean isEnabled() {
- return false;
+ return isEnabled;
+ }
+
+ @Override
+ public void addCallback(FlashlightListener listener) {
+ super.addCallback(listener);
+ callbacks.add(listener);
+ }
+
+ @Override
+ public void removeCallback(FlashlightListener listener) {
+ super.removeCallback(listener);
+ callbacks.remove(listener);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a92b65e..4d44c886 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3796,13 +3796,13 @@
}
private void createNotificationChannelsImpl(String pkg, int uid,
- ParceledListSlice channelsList, boolean fromTargetApp) {
- createNotificationChannelsImpl(pkg, uid, channelsList, fromTargetApp,
+ ParceledListSlice channelsList) {
+ createNotificationChannelsImpl(pkg, uid, channelsList,
ActivityTaskManager.INVALID_TASK_ID);
}
private void createNotificationChannelsImpl(String pkg, int uid,
- ParceledListSlice channelsList, boolean fromTargetApp, int startingTaskId) {
+ ParceledListSlice channelsList, int startingTaskId) {
List<NotificationChannel> channels = channelsList.getList();
final int channelsSize = channels.size();
ParceledListSlice<NotificationChannel> oldChannels =
@@ -3814,7 +3814,7 @@
final NotificationChannel channel = channels.get(i);
Objects.requireNonNull(channel, "channel in list is null");
needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(pkg, uid,
- channel, fromTargetApp,
+ channel, true /* fromTargetApp */,
mConditionProviders.isPackageOrComponentAllowed(
pkg, UserHandle.getUserId(uid)));
if (needsPolicyFileChange) {
@@ -3850,7 +3850,6 @@
@Override
public void createNotificationChannels(String pkg, ParceledListSlice channelsList) {
checkCallerIsSystemOrSameApp(pkg);
- boolean fromTargetApp = !isCallerSystemOrPhone(); // if not system, it's from the app
int taskId = ActivityTaskManager.INVALID_TASK_ID;
try {
int uid = mPackageManager.getPackageUid(pkg, 0,
@@ -3859,15 +3858,14 @@
} catch (RemoteException e) {
// Do nothing
}
- createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, fromTargetApp,
- taskId);
+ createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, taskId);
}
@Override
public void createNotificationChannelsForPackage(String pkg, int uid,
ParceledListSlice channelsList) {
enforceSystemOrSystemUI("only system can call this");
- createNotificationChannelsImpl(pkg, uid, channelsList, false /* fromTargetApp */);
+ createNotificationChannelsImpl(pkg, uid, channelsList);
}
@Override
@@ -3882,8 +3880,7 @@
CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
conversationChannel.setConversationId(parentId, conversationId);
createNotificationChannelsImpl(
- pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)),
- false /* fromTargetApp */);
+ pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
mRankingHandler.requestSort();
handleSavePolicyFile();
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 9791158..d8aa469 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -916,7 +916,7 @@
throw new IllegalArgumentException("Reserved id");
}
NotificationChannel existing = r.channels.get(channel.getId());
- if (existing != null) {
+ if (existing != null && fromTargetApp) {
// Actually modifying an existing channel - keep most of the existing settings
if (existing.isDeleted()) {
// The existing channel was deleted - undelete it.
@@ -1002,7 +1002,9 @@
}
if (fromTargetApp) {
channel.setLockscreenVisibility(r.visibility);
- channel.setAllowBubbles(NotificationChannel.DEFAULT_ALLOW_BUBBLE);
+ channel.setAllowBubbles(existing != null
+ ? existing.getAllowBubbles()
+ : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
}
clearLockedFieldsLocked(channel);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 077caa4..3f3b052 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1101,10 +1101,6 @@
new NotificationChannel("id", "name", IMPORTANCE_HIGH);
mBinderService.updateNotificationChannelForPackage(PKG, mUid, updatedChannel);
- // pretend only this following part is called by the app (system permissions are required to
- // update the notification channel on behalf of the user above)
- mService.isSystemUid = false;
-
// Recreating with a lower importance leaves channel unchanged.
final NotificationChannel dupeChannel =
new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_LOW);
@@ -1130,46 +1126,6 @@
}
@Test
- public void testCreateNotificationChannels_fromAppCannotSetFields() throws Exception {
- // Confirm that when createNotificationChannels is called from the relevant app and not
- // system, then it cannot set fields that can't be set by apps
- mService.isSystemUid = false;
-
- final NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
- channel.setBypassDnd(true);
- channel.setAllowBubbles(true);
-
- mBinderService.createNotificationChannels(PKG,
- new ParceledListSlice(Arrays.asList(channel)));
-
- final NotificationChannel createdChannel =
- mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
- assertFalse(createdChannel.canBypassDnd());
- assertFalse(createdChannel.canBubble());
- }
-
- @Test
- public void testCreateNotificationChannels_fromSystemCanSetFields() throws Exception {
- // Confirm that when createNotificationChannels is called from system,
- // then it can set fields that can't be set by apps
- mService.isSystemUid = true;
-
- final NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
- channel.setBypassDnd(true);
- channel.setAllowBubbles(true);
-
- mBinderService.createNotificationChannels(PKG,
- new ParceledListSlice(Arrays.asList(channel)));
-
- final NotificationChannel createdChannel =
- mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
- assertTrue(createdChannel.canBypassDnd());
- assertTrue(createdChannel.canBubble());
- }
-
- @Test
public void testBlockedNotifications_suspended() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(true);
@@ -3132,8 +3088,6 @@
@Test
public void testDeleteChannelGroupChecksForFgses() throws Exception {
- // the setup for this test requires it to seem like it's coming from the app
- mService.isSystemUid = false;
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
.thenReturn(singletonList(mock(AssociationInfo.class)));
CountDownLatch latch = new CountDownLatch(2);
@@ -3146,7 +3100,7 @@
ParceledListSlice<NotificationChannel> pls =
new ParceledListSlice(ImmutableList.of(notificationChannel));
try {
- mBinderService.createNotificationChannels(PKG, pls);
+ mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -3165,10 +3119,8 @@
ParceledListSlice<NotificationChannel> pls =
new ParceledListSlice(ImmutableList.of(notificationChannel));
try {
- // Because existing channels won't have their groups overwritten when the call
- // is from the app, this call won't take the channel out of the group
- mBinderService.createNotificationChannels(PKG, pls);
- mBinderService.deleteNotificationChannelGroup(PKG, "group");
+ mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+ mBinderService.deleteNotificationChannelGroup(PKG, "group");
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -8673,7 +8625,7 @@
assertEquals("friend", friendChannel.getConversationId());
assertEquals(null, original.getConversationId());
assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
- assertEquals(original.canBubble(), friendChannel.canBubble()); // called by system
+ assertFalse(friendChannel.canBubble()); // can't be modified by app
assertFalse(original.getId().equals(friendChannel.getId()));
assertNotNull(friendChannel.getId());
}