diff options
187 files changed, 2852 insertions, 1704 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING index 6a4a52a5658b..9ec799f73b41 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING @@ -4,7 +4,6 @@ "name": "CtsUsageStatsTestCases", "options": [ {"include-filter": "android.app.usage.cts.UsageStatsTest"}, - {"include-filter": "android.app.usage.cts.BroadcastResponseStatsTest"}, {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.MediumTest"}, @@ -12,6 +11,13 @@ ] }, { + "name": "CtsBRSTestCases", + "options": [ + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + }, + { "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.usage"}, diff --git a/core/api/current.txt b/core/api/current.txt index 3392d250d426..0b10d648e36f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -33699,7 +33699,7 @@ package android.os { method public boolean isInteractive(); method public boolean isLowPowerStandbyEnabled(); method public boolean isPowerSaveMode(); - method public boolean isRebootingUserspaceSupported(); + method @Deprecated public boolean isRebootingUserspaceSupported(); method @Deprecated public boolean isScreenOn(); method public boolean isSustainedPerformanceModeSupported(); method public boolean isWakeLockLevelSupported(int); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a02fd84ce33c..eca2015fa3ed 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10748,7 +10748,7 @@ package android.os { field @RequiresPermission(android.Manifest.permission.MANAGE_LOW_POWER_STANDBY) public static final String ACTION_LOW_POWER_STANDBY_PORTS_CHANGED = "android.os.action.LOW_POWER_STANDBY_PORTS_CHANGED"; field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1 field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0 - field public static final String REBOOT_USERSPACE = "userspace"; + field @Deprecated public static final String REBOOT_USERSPACE = "userspace"; field public static final int SOUND_TRIGGER_MODE_ALL_DISABLED = 2; // 0x2 field public static final int SOUND_TRIGGER_MODE_ALL_ENABLED = 0; // 0x0 field public static final int SOUND_TRIGGER_MODE_CRITICAL_ONLY = 1; // 0x1 diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java index a86c0c9a4463..0438ecce78ef 100644 --- a/core/java/android/content/res/Element.java +++ b/core/java/android/content/res/Element.java @@ -128,6 +128,7 @@ public class Element { protected static final String TAG_ATTR_VALUE = "value"; protected static final String TAG_ATTR_VERSION_NAME = "versionName"; protected static final String TAG_ATTR_WRITE_PERMISSION = "writePermission"; + protected static final String TAG_ATTR_ZYGOTE_PRELOAD_NAME = "zygotePreloadName"; // The length of mTagCounters corresponds to the number of tags defined in getCounterIdx. If new // tags are added then the size here should be increased to match. @@ -374,6 +375,7 @@ public class Element { case TAG_ATTR_TASK_AFFINITY: case TAG_ATTR_WRITE_PERMISSION: case TAG_ATTR_VERSION_NAME: + case TAG_ATTR_ZYGOTE_PRELOAD_NAME: return MAX_ATTR_LEN_NAME; case TAG_ATTR_PATH: case TAG_ATTR_PATH_ADVANCED_PATTERN: @@ -488,6 +490,7 @@ public class Element { case R.styleable.AndroidManifestApplication_requiredAccountType: case R.styleable.AndroidManifestApplication_restrictedAccountType: case R.styleable.AndroidManifestApplication_taskAffinity: + case R.styleable.AndroidManifestApplication_zygotePreloadName: return MAX_ATTR_LEN_NAME; default: return DEFAULT_MAX_STRING_ATTR_LENGTH; @@ -738,6 +741,7 @@ public class Element { switch (name) { case TAG_ATTR_BACKUP_AGENT: case TAG_ATTR_NAME: + case TAG_ATTR_ZYGOTE_PRELOAD_NAME: return true; default: return false; @@ -766,7 +770,8 @@ public class Element { return index == R.styleable.AndroidManifestActivityAlias_targetActivity; case TAG_APPLICATION: return index == R.styleable.AndroidManifestApplication_backupAgent - || index == R.styleable.AndroidManifestApplication_name; + || index == R.styleable.AndroidManifestApplication_name + || index == R.styleable.AndroidManifestApplication_zygotePreloadName; case TAG_INSTRUMENTATION: return index == R.styleable.AndroidManifestInstrumentation_name; case TAG_PROVIDER: diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java index 977ef60c43b6..bcea7978a804 100644 --- a/core/java/android/os/PerformanceHintManager.java +++ b/core/java/android/os/PerformanceHintManager.java @@ -55,15 +55,20 @@ public final class PerformanceHintManager { * duration. * * @param tids The list of threads to be associated with this session. They must be part of - * this process' thread group. + * this process' thread group * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new - * session. + * session * @return the new session if it is supported on this device, null if hint session is not - * supported on this device. + * supported on this device or the tid doesn't belong to the application + * @throws IllegalArgumentException if the thread id list is empty, or + * initialTargetWorkDurationNanos is non-positive */ @Nullable public Session createHintSession(@NonNull int[] tids, long initialTargetWorkDurationNanos) { - Preconditions.checkNotNull(tids, "tids cannot be null"); + Objects.requireNonNull(tids, "tids cannot be null"); + if (tids.length == 0) { + throw new IllegalArgumentException("thread id list can't be empty."); + } Preconditions.checkArgumentPositive(initialTargetWorkDurationNanos, "the hint target duration should be positive."); long nativeSessionPtr = nativeCreateSession(mNativeManagerPtr, tids, @@ -75,7 +80,7 @@ public final class PerformanceHintManager { /** * Get preferred update rate information for this device. * - * @return the preferred update rate supported by device software. + * @return the preferred update rate supported by device software */ public long getPreferredUpdateRateNanos() { return nativeGetPreferredUpdateRateNanos(mNativeManagerPtr); @@ -209,7 +214,7 @@ public final class PerformanceHintManager { /** * Sends performance hints to inform the hint session of changes in the workload. * - * @param hint The hint to send to the session. + * @param hint The hint to send to the session * * @hide */ @@ -230,11 +235,11 @@ public final class PerformanceHintManager { * Note that this is not an oneway method. * * @param tids The list of threads to be associated with this session. They must be - * part of this app's thread group. + * part of this app's thread group * - * @throws IllegalStateException if the hint session is not in the foreground. - * @throws IllegalArgumentException if the thread id list is empty. - * @throws SecurityException if any thread id doesn't belong to the application. + * @throws IllegalStateException if the hint session is not in the foreground + * @throws IllegalArgumentException if the thread id list is empty + * @throws SecurityException if any thread id doesn't belong to the application */ public void setThreads(@NonNull int[] tids) { if (mNativeSessionPtr == 0) { diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index c6b9d20b450d..d676509d9317 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -33,7 +33,6 @@ import android.app.PropertyInvalidatedCache; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.service.dreams.Sandman; -import android.sysprop.InitProperties; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -868,6 +867,8 @@ public final class PowerManager { /** * The 'reason' value used for rebooting userspace. + * + * @deprecated userspace reboot is not supported * @hide */ @SystemApi @@ -1824,16 +1825,18 @@ public final class PowerManager { * <p>This method exists solely for the sake of re-using same logic between {@code PowerManager} * and {@code PowerManagerService}. * + * @deprecated TODO(b/292469129): remove this method. * @hide */ public static boolean isRebootingUserspaceSupportedImpl() { - return InitProperties.is_userspace_reboot_supported().orElse(false); + return false; } /** * Returns {@code true} if this device supports rebooting userspace. + * + * @deprecated userspace reboot is deprecated, this method always returns {@code false}. */ - // TODO(b/138605180): add link to documentation once it's ready. public boolean isRebootingUserspaceSupported() { return isRebootingUserspaceSupportedImpl(); } diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index dae9b5ea6a43..60622f18fe3b 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -99,13 +99,10 @@ "BatteryStats[^/]*\\.java", "BatteryUsageStats[^/]*\\.java", "PowerComponents\\.java", + "PowerMonitor[^/]*\\.java", "[^/]*BatteryConsumer[^/]*\\.java" ], - "name": "FrameworksServicesTests", - "options": [ - { "include-filter": "com.android.server.power.stats" }, - { "exclude-filter": "com.android.server.power.stats.BatteryStatsTests" } - ] + "name": "PowerStatsTests" }, { "file_patterns": [ diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java index 987ac2e90240..d588c487844b 100644 --- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java @@ -809,11 +809,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; // cancelled } InputConnection ic = getInputConnection(); - // Note we do NOT check isActive() here, because this is safe - // for an IME to call at any time, and we need to allow it - // through to clean up our state after the IME has switched to - // another client. - if (ic == null) { + if (ic == null || !isActive()) { Log.w(TAG, "finishComposingTextFromImm on inactive InputConnection"); return; } @@ -837,11 +833,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; // cancelled } InputConnection ic = getInputConnection(); - // Note we do NOT check isActive() here, because this is safe - // for an IME to call at any time, and we need to allow it - // through to clean up our state after the IME has switched to - // another client. - if (ic == null) { + if (ic == null && !isActive()) { Log.w(TAG, "finishComposingText on inactive InputConnection"); return; } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 1d6778b8a4a9..55b2251ac196 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -1498,6 +1498,11 @@ public class HorizontalScrollView extends FrameLayout { * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume. */ private int consumeFlingInStretch(int unconsumed) { + int scrollX = getScrollX(); + if (scrollX < 0 || scrollX > getScrollRange()) { + // We've overscrolled, so don't stretch + return unconsumed; + } if (unconsumed > 0 && mEdgeGlowLeft != null && mEdgeGlowLeft.getDistance() != 0f) { int size = getWidth(); float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size; diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index eeb6b43a89f2..d330ebf73323 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -1566,6 +1566,11 @@ public class ScrollView extends FrameLayout { * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume. */ private int consumeFlingInStretch(int unconsumed) { + int scrollY = getScrollY(); + if (scrollY < 0 || scrollY > getScrollRange()) { + // We've overscrolled, so don't stretch + return unconsumed; + } if (unconsumed > 0 && mEdgeGlowTop != null && mEdgeGlowTop.getDistance() != 0f) { int size = getHeight(); float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size; diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java deleted file mode 100644 index f34aabbeded6..000000000000 --- a/core/java/com/android/internal/app/NetInitiatedActivity.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2007 Google Inc. - * - * 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.internal.app; - -import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.location.LocationManagerInternal; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.Log; - -import com.android.internal.R; -import com.android.internal.location.GpsNetInitiatedHandler; -import com.android.server.LocalServices; - -/** - * This activity is shown to the user for them to accept or deny network-initiated - * requests. It uses the alert dialog style. It will be launched from a notification. - */ -public class NetInitiatedActivity extends AlertActivity implements DialogInterface.OnClickListener { - - private static final String TAG = "NetInitiatedActivity"; - - private static final boolean DEBUG = true; - - private static final int POSITIVE_BUTTON = AlertDialog.BUTTON_POSITIVE; - private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON_NEGATIVE; - - private static final int GPS_NO_RESPONSE_TIME_OUT = 1; - // Received ID from intent, -1 when no notification is in progress - private int notificationId = -1; - private int timeout = -1; - private int default_response = -1; - private int default_response_timeout = 6; - - private final Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - switch (msg.what) { - case GPS_NO_RESPONSE_TIME_OUT: { - if (notificationId != -1) { - sendUserResponse(default_response); - } - finish(); - } - break; - default: - } - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); - - // Set up the "dialog" - final Intent intent = getIntent(); - final AlertController.AlertParams p = mAlertParams; - Context context = getApplicationContext(); - p.mTitle = intent.getStringExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_TITLE); - p.mMessage = intent.getStringExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_MESSAGE); - p.mPositiveButtonText = String.format(context.getString(R.string.gpsVerifYes)); - p.mPositiveButtonListener = this; - p.mNegativeButtonText = String.format(context.getString(R.string.gpsVerifNo)); - p.mNegativeButtonListener = this; - - notificationId = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_NOTIF_ID, -1); - timeout = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_TIMEOUT, default_response_timeout); - default_response = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_DEFAULT_RESPONSE, GpsNetInitiatedHandler.GPS_NI_RESPONSE_ACCEPT); - if (DEBUG) Log.d(TAG, "onCreate() : notificationId: " + notificationId + " timeout: " + timeout + " default_response:" + default_response); - - mHandler.sendMessageDelayed(mHandler.obtainMessage(GPS_NO_RESPONSE_TIME_OUT), (timeout * 1000)); - setupAlert(); - } - - @Override - protected void onResume() { - super.onResume(); - if (DEBUG) Log.d(TAG, "onResume"); - } - - @Override - protected void onPause() { - super.onPause(); - if (DEBUG) Log.d(TAG, "onPause"); - } - - /** - * {@inheritDoc} - */ - public void onClick(DialogInterface dialog, int which) { - if (which == POSITIVE_BUTTON) { - sendUserResponse(GpsNetInitiatedHandler.GPS_NI_RESPONSE_ACCEPT); - } - if (which == NEGATIVE_BUTTON) { - sendUserResponse(GpsNetInitiatedHandler.GPS_NI_RESPONSE_DENY); - } - - // No matter what, finish the activity - finish(); - notificationId = -1; - } - - // Respond to NI Handler under GnssLocationProvider, 1 = accept, 2 = deny - private void sendUserResponse(int response) { - if (DEBUG) Log.d(TAG, "sendUserResponse, response: " + response); - LocationManagerInternal lm = LocalServices.getService(LocationManagerInternal.class); - lm.sendNiResponse(notificationId, response); - } -} diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 2445daf89b64..ac15f11ee989 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -40,6 +40,7 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.UiThread; @@ -68,6 +69,7 @@ import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Insets; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; @@ -93,6 +95,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.Button; @@ -488,6 +491,14 @@ public class ResolverActivity extends Activity implements rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets); mResolverDrawerLayout = rdl; + + for (int i = 0, size = mMultiProfilePagerAdapter.getCount(); i < size; i++) { + View view = mMultiProfilePagerAdapter.getItem(i).rootView.findViewById( + R.id.resolver_list); + if (view != null) { + view.setAccessibilityDelegate(new AppListAccessibilityDelegate(rdl)); + } + } } mProfileView = findViewById(R.id.profile_button); @@ -2607,4 +2618,41 @@ public class ResolverActivity extends Activity implements } return resolveInfo.userHandle; } + + /** + * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially + * invisible target in the list. + */ + private static class AppListAccessibilityDelegate extends View.AccessibilityDelegate { + private final ResolverDrawerLayout mDrawer; + @Nullable + private final View mBottomBar; + private final Rect mRect = new Rect(); + + private AppListAccessibilityDelegate(ResolverDrawerLayout drawer) { + mDrawer = drawer; + mBottomBar = mDrawer.findViewById(R.id.button_bar_container); + } + + @Override + public boolean onRequestSendAccessibilityEvent(@androidx.annotation.NonNull ViewGroup host, + @NonNull View child, + @NonNull AccessibilityEvent event) { + boolean result = super.onRequestSendAccessibilityEvent(host, child, event); + if (result && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED + && mDrawer.isCollapsed()) { + child.getBoundsOnScreen(mRect); + int childTop = mRect.top; + int childBottom = mRect.bottom; + mDrawer.getBoundsOnScreen(mRect, true); + int bottomBarHeight = mBottomBar == null ? 0 : mBottomBar.getHeight(); + int drawerTop = mRect.top; + int drawerBottom = mRect.bottom - bottomBarHeight; + if (drawerTop > childTop || childBottom > drawerBottom) { + mDrawer.setCollapsed(false); + } + } + return result; + } + } } diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING index 60b160ad18c2..d552e0b8c643 100644 --- a/core/java/com/android/internal/os/TEST_MAPPING +++ b/core/java/com/android/internal/os/TEST_MAPPING @@ -38,11 +38,18 @@ ], "name": "FrameworksServicesTests", "options": [ - { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" } ] }, { + "file_patterns": [ + "Battery[^/]*\\.java", + "Kernel[^/]*\\.java", + "[^/]*Power[^/]*\\.java" + ], + "name": "PowerStatsTests" + }, + { "name": "FrameworksCoreTests", "options": [ { diff --git a/core/java/com/android/internal/power/TEST_MAPPING b/core/java/com/android/internal/power/TEST_MAPPING index c6cab183d970..1946f5cc99eb 100644 --- a/core/java/com/android/internal/power/TEST_MAPPING +++ b/core/java/com/android/internal/power/TEST_MAPPING @@ -8,11 +8,7 @@ ] }, { - "name": "FrameworksServicesTests", - "options": [ - { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } - ] + "name": "PowerStatsTests" } ] } diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 4cf17b78f489..199854818989 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -523,13 +523,14 @@ android_media_AudioSystem_dyn_policy_callback(int event, String8 regId, int val) } jclass clazz = env->FindClass(kClassPathName); - const char* zechars = regId.string(); - jstring zestring = env->NewStringUTF(zechars); + const char *regIdString = regId.string(); + jstring regIdJString = env->NewStringUTF(regIdString); env->CallStaticVoidMethod(clazz, gAudioPolicyEventHandlerMethods.postDynPolicyEventFromNative, - event, zestring, val); + event, regIdJString, val); - env->ReleaseStringUTFChars(zestring, zechars); + const char *regIdJChars = env->GetStringUTFChars(regIdJString, NULL); + env->ReleaseStringUTFChars(regIdJString, regIdJChars); env->DeleteLocalRef(clazz); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 86cf80e8847c..67710f64e1e4 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -821,7 +821,6 @@ <protected-broadcast android:name="android.intent.action.PROFILE_REMOVED" /> <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_SENT_ACTION" /> <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_DELIVERY_ACTION" /> - <protected-broadcast android:name="com.android.internal.telephony.data.ACTION_RETRY" /> <protected-broadcast android:name="android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED" /> <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW" /> <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a11eaa91b2a6..d828f33ca514 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1786,10 +1786,6 @@ <string name="biometric_dialog_default_title">Verify it\u2019s you</string> <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] --> <string name="biometric_dialog_default_subtitle">Use your biometric to continue</string> - <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with fingerprint. [CHAR LIMIT=70] --> - <string name="biometric_dialog_fingerprint_subtitle">Use your fingerprint to continue</string> - <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with face. [CHAR LIMIT=70] --> - <string name="biometric_dialog_face_subtitle">Use your face to continue</string> <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=90] --> <string name="biometric_or_screen_lock_dialog_default_subtitle">Use your biometric or screen lock to continue</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5806e93d4a93..63f29a8fc1c6 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2585,8 +2585,6 @@ <java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" /> <java-symbol type="string" name="biometric_dialog_default_title" /> <java-symbol type="string" name="biometric_dialog_default_subtitle" /> - <java-symbol type="string" name="biometric_dialog_face_subtitle" /> - <java-symbol type="string" name="biometric_dialog_fingerprint_subtitle" /> <java-symbol type="string" name="biometric_or_screen_lock_dialog_default_subtitle" /> <java-symbol type="string" name="biometric_error_hw_unavailable" /> <java-symbol type="string" name="biometric_error_user_canceled" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index c7aaeb0339bd..c14da299c6ef 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -91,7 +91,6 @@ android_test { java_resources: [":ApkVerityTestCertDer"], data: [ - ":BstatsTestApp", ":BinderDeathRecipientHelperApp1", ":BinderDeathRecipientHelperApp2", ":com.android.cts.helpers.aosp", diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 129de649a4b5..31755efb88ed 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -231,6 +231,28 @@ </intent-filter> </activity> + <activity android:name="android.widget.HorizontalScrollViewActivity" + android:label="HorizontalScrollViewActivity" + android:screenOrientation="portrait" + android:exported="true" + android:theme="@android:style/Theme.Material.Light"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" /> + </intent-filter> + </activity> + + <activity android:name="android.widget.ScrollViewActivity" + android:label="ScrollViewActivity" + android:screenOrientation="portrait" + android:exported="true" + android:theme="@android:style/Theme.Material.Light"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" /> + </intent-filter> + </activity> + <activity android:name="android.widget.DatePickerActivity" android:label="DatePickerActivity" android:screenOrientation="portrait" diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml index 3e4c47b36ed9..05b309b2cd52 100644 --- a/core/tests/coretests/AndroidTest.xml +++ b/core/tests/coretests/AndroidTest.xml @@ -20,7 +20,6 @@ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="FrameworksCoreTests.apk" /> - <option name="test-file-name" value="BstatsTestApp.apk" /> <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" /> <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" /> </target_preparer> diff --git a/core/tests/coretests/BstatsTestApp/OWNERS b/core/tests/coretests/BstatsTestApp/OWNERS deleted file mode 100644 index 4068e2bc03b7..000000000000 --- a/core/tests/coretests/BstatsTestApp/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /BATTERY_STATS_OWNERS diff --git a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml new file mode 100644 index 000000000000..866e1a95c3f5 --- /dev/null +++ b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2015 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 + --> + +<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/horizontal_scroll_view"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + </LinearLayout> +</HorizontalScrollView> diff --git a/core/tests/coretests/res/layout/activity_scroll_view.xml b/core/tests/coretests/res/layout/activity_scroll_view.xml new file mode 100644 index 000000000000..61fabf8ee437 --- /dev/null +++ b/core/tests/coretests/res/layout/activity_scroll_view.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2015 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 + --> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/scroll_view"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + </LinearLayout> +</ScrollView> diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java index 2c03fdc2ef0d..b0826ab77035 100644 --- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java +++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java @@ -69,6 +69,24 @@ public class PerformanceHintManagerTest { } @Test + public void testCreateHintSession_noTids() { + assertThrows(NullPointerException.class, () -> { + mPerformanceHintManager.createHintSession( + null, DEFAULT_TARGET_NS); + }); + assertThrows(IllegalArgumentException.class, () -> { + mPerformanceHintManager.createHintSession( + new int[]{}, DEFAULT_TARGET_NS); + }); + } + + @Test + public void testCreateHintSession_invalidTids() { + assertNull(mPerformanceHintManager.createHintSession( + new int[]{-1}, DEFAULT_TARGET_NS)); + } + + @Test public void testGetPreferredUpdateRateNanos() { if (createSession() != null) { assertTrue(mPerformanceHintManager.getPreferredUpdateRateNanos() > 0); diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java new file mode 100644 index 000000000000..21013545008c --- /dev/null +++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java @@ -0,0 +1,34 @@ +/* + * 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 android.widget; + +import android.app.Activity; +import android.os.Bundle; + +import com.android.frameworks.coretests.R; + +/** + * An activity for testing the TextView widget. + */ +public class HorizontalScrollViewActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_horizontal_scroll_view); + } +} diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java new file mode 100644 index 000000000000..86f26e59e370 --- /dev/null +++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java @@ -0,0 +1,71 @@ +/* + * 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 android.widget; + +import static org.junit.Assert.assertEquals; + +import android.platform.test.annotations.Presubmit; +import android.util.PollingCheck; + +import androidx.test.filters.MediumTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@MediumTest +@Presubmit +public class HorizontalScrollViewFunctionalTest { + private HorizontalScrollViewActivity mActivity; + private HorizontalScrollView mHorizontalScrollView; + @Rule + public ActivityTestRule<HorizontalScrollViewActivity> mActivityRule = new ActivityTestRule<>( + HorizontalScrollViewActivity.class); + + @Before + public void setUp() throws Exception { + mActivity = mActivityRule.getActivity(); + mHorizontalScrollView = mActivity.findViewById(R.id.horizontal_scroll_view); + } + + @Test + public void testScrollAfterFlingTop() { + mHorizontalScrollView.scrollTo(100, 0); + mHorizontalScrollView.fling(-10000); + PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0); + PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f); + assertEquals(0, mHorizontalScrollView.getScrollX()); + } + + @Test + public void testScrollAfterFlingBottom() { + int childWidth = mHorizontalScrollView.getChildAt(0).getWidth(); + int maxScroll = childWidth - mHorizontalScrollView.getWidth(); + mHorizontalScrollView.scrollTo(maxScroll - 100, 0); + mHorizontalScrollView.fling(10000); + PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0); + PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f); + assertEquals(maxScroll, mHorizontalScrollView.getScrollX()); + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt b/core/tests/coretests/src/android/widget/ScrollViewActivity.java index c8f46a72d64f..899d63163aa4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt +++ b/core/tests/coretests/src/android/widget/ScrollViewActivity.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Android Open Source Project + * 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. @@ -14,12 +14,21 @@ * limitations under the License. */ -package com.android.systemui.scene.shared.model +package android.widget; -/** Models a transition between two scenes. */ -data class SceneTransitionModel( - /** The scene we transitioned away from. */ - val from: SceneKey, - /** The scene we transitioned into. */ - val to: SceneKey, -) +import android.app.Activity; +import android.os.Bundle; + +import com.android.frameworks.coretests.R; + +/** + * An activity for testing the TextView widget. + */ +public class ScrollViewActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_scroll_view); + } +} diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java new file mode 100644 index 000000000000..a49bb6af13d2 --- /dev/null +++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java @@ -0,0 +1,71 @@ +/* + * 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 android.widget; + +import static org.junit.Assert.assertEquals; + +import android.platform.test.annotations.Presubmit; +import android.util.PollingCheck; + +import androidx.test.filters.MediumTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@MediumTest +@Presubmit +public class ScrollViewFunctionalTest { + private ScrollViewActivity mActivity; + private ScrollView mScrollView; + @Rule + public ActivityTestRule<ScrollViewActivity> mActivityRule = new ActivityTestRule<>( + ScrollViewActivity.class); + + @Before + public void setUp() throws Exception { + mActivity = mActivityRule.getActivity(); + mScrollView = mActivity.findViewById(R.id.scroll_view); + } + + @Test + public void testScrollAfterFlingTop() { + mScrollView.scrollTo(0, 100); + mScrollView.fling(-10000); + PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() > 0); + PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() == 0f); + assertEquals(0, mScrollView.getScrollY()); + } + + @Test + public void testScrollAfterFlingBottom() { + int childHeight = mScrollView.getChildAt(0).getHeight(); + int maxScroll = childHeight - mScrollView.getHeight(); + mScrollView.scrollTo(0, maxScroll - 100); + mScrollView.fling(10000); + PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() > 0); + PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() == 0f); + assertEquals(maxScroll, mScrollView.getScrollY()); + } +} + diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index ee6996d3d23d..2c100653dae0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -661,14 +661,26 @@ public class BubblePositioner { final boolean startOnLeft = mContext.getResources().getConfiguration().getLayoutDirection() != LAYOUT_DIRECTION_RTL; - final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset( - R.dimen.bubble_stack_starting_offset_y); - // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge - return new BubbleStackView.RelativeStackPosition( - startOnLeft, - startingVerticalOffset / mPositionRect.height()) - .getAbsolutePositionInRegion(getAllowableStackPositionRegion( - 1 /* default starts with 1 bubble */)); + final RectF allowableStackPositionRegion = getAllowableStackPositionRegion( + 1 /* default starts with 1 bubble */); + if (isLargeScreen()) { + // We want the stack to be visually centered on the edge, so we need to base it + // of a rect that includes insets. + final float desiredY = mScreenRect.height() / 2f - (mBubbleSize / 2f); + final float offset = desiredY / mScreenRect.height(); + return new BubbleStackView.RelativeStackPosition( + startOnLeft, + offset) + .getAbsolutePositionInRegion(allowableStackPositionRegion); + } else { + final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset( + R.dimen.bubble_stack_starting_offset_y); + // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge + return new BubbleStackView.RelativeStackPosition( + startOnLeft, + startingVerticalOffset / mPositionRect.height()) + .getAbsolutePositionInRegion(allowableStackPositionRegion); + } } /** diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java new file mode 100644 index 000000000000..139724f709c7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java @@ -0,0 +1,281 @@ +/* + * 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.wm.shell.bubbles; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.View.LAYOUT_DIRECTION_LTR; +import static android.view.View.LAYOUT_DIRECTION_RTL; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.res.Configuration; +import android.graphics.Insets; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableResources; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests operations and the resulting state managed by {@link BubblePositioner}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class BubblePositionerTest extends ShellTestCase { + + private static final int MIN_WIDTH_FOR_TABLET = 600; + + private BubblePositioner mPositioner; + private Configuration mConfiguration; + + @Mock + private WindowManager mWindowManager; + @Mock + private WindowMetrics mWindowMetrics; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mConfiguration = spy(new Configuration()); + TestableResources testableResources = mContext.getOrCreateTestableResources(); + testableResources.overrideConfiguration(mConfiguration); + + mPositioner = new BubblePositioner(mContext, mWindowManager); + } + + @Test + public void testUpdate() { + Insets insets = Insets.of(10, 20, 5, 15); + Rect screenBounds = new Rect(0, 0, 1000, 1200); + Rect availableRect = new Rect(screenBounds); + availableRect.inset(insets); + + new WindowManagerConfig() + .setInsets(insets) + .setScreenBounds(screenBounds) + .setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect); + assertThat(mPositioner.isLandscape()).isFalse(); + assertThat(mPositioner.isLargeScreen()).isFalse(); + assertThat(mPositioner.getInsets()).isEqualTo(insets); + } + + @Test + public void testShowBubblesVertically_phonePortrait() { + new WindowManagerConfig().setOrientation(ORIENTATION_PORTRAIT).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.showBubblesVertically()).isFalse(); + } + + @Test + public void testShowBubblesVertically_phoneLandscape() { + new WindowManagerConfig().setOrientation(ORIENTATION_LANDSCAPE).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.isLandscape()).isTrue(); + assertThat(mPositioner.showBubblesVertically()).isTrue(); + } + + @Test + public void testShowBubblesVertically_tablet() { + new WindowManagerConfig().setLargeScreen().setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.showBubblesVertically()).isTrue(); + } + + /** If a resting position hasn't been set, calling it will return the default position. */ + @Test + public void testGetRestingPosition_returnsDefaultPosition() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + PointF restingPosition = mPositioner.getRestingPosition(); + PointF defaultPosition = mPositioner.getDefaultStartPosition(); + + assertThat(restingPosition).isEqualTo(defaultPosition); + } + + /** If a resting position has been set, it'll return that instead of the default position. */ + @Test + public void testGetRestingPosition_returnsRestingPosition() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + PointF restingPosition = new PointF(100, 100); + mPositioner.setRestingPosition(restingPosition); + + assertThat(mPositioner.getRestingPosition()).isEqualTo(restingPosition); + } + + /** Test that the default resting position on phone is in upper left. */ + @Test + public void testGetRestingPosition_bubble_onPhone() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testGetRestingPosition_bubble_onPhone_RTL() { + new WindowManagerConfig().setLayoutDirection(LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + /** Test that the default resting position on tablet is middle left. */ + @Test + public void testGetRestingPosition_chatBubble_onTablet() { + new WindowManagerConfig().setLargeScreen().setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testGetRestingPosition_chatBubble_onTablet_RTL() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + /** + * Calculates the Y position bubbles should be placed based on the config. Based on + * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and + * {@link BubbleStackView.RelativeStackPosition}. + */ + private float getDefaultYPosition() { + final boolean isTablet = mPositioner.isLargeScreen(); + + // On tablet the position is centered, on phone it is an offset from the top. + final float desiredY = isTablet + ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f) + : mContext.getResources().getDimensionPixelOffset( + R.dimen.bubble_stack_starting_offset_y); + // Since we're visually centering the bubbles on tablet, use total screen height rather + // than the available height. + final float height = isTablet + ? mPositioner.getScreenRect().height() + : mPositioner.getAvailableRect().height(); + float offsetPercent = desiredY / height; + offsetPercent = Math.max(0f, Math.min(1f, offsetPercent)); + final RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent; + } + + /** + * Sets up window manager to return config values based on what you need for the test. + * By default it sets up a portrait phone without any insets. + */ + private class WindowManagerConfig { + private Rect mScreenBounds = new Rect(0, 0, 1000, 2000); + private boolean mIsLargeScreen = false; + private int mOrientation = ORIENTATION_PORTRAIT; + private int mLayoutDirection = LAYOUT_DIRECTION_LTR; + private Insets mInsets = Insets.of(0, 0, 0, 0); + + public WindowManagerConfig setScreenBounds(Rect screenBounds) { + mScreenBounds = screenBounds; + return this; + } + + public WindowManagerConfig setLargeScreen() { + mIsLargeScreen = true; + return this; + } + + public WindowManagerConfig setOrientation(int orientation) { + mOrientation = orientation; + return this; + } + + public WindowManagerConfig setLayoutDirection(int layoutDirection) { + mLayoutDirection = layoutDirection; + return this; + } + + public WindowManagerConfig setInsets(Insets insets) { + mInsets = insets; + return this; + } + + public void setUpConfig() { + mConfiguration.smallestScreenWidthDp = mIsLargeScreen + ? MIN_WIDTH_FOR_TABLET + : MIN_WIDTH_FOR_TABLET - 1; + mConfiguration.orientation = mOrientation; + + when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection); + WindowInsets windowInsets = mock(WindowInsets.class); + when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(mInsets); + when(mWindowMetrics.getWindowInsets()).thenReturn(windowInsets); + when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds); + when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); + } + } +} diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java index d59756d02348..a48cc19995c9 100644 --- a/location/java/android/location/LocationManagerInternal.java +++ b/location/java/android/location/LocationManagerInternal.java @@ -87,12 +87,6 @@ public abstract class LocationManagerInternal { public abstract boolean isProvider(@Nullable String provider, @NonNull CallerIdentity identity); /** - * Should only be used by GNSS code. - */ - // TODO: there is no reason for this to exist as part of any API. move all the logic into gnss - public abstract void sendNiResponse(int notifId, int userResponse); - - /** * Returns the GNSS provided time. * * @return LocationTime object that includes the current time, according to the GNSS location diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java index fba4249260ef..ee2510ff9695 100644 --- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java +++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java @@ -18,28 +18,14 @@ package com.android.internal.location; import android.Manifest; import android.annotation.RequiresPermission; -import android.app.Notification; -import android.app.NotificationManager; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.location.INetInitiatedListener; import android.location.LocationManager; -import android.os.RemoteException; import android.os.SystemClock; -import android.os.UserHandle; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.telephony.emergency.EmergencyNumber; import android.util.Log; -import com.android.internal.R; -import com.android.internal.notification.SystemNotificationChannels; -import com.android.internal.telephony.GsmAlphabet; - -import java.io.UnsupportedEncodingException; import java.util.concurrent.TimeUnit; /** @@ -53,95 +39,20 @@ public class GpsNetInitiatedHandler { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - // string constants for defining data fields in NI Intent - public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id"; - public static final String NI_INTENT_KEY_TITLE = "title"; - public static final String NI_INTENT_KEY_MESSAGE = "message"; - public static final String NI_INTENT_KEY_TIMEOUT = "timeout"; - public static final String NI_INTENT_KEY_DEFAULT_RESPONSE = "default_resp"; - - // the extra command to send NI response to GnssLocationProvider - public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response"; - - // the extra command parameter names in the Bundle - public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id"; - public static final String NI_EXTRA_CMD_RESPONSE = "response"; - - // these need to match GpsNiType constants in gps_ni.h - public static final int GPS_NI_TYPE_VOICE = 1; - public static final int GPS_NI_TYPE_UMTS_SUPL = 2; - public static final int GPS_NI_TYPE_UMTS_CTRL_PLANE = 3; - public static final int GPS_NI_TYPE_EMERGENCY_SUPL = 4; - - // these need to match GpsUserResponseType constants in gps_ni.h - public static final int GPS_NI_RESPONSE_ACCEPT = 1; - public static final int GPS_NI_RESPONSE_DENY = 2; - public static final int GPS_NI_RESPONSE_NORESP = 3; - public static final int GPS_NI_RESPONSE_IGNORE = 4; - - // these need to match GpsNiNotifyFlags constants in gps_ni.h - public static final int GPS_NI_NEED_NOTIFY = 0x0001; - public static final int GPS_NI_NEED_VERIFY = 0x0002; - public static final int GPS_NI_PRIVACY_OVERRIDE = 0x0004; - - // these need to match GpsNiEncodingType in gps_ni.h - public static final int GPS_ENC_NONE = 0; - public static final int GPS_ENC_SUPL_GSM_DEFAULT = 1; - public static final int GPS_ENC_SUPL_UTF8 = 2; - public static final int GPS_ENC_SUPL_UCS2 = 3; - public static final int GPS_ENC_UNKNOWN = -1; - private final Context mContext; private final TelephonyManager mTelephonyManager; // parent gps location provider private final LocationManager mLocationManager; - // configuration of notificaiton behavior - private boolean mPlaySounds = false; - private boolean mPopupImmediately = true; - - // read the SUPL_ES form gps.conf - private volatile boolean mIsSuplEsEnabled; - // Set to true if the phone is having emergency call. private volatile boolean mIsInEmergencyCall; - // If Location function is enabled. - private volatile boolean mIsLocationEnabled = false; - - private final INetInitiatedListener mNetInitiatedListener; - - // Set to true if string from HAL is encoded as Hex, e.g., "3F0039" - @UnsupportedAppUsage - static private boolean mIsHexInput = true; // End time of emergency call, and extension, if set private volatile long mCallEndElapsedRealtimeMillis = 0; private volatile long mEmergencyExtensionMillis = 0; - public static class GpsNiNotification - { - @android.compat.annotation.UnsupportedAppUsage - public GpsNiNotification() { - } - public int notificationId; - public int niType; - public boolean needNotify; - public boolean needVerify; - public boolean privacyOverride; - public int timeout; - public int defaultResponse; - @UnsupportedAppUsage - public String requestorId; - @UnsupportedAppUsage - public String text; - @UnsupportedAppUsage - public int requestorIdEncoding; - @UnsupportedAppUsage - public int textEncoding; - } - /** Callbacks for Emergency call events. */ public interface EmergencyCallCallback { /** Callback invoked when an emergency call starts */ @@ -182,72 +93,20 @@ public class GpsNetInitiatedHandler { // reference here. private final EmergencyCallListener mEmergencyCallListener = new EmergencyCallListener(); - /** - * The notification that is shown when a network-initiated notification - * (and verification) event is received. - * <p> - * This is lazily created, so use {@link #setNINotification()}. - */ - private Notification.Builder mNiNotificationBuilder; - private final EmergencyCallCallback mEmergencyCallCallback; public GpsNetInitiatedHandler(Context context, - INetInitiatedListener netInitiatedListener, EmergencyCallCallback emergencyCallCallback, boolean isSuplEsEnabled) { mContext = context; - - if (netInitiatedListener == null) { - throw new IllegalArgumentException("netInitiatedListener is null"); - } else { - mNetInitiatedListener = netInitiatedListener; - } mEmergencyCallCallback = emergencyCallCallback; - setSuplEsEnabled(isSuplEsEnabled); mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE); - updateLocationMode(); mTelephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); mTelephonyManager.registerTelephonyCallback(mContext.getMainExecutor(), mEmergencyCallListener); - BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(LocationManager.MODE_CHANGED_ACTION)) { - updateLocationMode(); - if (DEBUG) Log.d(TAG, "location enabled :" + getLocationEnabled()); - } - } - }; - mContext.registerReceiver(broadcastReceiver, - new IntentFilter(LocationManager.MODE_CHANGED_ACTION)); - } - - public void setSuplEsEnabled(boolean isEnabled) { - mIsSuplEsEnabled = isEnabled; - } - - public boolean getSuplEsEnabled() { - return mIsSuplEsEnabled; - } - - /** - * Updates Location enabler based on location setting. - */ - public void updateLocationMode() { - mIsLocationEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); - } - - /** - * Checks if user agreed to use location. - */ - public boolean getLocationEnabled() { - return mIsLocationEnabled; } /** @@ -289,346 +148,4 @@ public class GpsNetInitiatedHandler { public void setEmergencyExtensionSeconds(int emergencyExtensionSeconds) { mEmergencyExtensionMillis = TimeUnit.SECONDS.toMillis(emergencyExtensionSeconds); } - - // Handles NI events from HAL - @UnsupportedAppUsage - public void handleNiNotification(GpsNiNotification notif) { - if (DEBUG) Log.d(TAG, "in handleNiNotification () :" - + " notificationId: " + notif.notificationId - + " requestorId: " + notif.requestorId - + " text: " + notif.text - + " mIsSuplEsEnabled" + getSuplEsEnabled() - + " mIsLocationEnabled" + getLocationEnabled()); - - if (getSuplEsEnabled()) { - handleNiInEs(notif); - } else { - handleNi(notif); - } - - ////////////////////////////////////////////////////////////////////////// - // A note about timeout - // According to the protocol, in the need_notify and need_verify case, - // a default response should be sent when time out. - // - // In some GPS hardware, the GPS driver (under HAL) can handle the timeout case - // and this class GpsNetInitiatedHandler does not need to do anything. - // - // However, the UI should at least close the dialog when timeout. Further, - // for more general handling, timeout response should be added to the Handler here. - // - } - - // handle NI form HAL when SUPL_ES is disabled. - private void handleNi(GpsNiNotification notif) { - if (DEBUG) Log.d(TAG, "in handleNi () :" - + " needNotify: " + notif.needNotify - + " needVerify: " + notif.needVerify - + " privacyOverride: " + notif.privacyOverride - + " mPopupImmediately: " + mPopupImmediately - + " mInEmergency: " + getInEmergency()); - - if (!getLocationEnabled() && !getInEmergency()) { - // Location is currently disabled, ignore all NI requests. - try { - mNetInitiatedListener.sendNiResponse(notif.notificationId, - GPS_NI_RESPONSE_IGNORE); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in sendNiResponse"); - } - } - if (notif.needNotify) { - // If NI does not need verify or the dialog is not requested - // to pop up immediately, the dialog box will not pop up. - if (notif.needVerify && mPopupImmediately) { - // Popup the dialog box now - openNiDialog(notif); - } else { - // Show the notification - setNiNotification(notif); - } - } - // ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify; - // 3. privacy override. - if (!notif.needVerify || notif.privacyOverride) { - try { - mNetInitiatedListener.sendNiResponse(notif.notificationId, - GPS_NI_RESPONSE_ACCEPT); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in sendNiResponse"); - } - } - } - - // handle NI from HAL when the SUPL_ES is enabled - private void handleNiInEs(GpsNiNotification notif) { - - if (DEBUG) Log.d(TAG, "in handleNiInEs () :" - + " niType: " + notif.niType - + " notificationId: " + notif.notificationId); - - // UE is in emergency mode when in emergency call mode or in emergency call back mode - /* - 1. When SUPL ES bit is off and UE is not in emergency mode: - Call handleNi() to do legacy behaviour. - 2. When SUPL ES bit is on and UE is in emergency mode: - Call handleNi() to do acceptance behaviour. - 3. When SUPL ES bit is off but UE is in emergency mode: - Ignore the emergency SUPL INIT. - 4. When SUPL ES bit is on but UE is not in emergency mode: - Ignore the emergency SUPL INIT. - */ - boolean isNiTypeES = (notif.niType == GPS_NI_TYPE_EMERGENCY_SUPL); - if (isNiTypeES != getInEmergency()) { - try { - mNetInitiatedListener.sendNiResponse(notif.notificationId, - GPS_NI_RESPONSE_IGNORE); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in sendNiResponse"); - } - } else { - handleNi(notif); - } - } - - /** - * Posts a notification in the status bar using the contents in {@code notif} object. - */ - private synchronized void setNiNotification(GpsNiNotification notif) { - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - if (notificationManager == null) { - return; - } - - String title = getNotifTitle(notif, mContext); - String message = getNotifMessage(notif, mContext); - - if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId + - ", title: " + title + - ", message: " + message); - - // Construct Notification - if (mNiNotificationBuilder == null) { - mNiNotificationBuilder = new Notification.Builder(mContext, - SystemNotificationChannels.NETWORK_ALERTS) - .setSmallIcon(com.android.internal.R.drawable.stat_sys_gps_on) - .setWhen(0) - .setOngoing(true) - .setAutoCancel(true) - .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)); - } - - if (mPlaySounds) { - mNiNotificationBuilder.setDefaults(Notification.DEFAULT_SOUND); - } else { - mNiNotificationBuilder.setDefaults(0); - } - - mNiNotificationBuilder.setTicker(getNotifTicker(notif, mContext)) - .setContentTitle(title) - .setContentText(message); - - notificationManager.notifyAsUser(null, notif.notificationId, mNiNotificationBuilder.build(), - UserHandle.ALL); - } - - // Opens the notification dialog and waits for user input - private void openNiDialog(GpsNiNotification notif) - { - Intent intent = getDlgIntent(notif); - - if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId + - ", requestorId: " + notif.requestorId + - ", text: " + notif.text); - - mContext.startActivity(intent); - } - - // Construct the intent for bringing up the dialog activity, which shows the - // notification and takes user input - private Intent getDlgIntent(GpsNiNotification notif) - { - Intent intent = new Intent(); - String title = getDialogTitle(notif, mContext); - String message = getDialogMessage(notif, mContext); - - // directly bring up the NI activity - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class); - - // put data in the intent - intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId); - intent.putExtra(NI_INTENT_KEY_TITLE, title); - intent.putExtra(NI_INTENT_KEY_MESSAGE, message); - intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout); - intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse); - - if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message + - ", timeout: " + notif.timeout); - - return intent; - } - - // Converts a string (or Hex string) to a char array - static byte[] stringToByteArray(String original, boolean isHex) - { - int length = isHex ? original.length() / 2 : original.length(); - byte[] output = new byte[length]; - int i; - - if (isHex) - { - for (i = 0; i < length; i++) - { - output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16); - } - } - else { - for (i = 0; i < length; i++) - { - output[i] = (byte) original.charAt(i); - } - } - - return output; - } - - /** - * Unpacks an byte array containing 7-bit packed characters into a String. - * - * @param input a 7-bit packed char array - * @return the unpacked String - */ - static String decodeGSMPackedString(byte[] input) - { - final char PADDING_CHAR = 0x00; - int lengthBytes = input.length; - int lengthSeptets = (lengthBytes * 8) / 7; - String decoded; - - /* Special case where the last 7 bits in the last byte could hold a valid - * 7-bit character or a padding character. Drop the last 7-bit character - * if it is a padding character. - */ - if (lengthBytes % 7 == 0) { - if (lengthBytes > 0) { - if ((input[lengthBytes - 1] >> 1) == PADDING_CHAR) { - lengthSeptets = lengthSeptets - 1; - } - } - } - - decoded = GsmAlphabet.gsm7BitPackedToString(input, 0, lengthSeptets); - - // Return "" if decoding of GSM packed string fails - if (null == decoded) { - Log.e(TAG, "Decoding of GSM packed string failed"); - decoded = ""; - } - - return decoded; - } - - static String decodeUTF8String(byte[] input) - { - String decoded = ""; - try { - decoded = new String(input, "UTF-8"); - } - catch (UnsupportedEncodingException e) - { - throw new AssertionError(); - } - return decoded; - } - - static String decodeUCS2String(byte[] input) - { - String decoded = ""; - try { - decoded = new String(input, "UTF-16"); - } - catch (UnsupportedEncodingException e) - { - throw new AssertionError(); - } - return decoded; - } - - /** Decode NI string - * - * @param original The text string to be decoded - * @param isHex Specifies whether the content of the string has been encoded as a Hex string. Encoding - * a string as Hex can allow zeros inside the coded text. - * @param coding Specifies the coding scheme of the string, such as GSM, UTF8, UCS2, etc. This coding scheme - * needs to match those used passed to HAL from the native GPS driver. Decoding is done according - * to the <code> coding </code>, after a Hex string is decoded. Generally, if the - * notification strings don't need further decoding, <code> coding </code> encoding can be - * set to -1, and <code> isHex </code> can be false. - * @return the decoded string - */ - @UnsupportedAppUsage - static private String decodeString(String original, boolean isHex, int coding) - { - if (coding == GPS_ENC_NONE || coding == GPS_ENC_UNKNOWN) { - return original; - } - - byte[] input = stringToByteArray(original, isHex); - - switch (coding) { - case GPS_ENC_SUPL_GSM_DEFAULT: - return decodeGSMPackedString(input); - - case GPS_ENC_SUPL_UTF8: - return decodeUTF8String(input); - - case GPS_ENC_SUPL_UCS2: - return decodeUCS2String(input); - - default: - Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original); - return original; - } - } - - // change this to configure notification display - static private String getNotifTicker(GpsNiNotification notif, Context context) - { - String ticker = String.format(context.getString(R.string.gpsNotifTicker), - decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding), - decodeString(notif.text, mIsHexInput, notif.textEncoding)); - return ticker; - } - - // change this to configure notification display - static private String getNotifTitle(GpsNiNotification notif, Context context) - { - String title = String.format(context.getString(R.string.gpsNotifTitle)); - return title; - } - - // change this to configure notification display - static private String getNotifMessage(GpsNiNotification notif, Context context) - { - String message = String.format(context.getString(R.string.gpsNotifMessage), - decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding), - decodeString(notif.text, mIsHexInput, notif.textEncoding)); - return message; - } - - // change this to configure dialog display (for verification) - static public String getDialogTitle(GpsNiNotification notif, Context context) - { - return getNotifTitle(notif, context); - } - - // change this to configure dialog display (for verification) - static private String getDialogMessage(GpsNiNotification notif, Context context) - { - return getNotifMessage(notif, context); - } - } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index 2d6cc699fc11..df91d98b8360 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -806,12 +806,12 @@ public class PackageInstallerActivity extends AlertActivity { } new Handler(Looper.getMainLooper()).postDelayed(() -> { if (!isDestroyed()) { + startActivity(getIntent()); // The start flag (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP) doesn't // work for the multiple user case, i.e. the caller task user and started // Activity user are not the same. To avoid having multiple PIAs in the task, // finish the current PackageInstallerActivity finish(); - startActivity(getIntent()); } }, 500); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 58c9f777de33..6778d5a08506 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -562,7 +562,7 @@ android:exported="true" android:launchMode="singleTop" android:permission="android.permission.MANAGE_SENSOR_PRIVACY" - android:theme="@style/Theme.SystemUI.Dialog.Alert" + android:theme="@style/Theme.SystemUI.Dialog.Alert.SensorPrivacy" android:finishOnCloseSystemDialogs="true" android:showForAllUsers="true"> </activity> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 3dfdbbaaee77..f91baf298347 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -77,7 +77,7 @@ fun SceneContainer( SceneTransitionLayout( currentScene = currentSceneKey.toTransitionSceneKey(), - onChangeScene = { sceneKey -> viewModel.setCurrentScene(sceneKey.toModel()) }, + onChangeScene = viewModel::onSceneChanged, transitions = transitions {}, state = state, modifier = modifier.fillMaxSize(), @@ -154,3 +154,7 @@ private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction { is UserAction.Back -> Back } } + +private fun SceneContainerViewModel.onSceneChanged(sceneKey: SceneTransitionSceneKey) { + onSceneChanged(sceneKey.toModel()) +} diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 66c57fc2a9ac..36f7b96a267c 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -22,42 +22,6 @@ android:layout_width="match_parent" android:outlineProvider="none" > - <LinearLayout - android:id="@id/keyguard_indication_area" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom" - android:layout_gravity="bottom|center_horizontal" - android:orientation="vertical"> - - <com.android.systemui.statusbar.phone.KeyguardIndicationTextView - android:id="@id/keyguard_indication_text" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:paddingStart="@dimen/keyguard_indication_text_padding" - android:paddingEnd="@dimen/keyguard_indication_text_padding" - android:textAppearance="@style/TextAppearance.Keyguard.BottomArea" - android:accessibilityLiveRegion="polite"/> - - <com.android.systemui.statusbar.phone.KeyguardIndicationTextView - android:id="@id/keyguard_indication_text_bottom" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" - android:minHeight="@dimen/keyguard_indication_text_min_height" - android:layout_gravity="center_horizontal" - android:paddingStart="@dimen/keyguard_indication_text_padding" - android:paddingEnd="@dimen/keyguard_indication_text_padding" - android:textAppearance="@style/TextAppearance.Keyguard.BottomArea" - android:maxLines="2" - android:ellipsize="end" - android:alpha=".8" - android:accessibilityLiveRegion="polite" - android:visibility="gone"/> - - </LinearLayout> - <com.android.systemui.animation.view.LaunchableImageView android:id="@+id/start_button" android:layout_height="@dimen/keyguard_affordance_fixed_height" diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 763930db1d55..c2dba6c41d12 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -38,4 +38,6 @@ protected. --> <bool name="flag_battery_shield_icon">false</bool> + <!-- Whether face auth will immediately stop when the display state is OFF --> + <bool name="flag_stop_face_auth_on_display_off">false</bool> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index d520670ec012..10340c6847f7 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -451,6 +451,11 @@ <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" /> + <style name="Theme.SystemUI.Dialog.Alert.SensorPrivacy" parent="Theme.SystemUI.Dialog.Alert"> + <item name="android:windowNoTitle">true</item> + <item name="android:windowContentOverlay">@null</item> + </style> + <style name="Theme.SystemUI.Dialog.GlobalActions" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen"> <item name="android:colorError">@*android:color/error_color_material_dark</item> <item name="android:windowIsFloating">true</item> diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt index 22cdb30376d0..2abb7a41ef08 100644 --- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt +++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt @@ -33,6 +33,7 @@ import com.android.keyguard.InternalFaceAuthReasons.AUTH_REQUEST_DURING_CANCELLA import com.android.keyguard.InternalFaceAuthReasons.BIOMETRIC_ENABLED import com.android.keyguard.InternalFaceAuthReasons.CAMERA_LAUNCHED import com.android.keyguard.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE +import com.android.keyguard.InternalFaceAuthReasons.DISPLAY_OFF import com.android.keyguard.InternalFaceAuthReasons.DREAM_STARTED import com.android.keyguard.InternalFaceAuthReasons.DREAM_STOPPED import com.android.keyguard.InternalFaceAuthReasons.ENROLLMENTS_CHANGED @@ -131,6 +132,7 @@ private object InternalFaceAuthReasons { const val NON_STRONG_BIOMETRIC_ALLOWED_CHANGED = "Face auth stopped because non strong biometric allowed changed" const val POSTURE_CHANGED = "Face auth started/stopped due to device posture changed." + const val DISPLAY_OFF = "Face auth stopped due to display state OFF." } /** @@ -221,7 +223,8 @@ constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) : FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED), @UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED) FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED), - @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION); + @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION), + @UiEvent(doc = DISPLAY_OFF) FACE_AUTH_DISPLAY_OFF(1461, DISPLAY_OFF); override fun getId(): Int = this.id diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt index 461d390fd477..bb799fc55171 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt @@ -27,6 +27,7 @@ data class KeyguardFaceListenModel( override var userId: Int = 0, override var listening: Boolean = false, // keep sorted + var allowedDisplayState: Boolean = false, var alternateBouncerShowing: Boolean = false, var authInterruptActive: Boolean = false, var biometricSettingEnabledForUser: Boolean = false, @@ -57,6 +58,8 @@ data class KeyguardFaceListenModel( userId.toString(), listening.toString(), // keep sorted + allowedDisplayState.toString(), + alternateBouncerShowing.toString(), authInterruptActive.toString(), biometricSettingEnabledForUser.toString(), bouncerFullyShown.toString(), @@ -74,7 +77,6 @@ data class KeyguardFaceListenModel( supportsDetect.toString(), switchingUser.toString(), systemUser.toString(), - alternateBouncerShowing.toString(), udfpsFingerDown.toString(), userNotTrustedOrDetectionIsNeeded.toString(), ) @@ -96,7 +98,9 @@ data class KeyguardFaceListenModel( userId = model.userId listening = model.listening // keep sorted + allowedDisplayState = model.allowedDisplayState alternateBouncerShowing = model.alternateBouncerShowing + authInterruptActive = model.authInterruptActive biometricSettingEnabledForUser = model.biometricSettingEnabledForUser bouncerFullyShown = model.bouncerFullyShown faceAndFpNotAuthenticated = model.faceAndFpNotAuthenticated @@ -105,7 +109,6 @@ data class KeyguardFaceListenModel( faceLockedOut = model.faceLockedOut goingToSleep = model.goingToSleep keyguardAwake = model.keyguardAwake - goingToSleep = model.goingToSleep keyguardGoingAway = model.keyguardGoingAway listeningForFaceAssistant = model.listeningForFaceAssistant occludingAppRequestingFaceAuth = model.occludingAppRequestingFaceAuth @@ -140,6 +143,8 @@ data class KeyguardFaceListenModel( "userId", "listening", // keep sorted + "allowedDisplayState", + "alternateBouncerShowing", "authInterruptActive", "biometricSettingEnabledForUser", "bouncerFullyShown", @@ -157,7 +162,6 @@ data class KeyguardFaceListenModel( "supportsDetect", "switchingUser", "systemUser", - "udfpsBouncerShowing", "udfpsFingerDown", "userNotTrustedOrDetectionIsNeeded", ) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index aff25914ec36..57a859126db2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -476,18 +476,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) { // When the scene framework transitions from bouncer to gone, we dismiss the keyguard. mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow( - mSceneInteractor.get().getTransitions(), - sceneTransitionModel -> { - if (sceneTransitionModel != null - && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE - && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) { - final int selectedUserId = mUserInteractor.getSelectedUserId(); - showNextSecurityScreenOrFinish( - /* authenticated= */ true, - selectedUserId, - /* bypassSecondaryLockScreen= */ true, - mSecurityModel.getSecurityMode(selectedUserId)); - } + mSceneInteractor.get().finishedSceneTransitions( + /* from= */ SceneKey.Bouncer.INSTANCE, + /* to= */ SceneKey.Gone.INSTANCE), + unused -> { + final int selectedUserId = mUserInteractor.getSelectedUserId(); + showNextSecurityScreenOrFinish( + /* authenticated= */ true, + selectedUserId, + /* bypassSecondaryLockScreen= */ true, + mSecurityModel.getSecurityMode(selectedUserId)); }); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index c03053d0a8a2..853a62a09f83 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -41,6 +41,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent; +import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_DISPLAY_OFF; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED; @@ -135,6 +136,7 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.view.Display; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -175,6 +177,7 @@ import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.WeatherData; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -335,6 +338,25 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } }; + private final DisplayTracker.Callback mDisplayCallback = new DisplayTracker.Callback() { + @Override + public void onDisplayChanged(int displayId) { + if (displayId != Display.DEFAULT_DISPLAY) { + return; + } + + if (mDisplayTracker.getDisplay(mDisplayTracker.getDefaultDisplayId()).getState() + == Display.STATE_OFF) { + mAllowedDisplayStateForFaceAuth = false; + updateFaceListeningState( + BIOMETRIC_ACTION_STOP, + FACE_AUTH_DISPLAY_OFF + ); + } else { + mAllowedDisplayStateForFaceAuth = true; + } + } + }; private final FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig; HashMap<Integer, SimData> mSimDatas = new HashMap<>(); @@ -355,6 +377,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mOccludingAppRequestingFp; private boolean mOccludingAppRequestingFace; private boolean mSecureCameraLaunched; + private boolean mAllowedDisplayStateForFaceAuth = true; @VisibleForTesting protected boolean mTelephonyCapable; private boolean mAllowFingerprintOnCurrentOccludingActivity; @@ -403,6 +426,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private KeyguardFaceAuthInteractor mFaceAuthInteractor; private final TaskStackChangeListeners mTaskStackChangeListeners; private final IActivityTaskManager mActivityTaskManager; + private final DisplayTracker mDisplayTracker; private final LockPatternUtils mLockPatternUtils; @VisibleForTesting @DevicePostureInt @@ -2187,6 +2211,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp"); Assert.isMainThread(); + mAllowedDisplayStateForFaceAuth = true; updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); if (mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason)) { FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason); @@ -2342,7 +2367,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider, FeatureFlags featureFlags, TaskStackChangeListeners taskStackChangeListeners, - IActivityTaskManager activityTaskManagerService) { + IActivityTaskManager activityTaskManagerService, + DisplayTracker displayTracker) { mContext = context; mSubscriptionManager = subscriptionManager; mUserTracker = userTracker; @@ -2390,6 +2416,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab .collect(Collectors.toSet()); mTaskStackChangeListeners = taskStackChangeListeners; mActivityTaskManager = activityTaskManagerService; + mDisplayTracker = displayTracker; + if (mFeatureFlags.isEnabled(Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF)) { + mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor); + } mHandler = new Handler(mainLooper) { @Override @@ -3199,7 +3229,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && (!mSecureCameraLaunched || mAlternateBouncerShowing) && faceAndFpNotAuthenticated && !mGoingToSleep - && isPostureAllowedForFaceAuth; + && isPostureAllowedForFaceAuth + && mAllowedDisplayStateForFaceAuth; // Aggregate relevant fields for debug logging. logListenerModelData( @@ -3207,6 +3238,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab System.currentTimeMillis(), user, shouldListen, + mAllowedDisplayStateForFaceAuth, mAlternateBouncerShowing, mAuthInterruptActive, biometricEnabledForUser, @@ -4400,6 +4432,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker); mTrustManager.unregisterTrustListener(this); + mDisplayTracker.removeCallback(mDisplayCallback); mHandler.removeCallbacksAndMessages(null); } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 4845a6173be9..951a6aeef11b 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -25,6 +25,7 @@ import static com.android.keyguard.LockIconView.ICON_UNLOCK; import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.content.res.Configuration; @@ -39,6 +40,7 @@ import android.os.VibrationAttributes; import android.util.DisplayMetrics; import android.util.Log; import android.util.MathUtils; +import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -613,12 +615,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_HOVER_ENTER: if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) { - mVibrator.vibrate( - Process.myUid(), - getContext().getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "lock-icon-down", - TOUCH_VIBRATION_ATTRIBUTES); + vibrateOnTouchExploration(); } // The pointer that causes ACTION_DOWN is always at index 0. @@ -699,13 +696,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mOnGestureDetectedRunnable.run(); } - // play device entry haptic (same as biometric success haptic) - mVibrator.vibrate( - Process.myUid(), - getContext().getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "lock-screen-lock-icon-longpress", - TOUCH_VIBRATION_ATTRIBUTES); + // play device entry haptic (consistent with UDFPS controller longpress) + vibrateOnLongPress(); mKeyguardViewController.showPrimaryBouncer(/* scrim */ true); } @@ -753,6 +745,37 @@ public class LockIconViewController extends ViewController<LockIconView> impleme }); } + @VisibleForTesting + void vibrateOnTouchExploration() { + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + mVibrator.performHapticFeedback( + mView, + HapticFeedbackConstants.CONTEXT_CLICK + ); + } else { + mVibrator.vibrate( + Process.myUid(), + getContext().getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "lock-icon-down", + TOUCH_VIBRATION_ATTRIBUTES); + } + } + + @VisibleForTesting + void vibrateOnLongPress() { + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + mVibrator.performHapticFeedback(mView, UdfpsController.LONG_PRESS); + } else { + mVibrator.vibrate( + Process.myUid(), + getContext().getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "lock-screen-lock-icon-longpress", + TOUCH_VIBRATION_ATTRIBUTES); + } + } + private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { @Override public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index ffcae1cacb00..1bf3a9ead08e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -116,12 +116,12 @@ constructor( repository.setMessage( message ?: promptMessage(authenticationInteractor.getAuthenticationMethod()) ) - sceneInteractor.setCurrentScene( + sceneInteractor.changeScene( scene = SceneModel(SceneKey.Bouncer), loggingReason = "request to unlock device while authentication required", ) } else { - sceneInteractor.setCurrentScene( + sceneInteractor.changeScene( scene = SceneModel(SceneKey.Gone), loggingReason = "request to unlock device while authentication isn't required", ) @@ -169,7 +169,7 @@ constructor( authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null if (isAuthenticated) { - sceneInteractor.setCurrentScene( + sceneInteractor.changeScene( scene = SceneModel(SceneKey.Gone), loggingReason = "successful authentication", ) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index a30a3e1f855c..6037bbd837f0 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -244,10 +244,6 @@ object Flags { /** Keyguard Migration */ - /** Migrate the indication area to the new keyguard root view. */ - // TODO(b/280067944): Tracking bug. - @JvmField val MIGRATE_INDICATION_AREA = releasedFlag(236, "migrate_indication_area") - /** * Migrate the bottom area to the new keyguard root view. Because there is no such thing as a * "bottom area" after this, this also breaks it up into many smaller, modular pieces. @@ -294,6 +290,11 @@ object Flags { teamfood = true ) + /** Stop running face auth when the display state changes to OFF. */ + // TODO(b/294221702): Tracking bug. + @JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(245, + R.bool.flag_stop_face_auth_on_display_off, "stop_face_auth_on_display_off") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -374,6 +375,9 @@ object Flags { @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON = unreleasedFlag(614, "incompatible_charging_battery_icon") + // TODO(b/293585143): Tracking Bug + val INSTANT_TETHER = unreleasedFlag(615, "instant_tether") + // 700 - dialer/calls // TODO(b/254512734): Tracking Bug val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index dffc19d566ff..2b6f77d280f8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -137,14 +137,6 @@ constructor( fun bindIndicationArea() { indicationAreaHandle?.dispose() - // At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available. - // Disable one of them - if (!featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) { - keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let { - keyguardRootView.removeView(it) - } - } - indicationAreaHandle = KeyguardIndicationAreaBinder.bind( notificationShadeWindowView, diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index fee3960ff0e1..350fa38f2052 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -18,50 +18,49 @@ package com.android.systemui.scene.data.repository +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn /** Source of truth for scene framework application state. */ class SceneContainerRepository @Inject constructor( + @Application applicationScope: CoroutineScope, private val config: SceneContainerConfig, ) { + private val _desiredScene = MutableStateFlow(SceneModel(config.initialSceneKey)) + val desiredScene: StateFlow<SceneModel> = _desiredScene.asStateFlow() private val _isVisible = MutableStateFlow(true) val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow() - private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey)) - val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow() - - private val transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) - val transitionProgress: Flow<Float> = - transitionState.flatMapLatest { observableTransitionStateFlow -> - observableTransitionStateFlow?.flatMapLatest { observableTransitionState -> - when (observableTransitionState) { - is ObservableTransitionState.Idle -> flowOf(1f) - is ObservableTransitionState.Transition -> observableTransitionState.progress - } - } - ?: flowOf(1f) - } - - private val _transitions = MutableStateFlow<SceneTransitionModel?>(null) - val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow() + private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey) + private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) + val transitionState: StateFlow<ObservableTransitionState> = + _transitionState + .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = defaultTransitionState, + ) /** - * Returns the keys to all scenes in the container with the given name. + * Returns the keys to all scenes in the container. * * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. @@ -70,40 +69,19 @@ constructor( return config.sceneKeys } - /** Sets the current scene in the container with the given name. */ - fun setCurrentScene(scene: SceneModel) { + fun setDesiredScene(scene: SceneModel) { check(allSceneKeys().contains(scene.key)) { """ - Cannot set current scene key to "${scene.key}". The configuration does not contain a - scene with that key. - """ - .trimIndent() - } - - _currentScene.value = scene - } - - /** Sets the scene transition in the container with the given name. */ - fun setSceneTransition(from: SceneKey, to: SceneKey) { - check(allSceneKeys().contains(from)) { - """ - Cannot set current scene key to "$from". The configuration does not contain a scene - with that key. - """ - .trimIndent() - } - check(allSceneKeys().contains(to)) { - """ - Cannot set current scene key to "$to". The configuration does not contain a scene - with that key. + Cannot set the desired scene key to "${scene.key}". The configuration does not + contain a scene with that key. """ .trimIndent() } - _transitions.value = SceneTransitionModel(from = from, to = to) + _desiredScene.value = scene } - /** Sets whether the container with the given name is visible. */ + /** Sets whether the container is visible. */ fun setVisible(isVisible: Boolean) { _isVisible.value = isVisible } @@ -114,6 +92,6 @@ constructor( * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { - this.transitionState.value = transitionState + _transitionState.value = transitionState } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 64715bc26674..cf7abdd34b70 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -23,12 +23,15 @@ import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.RemoteUserInput import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel +import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull /** * Generic business logic and app state accessors for the scene framework. @@ -46,7 +49,54 @@ constructor( ) { /** - * Returns the keys of all scenes in the container with the given name. + * The currently *desired* scene. + * + * **Important:** this value will _commonly be different_ from what is being rendered in the UI, + * by design. + * + * There are two intended sources for this value: + * 1. Programmatic requests to transition to another scene (calls to [changeScene]). + * 2. Reports from the UI about completing a transition to another scene (calls to + * [onSceneChanged]). + * + * Both the sources above cause the value of this flow to change; however, they cause mismatches + * in different ways. + * + * **Updates from programmatic transitions** + * + * When an external bit of code asks the framework to switch to another scene, the value here + * will update immediately. Downstream, the UI will detect this change and initiate the + * transition animation. As the transition animation progresses, a threshold will be reached, at + * which point the UI and the state here will match each other. + * + * **Updates from the UI** + * + * When the user interacts with the UI, the UI runs a transition animation that tracks the user + * pointer (for example, the user's finger). During this time, the state value here and what the + * UI shows will likely not match. Once/if a threshold is met, the UI reports it and commits the + * change, making the value here match the UI again. + */ + val desiredScene: StateFlow<SceneModel> = repository.desiredScene + + /** + * The current state of the transition. + * + * Consumers should use this state to know: + * 1. Whether there is an ongoing transition or if the system is at rest. + * 2. When transitioning, which scenes are being transitioned between. + * 3. When transitioning, what the progress of the transition is. + */ + val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState + + /** Whether the scene container is visible. */ + val isVisible: StateFlow<Boolean> = repository.isVisible + + private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null) + /** A flow of motion events originating from outside of the scene framework. */ + val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow() + + /** + * Returns the keys of all scenes in the container. * * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. @@ -55,26 +105,20 @@ constructor( return repository.allSceneKeys() } - /** Sets the scene in the container with the given name. */ - fun setCurrentScene(scene: SceneModel, loggingReason: String) { - val currentSceneKey = repository.currentScene.value.key - if (currentSceneKey == scene.key) { - return - } - - logger.logSceneChange( - from = currentSceneKey, - to = scene.key, - reason = loggingReason, - ) - repository.setCurrentScene(scene) - repository.setSceneTransition(from = currentSceneKey, to = scene.key) + /** + * Requests a scene change to the given scene. + * + * The change is animated. Therefore, while the value in [desiredScene] will update immediately, + * it will be some time before the UI will switch to the desired scene. The scene change + * requested is remembered here but served by the UI layer, which will start a transition + * animation. Once enough of the transition has occurred, the system will come into agreement + * between the [desiredScene] and the UI. + */ + fun changeScene(scene: SceneModel, loggingReason: String) { + updateDesiredScene(scene, loggingReason, logger::logSceneChangeRequested) } - /** The current scene in the container with the given name. */ - val currentScene: StateFlow<SceneModel> = repository.currentScene - - /** Sets the visibility of the container with the given name. */ + /** Sets the visibility of the container. */ fun setVisible(isVisible: Boolean, loggingReason: String) { val wasVisible = repository.isVisible.value if (wasVisible == isVisible) { @@ -89,9 +133,6 @@ constructor( return repository.setVisible(isVisible) } - /** Whether the container with the given name is visible. */ - val isVisible: StateFlow<Boolean> = repository.isVisible - /** * Binds the given flow so the system remembers it. * @@ -101,23 +142,53 @@ constructor( repository.setTransitionState(transitionState) } - /** Progress of the transition into the current scene in the container with the given name. */ - val transitionProgress: Flow<Float> = repository.transitionProgress - /** - * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene - * transition occurs. The flow begins with a `null` value at first, because the initial scene is - * not something that we transition to from another scene. + * Returns a stream of events that emits one [Unit] every time the framework transitions from + * [from] to [to]. */ - val transitions: StateFlow<SceneTransitionModel?> = repository.transitions - - private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null) - - /** A flow of motion events originating from outside of the scene framework. */ - val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow() + fun finishedSceneTransitions(from: SceneKey, to: SceneKey): Flow<Unit> { + return transitionState + .mapNotNull { it as? ObservableTransitionState.Idle } + .map { idleState -> idleState.scene } + .distinctUntilChanged() + .pairwise() + .mapNotNull { (previousSceneKey, currentSceneKey) -> + Unit.takeIf { previousSceneKey == from && currentSceneKey == to } + } + } /** Handles a remote user input. */ fun onRemoteUserInput(input: RemoteUserInput) { _remoteUserInput.value = input } + + /** + * Notifies that the UI has transitioned sufficiently to the given scene. + * + * *Not intended for external use!* + * + * Once a transition between one scene and another passes a threshold, the UI invokes this + * method to report it, updating the value in [desiredScene] to match what the UI shows. + */ + internal fun onSceneChanged(scene: SceneModel, loggingReason: String) { + updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted) + } + + private fun updateDesiredScene( + scene: SceneModel, + loggingReason: String, + log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit, + ) { + val currentSceneKey = desiredScene.value.key + if (currentSceneKey == scene.key) { + return + } + + log( + /* from= */ currentSceneKey, + /* to= */ scene.key, + /* loggingReason= */ loggingReason, + ) + repository.setDesiredScene(scene) + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index bd233f80b47b..afefccb27214 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -30,6 +30,7 @@ import com.android.systemui.model.SysUiState import com.android.systemui.model.updateFlags import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.logger.SceneLogger +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING @@ -40,8 +41,8 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_B import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch /** @@ -73,14 +74,31 @@ constructor( } } - /** Updates the visibility of the scene container based on the current scene. */ + /** Updates the visibility of the scene container. */ private fun hydrateVisibility() { applicationScope.launch { - sceneInteractor.currentScene - .map { it.key } + sceneInteractor.transitionState + .mapNotNull { state -> + when (state) { + is ObservableTransitionState.Idle -> { + if (state.scene != SceneKey.Gone) { + true to "scene is not Gone" + } else { + false to "scene is Gone" + } + } + is ObservableTransitionState.Transition -> { + if (state.fromScene == SceneKey.Gone) { + true to "scene transitioning away from Gone" + } else { + null + } + } + } + } .distinctUntilChanged() - .collect { sceneKey -> - sceneInteractor.setVisible(sceneKey != SceneKey.Gone, "scene is $sceneKey") + .collect { (isVisible, loggingReason) -> + sceneInteractor.setVisible(isVisible, loggingReason) } } } @@ -89,43 +107,55 @@ constructor( private fun automaticallySwitchScenes() { applicationScope.launch { authenticationInteractor.isUnlocked - .map { isUnlocked -> - val currentSceneKey = sceneInteractor.currentScene.value.key + .mapNotNull { isUnlocked -> + val renderedScenes = + when (val transitionState = sceneInteractor.transitionState.value) { + is ObservableTransitionState.Idle -> setOf(transitionState.scene) + is ObservableTransitionState.Transition -> + setOf( + transitionState.progress, + transitionState.toScene, + ) + } val isBypassEnabled = authenticationInteractor.isBypassEnabled() when { isUnlocked -> - when (currentSceneKey) { + when { // When the device becomes unlocked in Bouncer, go to Gone. - is SceneKey.Bouncer -> + renderedScenes.contains(SceneKey.Bouncer) -> SceneKey.Gone to "device unlocked in Bouncer scene" + // When the device becomes unlocked in Lockscreen, go to Gone if // bypass is enabled. - is SceneKey.Lockscreen -> + renderedScenes.contains(SceneKey.Lockscreen) -> if (isBypassEnabled) { SceneKey.Gone to "device unlocked in Lockscreen scene with bypass" } else { null } + // We got unlocked while on a scene that's not Lockscreen or // Bouncer, no need to change scenes. else -> null } + // When the device becomes locked, to Lockscreen. !isUnlocked -> - when (currentSceneKey) { + when { // Already on lockscreen or bouncer, no need to change scenes. - is SceneKey.Lockscreen, - is SceneKey.Bouncer -> null + renderedScenes.contains(SceneKey.Lockscreen) || + renderedScenes.contains(SceneKey.Bouncer) -> null + // We got locked while on a scene that's not Lockscreen or Bouncer, // go to Lockscreen. else -> - SceneKey.Lockscreen to "device locked in $currentSceneKey scene" + SceneKey.Lockscreen to + "device locked in non-Lockscreen and non-Bouncer scene" } else -> null } } - .filterNotNull() .collect { (targetSceneKey, loggingReason) -> switchToScene( targetSceneKey = targetSceneKey, @@ -143,7 +173,7 @@ constructor( WakefulnessState.STARTING_TO_SLEEP -> { switchToScene( targetSceneKey = SceneKey.Lockscreen, - loggingReason = "device is asleep", + loggingReason = "device is starting to sleep", ) } WakefulnessState.STARTING_TO_WAKE -> { @@ -165,8 +195,9 @@ constructor( /** Keeps [SysUiState] up-to-date */ private fun hydrateSystemUiState() { applicationScope.launch { - sceneInteractor.currentScene - .map { it.key } + sceneInteractor.transitionState + .mapNotNull { it as? ObservableTransitionState.Idle } + .map { it.scene } .distinctUntilChanged() .collect { sceneKey -> sysUiState.updateFlags( @@ -183,7 +214,7 @@ constructor( } private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) { - sceneInteractor.setCurrentScene( + sceneInteractor.changeScene( scene = SceneModel(targetSceneKey), loggingReason = loggingReason, ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt index 0adbd5ad19a7..62136dcd8e1d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt @@ -37,7 +37,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } - fun logSceneChange( + fun logSceneChangeRequested( from: SceneKey, to: SceneKey, reason: String, @@ -50,7 +50,24 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: str2 = to.toString() str3 = reason }, - messagePrinter = { "$str1 → $str2, reason: $str3" }, + messagePrinter = { "Scene change requested: $str1 → $str2, reason: $str3" }, + ) + } + + fun logSceneChangeCommitted( + from: SceneKey, + to: SceneKey, + reason: String, + ) { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { + str1 = from.toString() + str2 = to.toString() + str3 = reason + }, + messagePrinter = { "Scene change committed: $str1 → $str2, reason: $str3" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index b4ebaece21f1..3e9bbe464e2c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -45,15 +45,15 @@ constructor( */ val allSceneKeys: List<SceneKey> = interactor.allSceneKeys() - /** The current scene. */ - val currentScene: StateFlow<SceneModel> = interactor.currentScene + /** The scene that should be rendered. */ + val currentScene: StateFlow<SceneModel> = interactor.desiredScene /** Whether the container is visible. */ val isVisible: StateFlow<Boolean> = interactor.isVisible - /** Requests a transition to the scene with the given key. */ - fun setCurrentScene(scene: SceneModel) { - interactor.setCurrentScene( + /** Notifies that the UI has transitioned sufficiently to the given scene. */ + fun onSceneChanged(scene: SceneModel) { + interactor.onSceneChanged( scene = scene, loggingReason = SCENE_TRANSITION_LOGGING_REASON, ) diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index d33d113f6cbd..2f0fc5127009 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -228,6 +228,8 @@ open class SensorUseStartedActivity @Inject constructor( } override fun onDismiss(dialog: DialogInterface?) { - finish() + if (!isChangingConfigurations) { + finish() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt index 468a75d8276e..e7ee961e1888 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt @@ -48,6 +48,9 @@ interface DisplayTracker { /** Remove a [Callback] previously added. */ fun removeCallback(callback: Callback) + /** Gets the Display with the given displayId */ + fun getDisplay(displayId: Int): Display + /** Ćallback for notifying of changes. */ interface Callback { diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt index 5169f88c373c..68cc483fbe80 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt @@ -115,6 +115,10 @@ internal constructor( } } + override fun getDisplay(displayId: Int): Display { + return displayManager.getDisplay(displayId) + } + @WorkerThread private fun onDisplayAdded(displayId: Int, list: List<DisplayTrackerDataItem>) { Assert.isNotMainThread() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index b11b4727c3c3..b29d46174bd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -49,6 +49,9 @@ interface WifiRepository { const val COL_NAME_IS_ENABLED = "isEnabled" /** Column name to use for [isWifiDefault] for table logging. */ const val COL_NAME_IS_DEFAULT = "isDefault" + + const val CARRIER_MERGED_INVALID_SUB_ID_REASON = + "Wifi network was carrier merged but had invalid sub ID" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt index 7d2501ca0e79..ab9b516b837f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt @@ -24,6 +24,7 @@ import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK import com.android.systemui.demomode.DemoModeController import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted @@ -56,12 +57,14 @@ constructor( val activity = getString("activity").toActivity() val ssid = getString("ssid") val validated = getString("fully").toBoolean() + val hotspotDeviceType = getString("hotspot").toHotspotDeviceType() return FakeWifiEventModel.Wifi( level = level, activity = activity, ssid = ssid, validated = validated, + hotspotDeviceType, ) } @@ -82,6 +85,20 @@ constructor( else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE } + private fun String?.toHotspotDeviceType(): WifiNetworkModel.HotspotDeviceType { + return when (this) { + null, + "none" -> WifiNetworkModel.HotspotDeviceType.NONE + "unknown" -> WifiNetworkModel.HotspotDeviceType.UNKNOWN + "phone" -> WifiNetworkModel.HotspotDeviceType.PHONE + "tablet" -> WifiNetworkModel.HotspotDeviceType.TABLET + "laptop" -> WifiNetworkModel.HotspotDeviceType.LAPTOP + "watch" -> WifiNetworkModel.HotspotDeviceType.WATCH + "auto" -> WifiNetworkModel.HotspotDeviceType.AUTO + else -> WifiNetworkModel.HotspotDeviceType.INVALID + } + } + companion object { const val DEFAULT_CARRIER_MERGED_SUB_ID = 10 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index a57be665f105..99b680056d7f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -97,6 +97,7 @@ constructor( isValidated = validated ?: true, level = level ?: 0, ssid = ssid ?: DEMO_NET_SSID, + hotspotDeviceType = hotspotDeviceType, // These fields below aren't supported in demo mode, since they aren't needed to satisfy // the interface. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt index f5035cbc0215..b2e843e283f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model import android.telephony.Annotation +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel /** * Model for demo wifi commands, ported from [NetworkControllerImpl] @@ -29,6 +30,8 @@ sealed interface FakeWifiEventModel { @Annotation.DataActivityType val activity: Int, val ssid: String?, val validated: Boolean?, + val hotspotDeviceType: WifiNetworkModel.HotspotDeviceType = + WifiNetworkModel.HotspotDeviceType.NONE, ) : FakeWifiEventModel data class CarrierMerged( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt new file mode 100644 index 000000000000..f1b98b3972e1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.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.pipeline.wifi.data.repository.prod + +import android.net.wifi.WifiManager +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel +import java.util.concurrent.Executor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +/** + * Object to provide shared helper functions between [WifiRepositoryImpl] and + * [WifiRepositoryViaTrackerLib]. + */ +object WifiRepositoryHelper { + /** Creates a flow that fetches the [DataActivityModel] from [WifiManager]. */ + fun createActivityFlow( + wifiManager: WifiManager, + @Main mainExecutor: Executor, + scope: CoroutineScope, + tableLogBuffer: TableLogBuffer, + inputLogger: (String) -> Unit, + ): StateFlow<DataActivityModel> { + return conflatedCallbackFlow { + val callback = + WifiManager.TrafficStateCallback { state -> + inputLogger.invoke(prettyPrintActivity(state)) + trySend(state.toWifiDataActivityModel()) + } + wifiManager.registerTrafficStateCallback(mainExecutor, callback) + awaitClose { wifiManager.unregisterTrafficStateCallback(callback) } + } + .logDiffsForTable( + tableLogBuffer, + columnPrefix = ACTIVITY_PREFIX, + initialValue = ACTIVITY_DEFAULT, + ) + .stateIn( + scope, + started = SharingStarted.WhileSubscribed(), + initialValue = ACTIVITY_DEFAULT, + ) + } + + // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer. + private fun prettyPrintActivity(activity: Int): String { + return when (activity) { + WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE" + WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN" + WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT" + WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT" + else -> "INVALID" + } + } + + private const val ACTIVITY_PREFIX = "wifiActivity" + val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index 995de6d2fc61..afd15765d163 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -28,7 +28,6 @@ import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.NetworkRequest import android.net.wifi.WifiInfo import android.net.wifi.WifiManager -import android.net.wifi.WifiManager.TrafficStateCallback import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -40,10 +39,10 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger @@ -218,29 +217,15 @@ constructor( ) override val wifiActivity: StateFlow<DataActivityModel> = - conflatedCallbackFlow { - val callback = TrafficStateCallback { state -> - logger.logActivity(prettyPrintActivity(state)) - trySend(state.toWifiDataActivityModel()) - } - wifiManager.registerTrafficStateCallback(mainExecutor, callback) - awaitClose { wifiManager.unregisterTrafficStateCallback(callback) } - } - .logDiffsForTable( - wifiTableLogBuffer, - columnPrefix = ACTIVITY_PREFIX, - initialValue = ACTIVITY_DEFAULT, - ) - .stateIn( - scope, - started = SharingStarted.WhileSubscribed(), - initialValue = ACTIVITY_DEFAULT, - ) + WifiRepositoryHelper.createActivityFlow( + wifiManager, + mainExecutor, + scope, + wifiTableLogBuffer, + logger::logActivity, + ) companion object { - private const val ACTIVITY_PREFIX = "wifiActivity" - - val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false) // Start out with no known wifi network. // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an // initial fetch to get a starting wifi network. But, it uses a deprecated API @@ -277,6 +262,8 @@ constructor( isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED), level = wifiManager.calculateSignalLevel(wifiInfo.rssi), wifiInfo.ssid, + // This repository doesn't support any hotspot information. + WifiNetworkModel.HotspotDeviceType.NONE, wifiInfo.isPasspointAp, wifiInfo.isOsuAp, wifiInfo.passpointProviderFriendlyName @@ -284,16 +271,6 @@ constructor( } } - private fun prettyPrintActivity(activity: Int): String { - return when (activity) { - TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE" - TrafficStateCallback.DATA_ACTIVITY_IN -> "IN" - TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT" - TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT" - else -> "INVALID" - } - } - private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest = NetworkRequest.Builder() .clearCapabilities() @@ -301,9 +278,6 @@ constructor( .addTransportType(TRANSPORT_WIFI) .addTransportType(TRANSPORT_CELLULAR) .build() - - private const val CARRIER_MERGED_INVALID_SUB_ID_REASON = - "Wifi network was carrier merged but had invalid sub ID" } @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index 127136789ba6..175563bb0764 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -17,12 +17,15 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod import android.net.wifi.WifiManager +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.table.TableLogBuffer @@ -31,20 +34,25 @@ import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType +import com.android.wifitrackerlib.HotspotNetworkEntry import com.android.wifitrackerlib.MergedCarrierEntry import com.android.wifitrackerlib.WifiEntry +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE import com.android.wifitrackerlib.WifiPickerTracker import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow @@ -62,6 +70,7 @@ import kotlinx.coroutines.flow.stateIn class WifiRepositoryViaTrackerLib @Inject constructor( + featureFlags: FeatureFlags, @Application private val scope: CoroutineScope, @Main private val mainExecutor: Executor, private val wifiPickerTrackerFactory: WifiPickerTrackerFactory, @@ -75,6 +84,8 @@ constructor( mainExecutor.execute { it.currentState = Lifecycle.State.CREATED } } + private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER) + private var wifiPickerTracker: WifiPickerTracker? = null private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run { @@ -128,19 +139,21 @@ constructor( } } - // TODO(b/292591403): [WifiPickerTrackerFactory] currently scans to see all - // available wifi networks every 10s. Because SysUI only needs to display the - // **connected** network, we don't need scans to be running. We should disable these - // scans (ideal) or at least run them very infrequently. - wifiPickerTracker = wifiPickerTrackerFactory.create(lifecycle, callback) + wifiPickerTracker = + wifiPickerTrackerFactory.create(lifecycle, callback).apply { + // By default, [WifiPickerTracker] will scan to see all available wifi + // networks in the area. Because SysUI only needs to display the + // **connected** network, we don't need scans to be running (and in fact, + // running scans is costly and should be avoided whenever possible). + this?.disableScanning() + } // The lifecycle must be STARTED in order for the callback to receive events. mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED } awaitClose { mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED } } } - // TODO(b/292534484): Update to Eagerly once scans are disabled. (Here and other flows) - .stateIn(scope, SharingStarted.WhileSubscribed(), current) + .stateIn(scope, SharingStarted.Eagerly, current) } override val isWifiEnabled: StateFlow<Boolean> = @@ -153,7 +166,7 @@ constructor( columnName = COL_NAME_IS_ENABLED, initialValue = false, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .stateIn(scope, SharingStarted.Eagerly, false) override val wifiNetwork: StateFlow<WifiNetworkModel> = wifiPickerTrackerInfo @@ -164,7 +177,7 @@ constructor( columnPrefix = "", initialValue = WIFI_NETWORK_DEFAULT, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), WIFI_NETWORK_DEFAULT) + .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT) /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */ private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel { @@ -172,30 +185,58 @@ constructor( return WIFI_NETWORK_DEFAULT } return if (this is MergedCarrierEntry) { + this.convertCarrierMergedToModel() + } else { + this.convertNormalToModel() + } + } + + private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel { + return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) { + WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON) + } else { WifiNetworkModel.CarrierMerged( networkId = NETWORK_ID, - // TODO(b/292534484): Fetch the real subscription ID from [MergedCarrierEntry]. - subscriptionId = TEMP_SUB_ID, + subscriptionId = this.subscriptionId, level = this.level, // WifiManager APIs to calculate the signal level start from 0, so // maxSignalLevel + 1 represents the total level buckets count. numberOfLevels = wifiManager.maxSignalLevel + 1, ) - } else { - WifiNetworkModel.Active( - networkId = NETWORK_ID, - isValidated = this.hasInternetAccess(), - level = this.level, - ssid = this.ssid, - // TODO(b/292534484): Fetch the real values from [WifiEntry] (#getTitle might be - // appropriate). - isPasspointAccessPoint = false, - isOnlineSignUpForPasspointAccessPoint = false, - passpointProviderFriendlyName = null, - ) } } + private fun WifiEntry.convertNormalToModel(): WifiNetworkModel { + if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) { + // If our level means the network is unreachable or the level is otherwise invalid, we + // don't have an active network. + return WifiNetworkModel.Inactive + } + + val hotspotDeviceType = + if (isInstantTetherEnabled && this is HotspotNetworkEntry) { + this.deviceType.toHotspotDeviceType() + } else { + WifiNetworkModel.HotspotDeviceType.NONE + } + + return WifiNetworkModel.Active( + networkId = NETWORK_ID, + isValidated = this.hasInternetAccess(), + level = this.level, + ssid = this.title, + hotspotDeviceType = hotspotDeviceType, + // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for + // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can + // always be false/null in this repository. + // TODO(b/292534484): Remove these fields from the wifi network model once this + // repository is fully enabled. + isPasspointAccessPoint = false, + isOnlineSignUpForPasspointAccessPoint = false, + passpointProviderFriendlyName = null, + ) + } + override val isWifiDefault: StateFlow<Boolean> = wifiPickerTrackerInfo .map { it.isDefault } @@ -206,12 +247,16 @@ constructor( columnName = COL_NAME_IS_DEFAULT, initialValue = false, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .stateIn(scope, SharingStarted.Eagerly, false) - // TODO(b/292534484): Re-use WifiRepositoryImpl code to implement wifi activity since - // WifiTrackerLib doesn't expose activity details. override val wifiActivity: StateFlow<DataActivityModel> = - MutableStateFlow(DataActivityModel(false, false)) + WifiRepositoryHelper.createActivityFlow( + wifiManager, + mainExecutor, + scope, + wifiTrackerLibTableLogBuffer, + this::logActivity, + ) private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) { inputLogger.log( @@ -231,6 +276,10 @@ constructor( ) } + private fun logActivity(activity: String) { + inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" }) + } + /** * Data class storing all the information fetched from [WifiPickerTracker]. * @@ -249,6 +298,7 @@ constructor( class Factory @Inject constructor( + private val featureFlags: FeatureFlags, @Application private val scope: CoroutineScope, @Main private val mainExecutor: Executor, private val wifiPickerTrackerFactory: WifiPickerTrackerFactory, @@ -257,6 +307,7 @@ constructor( ) { fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib { return WifiRepositoryViaTrackerLib( + featureFlags, scope, mainExecutor, wifiPickerTrackerFactory, @@ -283,13 +334,5 @@ constructor( * to [WifiRepositoryViaTrackerLib]. */ private const val NETWORK_ID = -1 - - /** - * A temporary subscription ID until WifiTrackerLib exposes a method to fetch the - * subscription ID. - * - * Use -2 because [SubscriptionManager.INVALID_SUBSCRIPTION_ID] is -1. - */ - private const val TEMP_SUB_ID = -2 } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt index 4b33c88cea30..7078a2e1728c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt @@ -17,11 +17,13 @@ package com.android.systemui.statusbar.pipeline.wifi.shared.model import android.net.wifi.WifiManager.UNKNOWN_SSID +import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo import android.telephony.SubscriptionManager import androidx.annotation.VisibleForTesting import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType /** Provides information about the current wifi network. */ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { @@ -52,6 +54,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -83,6 +86,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -110,6 +114,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -184,6 +189,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_LEVEL, level) row.logChange(COL_NUM_LEVELS, numberOfLevels) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -209,6 +215,12 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { /** See [android.net.wifi.WifiInfo.ssid]. */ val ssid: String? = null, + /** + * The type of device providing a hotspot connection, or [HotspotDeviceType.NONE] if this + * isn't a hotspot connection. + */ + val hotspotDeviceType: HotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE, + /** See [android.net.wifi.WifiInfo.isPasspointAp]. */ val isPasspointAccessPoint: Boolean = false, @@ -247,6 +259,9 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { if (prevVal.ssid != ssid) { row.logChange(COL_SSID, ssid) } + if (prevVal.hotspotDeviceType != hotspotDeviceType) { + row.logChange(COL_HOTSPOT, hotspotDeviceType.name) + } // TODO(b/238425913): The passpoint-related values are frequently never used, so it // would be great to not log them when they're not used. @@ -272,6 +287,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_LEVEL, level) row.logChange(COL_NUM_LEVELS, null) row.logChange(COL_SSID, ssid) + row.logChange(COL_HOTSPOT, hotspotDeviceType.name) row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint) row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint) row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName) @@ -298,13 +314,51 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } companion object { + // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX] instead + // once the migration to WifiTrackerLib is complete. @VisibleForTesting internal const val MAX_VALID_LEVEL = 4 } } companion object { + // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN] instead + // once the migration to WifiTrackerLib is complete. @VisibleForTesting internal const val MIN_VALID_LEVEL = 0 } + + /** + * Enum for the type of device providing the hotspot connection, or [NONE] if this connection + * isn't a hotspot. + */ + enum class HotspotDeviceType { + /* This wifi connection isn't a hotspot. */ + NONE, + /** The device type for this hotspot is unknown. */ + UNKNOWN, + PHONE, + TABLET, + LAPTOP, + WATCH, + AUTO, + /** The device type sent for this hotspot is invalid to SysUI. */ + INVALID, + } + + /** + * Converts a device type from [com.android.wifitrackerlib.HotspotNetworkEntry.deviceType] to + * our internal representation. + */ + fun @receiver:DeviceType Int.toHotspotDeviceType(): HotspotDeviceType { + return when (this) { + NetworkProviderInfo.DEVICE_TYPE_UNKNOWN -> HotspotDeviceType.UNKNOWN + NetworkProviderInfo.DEVICE_TYPE_PHONE -> HotspotDeviceType.PHONE + NetworkProviderInfo.DEVICE_TYPE_TABLET -> HotspotDeviceType.TABLET + NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> HotspotDeviceType.LAPTOP + NetworkProviderInfo.DEVICE_TYPE_WATCH -> HotspotDeviceType.WATCH + NetworkProviderInfo.DEVICE_TYPE_AUTO -> HotspotDeviceType.AUTO + else -> HotspotDeviceType.INVALID + } + } } const val TYPE_CARRIER_MERGED = "CarrierMerged" @@ -319,6 +373,7 @@ const val COL_VALIDATED = "isValidated" const val COL_LEVEL = "level" const val COL_NUM_LEVELS = "maxLevel" const val COL_SSID = "ssid" +const val COL_HOTSPOT = "hotspot" const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint" const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint" const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName" diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index e7d420bcb32b..9016220b40cf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java @@ -419,7 +419,15 @@ public class CarrierTextManagerTest extends SysuiTestCase { assertFalse(mWifiRepository.isWifiConnectedWithValidSsid()); mWifiRepository.setWifiNetwork( - new WifiNetworkModel.Active(0, false, 0, "", false, false, null)); + new WifiNetworkModel.Active( + /* networkId= */ 0, + /* isValidated= */ false, + /* level= */ 0, + /* ssid= */ "", + /* hotspotDeviceType= */ WifiNetworkModel.HotspotDeviceType.NONE, + /* isPasspointAccessPoint= */ false, + /* isOnlineSignUpForPasspointAccessPoint= */ false, + /* passpointProviderFriendlyName= */ null)); assertTrue(mWifiRepository.isWifiConnectedWithValidSsid()); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index efb981e5cebb..a6bd9363a08b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -50,6 +50,7 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.FalsingManager import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.statusbar.policy.ConfigurationController @@ -66,6 +67,8 @@ import com.google.common.truth.Truth import java.util.Optional import junit.framework.Assert import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -139,6 +142,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { private lateinit var testableResources: TestableResources private lateinit var sceneTestUtils: SceneTestUtils private lateinit var sceneInteractor: SceneInteractor + private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState> private lateinit var underTest: KeyguardSecurityContainerController @@ -198,6 +202,9 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID) sceneTestUtils = SceneTestUtils(this) sceneInteractor = sceneTestUtils.sceneInteractor() + sceneTransitionStateFlow = + MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen)) + sceneInteractor.setTransitionState(sceneTransitionStateFlow) underTest = KeyguardSecurityContainerController( @@ -733,20 +740,39 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // is // not enough to trigger a dismissal of the keyguard. underTest.onViewAttached() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition( + SceneKey.Lockscreen, + SceneKey.Bouncer, + flowOf(.5f) + ) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) // While listening, going from the bouncer scene to the gone scene, does dismiss the // keyguard. - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) runCurrent() verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) // While listening, moving back to the bouncer scene does not dismiss the keyguard // again. clearInvocations(viewMediatorCallback) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) @@ -754,12 +780,22 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // scene // does not dismiss the keyguard while we're not listening. underTest.onViewDetached() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) // While not listening, moving back to the bouncer does not dismiss the keyguard. - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) @@ -767,7 +803,12 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // gone // scene now does dismiss the keyguard again. underTest.onViewAttached() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) runCurrent() verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 5abab6239b1e..6f3322ab1b85 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -40,6 +40,7 @@ import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_T import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser; import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS; +import static com.android.systemui.flags.Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; @@ -89,6 +90,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.display.DisplayManagerGlobal; import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorProperties; @@ -121,6 +123,9 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.text.TextUtils; +import android.view.Display; +import android.view.DisplayAdjustments; +import android.view.DisplayInfo; import androidx.annotation.NonNull; @@ -143,6 +148,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -304,10 +310,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mFingerprintAuthenticatorsRegisteredCallback; private IFaceAuthenticatorsRegisteredCallback mFaceAuthenticatorsRegisteredCallback; private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999); + private FakeDisplayTracker mDisplayTracker; @Before public void setup() throws RemoteException { MockitoAnnotations.initMocks(this); + mDisplayTracker = new FakeDisplayTracker(mContext); when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId); when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true); @@ -348,6 +356,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { allowTestableLooperAsMainThread(); mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false); + mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, false); when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI); @@ -358,6 +367,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { anyInt()); mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); + setupBiometrics(mKeyguardUpdateMonitor); + } + + private void setupBiometrics(KeyguardUpdateMonitor keyguardUpdateMonitor) + throws RemoteException { captureAuthenticatorsRegisteredCallbacks(); setupFaceAuth(/* isClass3 */ false); setupFingerprintAuth(/* isClass3 */ true); @@ -367,9 +381,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mBiometricEnabledOnKeyguardCallback = mBiometricEnabledCallbackArgCaptor.getValue(); biometricsEnabledForCurrentUser(); - mHandler = spy(mKeyguardUpdateMonitor.getHandler()); + mHandler = spy(keyguardUpdateMonitor.getHandler()); try { - FieldSetter.setField(mKeyguardUpdateMonitor, + FieldSetter.setField(keyguardUpdateMonitor, KeyguardUpdateMonitor.class.getDeclaredField("mHandler"), mHandler); } catch (NoSuchFieldException e) { @@ -3029,6 +3043,79 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE); } + @Test + public void stopFaceAuthOnDisplayOffFlagNotEnabled_doNotRegisterForDisplayCallback() { + assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(0); + } + + @Test + public void onDisplayOn_nothingHappens() throws RemoteException { + // GIVEN + keyguardIsVisible(); + enableStopFaceAuthOnDisplayOff(); + + // WHEN the default display state changes to ON + triggerDefaultDisplayStateChangeToOn(); + + // THEN face auth is NOT started since we rely on STARTED_WAKING_UP to start face auth, + // NOT the display on event + verifyFaceAuthenticateNeverCalled(); + verifyFaceDetectNeverCalled(); + } + + @Test + public void onDisplayOff_stopFaceAuth() throws RemoteException { + enableStopFaceAuthOnDisplayOff(); + + // GIVEN device is listening for face + mKeyguardUpdateMonitor.setKeyguardShowing(true, false); + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + verifyFaceAuthenticateCall(); + + final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); + mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN the default display state changes to OFF + triggerDefaultDisplayStateChangeToOff(); + + // THEN face listening is stopped. + verify(faceCancel).cancel(); + verify(callback).onBiometricRunningStateChanged( + eq(false), eq(BiometricSourceType.FACE)); // beverlyt + + } + + private void triggerDefaultDisplayStateChangeToOn() { + triggerDefaultDisplayStateChangeTo(true); + } + + private void triggerDefaultDisplayStateChangeToOff() { + triggerDefaultDisplayStateChangeTo(false); + } + + /** + * @param on true for Display.STATE_ON, else Display.STATE_OFF + */ + private void triggerDefaultDisplayStateChangeTo(boolean on) { + DisplayManagerGlobal displayManagerGlobal = mock(DisplayManagerGlobal.class); + DisplayInfo displayInfoWithDisplayState = new DisplayInfo(); + displayInfoWithDisplayState.state = on ? Display.STATE_ON : Display.STATE_OFF; + when(displayManagerGlobal.getDisplayInfo(mDisplayTracker.getDefaultDisplayId())) + .thenReturn(displayInfoWithDisplayState); + mDisplayTracker.setAllDisplays(new Display[]{ + new Display( + displayManagerGlobal, + mDisplayTracker.getDefaultDisplayId(), + displayInfoWithDisplayState, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS + ) + }); + mDisplayTracker.triggerOnDisplayChanged(mDisplayTracker.getDefaultDisplayId()); + } + private void verifyFingerprintAuthenticateNeverCalled() { verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any()); verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(), @@ -3297,6 +3384,18 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); } + private void enableStopFaceAuthOnDisplayOff() throws RemoteException { + cleanupKeyguardUpdateMonitor(); + clearInvocations(mFaceManager); + clearInvocations(mFingerprintManager); + clearInvocations(mBiometricManager); + clearInvocations(mStatusBarStateController); + mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, true); + mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); + setupBiometrics(mKeyguardUpdateMonitor); + assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(1); + } + private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) { int subscription = simInited ? 1/* mock subid=1 */ : SubscriptionManager.PLACEHOLDER_SUBSCRIPTION_ID_BASE; @@ -3374,7 +3473,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager, mFaceWakeUpTriggersConfig, mDevicePostureController, Optional.of(mInteractiveToAuthProvider), mFeatureFlags, - mTaskStackChangeListeners, mActivityTaskManager); + mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java index ed6a891a6094..45021ba1b300 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java @@ -20,7 +20,9 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN import static com.android.keyguard.LockIconView.ICON_LOCK; import static com.android.keyguard.LockIconView.ICON_UNLOCK; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; @@ -33,11 +35,13 @@ import android.hardware.biometrics.BiometricSourceType; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Pair; +import android.view.HapticFeedbackConstants; import android.view.View; import androidx.test.filters.SmallTest; import com.android.settingslib.udfps.UdfpsOverlayParams; +import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.doze.util.BurnInHelperKt; import org.junit.Test; @@ -339,4 +343,59 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { // THEN the lock icon is shown verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } + + @Test + public void playHaptic_onTouchExploration_NoOneWayHaptics_usesVibrate() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); + + // WHEN request to vibrate on touch exploration + mUnderTest.vibrateOnTouchExploration(); + + // THEN vibrates + verify(mVibrator).vibrate( + anyInt(), + any(), + eq(UdfpsController.EFFECT_CLICK), + eq("lock-icon-down"), + any()); + } + + @Test + public void playHaptic_onTouchExploration_withOneWayHaptics_performHapticFeedback() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); + + // WHEN request to vibrate on touch exploration + mUnderTest.vibrateOnTouchExploration(); + + // THEN performHapticFeedback is used + verify(mVibrator).performHapticFeedback(any(), eq(HapticFeedbackConstants.CONTEXT_CLICK)); + } + + @Test + public void playHaptic_onLongPress_NoOneWayHaptics_usesVibrate() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); + + // WHEN request to vibrate on long press + mUnderTest.vibrateOnLongPress(); + + // THEN uses vibrate + verify(mVibrator).vibrate( + anyInt(), + any(), + eq(UdfpsController.EFFECT_CLICK), + eq("lock-screen-lock-icon-longpress"), + any()); + } + + @Test + public void playHaptic_onLongPress_withOneWayHaptics_performHapticFeedback() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); + + // WHEN request to vibrate on long press + mUnderTest.vibrateOnLongPress(); + + // THEN uses perform haptic feedback + verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); + + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index df4d2225f459..86e0c751085b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -70,7 +70,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) @@ -102,7 +102,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) @@ -139,7 +139,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) @@ -169,7 +169,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun passwordAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password @@ -202,7 +202,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun patternAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern @@ -236,7 +236,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_notLocked_switchesToGoneScene() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -249,7 +249,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) utils.authenticationRepository.setLockscreenEnabled(true) utils.authenticationRepository.setUnlocked(false) @@ -262,7 +262,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_customMessageShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password @@ -283,7 +283,7 @@ class BouncerInteractorTest : SysuiTestCase() { val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) val message by collectLastValue(underTest.message) - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() underTest.showOrUnlockDevice() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 4e9fe8d91da1..4380af80efbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -73,14 +73,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -93,14 +94,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onPasswordInputChanged() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -115,12 +117,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("password") @@ -133,14 +136,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("wrong") @@ -155,14 +159,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("wrong") diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 000200c606b3..ea2cad2ab517 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -76,7 +76,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -84,7 +84,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -98,7 +99,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragStart() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -106,7 +107,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -122,14 +124,15 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() @@ -169,7 +172,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -177,7 +180,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() @@ -201,7 +205,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -209,7 +213,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 4b667c393b62..531f86abdfbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -76,11 +76,13 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -93,12 +95,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onPinButtonClicked() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -113,12 +117,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onBackspaceButtonClicked() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -135,11 +141,13 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onPinEdit() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -157,12 +165,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onBackspaceButtonLongPressed() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -181,10 +191,12 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -199,12 +211,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -223,12 +237,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -255,11 +271,13 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) utils.authenticationRepository.setAutoConfirmEnabled(true) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -272,13 +290,15 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) utils.authenticationRepository.setAutoConfirmEnabled(true) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit -> diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 834b9c526669..45d7a5ebb60a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -107,7 +107,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() @@ -120,7 +120,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -133,7 +133,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() @@ -146,7 +146,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onLockButtonClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index bb365d05e9e2..2cb02058ab03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -56,7 +56,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -69,7 +69,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 56e3e9649fe7..181f8a7e3003 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -25,7 +25,6 @@ import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -39,6 +38,7 @@ import org.junit.runners.JUnit4 class SceneContainerRepositoryTest : SysuiTestCase() { private val utils = SceneTestUtils(this) + private val testScope = utils.testScope @Test fun allSceneKeys() { @@ -56,97 +56,82 @@ class SceneContainerRepositoryTest : SysuiTestCase() { } @Test - fun currentScene() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + fun desiredScene() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val currentScene by collectLastValue(underTest.desiredScene) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene(SceneModel(SceneKey.Shade)) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) - } + underTest.setDesiredScene(SceneModel(SceneKey.Shade)) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) + } @Test(expected = IllegalStateException::class) - fun setCurrentScene_noSuchSceneInContainer_throws() { + fun setDesiredScene_noSuchSceneInContainer_throws() { val underTest = utils.fakeSceneContainerRepository( utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), ) - underTest.setCurrentScene(SceneModel(SceneKey.Shade)) + underTest.setDesiredScene(SceneModel(SceneKey.Shade)) } @Test - fun isVisible() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val isVisible by collectLastValue(underTest.isVisible) - assertThat(isVisible).isTrue() + fun isVisible() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val isVisible by collectLastValue(underTest.isVisible) + assertThat(isVisible).isTrue() - underTest.setVisible(false) - assertThat(isVisible).isFalse() + underTest.setVisible(false) + assertThat(isVisible).isFalse() - underTest.setVisible(true) - assertThat(isVisible).isTrue() - } + underTest.setVisible(true) + assertThat(isVisible).isTrue() + } @Test - fun transitionProgress() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val sceneTransitionProgress by collectLastValue(underTest.transitionProgress) - assertThat(sceneTransitionProgress).isEqualTo(1f) - - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Lockscreen) - ) - underTest.setTransitionState(transitionState) - assertThat(sceneTransitionProgress).isEqualTo(1f) - - val progress = MutableStateFlow(1f) - transitionState.value = - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = SceneKey.Shade, - progress = progress, - ) - assertThat(sceneTransitionProgress).isEqualTo(1f) - - progress.value = 0.1f - assertThat(sceneTransitionProgress).isEqualTo(0.1f) - - progress.value = 0.9f - assertThat(sceneTransitionProgress).isEqualTo(0.9f) - - underTest.setTransitionState(null) - assertThat(sceneTransitionProgress).isEqualTo(1f) - } + fun transitionState_defaultsToIdle() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val transitionState by collectLastValue(underTest.transitionState) + + assertThat(transitionState) + .isEqualTo( + ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ) + } @Test - fun setSceneTransition() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val sceneTransition by collectLastValue(underTest.transitions) - assertThat(sceneTransition).isNull() + fun transitionState_reflectsUpdates() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + underTest.setTransitionState(transitionState) + val reflectedTransitionState by collectLastValue(underTest.transitionState) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + val progress = MutableStateFlow(1f) + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = progress, + ) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - underTest.setSceneTransition(SceneKey.Lockscreen, SceneKey.QuickSettings) - assertThat(sceneTransition) - .isEqualTo( - SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings) - ) - } + progress.value = 0.1f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - @Test(expected = IllegalStateException::class) - fun setSceneTransition_noFromSceneInContainer_throws() { - val underTest = - utils.fakeSceneContainerRepository( - utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), - ) - underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen) - } + progress.value = 0.9f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - @Test(expected = IllegalStateException::class) - fun setSceneTransition_noToSceneInContainer_throws() { - val underTest = - utils.fakeSceneContainerRepository( - utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), - ) - underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen) - } + underTest.setTransitionState(null) + assertThat(reflectedTransitionState) + .isEqualTo( + ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 4facc7a6a36d..0a93a7ca465f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -25,10 +25,12 @@ import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -39,6 +41,7 @@ import org.junit.runners.JUnit4 class SceneInteractorTest : SysuiTestCase() { private val utils = SceneTestUtils(this) + private val testScope = utils.testScope private val repository = utils.fakeSceneContainerRepository() private val underTest = utils.sceneInteractor(repository = repository) @@ -48,77 +51,156 @@ class SceneInteractorTest : SysuiTestCase() { } @Test - fun currentScene() = runTest { - val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + fun changeScene() = + testScope.runTest { + val desiredScene by collectLastValue(underTest.desiredScene) + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) - } + underTest.changeScene(SceneModel(SceneKey.Shade), "reason") + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade)) + } + + @Test + fun onSceneChanged() = + testScope.runTest { + val desiredScene by collectLastValue(underTest.desiredScene) + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + + underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade)) + } @Test - fun sceneTransitionProgress() = runTest { - val transitionProgress by collectLastValue(underTest.transitionProgress) - assertThat(transitionProgress).isEqualTo(1f) + fun transitionState() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + underTest.setTransitionState(transitionState) + val reflectedTransitionState by collectLastValue(underTest.transitionState) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - val progress = MutableStateFlow(0.55f) - repository.setTransitionState( - MutableStateFlow( + val progress = MutableStateFlow(1f) + transitionState.value = ObservableTransitionState.Transition( fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - ), - ) - ) - assertThat(transitionProgress).isEqualTo(0.55f) - } + ) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + progress.value = 0.1f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + progress.value = 0.9f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + underTest.setTransitionState(null) + assertThat(reflectedTransitionState) + .isEqualTo( + ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ) + } @Test - fun isVisible() = runTest { - val isVisible by collectLastValue(underTest.isVisible) - assertThat(isVisible).isTrue() + fun isVisible() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isVisible) + assertThat(isVisible).isTrue() - underTest.setVisible(false, "reason") - assertThat(isVisible).isFalse() + underTest.setVisible(false, "reason") + assertThat(isVisible).isFalse() - underTest.setVisible(true, "reason") - assertThat(isVisible).isTrue() - } + underTest.setVisible(true, "reason") + assertThat(isVisible).isTrue() + } @Test - fun sceneTransitions() = runTest { - val transitions by collectLastValue(underTest.transitions) - assertThat(transitions).isNull() - - val initialSceneKey = underTest.currentScene.value.key - underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason") - assertThat(transitions) - .isEqualTo( - SceneTransitionModel( - from = initialSceneKey, - to = SceneKey.Shade, + fun finishedSceneTransitions() = + testScope.runTest { + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) ) - ) - - underTest.setCurrentScene(SceneModel(SceneKey.QuickSettings), "reason") - assertThat(transitions) - .isEqualTo( - SceneTransitionModel( - from = SceneKey.Shade, - to = SceneKey.QuickSettings, + underTest.setTransitionState(transitionState) + var transitionCount = 0 + val job = launch { + underTest + .finishedSceneTransitions( + from = SceneKey.Shade, + to = SceneKey.QuickSettings, + ) + .collect { transitionCount++ } + } + + assertThat(transitionCount).isEqualTo(0) + + underTest.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), ) - ) - } + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade) + runCurrent() + assertThat(transitionCount).isEqualTo(0) + + underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Shade, + toScene = SceneKey.QuickSettings, + progress = flowOf(0.5f), + ) + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings) + runCurrent() + assertThat(transitionCount).isEqualTo(1) + + underTest.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.QuickSettings, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), + ) + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade) + runCurrent() + assertThat(transitionCount).isEqualTo(1) + + underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Shade, + toScene = SceneKey.QuickSettings, + progress = flowOf(0.5f), + ) + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings) + runCurrent() + assertThat(transitionCount).isEqualTo(2) - @Test - fun remoteUserInput() = runTest { - val remoteUserInput by collectLastValue(underTest.remoteUserInput) - assertThat(remoteUserInput).isNull() + job.cancel() + } - for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) { - underTest.onRemoteUserInput(input) - assertThat(remoteUserInput).isEqualTo(input) + @Test + fun remoteUserInput() = + testScope.runTest { + val remoteUserInput by collectLastValue(underTest.remoteUserInput) + assertThat(remoteUserInput).isNull() + + for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) { + underTest.onRemoteUserInput(input) + assertThat(remoteUserInput).isEqualTo(input) + } } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index bec0b77e6480..45db7a0b17f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -29,15 +29,17 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.model.SysUiState import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -77,59 +79,86 @@ class SceneContainerStartableTest : SysuiTestCase() { sceneLogger = mock(), ) - @Before - fun setUp() { - prepareState() - } - @Test fun hydrateVisibility_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentDesiredSceneKey by + collectLastValue(sceneInteractor.desiredScene.map { it.key }) val isVisible by collectLastValue(sceneInteractor.isVisible) - prepareState( - isFeatureEnabled = true, - isDeviceUnlocked = true, - initialSceneKey = SceneKey.Gone, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + val transitionStateFlow = + prepareState( + isFeatureEnabled = true, + isDeviceUnlocked = true, + initialSceneKey = SceneKey.Gone, + ) + assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone) assertThat(isVisible).isTrue() underTest.start() - assertThat(isVisible).isFalse() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Gone, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), + ) + assertThat(isVisible).isTrue() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade) + assertThat(isVisible).isTrue() + + sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Shade, + toScene = SceneKey.Gone, + progress = flowOf(0.5f), + ) assertThat(isVisible).isTrue() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason") + transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) + assertThat(isVisible).isFalse() } @Test fun hydrateVisibility_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentDesiredSceneKey by + collectLastValue(sceneInteractor.desiredScene.map { it.key }) val isVisible by collectLastValue(sceneInteractor.isVisible) - prepareState( - isFeatureEnabled = false, - isDeviceUnlocked = true, - initialSceneKey = SceneKey.Lockscreen, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + val transitionStateFlow = + prepareState( + isFeatureEnabled = false, + isDeviceUnlocked = true, + initialSceneKey = SceneKey.Gone, + ) + assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone) assertThat(isVisible).isTrue() underTest.start() + assertThat(isVisible).isTrue() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Gone, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), + ) assertThat(isVisible).isTrue() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade) assertThat(isVisible).isTrue() } @Test fun switchToLockscreenWhenDeviceLocks_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = true, @@ -146,7 +175,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToLockscreenWhenDeviceLocks_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -163,7 +192,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = false, @@ -180,7 +209,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -197,7 +226,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isBypassEnabled = true, @@ -214,7 +243,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isBypassEnabled = false, @@ -231,7 +260,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isBypassEnabled = true, @@ -248,7 +277,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = false, @@ -265,7 +294,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -282,6 +311,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun hydrateSystemUiState() = testScope.runTest { + val transitionStateFlow = prepareState() underTest.start() runCurrent() clearInvocations(sysUiState) @@ -294,9 +324,16 @@ class SceneContainerStartableTest : SysuiTestCase() { SceneKey.QuickSettings, ) .forEachIndexed { index, sceneKey -> - sceneInteractor.setCurrentScene(SceneModel(sceneKey), "reason") + sceneInteractor.changeScene(SceneModel(sceneKey), "reason") + runCurrent() + verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY) + + sceneInteractor.onSceneChanged(SceneModel(sceneKey), "reason") runCurrent() + verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY) + transitionStateFlow.value = ObservableTransitionState.Idle(sceneKey) + runCurrent() verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY) } } @@ -304,7 +341,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, initialSceneKey = SceneKey.Lockscreen, @@ -321,7 +358,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNotNone_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, initialSceneKey = SceneKey.Lockscreen, @@ -338,7 +375,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, initialSceneKey = SceneKey.Lockscreen, @@ -358,17 +395,27 @@ class SceneContainerStartableTest : SysuiTestCase() { isBypassEnabled: Boolean = false, initialSceneKey: SceneKey? = null, authenticationMethod: AuthenticationMethodModel? = null, - ) { + ): MutableStateFlow<ObservableTransitionState> { featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled) authenticationRepository.setUnlocked(isDeviceUnlocked) keyguardRepository.setBypassEnabled(isBypassEnabled) - initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneModel(it), "reason") } + val transitionStateFlow = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + sceneInteractor.setTransitionState(transitionStateFlow) + initialSceneKey?.let { + transitionStateFlow.value = ObservableTransitionState.Idle(it) + sceneInteractor.changeScene(SceneModel(it), "reason") + sceneInteractor.onSceneChanged(SceneModel(it), "reason") + } authenticationMethod?.let { authenticationRepository.setAuthenticationMethod(authenticationMethod) authenticationRepository.setLockscreenEnabled( authenticationMethod != AuthenticationMethodModel.None ) } + return transitionStateFlow } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index 9f3b12bd2042..da6c42694666 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -69,7 +69,8 @@ class SceneContainerViewModelTest : SysuiTestCase() { val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene(SceneModel(SceneKey.Shade)) + underTest.onSceneChanged(SceneModel(SceneKey.Shade)) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index d9301604c67f..7443097a2628 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -78,7 +78,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -91,7 +91,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt index 1bf431b4ea13..1c8dac14b089 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index fef042be65a8..2dbeb7aa7e90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -489,6 +489,26 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun wifiNetwork_neverHasHotspot() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) } + + getNetworkCallback() + .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(latest is WifiNetworkModel.Active).isTrue() + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE) + } + + @Test fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() = testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index 7002cbb6ab21..9959e00fd3f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -18,13 +18,18 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod import android.net.wifi.WifiManager import android.net.wifi.WifiManager.UNKNOWN_SSID +import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.util.concurrency.FakeExecutor @@ -34,8 +39,13 @@ import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import com.android.wifitrackerlib.HotspotNetworkEntry +import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType import com.android.wifitrackerlib.MergedCarrierEntry import com.android.wifitrackerlib.WifiEntry +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE import com.android.wifitrackerlib.WifiPickerTracker import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,6 +55,7 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.mockito.Mockito.verify /** * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests. @@ -57,10 +68,25 @@ import org.junit.Test @TestableLooper.RunWithLooper(setAsMainLooper = true) class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { - private lateinit var underTest: WifiRepositoryViaTrackerLib + // Using lazy means that the class will only be constructed once it's fetched. Because the + // repository internally sets some values on construction, we need to set up some test + // parameters (like feature flags) *before* construction. Using lazy allows us to do that setup + // inside each test case without needing to manually recreate the repository. + private val underTest: WifiRepositoryViaTrackerLib by lazy { + WifiRepositoryViaTrackerLib( + featureFlags, + testScope.backgroundScope, + executor, + wifiPickerTrackerFactory, + wifiManager, + logger, + tableLogger, + ) + } private val executor = FakeExecutor(FakeSystemClock()) private val logger = LogBuffer("name", maxSize = 100, logcatEchoTracker = mock()) + private val featureFlags = FakeFeatureFlags() private val tableLogger = mock<TableLogBuffer>() private val wifiManager = mock<WifiManager>().apply { whenever(this.maxSignalLevel).thenReturn(10) } @@ -74,12 +100,21 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { @Before fun setUp() { + featureFlags.set(Flags.INSTANT_TETHER, false) whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor))) .thenReturn(wifiPickerTracker) - underTest = createRepo() } @Test + fun wifiPickerTrackerCreation_scansDisabled() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + testScope.runCurrent() + + verify(wifiPickerTracker).disableScanning() + } + + @Test fun isWifiEnabled_enabled_true() = testScope.runTest { val latest by collectLastValue(underTest.isWifiEnabled) @@ -238,7 +273,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) - whenever(this.ssid).thenReturn(SSID) + whenever(this.title).thenReturn(TITLE) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -246,7 +281,240 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest is WifiNetworkModel.Active).isTrue() val latestActive = latest as WifiNetworkModel.Active assertThat(latestActive.level).isEqualTo(3) - assertThat(latestActive.ssid).isEqualTo(SSID) + assertThat(latestActive.ssid).isEqualTo(TITLE) + } + + @Test + fun accessPointInfo_alwaysFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + whenever(this.title).thenReturn(TITLE) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.isPasspointAccessPoint).isFalse() + assertThat(latestActive.isOnlineSignUpForPasspointAccessPoint).isFalse() + assertThat(latestActive.passpointProviderFriendlyName).isNull() + } + + @Test + fun wifiNetwork_unreachableLevel_inactiveNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + } + + @Test + fun wifiNetwork_levelTooHigh_inactiveNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MAX + 1) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + } + + @Test + fun wifiNetwork_levelTooLow_inactiveNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MIN - 1) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + } + + @Test + fun wifiNetwork_levelIsMax_activeNetworkWithMaxLevel() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MAX) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java) + assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MAX) + } + + @Test + fun wifiNetwork_levelIsMin_activeNetworkWithMinLevel() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MIN) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java) + assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MIN) + } + + @Test + fun wifiNetwork_notHotspot_none() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE) + } + + @Test + fun wifiNetwork_hotspot_unknown() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_UNKNOWN) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.UNKNOWN) + } + + @Test + fun wifiNetwork_hotspot_phone() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_PHONE) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.PHONE) + } + + @Test + fun wifiNetwork_hotspot_tablet() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_TABLET) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.TABLET) + } + + @Test + fun wifiNetwork_hotspot_laptop() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_LAPTOP) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.LAPTOP) + } + + @Test + fun wifiNetwork_hotspot_watch() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.WATCH) + } + + @Test + fun wifiNetwork_hotspot_auto() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_AUTO) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.AUTO) + } + + @Test + fun wifiNetwork_hotspot_invalid() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(1234) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.INVALID) + } + + @Test + fun wifiNetwork_hotspot_flagOff_valueNotUsed() = + testScope.runTest { + // WHEN the flag is off + featureFlags.set(Flags.INSTANT_TETHER, false) + + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + // THEN NONE is always used, even if the wifi entry does have a hotspot device type + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE) } @Test @@ -258,6 +526,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) + whenever(this.subscriptionId).thenReturn(567) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -265,7 +534,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() val latestMerged = latest as WifiNetworkModel.CarrierMerged assertThat(latestMerged.level).isEqualTo(3) - // numberOfLevels = maxSignalLevel + 1 + assertThat(latestMerged.subscriptionId).isEqualTo(567) } @Test @@ -288,30 +557,23 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latestMerged.numberOfLevels).isEqualTo(6) } - /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID. @Test fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() = testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiInfo = - mock<WifiInfo>().apply { - whenever(this.isPrimary).thenReturn(true) - whenever(this.isCarrierMerged).thenReturn(true) + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) - getNetworkCallback() - .onCapabilitiesChanged( - NETWORK, - createWifiNetworkCapabilities(wifiInfo), - ) + getCallback().onWifiEntriesChanged() assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java) } - */ - @Test fun wifiNetwork_notValidated_networkNotValidated() = testScope.runTest { @@ -382,7 +644,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) - whenever(this.ssid).thenReturn("AB") + whenever(this.title).thenReturn("AB") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -397,7 +659,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(4) - whenever(this.ssid).thenReturn("CD") + whenever(this.title).thenReturn("CD") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(newWifiEntry) getCallback().onWifiEntriesChanged() @@ -430,12 +692,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn(SSID) + whenever(this.title).thenReturn(TITLE) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(SSID) + assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(TITLE) // WHEN we lose our current network whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) @@ -480,7 +742,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(1) - whenever(this.ssid).thenReturn(SSID) + whenever(this.title).thenReturn(TITLE) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -488,7 +750,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest1 is WifiNetworkModel.Active).isTrue() val latest1Active = latest1 as WifiNetworkModel.Active assertThat(latest1Active.level).isEqualTo(1) - assertThat(latest1Active.ssid).isEqualTo(SSID) + assertThat(latest1Active.ssid).isEqualTo(TITLE) // WHEN we add a second subscriber after having already emitted a value val latest2 by collectLastValue(underTest.wifiNetwork) @@ -497,7 +759,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest2 is WifiNetworkModel.Active).isTrue() val latest2Active = latest2 as WifiNetworkModel.Active assertThat(latest2Active.level).isEqualTo(1) - assertThat(latest2Active.ssid).isEqualTo(SSID) + assertThat(latest2Active.ssid).isEqualTo(TITLE) } @Test @@ -541,40 +803,32 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() } - /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID. - @Test - fun isWifiConnectedWithValidSsid_invalidNetwork_false() = - testScope.runTest { - collectLastValue(underTest.wifiNetwork) - - val wifiInfo = - mock<WifiInfo>().apply { - whenever(this.isPrimary).thenReturn(true) - whenever(this.isCarrierMerged).thenReturn(true) - whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) - } - - getNetworkCallback() - .onCapabilitiesChanged( - NETWORK, - createWifiNetworkCapabilities(wifiInfo), - ) - testScope.runCurrent() + @Test + fun isWifiConnectedWithValidSsid_invalidNetwork_false() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) - assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() - } + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + testScope.runCurrent() - */ + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + } @Test - fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() = + fun isWifiConnectedWithValidSsid_activeNetwork_nullTitle_false() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn(null) + whenever(this.title).thenReturn(null) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -584,14 +838,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } @Test - fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() = + fun isWifiConnectedWithValidSsid_activeNetwork_unknownTitle_false() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn(UNKNOWN_SSID) + whenever(this.title).thenReturn(UNKNOWN_SSID) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -601,14 +855,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } @Test - fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() = + fun isWifiConnectedWithValidSsid_activeNetwork_validTitle_true() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn("fakeSsid") + whenever(this.title).thenReturn("fakeSsid") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -626,7 +880,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn("fakeSsid") + whenever(this.title).thenReturn("fakeSsid") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -643,23 +897,74 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() } + @Test + fun wifiActivity_callbackGivesNone_activityFlowHasNone() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false)) + } + + @Test + fun wifiActivity_callbackGivesIn_activityFlowHasIn() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false)) + } + + @Test + fun wifiActivity_callbackGivesOut_activityFlowHasOut() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true)) + } + + @Test + fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true)) + } + private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback { testScope.runCurrent() return callbackCaptor.value } - private fun createRepo(): WifiRepositoryViaTrackerLib { - return WifiRepositoryViaTrackerLib( - testScope.backgroundScope, - executor, - wifiPickerTrackerFactory, - wifiManager, - logger, - tableLogger, - ) + private fun getTrafficStateCallback(): WifiManager.TrafficStateCallback { + testScope.runCurrent() + val callbackCaptor = argumentCaptor<WifiManager.TrafficStateCallback>() + verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + + private fun createHotspotWithType(@DeviceType type: Int): HotspotNetworkEntry { + return mock<HotspotNetworkEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.deviceType).thenReturn(type) + } } private companion object { - const val SSID = "AB" + const val TITLE = "AB" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt index 4e0c309512e8..ba035bec340c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt @@ -136,7 +136,8 @@ class WifiNetworkModelTest : SysuiTestCase() { networkId = 5, isValidated = true, level = 3, - ssid = "Test SSID" + ssid = "Test SSID", + hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP, ) activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger) @@ -146,6 +147,7 @@ class WifiNetworkModelTest : SysuiTestCase() { assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) assertThat(logger.changes).contains(Pair(COL_LEVEL, "3")) assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID")) + assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "LAPTOP")) } @Test fun logDiffs_activeToInactive_resetsAllActiveFields() { @@ -165,6 +167,7 @@ class WifiNetworkModelTest : SysuiTestCase() { assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false")) assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString())) assertThat(logger.changes).contains(Pair(COL_SSID, "null")) + assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "null")) } @Test @@ -175,7 +178,8 @@ class WifiNetworkModelTest : SysuiTestCase() { networkId = 5, isValidated = true, level = 3, - ssid = "Test SSID" + ssid = "Test SSID", + hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.AUTO, ) val prevVal = WifiNetworkModel.CarrierMerged( @@ -191,6 +195,7 @@ class WifiNetworkModelTest : SysuiTestCase() { assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) assertThat(logger.changes).contains(Pair(COL_LEVEL, "3")) assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID")) + assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "AUTO")) } @Test fun logDiffs_activeToCarrierMerged_logsAllFields() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 62087df8c238..507267e2d185 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -97,7 +97,7 @@ class SceneTestUtils( fun fakeSceneContainerRepository( containerConfig: SceneContainerConfig = fakeSceneContainerConfig(), ): SceneContainerRepository { - return SceneContainerRepository(containerConfig) + return SceneContainerRepository(applicationScope(), containerConfig) } fun fakeSceneKeys(): List<SceneKey> { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt index 1403cea1c625..3fd11a1db96f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt @@ -26,7 +26,7 @@ class FakeDisplayTracker constructor(val context: Context) : DisplayTracker { override var defaultDisplayId: Int = Display.DEFAULT_DISPLAY override var allDisplays: Array<Display> = displayManager.displays - private val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList() + val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList() private val brightnessCallbacks: MutableList<DisplayTracker.Callback> = ArrayList() override fun addDisplayChangeCallback(callback: DisplayTracker.Callback, executor: Executor) { displayCallbacks.add(callback) @@ -43,12 +43,12 @@ class FakeDisplayTracker constructor(val context: Context) : DisplayTracker { brightnessCallbacks.remove(callback) } - fun setDefaultDisplay(displayId: Int) { - defaultDisplayId = displayId + override fun getDisplay(displayId: Int): Display { + return allDisplays.filter { display -> display.displayId == displayId }[0] } - fun setDisplays(displays: Array<Display>) { - allDisplays = displays + fun setDefaultDisplay(displayId: Int) { + defaultDisplayId = displayId } fun triggerOnDisplayAdded(displayId: Int) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 37abe1b1aee3..061b4221ecf7 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -4204,7 +4204,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // We don't send an empty response to IME so that it doesn't cause UI flicker // on the IME side if it arrives before the input view is finished on the IME. mInlineSessionController.resetInlineFillUiLocked(); - mCurrentViewId = null; + + if ((viewState.getState() & + ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) != 0) { + // View was exited before Inline Request sent back, do not set it to + // null yet to let onHandleAssistData finish processing + } else { + mCurrentViewId = null; + } + mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 846283c38327..fc51e2ec0954 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1369,6 +1369,13 @@ public abstract class PackageManagerInternal { public abstract void setPackageStoppedState(@NonNull String packageName, boolean stopped, @UserIdInt int userId); + /** + * Tells PackageManager when a component (except BroadcastReceivers) of the package is used + * and the package should get out of stopped state and be enabled. + */ + public abstract void notifyComponentUsed(@NonNull String packageName, + @UserIdInt int userId, @NonNull String recentCallingPackage); + /** @deprecated For legacy shell command only. */ @Deprecated public abstract void legacyDumpProfiles(@NonNull String packageName, diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index da2588b12054..4b589885473b 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -5116,10 +5116,9 @@ public final class ActiveServices { r.packageName, r.userId, UsageEvents.Event.APP_COMPONENT_USED); } - // Service is now being launched, its package can't be stopped. try { - mAm.mPackageManagerInt.setPackageStoppedState( - r.packageName, false, r.userId); + mAm.mPackageManagerInt.notifyComponentUsed( + r.packageName, r.userId, r.mRecentCallingPackage); } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + r.packageName + ": " + e); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c1f2f6731e43..a53c2fb44873 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3095,6 +3095,22 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** + * Enforces that the uid of the caller matches the uid of the package. + * + * @param packageName the name of the package to match uid against. + * @param callingUid the uid of the caller. + * @throws SecurityException if the calling uid doesn't match uid of the package. + */ + private void enforceCallingPackage(String packageName, int callingUid) { + final int userId = UserHandle.getUserId(callingUid); + final int packageUid = getPackageManagerInternal().getPackageUid(packageName, + /*flags=*/ 0, userId); + if (packageUid != callingUid) { + throw new SecurityException(packageName + " does not belong to uid " + callingUid); + } + } + @Override public void setPackageScreenCompatMode(String packageName, int mode) { mActivityTaskManager.setPackageScreenCompatMode(packageName, mode); @@ -13704,13 +13720,16 @@ public class ActivityManagerService extends IActivityManager.Stub // A backup agent has just come up @Override public void backupAgentCreated(String agentPackageName, IBinder agent, int userId) { + final int callingUid = Binder.getCallingUid(); + enforceCallingPackage(agentPackageName, callingUid); + // Resolve the target user id and enforce permissions. - userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, /* allowAll */ false, ALLOW_FULL_ONLY, "backupAgentCreated", null); if (DEBUG_BACKUP) { Slog.v(TAG_BACKUP, "backupAgentCreated: " + agentPackageName + " = " + agent + " callingUserId = " + UserHandle.getCallingUserId() + " userId = " + userId - + " callingUid = " + Binder.getCallingUid() + " uid = " + Process.myUid()); + + " callingUid = " + callingUid + " uid = " + Process.myUid()); } synchronized(this) { diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index e26ee9c9d747..65fd54afbbfb 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -505,12 +505,11 @@ public class ContentProviderHelper { cpr.appInfo.packageName, userId, Event.APP_COMPONENT_USED); } - // Content provider is now in use, its package can't be stopped. try { checkTime(startTime, "getContentProviderImpl: before set stopped state"); - mService.mPackageManagerInt.setPackageStoppedState( - cpr.appInfo.packageName, false, userId); + mService.mPackageManagerInt.notifyComponentUsed( + cpr.appInfo.packageName, userId, callingPackage); checkTime(startTime, "getContentProviderImpl: after set stopped state"); } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING index 0af9b2b4c26a..575db01931e6 100644 --- a/services/core/java/com/android/server/am/TEST_MAPPING +++ b/services/core/java/com/android/server/am/TEST_MAPPING @@ -88,11 +88,14 @@ "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"], "name": "FrameworksServicesTests", "options": [ - { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" } ] }, { + "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"], + "name": "PowerStatsTests" + }, + { "file_patterns": ["Broadcast.*"], "name": "FrameworksMockingServicesTests", "options": [ @@ -111,27 +114,14 @@ ] }, { - "name": "CtsUsageStatsTestCases", + "name": "CtsBRSTestCases", "file_patterns": [ "ActivityManagerService\\.java", "BroadcastQueue\\.java" ], "options": [ - { - "include-filter": "android.app.usage.cts.BroadcastResponseStatsTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { - "exclude-annotation": "androidx.test.filters.MediumTest" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } + { "exclude-annotation": "androidx.test.filters.FlakyTest" }, + { "exclude-annotation": "org.junit.Ignore" } ] } ], diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 279aaf9d3253..1898b8015462 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1308,10 +1308,13 @@ public class BiometricService extends SystemService { .getString(R.string.biometric_dialog_default_subtitle)); } else if (hasEligibleFingerprintSensor) { promptInfo.setSubtitle(getContext() - .getString(R.string.biometric_dialog_fingerprint_subtitle)); + .getString(R.string.fingerprint_dialog_default_subtitle)); } else if (hasEligibleFaceSensor) { promptInfo.setSubtitle(getContext() - .getString(R.string.biometric_dialog_face_subtitle)); + .getString(R.string.face_dialog_default_subtitle)); + } else { + promptInfo.setSubtitle(getContext() + .getString(R.string.screen_lock_dialog_default_subtitle)); } } diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 595b2e4113b2..74b7f0866b54 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -1750,13 +1750,6 @@ public class LocationManagerService extends ILocationManager.Stub implements } @Override - public void sendNiResponse(int notifId, int userResponse) { - if (mGnssManagerService != null) { - mGnssManagerService.sendNiResponse(notifId, userResponse); - } - } - - @Override public @Nullable LocationTime getGnssTimeMillis() { LocationProviderManager gpsManager = getLocationProviderManager(GPS_PROVIDER); if (gpsManager == null) { diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index ed5c1306733e..e97a12a83b1f 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -62,7 +62,6 @@ import android.content.pm.PackageManager; import android.database.ContentObserver; import android.location.GnssCapabilities; import android.location.GnssStatus; -import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; @@ -109,7 +108,6 @@ import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.location.GpsNetInitiatedHandler; -import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.HexDump; import com.android.server.FgThread; @@ -396,7 +394,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mC2KServerPort = mGnssConfiguration.getC2KPort(TCP_MIN_PORT); mNIHandler.setEmergencyExtensionSeconds(mGnssConfiguration.getEsExtensionSec()); mSuplEsEnabled = mGnssConfiguration.getSuplEs(0) == 1; - mNIHandler.setSuplEsEnabled(mSuplEsEnabled); if (mGnssVisibilityControl != null) { mGnssVisibilityControl.onConfigurationUpdated(mGnssConfiguration); } @@ -465,7 +462,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } }; mNIHandler = new GpsNetInitiatedHandler(context, - mNetInitiatedListener, emergencyCallCallback, mSuplEsEnabled); // Trigger PSDS data download when the network comes up after booting. @@ -1435,96 +1431,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements updateRequirements(); } - //============================================================= - // NI Client support - //============================================================= - private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() { - // Sends a response for an NI request to HAL. - @Override - public boolean sendNiResponse(int notificationId, int userResponse) { - // TODO Add Permission check - - if (DEBUG) { - Log.d(TAG, "sendNiResponse, notifId: " + notificationId - + ", response: " + userResponse); - } - mGnssNative.sendNiResponse(notificationId, userResponse); - - FrameworkStatsLog.write(FrameworkStatsLog.GNSS_NI_EVENT_REPORTED, - FrameworkStatsLog.GNSS_NI_EVENT_REPORTED__EVENT_TYPE__NI_RESPONSE, - notificationId, - /* niType= */ 0, - /* needNotify= */ false, - /* needVerify= */ false, - /* privacyOverride= */ false, - /* timeout= */ 0, - /* defaultResponse= */ 0, - /* requestorId= */ null, - /* text= */ null, - /* requestorIdEncoding= */ 0, - /* textEncoding= */ 0, - mSuplEsEnabled, - isGpsEnabled(), - userResponse); - - return true; - } - }; - - public INetInitiatedListener getNetInitiatedListener() { - return mNetInitiatedListener; - } - - /** Reports a NI notification. */ - private void reportNiNotification(int notificationId, int niType, int notifyFlags, int timeout, - int defaultResponse, String requestorId, String text, int requestorIdEncoding, - int textEncoding) { - Log.i(TAG, "reportNiNotification: entered"); - Log.i(TAG, "notificationId: " + notificationId - + ", niType: " + niType - + ", notifyFlags: " + notifyFlags - + ", timeout: " + timeout - + ", defaultResponse: " + defaultResponse); - - Log.i(TAG, "requestorId: " + requestorId - + ", text: " + text - + ", requestorIdEncoding: " + requestorIdEncoding - + ", textEncoding: " + textEncoding); - - GpsNiNotification notification = new GpsNiNotification(); - - notification.notificationId = notificationId; - notification.niType = niType; - notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0; - notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0; - notification.privacyOverride = - (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0; - notification.timeout = timeout; - notification.defaultResponse = defaultResponse; - notification.requestorId = requestorId; - notification.text = text; - notification.requestorIdEncoding = requestorIdEncoding; - notification.textEncoding = textEncoding; - - mNIHandler.handleNiNotification(notification); - FrameworkStatsLog.write(FrameworkStatsLog.GNSS_NI_EVENT_REPORTED, - FrameworkStatsLog.GNSS_NI_EVENT_REPORTED__EVENT_TYPE__NI_REQUEST, - notification.notificationId, - notification.niType, - notification.needNotify, - notification.needVerify, - notification.privacyOverride, - notification.timeout, - notification.defaultResponse, - notification.requestorId, - notification.text, - notification.requestorIdEncoding, - notification.textEncoding, - mSuplEsEnabled, - isGpsEnabled(), - /* userResponse= */ 0); - } - private void demandUtcTimeInjection() { if (DEBUG) Log.d(TAG, "demandUtcTimeInjection"); postWithWakeLockHeld(mNetworkTimeHelper::demandUtcTimeInjection); @@ -1829,14 +1735,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } @Override - public void onReportNiNotification(int notificationId, int niType, int notifyFlags, - int timeout, int defaultResponse, String requestorId, String text, - int requestorIdEncoding, int textEncoding) { - reportNiNotification(notificationId, niType, notifyFlags, timeout, - defaultResponse, requestorId, text, requestorIdEncoding, textEncoding); - } - - @Override public void onRequestSetID(@GnssNative.AGpsCallbacks.AgpsSetIdFlags int flags) { TelephonyManager phone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index c962bc4c20d8..133704d11b08 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -38,7 +38,6 @@ import android.location.LocationManager; import android.location.util.identity.CallerIdentity; import android.os.BatteryStats; import android.os.Binder; -import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.util.IndentingPrintWriter; @@ -275,17 +274,6 @@ public class GnssManagerService { } /** - * Send Ni Response, indicating a location request initiated by a network carrier. - */ - public void sendNiResponse(int notifId, int userResponse) { - try { - mGnssLocationProvider.getNetInitiatedListener().sendNiResponse(notifId, userResponse); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Dump info for debugging. */ public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) { diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index 7618419ab0e4..bdd488581817 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -284,9 +284,6 @@ public class GnssNative { /** Callbacks for notifications. */ public interface NotificationCallbacks { - void onReportNiNotification(int notificationId, int niType, int notifyFlags, - int timeout, int defaultResponse, String requestorId, String text, - int requestorIdEncoding, int textEncoding); void onReportNfwNotification(String proxyAppPackageName, byte protocolStack, String otherProtocolStackName, byte requestor, String requestorId, byte responseType, boolean inEmergencyMode, boolean isCachedLocation); @@ -933,14 +930,6 @@ public class GnssNative { } /** - * Send a network initiated respnse. - */ - public void sendNiResponse(int notificationId, int userResponse) { - Preconditions.checkState(mRegistered); - mGnssHal.sendNiResponse(notificationId, userResponse); - } - - /** * Request an eventual update of GNSS power statistics. */ public void requestPowerStats() { @@ -1244,16 +1233,6 @@ public class GnssNative { } @NativeEntryPoint - void reportNiNotification(int notificationId, int niType, int notifyFlags, - int timeout, int defaultResponse, String requestorId, String text, - int requestorIdEncoding, int textEncoding) { - Binder.withCleanCallingIdentity( - () -> mNotificationCallbacks.onReportNiNotification(notificationId, niType, - notifyFlags, timeout, defaultResponse, requestorId, text, - requestorIdEncoding, textEncoding)); - } - - @NativeEntryPoint void requestSetID(int flags) { Binder.withCleanCallingIdentity(() -> mAGpsCallbacks.onRequestSetID(flags)); } @@ -1488,10 +1467,6 @@ public class GnssNative { return native_is_gnss_visibility_control_supported(); } - protected void sendNiResponse(int notificationId, int userResponse) { - native_send_ni_response(notificationId, userResponse); - } - protected void requestPowerStats() { native_request_power_stats(); } @@ -1648,8 +1623,6 @@ public class GnssNative { private static native boolean native_is_gnss_visibility_control_supported(); - private static native void native_send_ni_response(int notificationId, int userResponse); - // power stats APIs private static native void native_request_power_stats(); diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index 4e7521070132..82587c529e29 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -745,6 +745,13 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { mService.setPackageStoppedState(snapshot(), packageName, stopped, userId); } + @Override + public void notifyComponentUsed(@NonNull String packageName, + @UserIdInt int userId, @NonNull String recentCallingPackage) { + mService.notifyComponentUsed(snapshot(), packageName, userId, + recentCallingPackage); + } + @NonNull @Override @Deprecated diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 770ed8b40d2e..dc42644b04c0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4582,6 +4582,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } + void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName, + @UserIdInt int userId, @NonNull String recentCallingPackage) { + PackageManagerService.this + .setPackageStoppedState(snapshot, packageName, false /* stopped */, + userId); + } + public class IPackageManagerImpl extends IPackageManagerBase { public IPackageManagerImpl() { diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING index 8374997e3fa2..19086a184068 100644 --- a/services/core/java/com/android/server/power/TEST_MAPPING +++ b/services/core/java/com/android/server/power/TEST_MAPPING @@ -17,15 +17,6 @@ ] }, { - "name": "FrameworksServicesTests", - "options": [ - {"include-filter": "com.android.server.power"}, - {"exclude-filter": "com.android.server.power.BatteryStatsTests"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"} - ] - }, - { "name": "PowerServiceTests", "options": [ {"include-filter": "com.android.server.power"}, @@ -48,15 +39,13 @@ { "name": "FrameworksServicesTests", "options": [ - {"include-filter": "com.android.server.power"}, - {"exclude-filter": "com.android.server.power.BatteryStatsTests"} + {"include-filter": "com.android.server.power"} ] }, { "name": "PowerServiceTests", "options": [ {"include-filter": "com.android.server.power"}, - {"exclude-filter": "com.android.server.power.BatteryStatsTests"}, {"exclude-annotation": "org.junit.Ignore"} ] } diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index 1a91d252c431..8dcf3e06330f 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -304,7 +304,8 @@ public final class HintManagerService extends SystemService { return mService; } - private boolean checkTidValid(int uid, int tgid, int [] tids) { + // returns the first invalid tid or null if not found + private Integer checkTidValid(int uid, int tgid, int [] tids) { // Make sure all tids belongs to the same UID (including isolated UID), // tids can belong to different application processes. List<Integer> isolatedPids = null; @@ -326,19 +327,24 @@ public final class HintManagerService extends SystemService { if (isolatedPids == null) { // To avoid deadlock, do not call into AMS if the call is from system. if (uid == Process.SYSTEM_UID) { - return false; + return threadId; } isolatedPids = mAmInternal.getIsolatedProcesses(uid); if (isolatedPids == null) { - return false; + return threadId; } } if (isolatedPids.contains(pidOfThreadId)) { continue; } - return false; + return threadId; } - return true; + return null; + } + + private String formatTidCheckErrMsg(int callingUid, int[] tids, Integer invalidTid) { + return "Tid" + invalidTid + " from list " + Arrays.toString(tids) + + " doesn't belong to the calling application" + callingUid; } @VisibleForTesting @@ -356,8 +362,11 @@ public final class HintManagerService extends SystemService { final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final long identity = Binder.clearCallingIdentity(); try { - if (!checkTidValid(callingUid, callingTgid, tids)) { - throw new SecurityException("Some tid doesn't belong to the application"); + final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids); + if (invalidTid != null) { + final String errMsg = formatTidCheckErrMsg(callingUid, tids, invalidTid); + Slogf.w(TAG, errMsg); + throw new SecurityException(errMsg); } long halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, callingUid, @@ -561,8 +570,11 @@ public final class HintManagerService extends SystemService { final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final long identity = Binder.clearCallingIdentity(); try { - if (!checkTidValid(callingUid, callingTgid, tids)) { - throw new SecurityException("Some tid doesn't belong to the application."); + final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids); + if (invalidTid != null) { + final String errMsg = formatTidCheckErrMsg(callingUid, tids, invalidTid); + Slogf.w(TAG, errMsg); + throw new SecurityException(errMsg); } } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 83949ccae366..d8cc8d386424 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -86,7 +86,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.os.IBinder; -import android.os.RemoteException; import android.os.UserHandle; import android.util.DisplayMetrics; import android.util.Slog; @@ -1325,14 +1324,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { } } - // Launching this app's activity, make sure the app is no longer - // considered stopped. try { mTaskSupervisor.getActivityMetricsLogger() .notifyBeforePackageUnstopped(next.packageName); - mAtmService.getPackageManager().setPackageStoppedState( - next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */ - } catch (RemoteException e1) { + mAtmService.getPackageManagerInternalLocked().notifyComponentUsed( + next.packageName, next.mUserId, + next.packageName); /* TODO: Verify if correct userid */ } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + next.packageName + ": " + e); diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index e1de05cf6c7c..11c40d7bcd9b 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -69,7 +69,6 @@ static jclass class_gnssPowerStats; -static jmethodID method_reportNiNotification; static jmethodID method_reportGnssPowerStats; static jmethodID method_reportNfwNotification; static jmethodID method_isInEmergencySession; @@ -92,8 +91,6 @@ using android::hardware::hidl_death_recipient; using android::hardware::gnss::V1_0::GnssLocationFlags; using android::hardware::gnss::V1_0::IGnssNavigationMessage; using android::hardware::gnss::V1_0::IGnssNavigationMessageCallback; -using android::hardware::gnss::V1_0::IGnssNi; -using android::hardware::gnss::V1_0::IGnssNiCallback; using android::hardware::gnss::V1_0::IGnssXtra; using android::hardware::gnss::V1_0::IGnssXtraCallback; using android::hardware::gnss::V2_0::ElapsedRealtimeFlags; @@ -127,7 +124,6 @@ using IGnssConfigurationAidl = android::hardware::gnss::IGnssConfiguration; using GnssLocationAidl = android::hardware::gnss::GnssLocation; using IGnssAntennaInfoAidl = android::hardware::gnss::IGnssAntennaInfo; -sp<IGnssNi> gnssNiIface = nullptr; sp<IGnssPowerIndication> gnssPowerIndicationIface = nullptr; std::unique_ptr<android::gnss::GnssHal> gnssHal = nullptr; @@ -195,42 +191,6 @@ Status GnssPowerIndicationCallback::gnssPowerStatsCb(const GnssPowerStats& data) return Status::ok(); } -/* - * GnssNiCallback implements callback methods required by the IGnssNi interface. - */ -struct GnssNiCallback : public IGnssNiCallback { - Return<void> niNotifyCb(const IGnssNiCallback::GnssNiNotification& notification) - override; -}; - -Return<void> GnssNiCallback::niNotifyCb( - const IGnssNiCallback::GnssNiNotification& notification) { - JNIEnv* env = getJniEnv(); - jstring requestorId = env->NewStringUTF(notification.requestorId.c_str()); - jstring text = env->NewStringUTF(notification.notificationMessage.c_str()); - - if (requestorId && text) { - env->CallVoidMethod(mCallbacksObj, method_reportNiNotification, - notification.notificationId, notification.niType, - notification.notifyFlags, notification.timeoutSec, - notification.defaultResponse, requestorId, text, - notification.requestorIdEncoding, - notification.notificationIdEncoding); - } else { - ALOGE("%s: OOM Error\n", __func__); - } - - if (requestorId) { - env->DeleteLocalRef(requestorId); - } - - if (text) { - env->DeleteLocalRef(text); - } - checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); -} - /* Initializes the GNSS service handle. */ static void android_location_gnss_hal_GnssNative_set_gps_service_handle() { gnssHal = std::make_unique<gnss::GnssHal>(); @@ -242,10 +202,6 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc android_location_gnss_hal_GnssNative_set_gps_service_handle(); // Cache methodIDs and class IDs. - - method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification", - "(IIIIILjava/lang/String;Ljava/lang/String;II)V"); - method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification", "(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V"); method_reportGnssPowerStats = @@ -305,7 +261,6 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject gnssAntennaInfoIface = gnssHal->getGnssAntennaInfoInterface(); gnssMeasurementCorrectionsIface = gnssHal->getMeasurementCorrectionsInterface(); gnssDebugIface = gnssHal->getGnssDebugInterface(); - gnssNiIface = gnssHal->getGnssNiInterface(); gnssConfigurationIface = gnssHal->getGnssConfigurationInterface(); gnssGeofencingIface = gnssHal->getGnssGeofenceInterface(); gnssBatchingIface = gnssHal->getGnssBatchingInterface(); @@ -376,15 +331,6 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl ALOGI("Unable to initialize IGnssGeofencing interface."); } - // Set IGnssNi.hal callback. - sp<IGnssNiCallback> gnssNiCbIface = new GnssNiCallback(); - if (gnssNiIface != nullptr) { - auto status = gnssNiIface->setCallback(gnssNiCbIface); - checkHidlReturn(status, "IGnssNi setCallback() failed."); - } else { - ALOGI("Unable to initialize IGnssNi interface."); - } - // Set IAGnssRil callback. if (agnssRilIface == nullptr || !agnssRilIface->setCallback(std::make_unique<gnss::AGnssRilCallback>())) { @@ -592,18 +538,6 @@ static void android_location_gnss_hal_GnssNative_set_agps_server(JNIEnv* env, jc } } -static void android_location_gnss_hal_GnssNative_send_ni_response(JNIEnv* /* env */, jclass, - jint notifId, jint response) { - if (gnssNiIface == nullptr) { - ALOGE("%s: IGnssNi interface not available.", __func__); - return; - } - - auto result = gnssNiIface->respond(notifId, - static_cast<IGnssNiCallback::GnssUserResponseType>(response)); - checkHidlReturn(result, "IGnssNi respond() failed."); -} - static jstring android_location_gnss_hal_GnssNative_get_internal_state(JNIEnv* env, jclass) { /* * TODO: Create a jobject to represent GnssDebug. @@ -987,8 +921,6 @@ static const JNINativeMethod sLocationProviderMethods[] = { reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_set_agps_server)}, {"native_inject_ni_supl_message_data", "([BII)V", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_ni_supl_message_data)}, - {"native_send_ni_response", "(II)V", - reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_send_ni_response)}, {"native_get_internal_state", "()Ljava/lang/String;", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_get_internal_state)}, {"native_is_gnss_visibility_control_supported", "()Z", diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java index 2d962acfe665..1a8b12a00714 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java @@ -686,9 +686,6 @@ public final class FakeGnssHal extends GnssNative.GnssHal { } @Override - protected void sendNiResponse(int notificationId, int userResponse) {} - - @Override protected void requestPowerStats() { Objects.requireNonNull(mGnssNative).reportGnssPowerStats(mState.mPowerStats); } diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp new file mode 100644 index 000000000000..05acd9b8eeb1 --- /dev/null +++ b/services/tests/powerstatstests/Android.bp @@ -0,0 +1,52 @@ +package { + // See: http://go/android-license-faq + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "PowerStatsTests", + + // Include all test java files. + srcs: [ + "src/**/*.java", + ], + + static_libs: [ + "services.core", + "coretests-aidl", + "platformprotosnano", + "junit", + "truth-prebuilt", + "androidx.test.runner", + "androidx.test.ext.junit", + "androidx.test.ext.truth", + "androidx.test.uiautomator_uiautomator", + "mockito-target-minus-junit4", + "servicestests-utils", + ], + + libs: [ + "android.test.base", + ], + + resource_dirs: ["res/"], + + data: [ + ":BstatsTestApp", + ], + + test_suites: [ + "automotive-tests", + "device-tests", + ], + + platform_apis: true, + + certificate: "platform", + + dxflags: ["--multi-dex"], + + optimize: { + enabled: false, + }, +} diff --git a/services/tests/powerstatstests/AndroidManifest.xml b/services/tests/powerstatstests/AndroidManifest.xml new file mode 100644 index 000000000000..d3a88d2bc38c --- /dev/null +++ b/services/tests/powerstatstests/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.powerstatstests"> + + <uses-permission android:name="android.permission.BATTERY_STATS" /> + <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/> + <uses-permission android:name="android.permission.MANAGE_USERS"/> + + <queries> + <package android:name="com.android.coretests.apps.bstatstestapp" /> + </queries> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.powerstatstests" + android:label="BatteryStats and PowerStats Services Tests"/> +</manifest> diff --git a/services/tests/powerstatstests/AndroidTest.xml b/services/tests/powerstatstests/AndroidTest.xml new file mode 100644 index 000000000000..79b07e812b28 --- /dev/null +++ b/services/tests/powerstatstests/AndroidTest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs Power Stats Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="PowerStatsTests.apk" /> + <option name="test-file-name" value="BstatsTestApp.apk" /> + </target_preparer> + + <option name="test-tag" value="PowerStatsTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.powerstatstests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" /> + </test> +</configuration> diff --git a/core/tests/coretests/BstatsTestApp/Android.bp b/services/tests/powerstatstests/BstatsTestApp/Android.bp index c82da9e7b449..c82da9e7b449 100644 --- a/core/tests/coretests/BstatsTestApp/Android.bp +++ b/services/tests/powerstatstests/BstatsTestApp/Android.bp diff --git a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml b/services/tests/powerstatstests/BstatsTestApp/AndroidManifest.xml index fcb1e71cc3f5..fcb1e71cc3f5 100644 --- a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml +++ b/services/tests/powerstatstests/BstatsTestApp/AndroidManifest.xml diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java index 2601f3571b98..2601f3571b98 100644 --- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java +++ b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java index d192fbd66c89..c731e536d032 100644 --- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java +++ b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java @@ -15,8 +15,6 @@ */ package com.android.coretests.apps.bstatstestapp; -import com.android.frameworks.coretests.aidl.ICmdCallback; - import android.content.Intent; import android.os.Bundle; import android.os.IBinder; @@ -24,6 +22,8 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; +import com.android.frameworks.coretests.aidl.ICmdCallback; + public class Common { private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver"; diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java index 892f60e8530f..892f60e8530f 100644 --- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java +++ b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java index 5c551d54b4d5..5c551d54b4d5 100644 --- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java +++ b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java index 0cd0643ee8c0..0cd0643ee8c0 100644 --- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java +++ b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java diff --git a/services/tests/servicestests/src/com/android/server/powerstats/OWNERS b/services/tests/powerstatstests/OWNERS index 12f13ea63db0..9ed0e051738d 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/OWNERS +++ b/services/tests/powerstatstests/OWNERS @@ -1 +1,4 @@ +# Bug component: 987260 + +include /BATTERY_STATS_OWNERS include /services/core/java/com/android/server/powerstats/OWNERS diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING new file mode 100644 index 000000000000..e1eb1e4fc0db --- /dev/null +++ b/services/tests/powerstatstests/TEST_MAPPING @@ -0,0 +1,22 @@ +{ + "presubmit": [ + { + "name": "PowerStatsTests", + "options": [ + {"include-filter": "com.android.server.power.stats"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ], + "postsubmit": [ + { + "name": "PowerStatsTests", + "options": [ + {"include-filter": "com.android.server.power.stats"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ] +} diff --git a/services/tests/servicestests/res/xml/irq_device_map_1.xml b/services/tests/powerstatstests/res/xml/irq_device_map_1.xml index 1f1a77b437ab..1f1a77b437ab 100644 --- a/services/tests/servicestests/res/xml/irq_device_map_1.xml +++ b/services/tests/powerstatstests/res/xml/irq_device_map_1.xml diff --git a/services/tests/servicestests/res/xml/irq_device_map_2.xml b/services/tests/powerstatstests/res/xml/irq_device_map_2.xml index 508c98d871da..508c98d871da 100644 --- a/services/tests/servicestests/res/xml/irq_device_map_2.xml +++ b/services/tests/powerstatstests/res/xml/irq_device_map_2.xml diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/powerstatstests/res/xml/irq_device_map_3.xml index fd55428c48df..fd55428c48df 100644 --- a/services/tests/servicestests/res/xml/irq_device_map_3.xml +++ b/services/tests/powerstatstests/res/xml/irq_device_map_3.xml diff --git a/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml b/services/tests/powerstatstests/res/xml/power_profile_test_legacy_modem.xml index 5335f9640738..5335f9640738 100644 --- a/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml +++ b/services/tests/powerstatstests/res/xml/power_profile_test_legacy_modem.xml diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml b/services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator.xml index f57bc0f70b5d..f57bc0f70b5d 100644 --- a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml +++ b/services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator.xml diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml b/services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator_multiactive.xml index 4f5e674c60f5..4f5e674c60f5 100644 --- a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml +++ b/services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator_multiactive.xml diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml b/services/tests/powerstatstests/res/xml/power_profile_test_modem_default.xml index ab016fbb1f5d..ab016fbb1f5d 100644 --- a/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml +++ b/services/tests/powerstatstests/res/xml/power_profile_test_modem_default.xml diff --git a/services/tests/servicestests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java index 319a280d10cc..319a280d10cc 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java index fb367b24168e..fb367b24168e 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java index 4ea0805ffaa7..4ea0805ffaa7 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java index 5a2d2e3d33fd..5a2d2e3d33fd 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java index 4d3fcb611f24..4d3fcb611f24 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java index 3f101a96d36c..3f101a96d36c 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCounterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCounterTest.java index 326639c54495..326639c54495 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCounterTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCounterTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java index 55ffa1a15a6b..55ffa1a15a6b 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java index d6acf8da7431..d6acf8da7431 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java index 99520cd6fbad..99520cd6fbad 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java index 4fde73bd8408..4fde73bd8408 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index 48ba765c3968..48ba765c3968 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java index f20f061230e2..5ebc6ca3f558 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java @@ -267,7 +267,7 @@ public class BatteryStatsImplTest { final long[][] delta3 = { {98545, 95768795, 76586, 548945, 57846}, {788876, 586, 578459, 8776984, 9578923}, - {3049509483598l, 4597834, 377654, 94589035, 7854}, + {3049509483598L, 4597834, 377654, 94589035, 7854}, {9493, 784, 99895, 8974893, 9879843} }; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java index 7ae111711b6b..7ae111711b6b 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java index 88b9522d4cb1..88b9522d4cb1 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsResetTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java index a0fb631812f4..a0fb631812f4 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsResetTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java index 784d673ed3f0..ee68bf8ae53c 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java @@ -1,17 +1,17 @@ /* * Copyright (C) 2016 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 + * 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 + * 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. + * 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.power.stats; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSensorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSensorTest.java index b8f0ce3456f8..9c70f376ca14 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSensorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSensorTest.java @@ -1,17 +1,17 @@ /* * Copyright (C) 2016 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 + * 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 + * 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. + * 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.power.stats; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsServTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsServTest.java index 200eb1d0ad15..200eb1d0ad15 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsServTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsServTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java index fcae42a76f1b..18a366c178fd 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java @@ -1,17 +1,17 @@ /* * Copyright (C) 2017 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 + * 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 + * 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. + * 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.power.stats; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java index 5b47423f0d59..5b47423f0d59 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimerTest.java index 14c5c5db5c1c..14c5c5db5c1c 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimerTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java index face849620d7..face849620d7 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java index 5df0acb65249..5df0acb65249 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 534aa89e1699..534aa89e1699 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java index b846e3a36656..b846e3a36656 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java index 266a22632a6d..266a22632a6d 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java index 4d4337c16757..4d4337c16757 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java index ccace40bb056..ccace40bb056 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java index 5fce32f0598a..5fce32f0598a 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java index 888bc623f669..888bc623f669 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java index 245faaf15cc8..245faaf15cc8 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java index 28f4799656b7..28f4799656b7 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java index 0f85fdc375fb..0f85fdc375fb 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java index 3f2a6d04c1e6..3f2a6d04c1e6 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java index 3d150af711f1..3d150af711f1 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java index c0f3c775ffe5..2edfc8e1e408 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java @@ -1,17 +1,17 @@ /* * Copyright (C) 2016 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 + * 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 + * 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. + * 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.power.stats; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java index 2e962c364ed2..2e962c364ed2 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterTest.java index 0eac625051fc..0eac625051fc 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java index 2cce449c6c05..2cce449c6c05 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java index d3ec0d7e3f6e..888a1688c2a1 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java @@ -46,7 +46,7 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.frameworks.servicestests.R; +import com.android.frameworks.powerstatstests.R; import com.google.common.collect.Range; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 6d3f1f27b572..6d3f1f27b572 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockClock.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockClock.java index 5e57cc36797b..5e57cc36797b 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MockClock.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockClock.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/OWNERS b/services/tests/powerstatstests/src/com/android/server/power/stats/OWNERS index 9a7db1cb4fe4..9a7db1cb4fe4 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/OWNERS +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java index 372307985f04..372307985f04 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java index 474527040839..474527040839 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java index 80cbe0da402e..80cbe0da402e 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java index 4dae2d548057..4dae2d548057 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/UserPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/UserPowerCalculatorTest.java index f14745ef2daa..f14745ef2daa 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/UserPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/UserPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java index f578aa3b46be..f578aa3b46be 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java index f1961855f12f..f1961855f12f 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java index 113be8b19518..113be8b19518 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java index b81b776019f9..0dc836ba0400 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java @@ -34,7 +34,7 @@ import android.util.SparseIntArray; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.android.frameworks.servicestests.R; +import com.android.frameworks.powerstatstests.R; import com.android.server.power.stats.wakeups.CpuWakeupStats.Wakeup; import org.junit.Test; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java index 47a8f49e7025..9af288496bb9 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java @@ -23,7 +23,7 @@ import android.content.Context; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.android.frameworks.servicestests.R; +import com.android.frameworks.powerstatstests.R; import com.android.internal.util.CollectionUtils; import org.junit.Test; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java index 99bc25abc4a1..99bc25abc4a1 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java diff --git a/services/tests/servicestests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java index 99621460f360..99621460f360 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java index 2ffe4aacda73..2ffe4aacda73 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 3272ce6a5865..92ff7ab86247 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -112,7 +112,6 @@ android_test { }, data: [ - ":BstatsTestApp", ":JobTestApp", ":SimpleServiceTestApp1", ":SimpleServiceTestApp2", diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index fbb0ca108ecd..b1d50399416a 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -28,7 +28,6 @@ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> - <option name="test-file-name" value="BstatsTestApp.apk" /> <option name="test-file-name" value="FrameworksServicesTests.apk" /> <option name="test-file-name" value="JobTestApp.apk" /> <option name="test-file-name" value="SuspendTestApp.apk" /> diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index fc62e75b7d59..e79ac0986dc8 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -16,6 +16,7 @@ package com.android.server.biometrics; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_CREDENTIAL; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; @@ -116,9 +117,9 @@ public class BiometricServiceTest { private static final String ERROR_LOCKOUT = "error_lockout"; private static final String FACE_SUBTITLE = "face_subtitle"; private static final String FINGERPRINT_SUBTITLE = "fingerprint_subtitle"; + private static final String CREDENTIAL_SUBTITLE = "credential_subtitle"; private static final String DEFAULT_SUBTITLE = "default_subtitle"; - private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty"; private static final int SENSOR_ID_FINGERPRINT = 0; @@ -143,6 +144,8 @@ public class BiometricServiceTest { @Mock IBiometricAuthenticator mFaceAuthenticator; @Mock + IBiometricAuthenticator mCredentialAuthenticator; + @Mock ITrustManager mTrustManager; @Mock DevicePolicyManager mDevicePolicyManager; @@ -196,10 +199,12 @@ public class BiometricServiceTest { .thenReturn(ERROR_NOT_RECOGNIZED); when(mResources.getString(R.string.biometric_error_user_canceled)) .thenReturn(ERROR_USER_CANCELED); - when(mContext.getString(R.string.biometric_dialog_face_subtitle)) + when(mContext.getString(R.string.face_dialog_default_subtitle)) .thenReturn(FACE_SUBTITLE); - when(mContext.getString(R.string.biometric_dialog_fingerprint_subtitle)) + when(mContext.getString(R.string.fingerprint_dialog_default_subtitle)) .thenReturn(FINGERPRINT_SUBTITLE); + when(mContext.getString(R.string.screen_lock_dialog_default_subtitle)) + .thenReturn(CREDENTIAL_SUBTITLE); when(mContext.getString(R.string.biometric_dialog_default_subtitle)) .thenReturn(DEFAULT_SUBTITLE); @@ -292,7 +297,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL); + Authenticators.DEVICE_CREDENTIAL, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricAuthenticator.TYPE_CREDENTIAL), @@ -312,7 +318,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL); + Authenticators.DEVICE_CREDENTIAL, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); assertNotNull(mBiometricService.mAuthSession); @@ -338,7 +345,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricAuthenticator.TYPE_NONE), @@ -357,7 +365,8 @@ public class BiometricServiceTest { mFingerprintAuthenticator); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(TYPE_FINGERPRINT), @@ -370,7 +379,8 @@ public class BiometricServiceTest { setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - Authenticators.BIOMETRIC_STRONG); + Authenticators.BIOMETRIC_STRONG, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricAuthenticator.TYPE_NONE), @@ -429,7 +439,8 @@ public class BiometricServiceTest { mFingerprintAuthenticator); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(TYPE_FINGERPRINT), @@ -441,9 +452,9 @@ public class BiometricServiceTest { public void testAuthenticateFace_shouldShowSubtitleForFace() throws Exception { setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, - null); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, true /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); assertEquals(FACE_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle()); @@ -453,9 +464,9 @@ public class BiometricServiceTest { public void testAuthenticateFingerprint_shouldShowSubtitleForFingerprint() throws Exception { setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, - null); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, true /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); assertEquals(FINGERPRINT_SUBTITLE, @@ -463,6 +474,19 @@ public class BiometricServiceTest { } @Test + public void testAuthenticateFingerprint_shouldShowSubtitleForCredential() throws Exception { + setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL); + + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, true /* useDefaultSubtitle */, + true /* deviceCredentialAllowed */); + waitForIdle(); + + assertEquals(CREDENTIAL_SUBTITLE, + mBiometricService.mAuthSession.mPromptInfo.getSubtitle()); + } + + @Test public void testAuthenticateBothFpAndFace_shouldShowDefaultSubtitle() throws Exception { final int[] modalities = new int[] { TYPE_FINGERPRINT, @@ -476,9 +500,9 @@ public class BiometricServiceTest { setupAuthForMultiple(modalities, strengths); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, - null); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, true /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); assertEquals(DEFAULT_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle()); @@ -492,7 +516,8 @@ public class BiometricServiceTest { // Disabled in user settings receives onError when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricAuthenticator.TYPE_NONE), @@ -506,7 +531,8 @@ public class BiometricServiceTest { anyInt() /* modality */, anyInt() /* userId */)) .thenReturn(true); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); final byte[] HAT = generateRandomHAT(); @@ -524,7 +550,8 @@ public class BiometricServiceTest { anyInt() /* modality */, anyInt() /* userId */)) .thenReturn(false); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded( SENSOR_ID_FACE, @@ -552,7 +579,8 @@ public class BiometricServiceTest { throws Exception { // Start testing the happy path invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); // Creates a pending auth session with the correct initial states @@ -632,7 +660,8 @@ public class BiometricServiceTest { .thenReturn(true); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK); + Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK, + false /* useDefaultSubtitle*/, false /* deviceCredentialAllowed */); waitForIdle(); assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL, @@ -702,7 +731,8 @@ public class BiometricServiceTest { invokeAuthenticate(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG); + Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG, + false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError(anyInt() /* modality */, @@ -754,7 +784,8 @@ public class BiometricServiceTest { false /* requireConfirmation */, null /* authenticators */); invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( @@ -887,7 +918,8 @@ public class BiometricServiceTest { setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK); + Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK, + false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */); waitForIdle(); assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState()); @@ -920,8 +952,9 @@ public class BiometricServiceTest { public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed() throws Exception { setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, null /* authenticators */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); mBiometricService.mAuthSession.mSensorReceiver.onError( @@ -957,8 +990,9 @@ public class BiometricServiceTest { setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) .thenReturn(lockoutMode); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, null /* authenticators */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); // Modality and error are sent @@ -996,8 +1030,9 @@ public class BiometricServiceTest { when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) .thenReturn(lockoutMode); when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, null /* authenticators */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); // The lockout error should be sent, instead of ERROR_NONE_ENROLLED. See b/286923477. @@ -1014,7 +1049,8 @@ public class BiometricServiceTest { .thenReturn(LockoutTracker.LOCKOUT_PERMANENT); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG); + Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG, + false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); @@ -1503,7 +1539,8 @@ public class BiometricServiceTest { assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, invokeCanAuthenticate(mBiometricService, authenticators)); long requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, authenticators); + false /* requireConfirmation */, authenticators, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(TYPE_FINGERPRINT), @@ -1539,7 +1576,8 @@ public class BiometricServiceTest { invokeCanAuthenticate(mBiometricService, authenticators)); requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - authenticators); + authenticators, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); assertTrue(Utils.isCredentialRequested(mBiometricService.mAuthSession.mPromptInfo)); verify(mBiometricService.mStatusBarService).showAuthenticationDialog( @@ -1749,6 +1787,11 @@ public class BiometricServiceTest { mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality, strength, mFaceAuthenticator); } + + if ((modality & TYPE_CREDENTIAL) != 0) { + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); + } } // TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for @@ -1799,7 +1842,8 @@ public class BiometricServiceTest { Integer authenticators) throws Exception { // Request auth, creates a pending session final long requestId = invokeAuthenticate( - service, receiver, requireConfirmation, authenticators); + service, receiver, requireConfirmation, authenticators, + false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */); waitForIdle(); startPendingAuthSession(mBiometricService); @@ -1827,7 +1871,8 @@ public class BiometricServiceTest { private static long invokeAuthenticate(IBiometricService.Stub service, IBiometricServiceReceiver receiver, boolean requireConfirmation, - Integer authenticators) throws Exception { + Integer authenticators, boolean useDefaultSubtitle, + boolean deviceCredentialAllowed) throws Exception { return service.authenticate( new Binder() /* token */, 0 /* operationId */, @@ -1835,7 +1880,8 @@ public class BiometricServiceTest { receiver, TEST_PACKAGE_NAME /* packageName */, createTestPromptInfo(requireConfirmation, authenticators, - false /* checkDevicePolicy */)); + false /* checkDevicePolicy */, useDefaultSubtitle, + deviceCredentialAllowed)); } private static long invokeAuthenticateForWorkApp(IBiometricService.Stub service, @@ -1847,16 +1893,19 @@ public class BiometricServiceTest { receiver, TEST_PACKAGE_NAME /* packageName */, createTestPromptInfo(false /* requireConfirmation */, authenticators, - true /* checkDevicePolicy */)); + true /* checkDevicePolicy */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */)); } private static PromptInfo createTestPromptInfo( boolean requireConfirmation, Integer authenticators, - boolean checkDevicePolicy) { + boolean checkDevicePolicy, + boolean useDefaultSubtitle, + boolean deviceCredentialAllowed) { final PromptInfo promptInfo = new PromptInfo(); promptInfo.setConfirmationRequested(requireConfirmation); - promptInfo.setUseDefaultSubtitle(true); + promptInfo.setUseDefaultSubtitle(useDefaultSubtitle); if (authenticators != null) { promptInfo.setAuthenticators(authenticators); @@ -1864,6 +1913,7 @@ public class BiometricServiceTest { if (checkDevicePolicy) { promptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicy); } + promptInfo.setDeviceCredentialAllowed(deviceCredentialAllowed); return promptInfo; } diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt index f834cb24f245..b65a53af56a0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt @@ -138,6 +138,17 @@ class AndroidPackageParsingValidationTest { R.styleable.AndroidManifestApplication_taskAffinity, 1024 ) + validateTagAttr( + tag, + "zygotePreloadName", + R.styleable.AndroidManifestApplication_zygotePreloadName, + 1024 + ) + validateTagAttrComponentName( + tag, + "zygotePreloadName", + R.styleable.AndroidManifestApplication_zygotePreloadName + ) validateTagCount("profileable", 100, tag) validateTagCount("uses-native-library", 100, tag) validateTagCount("receiver", 1000, tag) diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java deleted file mode 100644 index 48290e5ca1ef..000000000000 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.power.stats; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -@RunWith(Suite.class) -@Suite.SuiteClasses({ - AmbientDisplayPowerCalculatorTest.class, - AudioPowerCalculatorTest.class, - BatteryChargeCalculatorTest.class, - BatteryExternalStatsWorkerTest.class, - BatteryStatsCpuTimesTest.class, - BatteryStatsBackgroundStatsTest.class, - BatteryStatsBinderCallStatsTest.class, - BatteryStatsCounterTest.class, - BatteryStatsDualTimerTest.class, - BatteryStatsDurationTimerTest.class, - BatteryStatsHistoryIteratorTest.class, - BatteryStatsHistoryTest.class, - BatteryStatsImplTest.class, - BatteryStatsManagerTest.class, - BatteryStatsNoteTest.class, - BatteryStatsSamplingTimerTest.class, - BatteryStatsSensorTest.class, - BatteryStatsServTest.class, - BatteryStatsStopwatchTimerTest.class, - BatteryStatsTimeBaseTest.class, - BatteryStatsTimerTest.class, - BatteryUsageStatsProviderTest.class, - BatteryUsageStatsTest.class, - BatteryUsageStatsStoreTest.class, - BatteryStatsUserLifecycleTests.class, - BluetoothPowerCalculatorTest.class, - BstatsCpuTimesValidationTest.class, - CameraPowerCalculatorTest.class, - CpuPowerCalculatorTest.class, - CustomEnergyConsumerPowerCalculatorTest.class, - FlashlightPowerCalculatorTest.class, - GnssPowerCalculatorTest.class, - IdlePowerCalculatorTest.class, - KernelWakelockReaderTest.class, - LongSamplingCounterTest.class, - LongSamplingCounterArrayTest.class, - EnergyConsumerSnapshotTest.class, - MobileRadioPowerCalculatorTest.class, - ScreenPowerCalculatorTest.class, - SensorPowerCalculatorTest.class, - SystemServerCpuThreadReaderTest.class, - SystemServicePowerCalculatorTest.class, - UserPowerCalculatorTest.class, - VideoPowerCalculatorTest.class, - WakelockPowerCalculatorTest.class, - WifiPowerCalculatorTest.class, -}) -public class BatteryStatsTests { -} diff --git a/services/usage/java/com/android/server/usage/TEST_MAPPING b/services/usage/java/com/android/server/usage/TEST_MAPPING index a3fe6f2876d7..6e845433492b 100644 --- a/services/usage/java/com/android/server/usage/TEST_MAPPING +++ b/services/usage/java/com/android/server/usage/TEST_MAPPING @@ -20,22 +20,13 @@ ] }, { - "name": "CtsUsageStatsTestCases", + "name": "CtsBRSTestCases", "options": [ { - "include-filter": "android.app.usage.cts.BroadcastResponseStatsTest" - }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { - "exclude-annotation": "androidx.test.filters.MediumTest" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" + "exclude-annotation": "org.junit.Ignore" } ] } diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 78b86d398718..5bdcdf4d63ef 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -2661,7 +2661,9 @@ public final class Call { // remove ourselves from the Phone. Note that we do this after completing all state updates // so a client can cleanly transition all their UI to the state appropriate for a // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list. - if (mState == STATE_DISCONNECTED) { + // Check if the original state is already disconnected, otherwise onCallRemoved will be + // triggered before onCallAdded. + if (mState == STATE_DISCONNECTED && stateChanged) { fireCallDestroyed(); } } diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java index 95a8e16ace3d..61e829e75930 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -174,6 +174,9 @@ public final class Phone { checkCallTree(parcelableCall); call.internalUpdate(parcelableCall, mCallByTelecomCallId); fireCallAdded(call); + if (call.getState() == Call.STATE_DISCONNECTED) { + internalRemoveCall(call); + } } else { Log.w(this, "Call %s added, but it was already present", call.internalGetCallId()); checkCallTree(parcelableCall); diff --git a/tests/testables/src/android/testing/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java index 27d5b66b355e..0ec106e329f6 100644 --- a/tests/testables/src/android/testing/TestableResources.java +++ b/tests/testables/src/android/testing/TestableResources.java @@ -15,9 +15,11 @@ package android.testing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.util.Log; import android.util.SparseArray; @@ -54,6 +56,16 @@ public class TestableResources { } /** + * Sets a configuration for {@link #getResources()} to return to allow custom configs to + * be set and tested. + * + * @param configuration the configuration to return from resources. + */ + public void overrideConfiguration(Configuration configuration) { + when(mResources.getConfiguration()).thenReturn(configuration); + } + + /** * Sets the return value for the specified resource id. * <p> * Since resource ids are unique there is a single addOverride that will override the value |