Add logging for privacy indicators

Test: manual
Test: atest PrivacyItemControllerTest
Change-Id: I1e1d638584315bc4461d414291d362d5c72cc026
This commit is contained in:
Fabian Kozynski
2020-10-20 14:52:15 -04:00
parent 764a15f7c7
commit de65343311
10 changed files with 225 additions and 15 deletions

View File

@@ -120,6 +120,18 @@ public class LogModule {
return buffer;
}
/** Provides a logging buffer for all logs related to privacy indicators in SystemUI. */
@Provides
@SysUISingleton
@PrivacyLog
public static LogBuffer providePrivacyLogBuffer(
LogcatEchoTracker bufferFilter,
DumpManager dumpManager) {
LogBuffer buffer = new LogBuffer(("PrivacyLog"), 100, 10, bufferFilter);
buffer.attach(dumpManager);
return buffer;
}
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2019 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.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Qualifier;
/** A {@link LogBuffer} for privacy indicator-related messages. */
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface PrivacyLog {
}

View File

@@ -19,20 +19,31 @@ import com.android.systemui.R
typealias Privacy = PrivacyType
enum class PrivacyType(val nameId: Int, val iconId: Int) {
enum class PrivacyType(val nameId: Int, val iconId: Int, val logName: String) {
// This is uses the icons used by the corresponding permission groups in the AndroidManifest
TYPE_CAMERA(R.string.privacy_type_camera,
com.android.internal.R.drawable.perm_group_camera),
TYPE_MICROPHONE(R.string.privacy_type_microphone,
com.android.internal.R.drawable.perm_group_microphone),
TYPE_LOCATION(R.string.privacy_type_location,
com.android.internal.R.drawable.perm_group_location);
TYPE_CAMERA(
R.string.privacy_type_camera,
com.android.internal.R.drawable.perm_group_camera,
"camera"
),
TYPE_MICROPHONE(
R.string.privacy_type_microphone,
com.android.internal.R.drawable.perm_group_microphone,
"microphone"
),
TYPE_LOCATION(
R.string.privacy_type_location,
com.android.internal.R.drawable.perm_group_location,
"location"
);
fun getName(context: Context) = context.resources.getString(nameId)
fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme)
}
data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication)
data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication) {
fun toLog(): String = "(${privacyType.logName}, ${application.packageName}(${application.uid}))"
}
data class PrivacyApplication(val packageName: String, val uid: Int)

View File

@@ -32,6 +32,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -48,6 +49,7 @@ class PrivacyItemController @Inject constructor(
@Background private val bgExecutor: Executor,
private val deviceConfigProxy: DeviceConfigProxy,
private val userTracker: UserTracker,
private val logger: PrivacyLogger,
dumpManager: DumpManager
) : Dumpable {
@@ -158,6 +160,7 @@ class PrivacyItemController @Inject constructor(
}
val userId = UserHandle.getUserId(uid)
if (userId in currentUserIds) {
logger.logUpdatedItemFromAppOps(code, uid, packageName, active)
update(false)
}
}
@@ -194,6 +197,7 @@ class PrivacyItemController @Inject constructor(
bgExecutor.execute {
if (updateUsers) {
currentUserIds = userTracker.userProfiles.map { it.id }
logger.logCurrentProfilesChanged(currentUserIds)
}
updateListAndNotifyChanges.run()
}
@@ -260,6 +264,8 @@ class PrivacyItemController @Inject constructor(
}
val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
.mapNotNull { toPrivacyItem(it) }.distinct()
logger.logUpdatedPrivacyItemsList(
list.joinToString(separator = ", ", transform = PrivacyItem::toLog))
privacyList = list
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2020 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.privacy.logging
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.PrivacyLog
import javax.inject.Inject
private const val TAG = "PrivacyLog"
class PrivacyLogger @Inject constructor(
@PrivacyLog private val buffer: LogBuffer
) {
fun logUpdatedItemFromAppOps(code: Int, uid: Int, packageName: String, active: Boolean) {
log(LogLevel.INFO, {
int1 = code
int2 = uid
str1 = packageName
bool1 = active
}, {
"App Op: $int1 for $str1($int2), active=$bool1"
})
}
fun logUpdatedPrivacyItemsList(listAsString: String) {
log(LogLevel.INFO, {
str1 = listAsString
}, {
"Updated list: $str1"
})
}
fun logCurrentProfilesChanged(profiles: List<Int>) {
log(LogLevel.INFO, {
str1 = profiles.toString()
}, {
"Profiles changed: $str1"
})
}
fun logChipVisible(visible: Boolean) {
log(LogLevel.INFO, {
bool1 = visible
}, {
"Chip visible: $bool1"
})
}
fun logStatusBarIconsVisible(
showCamera: Boolean,
showMichrophone: Boolean,
showLocation: Boolean
) {
log(LogLevel.INFO, {
bool1 = showCamera
bool2 = showMichrophone
bool3 = showLocation
}, {
"Status bar icons visible: camera=$bool1, microphone=$bool2, location=$bool3"
})
}
private inline fun log(
logLevel: LogLevel,
initializer: LogMessage.() -> Unit,
noinline printer: LogMessage.() -> String
) {
buffer.log(TAG, logLevel, initializer, printer)
}
}

View File

@@ -43,6 +43,7 @@ import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.privacy.PrivacyChipEvent;
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
@@ -90,6 +91,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private final StatusIconContainer mIconContainer;
private final StatusBarIconController.TintedIconManager mIconManager;
private final DemoMode mDemoModeReceiver;
private final PrivacyLogger mPrivacyLogger;
private boolean mListening;
private AlarmClockInfo mNextAlarm;
@@ -213,7 +215,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
QSTileHost qsTileHost, StatusBarIconController statusBarIconController,
CommandQueue commandQueue, DemoModeController demoModeController,
UserTracker userTracker, QuickQSPanelController quickQSPanelController,
QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
PrivacyLogger privacyLogger) {
super(view);
mZenModeController = zenModeController;
mNextAlarmController = nextAlarmController;
@@ -228,6 +231,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mUserTracker = userTracker;
mLifecycle = new LifecycleRegistry(mLifecycleOwner);
mHeaderQsPanelController = quickQSPanelController;
mPrivacyLogger = privacyLogger;
mQSCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
@@ -323,6 +327,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private void setChipVisibility(boolean chipVisible) {
if (chipVisible && getChipEnabled()) {
mPrivacyChip.setVisibility(View.VISIBLE);
mPrivacyLogger.logChipVisible(true);
// Makes sure that the chip is logged as viewed at most once each time QS is opened
// mListening makes sure that the callback didn't return after the user closed QS
if (!mPrivacyChipLogged && mListening) {
@@ -330,6 +335,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW);
}
} else {
mPrivacyLogger.logChipVisible(false);
mPrivacyChip.setVisibility(View.GONE);
}
}

View File

@@ -50,6 +50,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.PrivacyType;
import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.screenrecord.RecordingController;
@@ -145,6 +146,7 @@ public class PhoneStatusBarPolicy
private final SensorPrivacyController mSensorPrivacyController;
private final RecordingController mRecordingController;
private final RingerModeTracker mRingerModeTracker;
private final PrivacyLogger mPrivacyLogger;
private boolean mZenVisible;
private boolean mVolumeVisible;
@@ -172,7 +174,8 @@ public class PhoneStatusBarPolicy
@Nullable TelecomManager telecomManager, @DisplayId int displayId,
@Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
RingerModeTracker ringerModeTracker,
PrivacyItemController privacyItemController) {
PrivacyItemController privacyItemController,
PrivacyLogger privacyLogger) {
mIconController = iconController;
mCommandQueue = commandQueue;
mBroadcastDispatcher = broadcastDispatcher;
@@ -197,6 +200,7 @@ public class PhoneStatusBarPolicy
mUiBgExecutor = uiBgExecutor;
mTelecomManager = telecomManager;
mRingerModeTracker = ringerModeTracker;
mPrivacyLogger = privacyLogger;
mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -675,6 +679,7 @@ public class PhoneStatusBarPolicy
|| mPrivacyItemController.getLocationAvailable()) {
mIconController.setIconVisibility(mSlotLocation, showLocation);
}
mPrivacyLogger.logStatusBarIconsVisible(showCamera, showMicrophone, showLocation);
}
@Override

View File

@@ -23,6 +23,7 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpsController
import com.android.systemui.dump.DumpManager
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.DeviceConfigProxyFake
@@ -65,6 +66,8 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() {
private lateinit var dumpManager: DumpManager
@Mock
private lateinit var userTracker: UserTracker
@Mock
private lateinit var logger: PrivacyLogger
private lateinit var privacyItemController: PrivacyItemController
private lateinit var executor: FakeExecutor
@@ -77,8 +80,8 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() {
executor,
deviceConfigProxy,
userTracker,
dumpManager
)
logger,
dumpManager)
}
@Before

View File

@@ -29,10 +29,12 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpItem
import com.android.systemui.appops.AppOpsController
import com.android.systemui.dump.DumpManager
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.time.FakeSystemClock
import org.hamcrest.Matchers.hasItem
import org.hamcrest.Matchers.not
@@ -86,6 +88,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
private lateinit var userTracker: UserTracker
@Mock
private lateinit var dumpManager: DumpManager
@Mock
private lateinit var logger: PrivacyLogger
@Captor
private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>>
@Captor
@@ -102,8 +106,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
executor,
deviceConfigProxy,
userTracker,
dumpManager
)
logger,
dumpManager)
}
@Before
@@ -300,6 +304,45 @@ class PrivacyItemControllerTest : SysuiTestCase() {
verify(callback, never()).onPrivacyItemsChanged(any())
}
@Test
fun testLogActiveChanged() {
privacyItemController.addCallback(callback)
executor.runAllReady()
verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
argCaptorCallback.value.onActiveStateChanged(
AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
verify(logger).logUpdatedItemFromAppOps(
AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
}
@Test
fun testLogListUpdated() {
doReturn(listOf(
AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0))
).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
privacyItemController.addCallback(callback)
executor.runAllReady()
verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
argCaptorCallback.value.onActiveStateChanged(
AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
executor.runAllReady()
val expected = PrivacyItem(
PrivacyType.TYPE_LOCATION,
PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID)
)
val captor = argumentCaptor<String>()
verify(logger, atLeastOnce()).logUpdatedPrivacyItemsList(capture(captor))
// Let's look at the last log
val values = captor.allValues
assertTrue(values[values.size - 1].contains(expected.toLog()))
}
private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)

View File

@@ -27,6 +27,7 @@ import com.android.systemui.demomode.DemoModeController
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.settings.UserTracker
@@ -86,6 +87,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
@Mock
private lateinit var qsCarrierGroupController: QSCarrierGroupController
@Mock
private lateinit var privacyLogger: PrivacyLogger
@Mock
private lateinit var iconContainer: StatusIconContainer
@Mock
private lateinit var qsCarrierGroup: QSCarrierGroup
@@ -123,7 +126,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
demoModeController,
userTracker,
quickQSPanelController,
qsCarrierGroupControllerBuilder
qsCarrierGroupControllerBuilder,
privacyLogger
)
}