Merge "Fix broken documentation in accessibility" into main
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index baed7f9..39800f7 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -637,15 +637,15 @@
/**
* Specifies a component name to be exempt from the current activity launch policy.
*
- * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity
- * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT},
+ * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}),
* then the specified component will be blocked from launching.
- * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity
- * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then
- * the specified component will be allowed to launch.</p>
+ * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches
+ * by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the
+ * specified component will be allowed to launch.</p>
*
- * <p>Note that changing the activity launch policy will not affect current set of exempt
- * components and it needs to be updated separately.</p>
+ * <p>Note that changing the activity launch policy will clear current set of exempt
+ * components.</p>
*
* @see #removeActivityPolicyExemption
* @see #setDevicePolicy
@@ -660,15 +660,15 @@
/**
* Makes the specified component name to adhere to the default activity launch policy.
*
- * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity
- * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT},
+ * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}),
* then the specified component will be allowed to launch.
- * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity
- * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then
- * the specified component will be blocked from launching.</p>
+ * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches
+ * by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the
+ * specified component will be blocked from launching.</p>
*
- * <p>Note that changing the activity launch policy will not affect current set of exempt
- * components and it needs to be updated separately.</p>
+ * <p>Note that changing the activity launch policy will clear current set of exempt
+ * components.</p>
*
* @see #addActivityPolicyExemption
* @see #setDevicePolicy
diff --git a/core/java/android/service/controls/Control.java b/core/java/android/service/controls/Control.java
index 3b757d6..33978be 100644
--- a/core/java/android/service/controls/Control.java
+++ b/core/java/android/service/controls/Control.java
@@ -50,7 +50,7 @@
* and zone. Some of these values are defined by the user and/or the {@link ControlsProviderService}
* and will be used to display the control as well as group them for management.
* <p>
- * Each object will have an associated {@link DeviceTypes.DeviceType}. This will determine the icons and colors
+ * Each object will have an associated {@link DeviceTypes}. This will determine the icons and colors
* used to display it.
* <p>
* An {@link Intent} linking to the provider Activity that expands on this {@link Control} and
@@ -420,7 +420,7 @@
* This fixes the values relating to state of the {@link Control} as required by
* {@link ControlsProviderService#createPublisherForAllAvailable}:
* <ul>
- * <li> Status: {@link Status#STATUS_UNKNOWN}
+ * <li> Status: {@link #STATUS_UNKNOWN}
* <li> Control template: {@link ControlTemplate#getNoTemplateObject}
* <li> Status text: {@code ""}
* <li> Auth Required: {@code true}
@@ -620,7 +620,7 @@
* <li> Device type: {@link DeviceTypes#TYPE_UNKNOWN}
* <li> Title: {@code ""}
* <li> Subtitle: {@code ""}
- * <li> Status: {@link Status#STATUS_UNKNOWN}
+ * <li> Status: {@link #STATUS_UNKNOWN}
* <li> Control template: {@link ControlTemplate#getNoTemplateObject}
* <li> Status text: {@code ""}
* <li> Auth Required: {@code true}
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index fce87db..0272bb9 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -155,7 +155,7 @@
* The user has interacted with a Control. The action is dictated by the type of
* {@link ControlAction} that was sent. A response can be sent via
* {@link Consumer#accept}, with the Integer argument being one of the provided
- * {@link ControlAction.ResponseResult}. The Integer should indicate whether the action
+ * {@link ControlAction} response results. The Integer should indicate whether the action
* was received successfully, or if additional prompts should be presented to
* the user. Any visual control updates should be sent via the Publisher.
diff --git a/core/java/android/service/controls/actions/ControlAction.java b/core/java/android/service/controls/actions/ControlAction.java
index 10f526d..4e38222 100644
--- a/core/java/android/service/controls/actions/ControlAction.java
+++ b/core/java/android/service/controls/actions/ControlAction.java
@@ -154,7 +154,7 @@
public static final @ResponseResult int RESPONSE_CHALLENGE_PASSPHRASE = 5;
/**
- * The {@link ActionType} associated with this class.
+ * The action type associated with this class.
*/
public abstract @ActionType int getActionType();
diff --git a/core/java/android/service/controls/templates/ControlTemplate.java b/core/java/android/service/controls/templates/ControlTemplate.java
index 3902d6a..0dd950d 100644
--- a/core/java/android/service/controls/templates/ControlTemplate.java
+++ b/core/java/android/service/controls/templates/ControlTemplate.java
@@ -137,7 +137,7 @@
}
/**
- * The {@link TemplateType} associated with this class.
+ * The template type associated with this class.
*/
public abstract @TemplateType int getTemplateType();
diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
index 49e00d6..7befbfb 100644
--- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
@@ -151,7 +151,7 @@
*/
@NonNull public abstract List<String> onGetRequestedPackages();
- private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+ private final Handler mHandler = Handler.createAsync(Looper.getMainLooper());
@Nullable private RemoteCallback mCallback;
@Override
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index cdf5eec3..70e1896 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2197,6 +2197,9 @@
float xOffset, float yOffset, float xPrecision, float yPrecision,
long downTimeNanos, long eventTimeNanos,
int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords) {
+ if (action == ACTION_CANCEL) {
+ flags |= FLAG_CANCELED;
+ }
mNativePtr = nativeInitialize(mNativePtr, deviceId, source, displayId, action, flags,
edgeFlags, metaState, buttonState, classification, xOffset, yOffset,
xPrecision, yPrecision, downTimeNanos, eventTimeNanos, pointerCount, pointerIds,
@@ -2387,6 +2390,11 @@
nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED);
}
+ private void setCanceled(boolean canceled) {
+ final int flags = getFlags();
+ nativeSetFlags(mNativePtr, canceled ? flags | FLAG_CANCELED : flags & ~FLAG_CANCELED);
+ }
+
/** @hide */
public boolean isTargetAccessibilityFocus() {
final int flags = getFlags();
@@ -3510,6 +3518,14 @@
* Sets this event's action.
*/
public final void setAction(int action) {
+ final int actionMasked = action & ACTION_MASK;
+ if (actionMasked == ACTION_CANCEL) {
+ setCanceled(true);
+ } else if (actionMasked == ACTION_POINTER_UP) {
+ // Do nothing - we don't know what the real intent here is
+ } else {
+ setCanceled(false);
+ }
nativeSetAction(mNativePtr, action);
}
@@ -4157,6 +4173,7 @@
/** @hide */
@Override
public final void cancel() {
+ setCanceled(true);
setAction(ACTION_CANCEL);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
index 67dc642..e1dea3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
@@ -25,7 +25,8 @@
private var _isBubbleBarEnabled =
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
- override val isBubbleBarEnabled = _isBubbleBarEnabled
+ override val isBubbleBarEnabled
+ get() = _isBubbleBarEnabled
override fun refresh() {
_isBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 1943b34..67531ad 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -30,6 +30,8 @@
import com.android.systemui.log.table.TableLogBuffer;
import com.android.systemui.log.table.TableLogBufferFactory;
import com.android.systemui.qs.QSFragmentLegacy;
+import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
+import com.android.systemui.qs.pipeline.shared.TileSpec;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.util.Compile;
import com.android.systemui.util.wakelock.WakeLockLog;
@@ -37,6 +39,9 @@
import dagger.Module;
import dagger.Provides;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Dagger module for providing instances of {@link LogBuffer}.
*/
@@ -173,8 +178,35 @@
@Provides
@SysUISingleton
@QSLog
- public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) {
- return factory.create("QSLog", 700 /* maxSize */, false /* systrace */);
+ public static LogBuffer provideQuickSettingsLogBuffer(
+ LogBufferFactory factory,
+ QSPipelineFlagsRepository flags
+ ) {
+ if (flags.getPipelineTilesEnabled()) {
+ // we use
+ return factory.create("QSLog", 450 /* maxSize */, false /* systrace */);
+ } else {
+ return factory.create("QSLog", 700 /* maxSize */, false /* systrace */);
+ }
+ }
+
+ /**
+ * Provides a logging buffer for all logs related to Quick Settings tiles. This LogBuffer is
+ * unique for each tile.
+ * go/qs-tile-refactor
+ */
+ @Provides
+ @QSTilesDefaultLog
+ public static LogBuffer provideQuickSettingsTilesLogBuffer(LogBufferFactory factory) {
+ return factory.create("QSTileLog", 25 /* maxSize */, false /* systrace */);
+ }
+
+ @Provides
+ @QSTilesLogBuffers
+ public static Map<TileSpec, LogBuffer> provideQuickSettingsTilesLogBufferCache() {
+ final Map<TileSpec, LogBuffer> buffers = new HashMap<>();
+ // Add chatty buffers here
+ return buffers;
}
/** Provides a logging buffer for logs related to Quick Settings configuration. */
@@ -420,7 +452,7 @@
/**
* Provides a {@link LogBuffer} for use by
- * {@link com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepositoryImpl}.
+ * {@link com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepositoryImpl}.
*/
@Provides
@SysUISingleton
@@ -431,7 +463,7 @@
/**
* Provides a {@link LogBuffer} for use by classes in the
- * {@link com.android.systemui.keyguard.bouncer} package.
+ * {@link com.android.systemui.keyguard.bouncer} package.
*/
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
new file mode 100644
index 0000000..6575cdd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * A default [com.android.systemui.log.LogBuffer] for QS tiles messages. It's used exclusively in
+ * [com.android.systemui.qs.tiles.base.logging.QSTileLogger]. If you need to increase it for you
+ * tile, add one to the map provided by the [QSTilesLogBuffers]
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class QSTilesDefaultLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesLogBuffers.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesLogBuffers.kt
new file mode 100644
index 0000000..62d49fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesLogBuffers.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * Provides a map with custom [com.android.systemui.log.LogBuffer] for QS tiles messages. Add
+ * buffers to it when the tile needs to be more verbose and the default buffer provided by
+ * [QSTilesDefaultLog] is not enough.
+ *
+ * This is not a multibinding. Add new logs directly to [LogModule]
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class QSTilesLogBuffers
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesVerboseLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesVerboseLog.java
new file mode 100644
index 0000000..b0c2f8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesVerboseLog.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.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 QS tiles messages. It's used exclusively in
+ * {@link com.android.systemui.qs.tiles.base.logging.QSTileLogger}
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface QSTilesVerboseLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 19012e2..fa18b35b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -27,11 +27,11 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.res.R;
import java.util.ArrayList;
import java.util.List;
@@ -562,6 +562,12 @@
if (shouldNotRunAnimation(tilesToReveal)) {
return;
}
+ // This method has side effects (beings the fake drag, if it returns true). If we have
+ // decided that we want to do a tile reveal, we do a last check to verify that we can
+ // actually perform a fake drag.
+ if (!beginFakeDrag()) {
+ return;
+ }
final int lastPageNumber = mPages.size() - 1;
final TileLayout lastPage = mPages.get(lastPageNumber);
@@ -596,8 +602,10 @@
}
private boolean shouldNotRunAnimation(Set<String> tilesToReveal) {
+ // None of these have side effects. That way, we don't need to rely on short-circuiting
+ // behavior
boolean noAnimationNeeded = tilesToReveal.isEmpty() || mPages.size() < 2;
- boolean scrollingInProgress = getScrollX() != 0 || !beginFakeDrag();
+ boolean scrollingInProgress = getScrollX() != 0 || !isFakeDragging();
// checking mRunningInTestHarness to disable animation in functional testing as it caused
// flakiness and is not needed there. Alternative solutions were more complex and would
// still be either potentially flaky or modify internal data.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 128c237..051eeb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -72,7 +72,8 @@
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
-public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
+public class CustomTile extends QSTileImpl<State> implements TileChangeListener,
+ CustomTileInterface {
public static final String PREFIX = "custom(";
private static final long CUSTOM_STALE_TIMEOUT = DateUtils.HOUR_IN_MILLIS;
@@ -181,7 +182,8 @@
private void updateDefaultTileAndIcon() {
try {
PackageManager pm = mUserContext.getPackageManager();
- int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE;
+ int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE;
if (isSystemApp(pm)) {
flags |= PackageManager.MATCH_DISABLED_COMPONENTS;
}
@@ -213,7 +215,7 @@
* Compare two icons, only works for resources.
*/
private boolean iconEquals(@Nullable android.graphics.drawable.Icon icon1,
- @Nullable android.graphics.drawable.Icon icon2) {
+ @Nullable android.graphics.drawable.Icon icon2) {
if (icon1 == icon2) {
return true;
}
@@ -252,10 +254,12 @@
}
}
+ @Override
public int getUser() {
return mUser;
}
+ @Override
public ComponentName getComponent() {
return mComponent;
}
@@ -265,6 +269,7 @@
return super.populate(logMaker).setComponentName(mComponent);
}
+ @Override
public Tile getQsTile() {
// TODO(b/191145007) Move to background thread safely
updateDefaultTileAndIcon();
@@ -276,6 +281,7 @@
*
* @param tile tile populated with state to apply
*/
+ @Override
public void updateTileState(Tile tile, int appUid) {
mServiceUid = appUid;
// This comes from a binder call IQSService.updateQsTile
@@ -310,10 +316,12 @@
mTile.setState(tile.getState());
}
+ @Override
public void onDialogShown() {
mIsShowingDialog = true;
}
+ @Override
public void onDialogHidden() {
mIsShowingDialog = false;
try {
@@ -507,6 +515,7 @@
return mComponent.getPackageName();
}
+ @Override
public void startUnlockAndRun() {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
try {
@@ -518,8 +527,10 @@
/**
* Starts an {@link android.app.Activity}
+ *
* @param pendingIntent A PendingIntent for an Activity to be launched immediately.
*/
+ @Override
public void startActivityAndCollapse(PendingIntent pendingIntent) {
if (!pendingIntent.isActivity()) {
Log.i(TAG, "Intent not for activity.");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileInterface.kt b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileInterface.kt
new file mode 100644
index 0000000..9e02320
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileInterface.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.service.quicksettings.Tile
+
+interface CustomTileInterface {
+
+ val user: Int
+ val qsTile: Tile
+ val component: ComponentName
+
+ fun getTileSpec(): String
+
+ fun refreshState()
+ fun updateTileState(tile: Tile, uid: Int)
+
+ fun onDialogShown()
+ fun onDialogHidden()
+
+ fun startActivityAndCollapse(pendingIntent: PendingIntent)
+
+ fun startUnlockAndRun()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index fc24022..acee8e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -67,9 +67,10 @@
static final int REDUCED_MAX_BOUND = 1;
private static final String TAG = "TileServices";
- private final ArrayMap<CustomTile, TileServiceManager> mServices = new ArrayMap<>();
- private final SparseArrayMap<ComponentName, CustomTile> mTiles = new SparseArrayMap<>();
- private final ArrayMap<IBinder, CustomTile> mTokenMap = new ArrayMap<>();
+ private final ArrayMap<CustomTileInterface, TileServiceManager> mServices = new ArrayMap<>();
+ private final SparseArrayMap<ComponentName, CustomTileInterface> mTiles =
+ new SparseArrayMap<>();
+ private final ArrayMap<IBinder, CustomTileInterface> mTokenMap = new ArrayMap<>();
private final Context mContext;
private final Handler mMainHandler;
private final Provider<Handler> mHandlerProvider;
@@ -120,7 +121,7 @@
return mHost;
}
- public TileServiceManager getTileWrapper(CustomTile tile) {
+ public TileServiceManager getTileWrapper(CustomTileInterface tile) {
ComponentName component = tile.getComponent();
int userId = tile.getUser();
TileServiceManager service = onCreateTileService(component, mBroadcastDispatcher);
@@ -140,7 +141,7 @@
broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor);
}
- public void freeService(CustomTile tile, TileServiceManager service) {
+ public void freeService(CustomTileInterface tile, TileServiceManager service) {
synchronized (mServices) {
service.setBindAllowed(false);
service.handleDestroy();
@@ -184,7 +185,7 @@
}
}
- private int verifyCaller(CustomTile tile) {
+ private int verifyCaller(CustomTileInterface tile) {
try {
String packageName = tile.getComponent().getPackageName();
int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
@@ -201,7 +202,7 @@
private void requestListening(ComponentName component) {
synchronized (mServices) {
int userId = mUserTracker.getUserId();
- CustomTile customTile = getTileForUserAndComponent(userId, component);
+ CustomTileInterface customTile = getTileForUserAndComponent(userId, component);
if (customTile == null) {
Log.d(TAG, "Couldn't find tile for " + component + "(" + userId + ")");
return;
@@ -227,7 +228,7 @@
@Override
public void updateQsTile(Tile tile, IBinder token) {
- CustomTile customTile = getTileForToken(token);
+ CustomTileInterface customTile = getTileForToken(token);
if (customTile != null) {
int uid = verifyCaller(customTile);
synchronized (mServices) {
@@ -247,7 +248,7 @@
@Override
public void onStartSuccessful(IBinder token) {
- CustomTile customTile = getTileForToken(token);
+ CustomTileInterface customTile = getTileForToken(token);
if (customTile != null) {
verifyCaller(customTile);
synchronized (mServices) {
@@ -267,7 +268,7 @@
@Override
public void onShowDialog(IBinder token) {
- CustomTile customTile = getTileForToken(token);
+ CustomTileInterface customTile = getTileForToken(token);
if (customTile != null) {
verifyCaller(customTile);
customTile.onDialogShown();
@@ -278,7 +279,7 @@
@Override
public void onDialogHidden(IBinder token) {
- CustomTile customTile = getTileForToken(token);
+ CustomTileInterface customTile = getTileForToken(token);
if (customTile != null) {
verifyCaller(customTile);
Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(false);
@@ -288,7 +289,7 @@
@Override
public void onStartActivity(IBinder token) {
- CustomTile customTile = getTileForToken(token);
+ CustomTileInterface customTile = getTileForToken(token);
if (customTile != null) {
verifyCaller(customTile);
mPanelInteractor.forceCollapsePanels();
@@ -301,7 +302,7 @@
}
@VisibleForTesting
- protected void startActivity(CustomTile customTile, PendingIntent pendingIntent) {
+ protected void startActivity(CustomTileInterface customTile, PendingIntent pendingIntent) {
if (customTile != null) {
verifyCaller(customTile);
customTile.startActivityAndCollapse(pendingIntent);
@@ -310,7 +311,7 @@
@Override
public void updateStatusIcon(IBinder token, Icon icon, String contentDescription) {
- CustomTile customTile = getTileForToken(token);
+ CustomTileInterface customTile = getTileForToken(token);
if (customTile != null) {
verifyCaller(customTile);
try {
@@ -340,7 +341,7 @@
@Nullable
@Override
public Tile getTile(IBinder token) {
- CustomTile customTile = getTileForToken(token);
+ CustomTileInterface customTile = getTileForToken(token);
if (customTile != null) {
verifyCaller(customTile);
return customTile.getQsTile();
@@ -367,7 +368,7 @@
@Override
public void startUnlockAndRun(IBinder token) {
- CustomTile customTile = getTileForToken(token);
+ CustomTileInterface customTile = getTileForToken(token);
if (customTile != null) {
verifyCaller(customTile);
customTile.startUnlockAndRun();
@@ -385,14 +386,14 @@
}
@Nullable
- public CustomTile getTileForToken(IBinder token) {
+ public CustomTileInterface getTileForToken(IBinder token) {
synchronized (mServices) {
return mTokenMap.get(token);
}
}
@Nullable
- private CustomTile getTileForUserAndComponent(int userId, ComponentName component) {
+ private CustomTileInterface getTileForUserAndComponent(int userId, ComponentName component) {
synchronized (mServices) {
return mTiles.get(userId, component);
}
@@ -419,11 +420,6 @@
};
private static final Comparator<TileServiceManager> SERVICE_SORT =
- new Comparator<TileServiceManager>() {
- @Override
- public int compare(TileServiceManager left, TileServiceManager right) {
- return -Integer.compare(left.getBindPriority(), right.getBindPriority());
- }
- };
+ (left, right) -> -Integer.compare(left.getBindPriority(), right.getBindPriority());
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt
index 38fe34e..42d3f81 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt
@@ -18,8 +18,6 @@
import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepositoryImpl
-import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
-import com.android.systemui.qs.footer.data.repository.UserSwitcherRepositoryImpl
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
import dagger.Binds
@@ -28,7 +26,6 @@
/** Dagger module to provide/bind footer actions singletons. */
@Module
interface FooterActionsModule {
- @Binds fun userSwitcherRepository(impl: UserSwitcherRepositoryImpl): UserSwitcherRepository
@Binds
fun foregroundServicesRepository(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index 8b2c3de..c91ed13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -38,10 +38,10 @@
import com.android.systemui.qs.QSSecurityFooterUtils
import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
-import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
import com.android.systemui.security.data.repository.SecurityRepository
import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.user.data.repository.UserSwitcherRepository
import com.android.systemui.user.domain.interactor.UserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalytics.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalytics.kt
new file mode 100644
index 0000000..0d15a5b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalytics.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.analytics
+
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.QSEvent
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Tracks QS tiles analytic events to [UiEventLogger]. */
+@SysUISingleton
+class QSTileAnalytics
+@Inject
+constructor(
+ private val uiEventLogger: UiEventLogger,
+) {
+
+ fun trackUserAction(config: QSTileConfig, action: QSTileUserAction) {
+ logAction(config, action)
+ }
+
+ private fun logAction(config: QSTileConfig, action: QSTileUserAction) {
+ uiEventLogger.logWithInstanceId(
+ action.getQSEvent(),
+ 0,
+ config.metricsSpec,
+ config.instanceId,
+ )
+ }
+
+ private fun QSTileUserAction.getQSEvent(): QSEvent =
+ when (this) {
+ is QSTileUserAction.Click -> QSEvent.QS_ACTION_CLICK
+ is QSTileUserAction.LongClick -> QSEvent.QS_ACTION_LONG_PRESS
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
new file mode 100644
index 0000000..70a683b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.logging
+
+import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.QSTilesDefaultLog
+import com.android.systemui.log.dagger.QSTilesLogBuffers
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.StatusBarState
+import javax.inject.Inject
+import javax.inject.Provider
+
+@SysUISingleton
+class QSTileLogger
+@Inject
+constructor(
+ @QSTilesLogBuffers logBuffers: Map<TileSpec, LogBuffer>,
+ @QSTilesDefaultLog private val defaultLogBufferProvider: Provider<LogBuffer>,
+ private val mStatusBarStateController: StatusBarStateController,
+) {
+ @GuardedBy("logBufferCache") private val logBufferCache = logBuffers.toMutableMap()
+
+ /**
+ * Tracks user action when it's first received by the ViewModel and before it reaches the
+ * pipeline
+ */
+ fun logUserAction(
+ userAction: QSTileUserAction,
+ tileSpec: TileSpec,
+ hasData: Boolean,
+ hasTileState: Boolean,
+ ) {
+ tileSpec
+ .getLogBuffer()
+ .log(
+ tileSpec.getLogTag(),
+ LogLevel.DEBUG,
+ {
+ str1 = userAction.toLogString()
+ int1 = mStatusBarStateController.state
+ bool1 = hasTileState
+ bool2 = hasData
+ },
+ {
+ "tile $str1: " +
+ "statusBarState=${StatusBarState.toString(int1)}, " +
+ "hasState=$bool1, " +
+ "hasData=$bool2"
+ }
+ )
+ }
+
+ /** Tracks user action when it's rejected by false gestures */
+ fun logUserActionRejectedByFalsing(
+ userAction: QSTileUserAction,
+ tileSpec: TileSpec,
+ ) {
+ tileSpec
+ .getLogBuffer()
+ .log(
+ tileSpec.getLogTag(),
+ LogLevel.DEBUG,
+ { str1 = userAction.toLogString() },
+ { "tile $str1: rejected by falsing" }
+ )
+ }
+
+ /** Tracks user action when it's rejected according to the policy */
+ fun logUserActionRejectedByPolicy(
+ userAction: QSTileUserAction,
+ tileSpec: TileSpec,
+ ) {
+ tileSpec
+ .getLogBuffer()
+ .log(
+ tileSpec.getLogTag(),
+ LogLevel.DEBUG,
+ { str1 = userAction.toLogString() },
+ { "tile $str1: rejected by policy" }
+ )
+ }
+
+ /**
+ * Tracks user actions when it reaches the pipeline and mixes with the last tile state and data
+ */
+ fun <T> logUserActionPipeline(
+ tileSpec: TileSpec,
+ userAction: QSTileUserAction,
+ tileState: QSTileState,
+ data: T,
+ ) {
+ tileSpec
+ .getLogBuffer()
+ .log(
+ tileSpec.getLogTag(),
+ LogLevel.DEBUG,
+ {
+ str1 = userAction.toLogString()
+ str2 = tileState.toLogString()
+ str3 = data.toString().take(DATA_MAX_LENGTH)
+ },
+ {
+ "tile $str1 pipeline: " +
+ "statusBarState=${StatusBarState.toString(int1)}, " +
+ "state=$str2, " +
+ "data=$str3"
+ }
+ )
+ }
+
+ /** Tracks state changes based on the data and trigger event. */
+ fun <T> logStateUpdate(
+ tileSpec: TileSpec,
+ trigger: StateUpdateTrigger,
+ tileState: QSTileState,
+ data: T,
+ ) {
+ tileSpec
+ .getLogBuffer()
+ .log(
+ tileSpec.getLogTag(),
+ LogLevel.DEBUG,
+ {
+ str1 = trigger.toLogString()
+ str2 = tileState.toLogString()
+ str3 = data.toString().take(DATA_MAX_LENGTH)
+ },
+ { "tile state update: trigger=$str1, state=$str2, data=$str3" }
+ )
+ }
+
+ private fun TileSpec.getLogTag(): String = "${TAG_FORMAT_PREFIX}_${this.spec}"
+
+ private fun TileSpec.getLogBuffer(): LogBuffer =
+ synchronized(logBufferCache) {
+ logBufferCache.getOrPut(this) { defaultLogBufferProvider.get() }
+ }
+
+ private fun StateUpdateTrigger.toLogString(): String =
+ when (this) {
+ is StateUpdateTrigger.ForceUpdate -> "force"
+ is StateUpdateTrigger.InitialRequest -> "init"
+ is StateUpdateTrigger.UserAction<*> -> action.toLogString()
+ }
+
+ private fun QSTileUserAction.toLogString(): String =
+ when (this) {
+ is QSTileUserAction.Click -> "click"
+ is QSTileUserAction.LongClick -> "long click"
+ }
+
+ /* Shortened version of a data class toString() */
+ private fun QSTileState.toLogString(): String =
+ "[label=$label, " +
+ "state=$activationState, " +
+ "s_label=$secondaryLabel, " +
+ "cd=$contentDescription, " +
+ "sd=$stateDescription, " +
+ "svi=$sideViewIcon, " +
+ "enabled=$enabledState, " +
+ "a11y=$expandedAccessibilityClassName" +
+ "]"
+
+ private companion object {
+ const val TAG_FORMAT_PREFIX = "QSLog"
+ const val DATA_MAX_LENGTH = 50
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
index 58a335e..2114751 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
@@ -20,12 +20,15 @@
import androidx.annotation.VisibleForTesting
import com.android.internal.util.Preconditions
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
@@ -33,6 +36,7 @@
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.kotlin.throttle
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher
@@ -70,6 +74,9 @@
private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
private val mapper: QSTileDataToStateMapper<DATA_TYPE>,
private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
+ private val falsingManager: FalsingManager,
+ private val qsTileAnalytics: QSTileAnalytics,
+ private val qsTileLogger: QSTileLogger,
private val backgroundDispatcher: CoroutineDispatcher,
private val tileScope: CoroutineScope,
) : QSTileViewModel {
@@ -81,6 +88,9 @@
@Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
@Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>,
disabledByPolicyInteractor: DisabledByPolicyInteractor,
+ falsingManager: FalsingManager,
+ qsTileAnalytics: QSTileAnalytics,
+ qsTileLogger: QSTileLogger,
@Background backgroundDispatcher: CoroutineDispatcher,
) : this(
config,
@@ -88,6 +98,9 @@
tileDataInteractor,
mapper,
disabledByPolicyInteractor,
+ falsingManager,
+ qsTileAnalytics,
+ qsTileLogger,
backgroundDispatcher,
CoroutineScope(SupervisorJob())
)
@@ -98,8 +111,10 @@
MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val forceUpdates: MutableSharedFlow<Unit> =
MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ private val spec
+ get() = config.tileSpec
- private lateinit var tileData: SharedFlow<DATA_TYPE>
+ private lateinit var tileData: SharedFlow<DataWithTrigger<DATA_TYPE>>
override lateinit var state: SharedFlow<QSTileState>
override val isAvailable: StateFlow<Boolean> =
@@ -128,8 +143,14 @@
@CallSuper
override fun onActionPerformed(userAction: QSTileUserAction) {
- Preconditions.checkState(tileData.replayCache.isNotEmpty())
Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
+
+ qsTileLogger.logUserAction(
+ userAction,
+ spec,
+ tileData.replayCache.isNotEmpty(),
+ state.replayCache.isNotEmpty()
+ )
userInputs.tryEmit(userAction)
}
@@ -142,7 +163,16 @@
state =
tileData
// TODO(b/299908705): log data and corresponding tile state
- .map { mapper.map(config, it) }
+ .map { dataWithTrigger ->
+ mapper.map(config, dataWithTrigger.data).also { state ->
+ qsTileLogger.logStateUpdate(
+ spec,
+ dataWithTrigger.trigger,
+ state,
+ dataWithTrigger.data
+ )
+ }
+ }
.flowOn(backgroundDispatcher)
.shareIn(
tileScope,
@@ -158,7 +188,7 @@
currentLifeState = lifecycle
}
- private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
+ private fun createTileDataFlow(): SharedFlow<DataWithTrigger<DATA_TYPE>> =
userIds
.flatMapLatest { userId ->
merge(
@@ -180,7 +210,7 @@
request.trigger.tileData as DATA_TYPE,
)
}
- dataFlow
+ dataFlow.map { DataWithTrigger(it, request.trigger) }
}
.flowOn(backgroundDispatcher)
.shareIn(
@@ -193,21 +223,53 @@
data class StateWithData<T>(val state: QSTileState, val data: T)
return when (config.policy) {
- is QSTilePolicy.NoRestrictions -> userInputs
- is QSTilePolicy.Restricted ->
- userInputs.filter {
- val result =
- disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction)
- !disabledByPolicyInteractor.handlePolicyResult(result)
+ is QSTilePolicy.NoRestrictions -> userInputs
+ is QSTilePolicy.Restricted ->
+ userInputs.filter { action ->
+ val result =
+ disabledByPolicyInteractor.isDisabled(
+ userId,
+ config.policy.userRestriction
+ )
+ !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled ->
+ if (isDisabled) {
+ qsTileLogger.logUserActionRejectedByPolicy(action, spec)
+ }
+ }
+ }
+ }
+ .filter { action ->
+ val isFalseAction =
+ when (action) {
+ is QSTileUserAction.Click ->
+ falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
+ is QSTileUserAction.LongClick ->
+ falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+ }
+ if (isFalseAction) {
+ qsTileLogger.logUserActionRejectedByFalsing(action, spec)
}
- // Skip the input until there is some data
- }.sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) {
- input,
- stateWithData ->
- StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data)
- }
+ !isFalseAction
+ }
+ .throttle(500)
+ // Skip the input until there is some data
+ .sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) {
+ input,
+ stateWithData ->
+ StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data).also {
+ qsTileLogger.logUserActionPipeline(
+ spec,
+ it.action,
+ stateWithData.state,
+ stateWithData.data
+ )
+ qsTileAnalytics.trackUserAction(config, it.action)
+ }
+ }
}
+ private data class DataWithTrigger<T>(val data: T, val trigger: StateUpdateTrigger)
+
interface Factory<T> {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index 1a6cf99..4a3bcae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -26,6 +26,7 @@
val tileIcon: Icon,
@StringRes val tileLabelRes: Int,
val instanceId: InstanceId,
+ val metricsSpec: String = tileSpec.spec,
val policy: QSTilePolicy = QSTilePolicy.NoRestrictions,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 249c831..e2de37f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -18,6 +18,8 @@
import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepository
+import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryImpl
import com.android.systemui.statusbar.data.repository.StatusBarModeRepository
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl
import com.android.systemui.statusbar.phone.LightBarController
@@ -49,6 +51,11 @@
abstract fun bindStatusBarModeRepositoryStart(impl: StatusBarModeRepositoryImpl): CoreStartable
@Binds
+ abstract fun bindKeyguardStatusBarRepository(
+ impl: KeyguardStatusBarRepositoryImpl
+ ): KeyguardStatusBarRepository
+
+ @Binds
@IntoMap
@ClassKey(OngoingCallController::class)
abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
new file mode 100644
index 0000000..8136de9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import android.content.Context
+import com.android.internal.R
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.user.data.repository.UserSwitcherRepository
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/**
+ * Repository for data that's specific to the status bar **on keyguard**. For data that applies to
+ * all status bars, use [StatusBarModeRepository].
+ */
+interface KeyguardStatusBarRepository {
+ /** True if we can show the user switcher on keyguard and false otherwise. */
+ val isKeyguardUserSwitcherEnabled: Flow<Boolean>
+}
+
+@SysUISingleton
+class KeyguardStatusBarRepositoryImpl
+@Inject
+constructor(
+ context: Context,
+ configurationController: ConfigurationController,
+ userSwitcherRepository: UserSwitcherRepository,
+) : KeyguardStatusBarRepository {
+ private val relevantConfigChanges: Flow<Unit> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onSmallestScreenWidthChanged() {
+ trySend(Unit)
+ }
+
+ override fun onDensityOrFontScaleChanged() {
+ trySend(Unit)
+ }
+ }
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
+
+ private val isKeyguardUserSwitcherConfigEnabled: Flow<Boolean> =
+ // The config depends on screen size and user enabled settings, so re-fetch whenever any of
+ // those change.
+ merge(userSwitcherRepository.isEnabled.map {}, relevantConfigChanges).map {
+ context.resources.getBoolean(R.bool.config_keyguardUserSwitcher)
+ }
+
+ /** True if we can show the user switcher on keyguard and false otherwise. */
+ override val isKeyguardUserSwitcherEnabled: Flow<Boolean> =
+ combine(
+ userSwitcherRepository.isEnabled,
+ isKeyguardUserSwitcherConfigEnabled,
+ ) { isEnabled, isKeyguardEnabled ->
+ isEnabled && isKeyguardEnabled
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractor.kt
new file mode 100644
index 0000000..e0c30e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class KeyguardStatusBarInteractor
+@Inject
+constructor(
+ keyguardStatusBarRepository: KeyguardStatusBarRepository,
+) {
+ /** True if we can show the user switcher on keyguard and false otherwise. */
+ val isKeyguardUserSwitcherEnabled: Flow<Boolean> =
+ keyguardStatusBarRepository.isKeyguardUserSwitcherEnabled
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 7efa705..58126ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -43,9 +43,9 @@
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -367,15 +367,22 @@
mMultiUserAvatar.setImageDrawable(picture);
}
- /** Should only be called from {@link KeyguardStatusBarViewController}. */
- void onBatteryLevelChanged(boolean charging) {
+ /**
+ * Should only be called from {@link KeyguardStatusBarViewController} or
+ * {@link com.android.systemui.statusbar.ui.binder.KeyguardStatusBarViewBinder}.
+ */
+ public void onBatteryChargingChanged(boolean charging) {
if (mBatteryCharging != charging) {
mBatteryCharging = charging;
updateVisibilities();
}
}
- void setKeyguardUserSwitcherEnabled(boolean enabled) {
+ /**
+ * Should only be called from {@link KeyguardStatusBarViewController} or
+ * {@link com.android.systemui.statusbar.ui.binder.KeyguardStatusBarViewBinder}.
+ */
+ public void setKeyguardUserSwitcherEnabled(boolean enabled) {
mKeyguardUserSwitcherEnabled = enabled;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 9cf9714..2960520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -172,7 +172,7 @@
new BatteryController.BatteryStateChangeCallback() {
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- mView.onBatteryLevelChanged(charging);
+ mView.onBatteryChargingChanged(charging);
}
};
@@ -430,11 +430,18 @@
/** Sets whether user switcher is enabled. */
public void setKeyguardUserSwitcherEnabled(boolean enabled) {
+ if (isMigrationEnabled()) {
+ return;
+ }
mView.setKeyguardUserSwitcherEnabled(enabled);
}
/** Sets whether this controller should listen to battery updates. */
public void setBatteryListening(boolean listening) {
+ if (isMigrationEnabled()) {
+ return;
+ }
+
if (listening == mBatteryListening) {
return;
}
@@ -472,6 +479,10 @@
/** Animate the keyguard status bar in. */
public void animateKeyguardStatusBarIn() {
+ if (isMigrationEnabled()) {
+ return;
+ }
+
mLogger.log(TAG, LogLevel.DEBUG, "animating status bar in");
if (mDisableStateTracker.isDisabled()) {
// If our view is disabled, don't allow us to animate in.
@@ -488,6 +499,10 @@
/** Animate the keyguard status bar out. */
public void animateKeyguardStatusBarOut(long startDelay, long duration) {
+ if (isMigrationEnabled()) {
+ return;
+ }
+
mLogger.log(TAG, LogLevel.DEBUG, "animating status bar out");
ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f);
anim.addUpdateListener(mAnimatorUpdateListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt
index c63ef9e..6988e21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt
@@ -22,6 +22,8 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.phone.KeyguardStatusBarView
import com.android.systemui.statusbar.ui.viewmodel.KeyguardStatusBarViewModel
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
/** Binds [KeyguardStatusBarViewModel] to [KeyguardStatusBarView]. */
object KeyguardStatusBarViewBinder {
@@ -32,8 +34,18 @@
) {
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.isVisible.collect { isVisible ->
- view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+ launch {
+ viewModel.isVisible.collect { isVisible ->
+ view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+ }
+ }
+
+ launch { viewModel.isBatteryCharging.collect { view.onBatteryChargingChanged(it) } }
+
+ launch {
+ viewModel.isKeyguardUserSwitcherEnabled.distinctUntilChanged().collect {
+ view.setKeyguardUserSwitcherEnabled(it)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index ddfed87..5da01e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -16,12 +16,18 @@
package com.android.systemui.statusbar.ui.viewmodel
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -41,6 +47,8 @@
constructor(
@Application scope: CoroutineScope,
keyguardInteractor: KeyguardInteractor,
+ keyguardStatusBarInteractor: KeyguardStatusBarInteractor,
+ batteryController: BatteryController,
) {
/** True if this view should be visible and false otherwise. */
val isVisible: StateFlow<Boolean> =
@@ -51,4 +59,26 @@
!isDozing && statusBarState == StatusBarState.KEYGUARD
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ /** True if the device's battery is currently charging and false otherwise. */
+ // Note: Never make this an eagerly-started state flow so that the callback is removed when the
+ // keyguard status bar view isn't attached.
+ val isBatteryCharging: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(
+ level: Int,
+ pluggedIn: Boolean,
+ charging: Boolean,
+ ) {
+ trySend(charging)
+ }
+ }
+ batteryController.addCallback(callback)
+ awaitClose { batteryController.removeCallback(callback) }
+ }
+
+ /** True if we can show the user switcher on keyguard and false otherwise. */
+ val isKeyguardUserSwitcherEnabled: Flow<Boolean> =
+ keyguardStatusBarInteractor.isKeyguardUserSwitcherEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt
index 18ae107..71352ef 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt
@@ -23,4 +23,6 @@
@Module
interface UserRepositoryModule {
@Binds fun bindRepository(impl: UserRepositoryImpl): UserRepository
+
+ @Binds fun userSwitcherRepository(impl: UserSwitcherRepositoryImpl): UserSwitcherRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt
rename to packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
index 5fa75ad..dc7fadd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.footer.data.repository
+package com.android.systemui.user.data.repository
import android.content.Context
import android.graphics.drawable.Drawable
@@ -22,7 +22,6 @@
import android.os.UserManager
import android.provider.Settings.Global.USER_SWITCHER_ENABLED
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -30,6 +29,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.SettingObserver
import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserSwitcherController
@@ -48,6 +48,9 @@
interface UserSwitcherRepository {
/** The current [UserSwitcherStatusModel]. */
val userSwitcherStatus: Flow<UserSwitcherStatusModel>
+
+ /** Whether the user switcher is currently enabled. */
+ val isEnabled: Flow<Boolean>
}
@SysUISingleton
@@ -66,8 +69,7 @@
private val showUserSwitcherForSingleUser =
context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)
- /** Whether the user switcher is currently enabled. */
- private val isEnabled: Flow<Boolean> = conflatedCallbackFlow {
+ override val isEnabled: Flow<Boolean> = conflatedCallbackFlow {
suspend fun updateState() {
trySendWithFailureLogging(isUserSwitcherEnabled(), TAG)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
new file mode 100644
index 0000000..2c4e10e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.analytics
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.QSEvent
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class QSTileAnalyticsTest : SysuiTestCase() {
+
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+
+ private lateinit var underTest: QSTileAnalytics
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = QSTileAnalytics(uiEventLogger)
+ }
+
+ @Test
+ fun testClickIsLogged() {
+ underTest.trackUserAction(config, QSTileUserAction.Click(null))
+
+ verify(uiEventLogger)
+ .logWithInstanceId(
+ eq(QSEvent.QS_ACTION_CLICK),
+ eq(0),
+ eq("test_spec"),
+ eq(InstanceId.fakeInstanceId(0))
+ )
+ }
+
+ @Test
+ fun testLongClickIsLogged() {
+ underTest.trackUserAction(config, QSTileUserAction.LongClick(null))
+
+ verify(uiEventLogger)
+ .logWithInstanceId(
+ eq(QSEvent.QS_ACTION_LONG_PRESS),
+ eq(0),
+ eq("test_spec"),
+ eq(InstanceId.fakeInstanceId(0))
+ )
+ }
+
+ private companion object {
+
+ val config = QSTileConfigTestBuilder.build()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
new file mode 100644
index 0000000..4401e0d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.logging
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dump.LogcatEchoTrackerAlways
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class QSTileLoggerTest : SysuiTestCase() {
+
+ @Mock private lateinit var statusBarController: StatusBarStateController
+
+ private val chattyLogBuffer = LogBuffer("TestChatty", 5, LogcatEchoTrackerAlways())
+ private val logBuffer = LogBuffer("Test", 1, LogcatEchoTrackerAlways())
+
+ private lateinit var underTest: QSTileLogger
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ QSTileLogger(
+ mapOf(TileSpec.create("chatty_tile") to chattyLogBuffer),
+ { logBuffer },
+ statusBarController
+ )
+ }
+
+ @Test
+ fun testChattyLog() {
+ underTest.logUserActionRejectedByFalsing(
+ QSTileUserAction.Click(null),
+ TileSpec.create("chatty_tile"),
+ )
+ underTest.logUserActionRejectedByFalsing(
+ QSTileUserAction.Click(null),
+ TileSpec.create("chatty_tile"),
+ )
+
+ val logs = chattyLogBuffer.getStringBuffer().lines().filter { it.isNotBlank() }
+ assertThat(logs).hasSize(2)
+ logs.forEach { assertThat(it).contains("tile click: rejected by falsing") }
+ }
+
+ @Test
+ fun testLogUserAction() {
+ underTest.logUserAction(
+ QSTileUserAction.Click(null),
+ TileSpec.create("test_spec"),
+ hasData = false,
+ hasTileState = false,
+ )
+
+ assertThat(logBuffer.getStringBuffer())
+ .contains("tile click: statusBarState=SHADE, hasState=false, hasData=false")
+ }
+
+ @Test
+ fun testLogUserActionRejectedByFalsing() {
+ underTest.logUserActionRejectedByFalsing(
+ QSTileUserAction.Click(null),
+ TileSpec.create("test_spec"),
+ )
+
+ assertThat(logBuffer.getStringBuffer()).contains("tile click: rejected by falsing")
+ }
+
+ @Test
+ fun testLogUserActionRejectedByPolicy() {
+ underTest.logUserActionRejectedByPolicy(
+ QSTileUserAction.Click(null),
+ TileSpec.create("test_spec"),
+ )
+
+ assertThat(logBuffer.getStringBuffer()).contains("tile click: rejected by policy")
+ }
+
+ @Test
+ fun testLogUserActionPipeline() {
+ underTest.logUserActionPipeline(
+ TileSpec.create("test_spec"),
+ QSTileUserAction.Click(null),
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
+ "test_data",
+ )
+
+ assertThat(logBuffer.getStringBuffer())
+ .contains(
+ "tile click pipeline: " +
+ "statusBarState=SHADE, " +
+ "state=[" +
+ "label=, " +
+ "state=INACTIVE, " +
+ "s_label=null, " +
+ "cd=null, " +
+ "sd=null, " +
+ "svi=None, " +
+ "enabled=ENABLED, " +
+ "a11y=null" +
+ "], " +
+ "data=test_data"
+ )
+ }
+
+ @Test
+ fun testLogStateUpdate() {
+ underTest.logStateUpdate(
+ TileSpec.create("test_spec"),
+ StateUpdateTrigger.ForceUpdate,
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
+ "test_data",
+ )
+
+ assertThat(logBuffer.getStringBuffer())
+ .contains(
+ "tile state update: " +
+ "trigger=force, " +
+ "state=[" +
+ "label=, " +
+ "state=INACTIVE, " +
+ "s_label=null, " +
+ "cd=null, " +
+ "sd=null, " +
+ "svi=None, " +
+ "enabled=ENABLED, " +
+ "a11y=null" +
+ "], " +
+ "data=test_data"
+ )
+ }
+
+ private fun LogBuffer.getStringBuffer(): String {
+ val stringWriter = StringWriter()
+ dump(PrintWriter(stringWriter), 0)
+ return stringWriter.buffer.toString()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
index 9024c6c..4760dfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -1,21 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.viewmodel
-import android.graphics.drawable.ShapeDrawable
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.android.internal.logging.InstanceId
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
import com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
@@ -26,6 +44,8 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
// TODO(b/299909368): Add more tests
@MediumTest
@@ -34,9 +54,13 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
+ @Mock private lateinit var qsTileLogger: QSTileLogger
+ @Mock private lateinit var qsTileAnalytics: QSTileAnalytics
+
private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
+ private val fakeFalsingManager = FalsingManagerFake()
private val testCoroutineDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testCoroutineDispatcher)
@@ -45,6 +69,7 @@
@Before
fun setup() {
+ MockitoAnnotations.initMocks(this)
underTest = createViewModel(testScope)
}
@@ -79,6 +104,9 @@
QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
},
fakeDisabledByPolicyInteractor,
+ fakeFalsingManager,
+ qsTileAnalytics,
+ qsTileLogger,
testCoroutineDispatcher,
scope.backgroundScope,
)
@@ -88,7 +116,7 @@
val TEST_QS_TILE_CONFIG =
QSTileConfig(
TileSpec.create("default"),
- Icon.Loaded(ShapeDrawable(), null),
+ Icon.Resource(0, null),
0,
InstanceId.fakeInstanceId(0),
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
new file mode 100644
index 0000000..f1e6a05
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeKeyguardStatusBarRepository : KeyguardStatusBarRepository {
+ override val isKeyguardUserSwitcherEnabled = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt
new file mode 100644
index 0000000..b1c994c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.user.data.repository.FakeUserSwitcherRepository
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+class KeyguardStatusBarRepositoryImplTest : SysuiTestCase() {
+ private val testScope = TestScope()
+ private val configurationController = mock<ConfigurationController>()
+ private val userSwitcherRepository = FakeUserSwitcherRepository()
+
+ val underTest =
+ KeyguardStatusBarRepositoryImpl(
+ context,
+ configurationController,
+ userSwitcherRepository,
+ )
+
+ private val configurationListener: ConfigurationController.ConfigurationListener
+ get() {
+ val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+ verify(configurationController).addCallback(capture(captor))
+ return captor.value
+ }
+
+ @Test
+ fun isKeyguardUserSwitcherEnabled_switcherNotEnabled_false() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+
+ userSwitcherRepository.isEnabled.value = false
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isKeyguardUserSwitcherEnabled_keyguardConfigNotEnabled_false() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+ userSwitcherRepository.isEnabled.value = true
+
+ context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isKeyguardUserSwitcherEnabled_switchEnabledAndKeyguardConfigEnabled_true() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+
+ userSwitcherRepository.isEnabled.value = true
+ context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isKeyguardUserSwitcherEnabled_refetchedOnSmallestWidthChanged() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+ userSwitcherRepository.isEnabled.value = true
+ context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true)
+ assertThat(latest).isTrue()
+
+ context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false)
+ configurationListener.onSmallestScreenWidthChanged()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isKeyguardUserSwitcherEnabled_refetchedOnDensityChanged() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+ userSwitcherRepository.isEnabled.value = true
+ context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true)
+ assertThat(latest).isTrue()
+
+ context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false)
+ configurationListener.onDensityOrFontScaleChanged()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isKeyguardUserSwitcherEnabled_refetchedOnEnabledChanged() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+
+ userSwitcherRepository.isEnabled.value = false
+ context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true)
+ assertThat(latest).isFalse()
+
+ // WHEN the switcher becomes enabled but the keyguard switcher becomes disabled
+ context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false)
+ userSwitcherRepository.isEnabled.value = true
+
+ // THEN the value is still false because the keyguard config is refetched
+ assertThat(latest).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index c0d248e..6484389 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -64,12 +64,13 @@
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.res.R;
import com.android.systemui.scene.SceneTestUtils;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
import com.android.systemui.shade.ShadeViewStateProvider;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository;
+import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -156,7 +157,6 @@
public void setup() throws Exception {
mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false);
mShadeViewStateProvider = new TestShadeViewStateProvider();
- mShadeViewStateProvider = new TestShadeViewStateProvider();
MockitoAnnotations.initMocks(this);
@@ -176,7 +176,9 @@
mViewModel =
new KeyguardStatusBarViewModel(
mTestScope.getBackgroundScope(),
- mKeyguardInteractor);
+ mKeyguardInteractor,
+ new KeyguardStatusBarInteractor(new FakeKeyguardStatusBarRepository()),
+ mBatteryController);
allowTestableLooperAsMainThread();
TestableLooper.get(this).runWithLooper(() -> {
@@ -320,6 +322,15 @@
}
@Test
+ public void setBatteryListening_true_flagOn_callbackNotAdded() {
+ mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, true);
+
+ mController.setBatteryListening(true);
+
+ verify(mBatteryController, never()).addCallback(any());
+ }
+
+ @Test
public void updateTopClipping_viewClippingUpdated() {
int viewTop = 20;
mKeyguardStatusBarView.setTop(viewTop);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index f4078d5..1bc346d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -29,13 +29,23 @@
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
+import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
+import org.mockito.Mockito.verify
@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardStatusBarViewModelTest : SysuiTestCase() {
private val testScope = TestScope()
private val sceneTestUtils = SceneTestUtils(this)
@@ -54,11 +64,18 @@
) {
sceneTestUtils.sceneInteractor()
}
+ private val keyguardStatusBarInteractor =
+ KeyguardStatusBarInteractor(
+ FakeKeyguardStatusBarRepository(),
+ )
+ private val batteryController = mock<BatteryController>()
private val underTest =
KeyguardStatusBarViewModel(
testScope.backgroundScope,
keyguardInteractor,
+ keyguardStatusBarInteractor,
+ batteryController,
)
@Test
@@ -102,4 +119,46 @@
assertThat(latest).isTrue()
}
+
+ @Test
+ fun isBatteryCharging_matchesCallback() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isBatteryCharging)
+ runCurrent()
+
+ val captor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(captor))
+ val callback = captor.value
+
+ callback.onBatteryLevelChanged(
+ /* level= */ 2,
+ /* pluggedIn= */ false,
+ /* charging= */ true,
+ )
+
+ assertThat(latest).isTrue()
+
+ callback.onBatteryLevelChanged(
+ /* level= */ 2,
+ /* pluggedIn= */ true,
+ /* charging= */ false,
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isBatteryCharging_unregistersWhenNotListening() =
+ testScope.runTest {
+ val job = underTest.isBatteryCharging.launchIn(this)
+ runCurrent()
+
+ val captor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(captor))
+
+ job.cancel()
+ runCurrent()
+
+ verify(batteryController).removeCallback(captor.value)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
new file mode 100644
index 0000000..758fe93a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user.data.repository
+
+import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeUserSwitcherRepository : UserSwitcherRepository {
+ override val isEnabled = MutableStateFlow(false)
+ override val userSwitcherStatus =
+ MutableStateFlow<UserSwitcherStatusModel>(UserSwitcherStatusModel.Disabled)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index cd009df..d6632a3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -38,16 +38,12 @@
import androidx.test.uiautomator.UiDevice;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.FakeBroadcastDispatcher;
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
-import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import org.junit.After;
@@ -128,20 +124,8 @@
// reference and are never sent to the Context. This will also prevent a real
// BroadcastDispatcher from actually registering receivers.
mDependency.injectTestDependency(BroadcastDispatcher.class, mFakeBroadcastDispatcher);
- // A lot of tests get the FalsingManager, often via several layers of indirection.
- // None of them actually need it.
- mDependency.injectTestDependency(FalsingManager.class, new FalsingManagerFake());
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
- // A lot of tests get the LocalBluetoothManager, often via several layers of indirection.
- // None of them actually need it.
- mDependency.injectMockDependency(LocalBluetoothManager.class);
-
- // Notifications tests are injecting one of these, causing many classes (including
- // KeyguardUpdateMonitor to be created (injected).
- // TODO(b/1531701009) Clean up NotificationContentView creation to prevent this
- mDependency.injectMockDependency(SmartReplyController.class);
-
// Make sure that all tests on any SystemUIDialog does not crash because this dependency
// is missing (constructing the actual one would throw).
// TODO(b/219008720): Remove this.
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 1a893f8..bf77b1a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -36,8 +36,6 @@
import com.android.systemui.qs.QSSecurityFooterUtils
import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepositoryImpl
-import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
-import com.android.systemui.qs.footer.data.repository.UserSwitcherRepositoryImpl
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -51,6 +49,8 @@
import com.android.systemui.statusbar.policy.SecurityController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.repository.UserSwitcherRepository
+import com.android.systemui.user.data.repository.UserSwitcherRepositoryImpl
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
new file mode 100644
index 0000000..201926d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.viewmodel
+
+import androidx.annotation.StringRes
+import com.android.internal.logging.InstanceId
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+object QSTileConfigTestBuilder {
+
+ fun build(configure: BuildingScope.() -> Unit = {}): QSTileConfig =
+ BuildingScope().apply(configure).build()
+
+ class BuildingScope {
+ var tileSpec: TileSpec = TileSpec.create("test_spec")
+ var tileIcon: Icon = Icon.Resource(0, ContentDescription.Resource(0))
+ @StringRes var tileLabel: Int = 0
+ var instanceId: InstanceId = InstanceId.fakeInstanceId(0)
+ var metricsSpec: String = tileSpec.spec
+ var policy: QSTilePolicy = QSTilePolicy.NoRestrictions
+
+ fun build() =
+ QSTileConfig(
+ tileSpec,
+ tileIcon,
+ tileLabel,
+ instanceId,
+ metricsSpec,
+ policy,
+ )
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 9dd0dca..852e36d 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -218,6 +218,9 @@
void setActivityLaunchDefaultAllowed(boolean activityLaunchDefaultAllowed) {
synchronized (mGenericWindowPolicyControllerLock) {
+ if (mActivityLaunchAllowedByDefault != activityLaunchDefaultAllowed) {
+ mActivityPolicyExemptions.clear();
+ }
mActivityLaunchAllowedByDefault = activityLaunchDefaultAllowed;
}
}
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
index 20de40e..3d610d3 100644
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -343,7 +343,7 @@
};
mContext.bindServiceAsUser(intent, mConnection,
- Context.BIND_AUTO_CREATE, UserHandle.of(UserHandle.USER_SYSTEM));
+ Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
Slog.i(TAG, "Explicit health check service is bound");
}
}
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c094c12..dd54334 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -522,7 +522,8 @@
Exception res = null;
final ContentResolver resolver = context.getContentResolver();
try {
- Settings.Global.resetToDefaultsAsUser(resolver, null, mode, UserHandle.USER_SYSTEM);
+ Settings.Global.resetToDefaultsAsUser(resolver, null, mode,
+ UserHandle.SYSTEM.getIdentifier());
} catch (Exception e) {
res = new RuntimeException("Failed to reset global settings", e);
}
@@ -779,12 +780,13 @@
}
private static int[] getAllUserIds() {
- int[] userIds = { UserHandle.USER_SYSTEM };
+ int systemUserId = UserHandle.SYSTEM.getIdentifier();
+ int[] userIds = { systemUserId };
try {
for (File file : FileUtils.listFilesOrEmpty(Environment.getDataSystemDeDirectory())) {
try {
final int userId = Integer.parseInt(file.getName());
- if (userId != UserHandle.USER_SYSTEM) {
+ if (userId != systemUserId) {
userIds = ArrayUtils.appendInt(userIds, userId);
}
} catch (NumberFormatException ignored) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 6e984bb..b05b397 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -330,7 +330,7 @@
String describeBlockedStateLocked() {
final String prefix;
if (mCurrentMonitor == null) {
- prefix = "Blocked in handler on ";
+ prefix = "Blocked in handler";
} else {
prefix = "Blocked in monitor " + mCurrentMonitor.getClass().getName();
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 83a3125..c9528d8 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -707,7 +707,8 @@
}
private boolean checkCallerHasSystemRoutingPermissions(int pid, int uid) {
- return checkCallerHasModifyAudioRoutingPermission(pid, uid);
+ return checkCallerHasModifyAudioRoutingPermission(pid, uid)
+ || checkCallerHasBluetoothPermissions(pid, uid);
}
private boolean checkCallerHasModifyAudioRoutingPermission(int pid, int uid) {
diff --git a/services/tests/servicestests/src/com/android/server/timezone/OWNERS b/services/tests/servicestests/src/com/android/server/timezone/OWNERS
index 6165260..d64cbcd 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/timezone/OWNERS
@@ -1,2 +1,2 @@
-# Bug component: 24949
+# Bug component: 847766
include /services/core/java/com/android/server/timezone/OWNERS
diff --git a/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java
new file mode 100644
index 0000000..6801c94
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usage;
+
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+import android.os.Trace;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Shared singleton default priority thread for usage stats message handling.
+ *
+ * @see com.android.internal.os.BackgroundThread
+ */
+public final class UsageStatsHandlerThread extends HandlerThread {
+ private static final long SLOW_DISPATCH_THRESHOLD_MS = 10_000;
+ private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000;
+ private static UsageStatsHandlerThread sInstance;
+ private static Handler sHandler;
+ private static Executor sHandlerExecutor;
+
+ private UsageStatsHandlerThread() {
+ super("usagestats.default", Process.THREAD_PRIORITY_DEFAULT);
+ }
+
+ private static void ensureThreadLocked() {
+ if (sInstance == null) {
+ sInstance = new UsageStatsHandlerThread();
+ sInstance.start();
+ final Looper looper = sInstance.getLooper();
+ looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
+ looper.setSlowLogThresholdMs(
+ SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
+ sHandler = new Handler(sInstance.getLooper());
+ sHandlerExecutor = new HandlerExecutor(sHandler);
+ }
+ }
+
+ /** Returns the UsageStatsHandlerThread singleton */
+ public static UsageStatsHandlerThread get() {
+ synchronized (UsageStatsHandlerThread.class) {
+ ensureThreadLocked();
+ return sInstance;
+ }
+ }
+
+ /** Returns the singleton handler for UsageStatsHandlerThread */
+ public static Handler getHandler() {
+ synchronized (UsageStatsHandlerThread.class) {
+ ensureThreadLocked();
+ return sHandler;
+ }
+ }
+
+ /** Returns the singleton handler executor for UsageStatsHandlerThread */
+ public static Executor getExecutor() {
+ synchronized (UsageStatsHandlerThread.class) {
+ ensureThreadLocked();
+ return sHandlerExecutor;
+ }
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 58b5ae5..18c960e 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -106,6 +106,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
@@ -201,7 +202,8 @@
static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9;
private final Object mLock = new Object();
- Handler mHandler;
+ private Handler mHandler;
+ private Handler mIoHandler;
AppOpsManager mAppOps;
UserManager mUserManager;
PackageManager mPackageManager;
@@ -233,7 +235,7 @@
private final SparseArray<LinkedList<Event>> mReportedEvents = new SparseArray<>();
final SparseArray<ArraySet<String>> mUsageReporters = new SparseArray();
final SparseArray<ActivityData> mVisibleActivities = new SparseArray();
- @GuardedBy("mLock")
+ @GuardedBy("mLaunchTimeAlarmQueues") // Don't hold the main lock
private final SparseArray<LaunchTimeAlarmQueue> mLaunchTimeAlarmQueues = new SparseArray<>();
@GuardedBy("mUsageEventListeners") // Don't hold the main lock when calling out
private final ArraySet<UsageStatsManagerInternal.UsageEventListener> mUsageEventListeners =
@@ -279,6 +281,38 @@
}
}
+ private final Handler.Callback mIoHandlerCallback = (msg) -> {
+ switch (msg.what) {
+ case MSG_UID_STATE_CHANGED: {
+ final int uid = msg.arg1;
+ final int procState = msg.arg2;
+
+ final int newCounter = (procState <= ActivityManager.PROCESS_STATE_TOP) ? 0 : 1;
+ synchronized (mUidToKernelCounter) {
+ final int oldCounter = mUidToKernelCounter.get(uid, 0);
+ if (newCounter != oldCounter) {
+ mUidToKernelCounter.put(uid, newCounter);
+ try {
+ FileUtils.stringToFile(KERNEL_COUNTER_FILE, uid + " " + newCounter);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to update counter set: " + e);
+ }
+ }
+ }
+ return true;
+ }
+ case MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK: {
+ final int userId = msg.arg1;
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+ "usageStatsHandleEstimatedLaunchTimesOnUser(" + userId + ")");
+ handleEstimatedLaunchTimesOnUserUnlock(userId);
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ return true;
+ }
+ }
+ return false;
+ };
+
private final Injector mInjector;
public UsageStatsService(Context context) {
@@ -298,7 +332,9 @@
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
mPackageManager = getContext().getPackageManager();
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mHandler = new H(BackgroundThread.get().getLooper());
+
+ mHandler = new H(UsageStatsHandlerThread.get().getLooper());
+ mIoHandler = new Handler(IoThread.get().getLooper(), mIoHandlerCallback);
mAppStandby = mInjector.getAppStandbyController(getContext());
mResponseStatsTracker = new BroadcastResponseStatsTracker(mAppStandby, getContext());
@@ -424,6 +460,9 @@
}
mUserUnlockedStates.remove(userId);
mUserState.put(userId, null); // release the service (mainly for GC)
+ }
+
+ synchronized (mLaunchTimeAlarmQueues) {
LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
if (alarmQueue != null) {
alarmQueue.removeAllAlarms();
@@ -476,11 +515,13 @@
}
reportEvent(unlockEvent, userId);
- mHandler.obtainMessage(MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK, userId, 0).sendToTarget();
+ mIoHandler.obtainMessage(MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK,
+ userId, 0).sendToTarget();
// Remove all the stats stored in memory and in system DE.
mReportedEvents.remove(userId);
deleteRecursively(new File(Environment.getDataSystemDeDirectory(userId), "usagestats"));
+
// Force a flush to disk for the current user to ensure important events are persisted.
// Note: there is a very very small chance that the system crashes between deleting
// the stats above from DE and persisting them to CE here in which case we will lose
@@ -599,7 +640,7 @@
private final IUidObserver mUidObserver = new UidObserver() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
- mHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
+ mIoHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
}
@Override
@@ -671,16 +712,18 @@
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED);
}
- private static void deleteRecursively(File f) {
- File[] files = f.listFiles();
- if (files != null) {
- for (File subFile : files) {
- deleteRecursively(subFile);
+ private static void deleteRecursively(final File path) {
+ if (path.isDirectory()) {
+ final File[] files = path.listFiles();
+ if (files != null) {
+ for (File subFile : files) {
+ deleteRecursively(subFile);
+ }
}
}
- if (f.exists() && !f.delete()) {
- Slog.e(TAG, "Failed to delete " + f);
+ if (path.exists() && !path.delete()) {
+ Slog.e(TAG, "Failed to delete " + path);
}
}
@@ -1241,6 +1284,9 @@
Slog.i(TAG, "Removing user " + userId + " and all data.");
mUserState.remove(userId);
mAppTimeLimit.onUserRemoved(userId);
+ }
+
+ synchronized (mLaunchTimeAlarmQueues) {
final LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
if (alarmQueue != null) {
alarmQueue.removeAllAlarms();
@@ -1271,6 +1317,13 @@
}
}
+ synchronized (mLaunchTimeAlarmQueues) {
+ final LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
+ if (alarmQueue != null) {
+ alarmQueue.removeAlarmForKey(packageName);
+ }
+ }
+
final int tokenRemoved;
synchronized (mLock) {
final long timeRemoved = System.currentTimeMillis();
@@ -1279,10 +1332,7 @@
// when the user service is initialized and package manager is queried.
return;
}
- final LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
- if (alarmQueue != null) {
- alarmQueue.removeAlarmForKey(packageName);
- }
+
final UserUsageStatsService userService = mUserState.get(userId);
if (userService == null) {
return;
@@ -1492,60 +1542,63 @@
estimatedLaunchTime = calculateEstimatedPackageLaunchTime(userId, packageName);
mAppStandby.setEstimatedLaunchTime(packageName, userId, estimatedLaunchTime);
- synchronized (mLock) {
- LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
- if (alarmQueue == null) {
- alarmQueue = new LaunchTimeAlarmQueue(
- userId, getContext(), BackgroundThread.get().getLooper());
- mLaunchTimeAlarmQueues.put(userId, alarmQueue);
- }
- alarmQueue.addAlarm(packageName,
- SystemClock.elapsedRealtime() + (estimatedLaunchTime - now));
- }
+ getOrCreateLaunchTimeAlarmQueue(userId).addAlarm(packageName,
+ SystemClock.elapsedRealtime() + (estimatedLaunchTime - now));
}
return estimatedLaunchTime;
}
+ private LaunchTimeAlarmQueue getOrCreateLaunchTimeAlarmQueue(int userId) {
+ synchronized (mLaunchTimeAlarmQueues) {
+ LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
+ if (alarmQueue == null) {
+ alarmQueue = new LaunchTimeAlarmQueue(
+ userId, getContext(), BackgroundThread.get().getLooper());
+ mLaunchTimeAlarmQueues.put(userId, alarmQueue);
+ }
+
+ return alarmQueue;
+ }
+ }
+
@CurrentTimeMillisLong
private long calculateEstimatedPackageLaunchTime(int userId, String packageName) {
- synchronized (mLock) {
- final long endTime = System.currentTimeMillis();
- final long beginTime = endTime - ONE_WEEK;
- final long unknownTime = endTime + UNKNOWN_LAUNCH_TIME_DELAY_MS;
- final UsageEvents events = queryEarliestEventsForPackage(
- userId, beginTime, endTime, packageName, Event.ACTIVITY_RESUMED);
- if (events == null) {
- if (DEBUG) {
- Slog.d(TAG, "No events for " + userId + ":" + packageName);
- }
- return unknownTime;
+ final long endTime = System.currentTimeMillis();
+ final long beginTime = endTime - ONE_WEEK;
+ final long unknownTime = endTime + UNKNOWN_LAUNCH_TIME_DELAY_MS;
+ final UsageEvents events = queryEarliestEventsForPackage(
+ userId, beginTime, endTime, packageName, Event.ACTIVITY_RESUMED);
+ if (events == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "No events for " + userId + ":" + packageName);
}
- final UsageEvents.Event event = new UsageEvents.Event();
- final boolean hasMoreThan24HoursOfHistory;
- if (events.getNextEvent(event)) {
- hasMoreThan24HoursOfHistory = endTime - event.getTimeStamp() > ONE_DAY;
- if (DEBUG) {
- Slog.d(TAG, userId + ":" + packageName + " history > 24 hours="
- + hasMoreThan24HoursOfHistory);
- }
- } else {
- if (DEBUG) {
- Slog.d(TAG, userId + ":" + packageName + " has no events");
- }
- return unknownTime;
- }
- do {
- if (event.getEventType() == Event.ACTIVITY_RESUMED) {
- final long timestamp = event.getTimeStamp();
- final long nextLaunch =
- calculateNextLaunchTime(hasMoreThan24HoursOfHistory, timestamp);
- if (nextLaunch > endTime) {
- return nextLaunch;
- }
- }
- } while (events.getNextEvent(event));
return unknownTime;
}
+ final UsageEvents.Event event = new UsageEvents.Event();
+ final boolean hasMoreThan24HoursOfHistory;
+ if (events.getNextEvent(event)) {
+ hasMoreThan24HoursOfHistory = endTime - event.getTimeStamp() > ONE_DAY;
+ if (DEBUG) {
+ Slog.d(TAG, userId + ":" + packageName + " history > 24 hours="
+ + hasMoreThan24HoursOfHistory);
+ }
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, userId + ":" + packageName + " has no events");
+ }
+ return unknownTime;
+ }
+ do {
+ if (event.getEventType() == Event.ACTIVITY_RESUMED) {
+ final long timestamp = event.getTimeStamp();
+ final long nextLaunch =
+ calculateNextLaunchTime(hasMoreThan24HoursOfHistory, timestamp);
+ if (nextLaunch > endTime) {
+ return nextLaunch;
+ }
+ }
+ } while (events.getNextEvent(event));
+ return unknownTime;
}
@CurrentTimeMillisLong
@@ -1566,61 +1619,54 @@
}
private void handleEstimatedLaunchTimesOnUserUnlock(int userId) {
- synchronized (mLock) {
- final long nowElapsed = SystemClock.elapsedRealtime();
- final long now = System.currentTimeMillis();
- final long beginTime = now - ONE_WEEK;
- final UsageEvents events = queryEarliestAppEvents(
- userId, beginTime, now, Event.ACTIVITY_RESUMED);
- if (events == null) {
- return;
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long now = System.currentTimeMillis();
+ final long beginTime = now - ONE_WEEK;
+ final UsageEvents events = queryEarliestAppEvents(
+ userId, beginTime, now, Event.ACTIVITY_RESUMED);
+ if (events == null) {
+ return;
+ }
+ final ArrayMap<String, Boolean> hasMoreThan24HoursOfHistory = new ArrayMap<>();
+ final UsageEvents.Event event = new UsageEvents.Event();
+ boolean changedTimes = false;
+ final LaunchTimeAlarmQueue alarmQueue = getOrCreateLaunchTimeAlarmQueue(userId);
+ for (boolean unprocessedEvent = events.getNextEvent(event); unprocessedEvent;
+ unprocessedEvent = events.getNextEvent(event)) {
+ final String packageName = event.getPackageName();
+ if (!hasMoreThan24HoursOfHistory.containsKey(packageName)) {
+ boolean hasHistory = now - event.getTimeStamp() > ONE_DAY;
+ if (DEBUG) {
+ Slog.d(TAG,
+ userId + ":" + packageName + " history > 24 hours=" + hasHistory);
+ }
+ hasMoreThan24HoursOfHistory.put(packageName, hasHistory);
}
- final ArrayMap<String, Boolean> hasMoreThan24HoursOfHistory = new ArrayMap<>();
- final UsageEvents.Event event = new UsageEvents.Event();
- LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
- if (alarmQueue == null) {
- alarmQueue = new LaunchTimeAlarmQueue(
- userId, getContext(), BackgroundThread.get().getLooper());
- mLaunchTimeAlarmQueues.put(userId, alarmQueue);
- }
- boolean changedTimes = false;
- for (boolean unprocessedEvent = events.getNextEvent(event); unprocessedEvent;
- unprocessedEvent = events.getNextEvent(event)) {
- final String packageName = event.getPackageName();
- if (!hasMoreThan24HoursOfHistory.containsKey(packageName)) {
- boolean hasHistory = now - event.getTimeStamp() > ONE_DAY;
+ if (event.getEventType() == Event.ACTIVITY_RESUMED) {
+ long estimatedLaunchTime =
+ mAppStandby.getEstimatedLaunchTime(packageName, userId);
+ if (estimatedLaunchTime < now || estimatedLaunchTime == Long.MAX_VALUE) {
+ //noinspection ConstantConditions
+ estimatedLaunchTime = calculateNextLaunchTime(
+ hasMoreThan24HoursOfHistory.get(packageName), event.getTimeStamp());
+ mAppStandby.setEstimatedLaunchTime(
+ packageName, userId, estimatedLaunchTime);
+ }
+ if (estimatedLaunchTime < now + ONE_WEEK) {
+ // Before a user is unlocked, we don't know when the app will be launched,
+ // so we give callers the UNKNOWN time. Now that we have a better estimate,
+ // we should notify them of the change.
if (DEBUG) {
- Slog.d(TAG,
- userId + ":" + packageName + " history > 24 hours=" + hasHistory);
+ Slog.d(TAG, "User " + userId + " unlock resulting in"
+ + " estimated launch time change for " + packageName);
}
- hasMoreThan24HoursOfHistory.put(packageName, hasHistory);
+ changedTimes |= stageChangedEstimatedLaunchTime(userId, packageName);
}
- if (event.getEventType() == Event.ACTIVITY_RESUMED) {
- long estimatedLaunchTime =
- mAppStandby.getEstimatedLaunchTime(packageName, userId);
- if (estimatedLaunchTime < now || estimatedLaunchTime == Long.MAX_VALUE) {
- //noinspection ConstantConditions
- estimatedLaunchTime = calculateNextLaunchTime(
- hasMoreThan24HoursOfHistory.get(packageName), event.getTimeStamp());
- mAppStandby.setEstimatedLaunchTime(
- packageName, userId, estimatedLaunchTime);
- }
- if (estimatedLaunchTime < now + ONE_WEEK) {
- // Before a user is unlocked, we don't know when the app will be launched,
- // so we give callers the UNKNOWN time. Now that we have a better estimate,
- // we should notify them of the change.
- if (DEBUG) {
- Slog.d(TAG, "User " + userId + " unlock resulting in"
- + " estimated launch time change for " + packageName);
- }
- changedTimes |= stageChangedEstimatedLaunchTime(userId, packageName);
- }
- alarmQueue.addAlarm(packageName, nowElapsed + (estimatedLaunchTime - now));
- }
+ alarmQueue.addAlarm(packageName, nowElapsed + (estimatedLaunchTime - now));
}
- if (changedTimes) {
- mHandler.sendEmptyMessage(MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED);
- }
+ }
+ if (changedTimes) {
+ mHandler.sendEmptyMessage(MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED);
}
}
@@ -1989,37 +2035,11 @@
case MSG_PACKAGE_REMOVED:
onPackageRemoved(msg.arg1, (String) msg.obj);
break;
- case MSG_UID_STATE_CHANGED: {
- final int uid = msg.arg1;
- final int procState = msg.arg2;
-
- final int newCounter = (procState <= ActivityManager.PROCESS_STATE_TOP) ? 0 : 1;
- synchronized (mUidToKernelCounter) {
- final int oldCounter = mUidToKernelCounter.get(uid, 0);
- if (newCounter != oldCounter) {
- mUidToKernelCounter.put(uid, newCounter);
- try {
- FileUtils.stringToFile(KERNEL_COUNTER_FILE, uid + " " + newCounter);
- } catch (IOException e) {
- Slog.w(TAG, "Failed to update counter set: " + e);
- }
- }
- }
- break;
- }
case MSG_ON_START:
synchronized (mLock) {
loadGlobalComponentUsageLocked();
}
break;
- case MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK: {
- final int userId = msg.arg1;
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
- "usageStatsHandleEstimatedLaunchTimesOnUser(" + userId + ")");
- handleEstimatedLaunchTimesOnUserUnlock(userId);
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- }
- break;
case MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED: {
removeMessages(MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED);
@@ -2587,11 +2607,12 @@
@Override
public void reportChooserSelection(@NonNull String packageName, int userId,
@NonNull String contentType, String[] annotations, @NonNull String action) {
- // A valid package name, content type, and action must be provided for these events
- Objects.requireNonNull(packageName);
- Objects.requireNonNull(contentType);
- Objects.requireNonNull(action);
- if (contentType.isBlank() || action.isBlank()) {
+ if (packageName == null) {
+ throw new IllegalArgumentException("Package selection must not be null.");
+ }
+ // A valid contentType and action must be provided for chooser selection events.
+ if (contentType == null || contentType.isBlank()
+ || action == null || action.isBlank()) {
return;
}