diff options
240 files changed, 4388 insertions, 1707 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 807fa48117b3..c8e1e4d205dd 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1150,6 +1150,11 @@ public class Activity extends ContextThemeWrapper * * <p>To keep the Intent instance for future use, call {@link #setIntent(Intent)}, and use * this method to retrieve it. + * + * <p>Note that in {@link #onNewIntent}, this method will return the original Intent. You can + * use {@link #setIntent(Intent)} to update it to the new Intent. + * + * @return {@link Intent} instance that started this activity, or that was kept for future use */ public Intent getIntent() { return mIntent; @@ -1170,9 +1175,14 @@ public class Activity extends ContextThemeWrapper } /** - * Returns the ComponentCaller instance of the app that launched this activity with the intent - * from {@link #getIntent()}. To keep the value of the ComponentCaller instance for new intents, - * call {@link #setIntent(Intent, ComponentCaller)} instead of {@link #setIntent(Intent)}. + * Returns the ComponentCaller instance of the app that started this activity. + * + * <p>To keep the ComponentCaller instance for future use, call + * {@link #setIntent(Intent, ComponentCaller)}, and use this method to retrieve it. + * + * <p>Note that in {@link #onNewIntent}, this method will return the original ComponentCaller. + * You can use {@link #setIntent(Intent, ComponentCaller)} to update it to the new + * ComponentCaller. * * @return {@link ComponentCaller} instance corresponding to the intent from * {@link #getIntent()}, or {@code null} if the activity was not launched with that @@ -7156,8 +7166,8 @@ public class Activity extends ContextThemeWrapper /** * Returns the ComponentCaller instance of the app that initially launched this activity. * - * <p>Note that calls to {@link #onNewIntent} have no effect on the returned value of this - * method. + * <p>Note that calls to {@link #onNewIntent} and {@link #setIntent} have no effect on the + * returned value of this method. * * @return {@link ComponentCaller} instance * @see ComponentCaller diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 86d061cecb88..e895d7be1102 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -823,6 +823,16 @@ public class IntentFilter implements Parcelable { } /** + * Returns the number of actions in the filter, or {@code 0} if there are no actions. + * <p> This method provides a safe alternative to {@link #countActions()}, which + * may throw an exception if there are no actions. + * @hide + */ + public final int safeCountActions() { + return mActions == null ? 0 : mActions.size(); + } + + /** * Return an action in the filter. */ public final String getAction(int index) { diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 57ee622de910..bf51f00e8446 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -304,6 +304,7 @@ public class CrossProfileApps { * <li>It is not equal to the calling user</li> * <li>It is in the same profile group of calling user profile</li> * <li>It is enabled</li> + * <li>It is not hidden (ex. profile type {@link UserManager#USER_TYPE_PROFILE_PRIVATE})</li> * </ul> * * @see UserManager#getUserProfiles() @@ -460,8 +461,8 @@ public class CrossProfileApps { * * <p>Specifically, returns whether the following are all true: * <ul> - * <li>{@code UserManager#getEnabledProfileIds(int)} returns at least one other profile for the - * calling user.</li> + * <li>{@code UserManager#getProfileIdsExcludingHidden(int)} returns at least one other + * profile for the calling user.</li> * <li>The calling app has requested * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest.</li> * <li>The calling app is not a profile owner within the profile group of the calling user.</li> diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 20522fad6652..7926afe30bd8 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -5577,8 +5577,8 @@ public class UserManager { } /** - * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a - * managed profile don't run, generate notifications, or consume data or battery. + * Enables or disables quiet mode for a profile. If quiet mode is enabled, apps in the profile + * don't run, generate notifications, or consume data or battery. * <p> * If a user's credential is needed to turn off quiet mode, a confirm credential screen will be * shown to the user. @@ -5586,8 +5586,11 @@ public class UserManager { * The change may not happen instantly, however apps can listen for * {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE} broadcasts in order to be notified of - * the change of the quiet mode. Apps can also check the current state of quiet mode by - * calling {@link #isQuietModeEnabled(UserHandle)}. + * the change of the quiet mode for managed profile. + * Apps can listen to generic broadcasts {@link Intent#ACTION_PROFILE_AVAILABLE} and + * {@link Intent#ACTION_PROFILE_UNAVAILABLE} to be notified of the change in quiet mode for + * any profiles. Apps can also check the current state of quiet mode by calling + * {@link #isQuietModeEnabled(UserHandle)}. * <p> * The caller must either be the foreground default launcher or have one of these permissions: * {@code MANAGE_USERS} or {@code MODIFY_QUIET_MODE}. @@ -5597,7 +5600,7 @@ public class UserManager { * @return {@code false} if user's credential is needed in order to turn off quiet mode, * {@code true} otherwise * @throws SecurityException if the caller is invalid - * @throws IllegalArgumentException if {@code userHandle} is not a managed profile + * @throws IllegalArgumentException if {@code userHandle} is not a profile * * @see #isQuietModeEnabled(UserHandle) */ @@ -5662,7 +5665,6 @@ public class UserManager { /** * Returns whether the given profile is in quiet mode or not. - * Notes: Quiet mode is only supported for managed profiles. * * @param userHandle The user handle of the profile to be queried. * @return true if the profile is in quiet mode, false otherwise. diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 95897855586d..8271caf57353 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -29,6 +29,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION; import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents; +import static com.android.window.flags.Flags.noVisibilityEventOnDisplayStateChange; import static com.android.window.flags.Flags.offloadColorExtraction; import static com.android.window.flags.Flags.windowSessionRelayoutInfo; @@ -2387,8 +2388,10 @@ public abstract class WallpaperService extends Service { @Override public void onDisplayChanged(int displayId) { if (mDisplay.getDisplayId() == displayId) { - boolean forceReport = mIsWearOs - && mDisplay.getState() != Display.STATE_DOZE_SUSPEND; + boolean forceReport = + !noVisibilityEventOnDisplayStateChange() + && mIsWearOs + && mDisplay.getState() != Display.STATE_DOZE_SUSPEND; reportVisibility(forceReport); } } diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 7023ef7afd2f..8836c8a3a113 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -211,3 +211,12 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "rust_hyphenator" + namespace: "text" + description: "Reimplement hyphenator for safe file read" + # Hyphenator is initialized in Zygote + is_fixed_read_only: true + bug: "346915432" +} diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 815fc5820189..0714285c43f6 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -220,7 +220,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false"); - DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "false"); + DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "true"); DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false"); DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false"); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a11bb788bbf0..2377b869e804 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -25,6 +25,7 @@ import static android.os.Trace.TRACE_TAG_VIEW; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.DragEvent.ACTION_DRAG_LOCATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED; import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; @@ -271,6 +272,7 @@ import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.os.IResultReceiver; import com.android.internal.os.SomeArgs; +import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneFallbackEventHandler; import com.android.internal.util.FastPrintWriter; import com.android.internal.view.BaseSurfaceHolder; @@ -1576,6 +1578,9 @@ public final class ViewRootImpl implements ViewParent, pendingInsetsController.replayAndAttach(mInsetsController); } } + if (mView instanceof DecorView) { + mWindowAttributes.privateFlags |= PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED; + } try { mOrigWindowType = mWindowAttributes.type; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 7b7ead4afb11..ae051f96fd03 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -3307,6 +3307,11 @@ public interface WindowManager extends ViewManager { @UnsupportedAppUsage public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 1 << 6; + /** Window flag: the client side view can intercept back progress, so system does not + * need to pilfer pointers. + * {@hide} */ + public static final int PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED = 1 << 7; + /** Window flag: a special option intended for system dialogs. When * this flag is set, the window will demand focus unconditionally when * it is created. @@ -3500,6 +3505,7 @@ public interface WindowManager extends ViewManager { SYSTEM_FLAG_SHOW_FOR_ALL_USERS, PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION, PRIVATE_FLAG_NO_MOVE_ANIMATION, + PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED, PRIVATE_FLAG_SYSTEM_ERROR, PRIVATE_FLAG_OPTIMIZE_MEASURE, PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS, diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 58a3c80cd74b..0d4c5560837c 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -4326,14 +4326,16 @@ public final class AutofillManager { if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) { if (sVerbose) { - Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleTrackedIds); + Log.v(TAG, "No more visible tracked save ids. Invisible = " + + mInvisibleTrackedIds); } finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED); } if (mVisibleDialogTrackedIds.isEmpty()) { if (sVerbose) { - Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleDialogTrackedIds); + Log.v(TAG, "No more visible tracked fill dialog ids. Invisible = " + + mInvisibleDialogTrackedIds); } processNoVisibleTrackedAllViews(); } diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java index 57bded7ff2a0..59639d04b45b 100644 --- a/core/java/android/window/BackNavigationInfo.java +++ b/core/java/android/window/BackNavigationInfo.java @@ -117,6 +117,8 @@ public final class BackNavigationInfo implements Parcelable { @NonNull private final Rect mTouchableRegion; + private final boolean mAppProgressGenerationAllowed; + /** * Create a new {@link BackNavigationInfo} instance. * @@ -132,7 +134,8 @@ public final class BackNavigationInfo implements Parcelable { boolean isAnimationCallback, @Nullable CustomAnimationInfo customAnimationInfo, int letterboxColor, - @Nullable Rect touchableRegion) { + @Nullable Rect touchableRegion, + boolean appProgressGenerationAllowed) { mType = type; mOnBackNavigationDone = onBackNavigationDone; mOnBackInvokedCallback = onBackInvokedCallback; @@ -141,6 +144,7 @@ public final class BackNavigationInfo implements Parcelable { mCustomAnimationInfo = customAnimationInfo; mLetterboxColor = letterboxColor; mTouchableRegion = new Rect(touchableRegion); + mAppProgressGenerationAllowed = appProgressGenerationAllowed; } private BackNavigationInfo(@NonNull Parcel in) { @@ -152,6 +156,7 @@ public final class BackNavigationInfo implements Parcelable { mCustomAnimationInfo = in.readTypedObject(CustomAnimationInfo.CREATOR); mLetterboxColor = in.readInt(); mTouchableRegion = in.readTypedObject(Rect.CREATOR); + mAppProgressGenerationAllowed = in.readBoolean(); } /** @hide */ @@ -165,6 +170,7 @@ public final class BackNavigationInfo implements Parcelable { dest.writeTypedObject(mCustomAnimationInfo, flags); dest.writeInt(mLetterboxColor); dest.writeTypedObject(mTouchableRegion, flags); + dest.writeBoolean(mAppProgressGenerationAllowed); } /** @@ -224,6 +230,14 @@ public final class BackNavigationInfo implements Parcelable { } /** + * @return The client side view is able to intercept back progress event. + * @hide + */ + public boolean isAppProgressGenerationAllowed() { + return mAppProgressGenerationAllowed; + } + + /** * Callback to be called when the back preview is finished in order to notify the server that * it can clean up the resources created for the animation. * @hide @@ -420,6 +434,7 @@ public final class BackNavigationInfo implements Parcelable { private int mLetterboxColor = Color.TRANSPARENT; private Rect mTouchableRegion; + private boolean mAppProgressGenerationAllowed; /** * @see BackNavigationInfo#getType() @@ -502,6 +517,15 @@ public final class BackNavigationInfo implements Parcelable { mTouchableRegion = new Rect(rect); return this; } + + /** + * @param allowed Whether client side view able to intercept back progress event. + */ + public Builder setAppProgressAllowed(boolean allowed) { + mAppProgressGenerationAllowed = allowed; + return this; + } + /** * Builds and returns an instance of {@link BackNavigationInfo} */ @@ -512,7 +536,8 @@ public final class BackNavigationInfo implements Parcelable { mAnimationCallback, mCustomAnimationInfo, mLetterboxColor, - mTouchableRegion); + mTouchableRegion, + mAppProgressGenerationAllowed); } } } diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig index 150b04e87d97..01c78a0bfb1d 100644 --- a/core/java/android/window/flags/wallpaper_manager.aconfig +++ b/core/java/android/window/flags/wallpaper_manager.aconfig @@ -31,4 +31,11 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "no_visibility_event_on_display_state_change" + namespace: "wear_frameworks" + description: "Prevent the system from sending visibility event on display state change." + bug: "331725519" +} diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 2194c897ff0d..40d760e0064e 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -537,8 +537,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); - if (cb != null && !isDestroyed()) { - cb.onContentChanged(); + if (!isDestroyed()) { + if (cb != null) { + cb.onContentChanged(); + } + if (mDecorContentParent != null) { + mDecorContentParent.notifyContentChanged(); + } } mContentParentExplicitlySet = true; } @@ -568,8 +573,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); - if (cb != null && !isDestroyed()) { - cb.onContentChanged(); + if (!isDestroyed()) { + if (cb != null) { + cb.onContentChanged(); + } + if (mDecorContentParent != null) { + mDecorContentParent.notifyContentChanged(); + } } mContentParentExplicitlySet = true; } @@ -586,8 +596,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mContentParent.addView(view, params); mContentParent.requestApplyInsets(); final Callback cb = getCallback(); - if (cb != null && !isDestroyed()) { - cb.onContentChanged(); + if (!isDestroyed()) { + if (cb != null) { + cb.onContentChanged(); + } + if (mDecorContentParent != null) { + mDecorContentParent.notifyContentChanged(); + } } } diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index 68328252abaf..ff57fd4fe2ce 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -898,6 +898,13 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar mDecorToolbar.dismissPopupMenus(); } + @Override + public void notifyContentChanged() { + mLastBaseContentInsets.setEmpty(); + mLastBaseInnerInsets = WindowInsets.CONSUMED; + mLastInnerInsets = WindowInsets.CONSUMED; + } + public static class LayoutParams extends MarginLayoutParams { public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); diff --git a/core/java/com/android/internal/widget/DecorContentParent.java b/core/java/com/android/internal/widget/DecorContentParent.java index ac524f929d1b..8d6cfd1018c0 100644 --- a/core/java/com/android/internal/widget/DecorContentParent.java +++ b/core/java/com/android/internal/widget/DecorContentParent.java @@ -22,6 +22,7 @@ import android.os.Parcelable; import android.util.SparseArray; import android.view.Menu; import android.view.Window; + import com.android.internal.view.menu.MenuPresenter; /** @@ -49,4 +50,5 @@ public interface DecorContentParent { void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates); void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates); void dismissPopups(); + void notifyContentChanged(); } diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index b6bf617b40ae..89fdeeb078d0 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -36,41 +36,43 @@ static std::string buildFileName(const std::string& locale) { return SYSTEM_HYPHENATOR_PREFIX + lowerLocale + SYSTEM_HYPHENATOR_SUFFIX; } -static const uint8_t* mmapPatternFile(const std::string& locale) { +static std::pair<const uint8_t*, size_t> mmapPatternFile(const std::string& locale) { const std::string hyFilePath = buildFileName(locale); const int fd = open(hyFilePath.c_str(), O_RDONLY | O_CLOEXEC); if (fd == -1) { - return nullptr; // Open failed. + return std::make_pair(nullptr, 0); // Open failed. } struct stat st = {}; if (fstat(fd, &st) == -1) { // Unlikely to happen. close(fd); - return nullptr; + return std::make_pair(nullptr, 0); } void* ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0 /* offset */); close(fd); if (ptr == MAP_FAILED) { - return nullptr; + return std::make_pair(nullptr, 0); } - return reinterpret_cast<const uint8_t*>(ptr); + return std::make_pair(reinterpret_cast<const uint8_t*>(ptr), st.st_size); } static void addHyphenatorWithoutPatternFile(const std::string& locale, int minPrefix, int minSuffix) { - minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary( - nullptr, minPrefix, minSuffix, locale)); + minikin::addHyphenator(locale, + minikin::Hyphenator::loadBinary(nullptr, 0, minPrefix, minSuffix, + locale)); } static void addHyphenator(const std::string& locale, int minPrefix, int minSuffix) { - const uint8_t* ptr = mmapPatternFile(locale); - if (ptr == nullptr) { + std::pair<const uint8_t*, size_t> r = mmapPatternFile(locale); + if (r.first == nullptr) { ALOGE("Unable to find pattern file or unable to map it for %s", locale.c_str()); return; } - minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary( - ptr, minPrefix, minSuffix, locale)); + minikin::addHyphenator(locale, + minikin::Hyphenator::loadBinary(r.first, r.second, minPrefix, minSuffix, + locale)); } static void addHyphenatorAlias(const std::string& from, const std::string& to) { diff --git a/core/res/res/drawable/ic_zen_mode_type_bedtime.xml b/core/res/res/drawable/ic_zen_mode_type_bedtime.xml new file mode 100644 index 000000000000..7428a71b262c --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_type_bedtime.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M524,920Q440,920 366.5,888Q293,856 238.5,801.5Q184,747 152,673.5Q120,600 120,516Q120,370 213,258.5Q306,147 450,120Q432,219 461,313.5Q490,408 561,479Q632,550 726.5,579Q821,608 920,590Q894,734 782,827Q670,920 524,920ZM524,840Q612,840 687,796Q762,752 805,675Q719,667 642,631.5Q565,596 504,535Q443,474 407,397Q371,320 364,234Q287,277 243.5,352.5Q200,428 200,516Q200,651 294.5,745.5Q389,840 524,840ZM504,535Q504,535 504,535Q504,535 504,535Q504,535 504,535Q504,535 504,535Q504,535 504,535Q504,535 504,535Q504,535 504,535Q504,535 504,535Q504,535 504,535Q504,535 504,535Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_type_driving.xml b/core/res/res/drawable/ic_zen_mode_type_driving.xml new file mode 100644 index 000000000000..3cc0066c46b6 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_type_driving.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M240,760L240,800Q240,817 228.5,828.5Q217,840 200,840L160,840Q143,840 131.5,828.5Q120,817 120,800L120,480L204,240Q210,222 225.5,211Q241,200 260,200L700,200Q719,200 734.5,211Q750,222 756,240L840,480L840,800Q840,817 828.5,828.5Q817,840 800,840L760,840Q743,840 731.5,828.5Q720,817 720,800L720,760L240,760ZM232,400L728,400L686,280L274,280L232,400ZM200,480L200,480L200,680L200,680L200,480ZM300,640Q325,640 342.5,622.5Q360,605 360,580Q360,555 342.5,537.5Q325,520 300,520Q275,520 257.5,537.5Q240,555 240,580Q240,605 257.5,622.5Q275,640 300,640ZM660,640Q685,640 702.5,622.5Q720,605 720,580Q720,555 702.5,537.5Q685,520 660,520Q635,520 617.5,537.5Q600,555 600,580Q600,605 617.5,622.5Q635,640 660,640ZM200,680L760,680L760,480L200,480L200,680Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_type_immersive.xml b/core/res/res/drawable/ic_zen_mode_type_immersive.xml new file mode 100644 index 000000000000..70913579b62b --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_type_immersive.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M440,560L520,560L520,280L440,280L440,560ZM560,500L640,500L640,320L560,320L560,500ZM320,480L400,480L400,320L320,320L320,480ZM240,880L240,708Q183,656 151.5,586.5Q120,517 120,440Q120,290 225,185Q330,80 480,80Q605,80 701.5,153.5Q798,227 827,345L879,550Q884,569 872,584.5Q860,600 840,600L760,600L760,720Q760,753 736.5,776.5Q713,800 680,800L600,800L600,880L520,880L520,720L680,720Q680,720 680,720Q680,720 680,720L680,520L788,520L750,365Q727,274 652,217Q577,160 480,160Q364,160 282,241Q200,322 200,438Q200,498 224.5,552Q249,606 294,648L320,672L320,880L240,880ZM494,520L494,520L494,520Q494,520 494,520Q494,520 494,520Q494,520 494,520Q494,520 494,520Q494,520 494,520Q494,520 494,520L494,520L494,520L494,520Q494,520 494,520Q494,520 494,520L494,520L494,520Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_type_managed.xml b/core/res/res/drawable/ic_zen_mode_type_managed.xml new file mode 100644 index 000000000000..5e224ebbf727 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_type_managed.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M234,684Q285,645 348,622.5Q411,600 480,600Q549,600 612,622.5Q675,645 726,684Q761,643 780.5,591Q800,539 800,480Q800,347 706.5,253.5Q613,160 480,160Q347,160 253.5,253.5Q160,347 160,480Q160,539 179.5,591Q199,643 234,684ZM480,520Q421,520 380.5,479.5Q340,439 340,380Q340,321 380.5,280.5Q421,240 480,240Q539,240 579.5,280.5Q620,321 620,380Q620,439 579.5,479.5Q539,520 480,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q533,800 580,784.5Q627,769 666,740Q627,711 580,695.5Q533,680 480,680Q427,680 380,695.5Q333,711 294,740Q333,769 380,784.5Q427,800 480,800ZM480,440Q506,440 523,423Q540,406 540,380Q540,354 523,337Q506,320 480,320Q454,320 437,337Q420,354 420,380Q420,406 437,423Q454,440 480,440ZM480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380ZM480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_type_other.xml b/core/res/res/drawable/ic_zen_mode_type_other.xml new file mode 100644 index 000000000000..d236b0d7202b --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_type_other.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M354,673L480,597L606,674L573,530L684,434L538,421L480,285L422,420L276,433L387,530L354,673ZM233,840L298,559L80,370L368,345L480,80L592,345L880,370L662,559L727,840L480,691L233,840ZM480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_type_schedule_calendar.xml b/core/res/res/drawable/ic_zen_mode_type_schedule_calendar.xml new file mode 100644 index 000000000000..40004899ad84 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_type_schedule_calendar.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> + + <path android:fillColor="@android:color/white" + android:pathData="M17.0,12.0l-5.0,0.0l0.0,5.0l5.0,0.0l0.0,-5.0zM16.0,1.0l0.0,2.0L8.0,3.0L8.0,1.0L6.0,1.0l0.0,2.0L5.0,3.0c-1.11,0.0 -1.9,0.9 -1.99,2.0L3.0,19.0c0.0,1.0 0.89,2.0 2.0,2.0l14.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L21.0,5.0c0.0,-1.1 -0.9,-2.0 -2.0,-2.0l-1.0,0.0L18.0,1.0l-2.0,0.0zm3.0,18.0L5.0,19.0L5.0,8.0l14.0,0.0l0.0,11.0z"/> +</vector> diff --git a/core/res/res/drawable/ic_zen_mode_type_schedule_time.xml b/core/res/res/drawable/ic_zen_mode_type_schedule_time.xml new file mode 100644 index 000000000000..57d596a98f6b --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_type_schedule_time.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M612,668L668,612L520,464L520,280L440,280L440,496L612,668ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM480,800Q613,800 706.5,706.5Q800,613 800,480Q800,347 706.5,253.5Q613,160 480,160Q347,160 253.5,253.5Q160,347 160,480Q160,613 253.5,706.5Q347,800 480,800Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_type_theater.xml b/core/res/res/drawable/ic_zen_mode_type_theater.xml new file mode 100644 index 000000000000..cc66b32391a8 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_type_theater.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M160,840L160,120L240,120L240,200L320,200L320,120L640,120L640,200L720,200L720,120L800,120L800,840L720,840L720,760L640,760L640,840L320,840L320,760L240,760L240,840L160,840ZM240,680L320,680L320,600L240,600L240,680ZM240,520L320,520L320,440L240,440L240,520ZM240,360L320,360L320,280L240,280L240,360ZM640,680L720,680L720,600L640,600L640,680ZM640,520L720,520L720,440L640,440L640,520ZM640,360L720,360L720,280L640,280L640,360ZM400,760L560,760L560,200L400,200L400,760ZM400,200L400,200L560,200L560,200L400,200Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_type_unknown.xml b/core/res/res/drawable/ic_zen_mode_type_unknown.xml new file mode 100644 index 000000000000..c1afd44ecfc4 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_type_unknown.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,880Q407,871 335,840.5Q263,810 206.5,753Q150,696 115,609Q80,522 80,400L80,360L120,360Q171,360 225,373Q279,386 326,412Q338,326 380.5,235.5Q423,145 480,80Q537,145 579.5,235.5Q622,326 634,412Q681,386 735,373Q789,360 840,360L880,360L880,400Q880,522 845,609Q810,696 753.5,753Q697,810 625.5,840.5Q554,871 480,880ZM478,798Q467,632 379.5,547Q292,462 162,442Q173,613 263.5,697Q354,781 478,798ZM480,544Q495,522 516.5,498.5Q538,475 558,458Q556,401 535.5,339Q515,277 480,218Q445,277 424.5,339Q404,401 402,458Q422,475 444,498.5Q466,522 480,544ZM558,780Q595,768 635,745Q675,722 709.5,682.5Q744,643 768.5,584Q793,525 798,442Q704,456 633,504.5Q562,553 524,628Q536,660 544.5,698Q553,736 558,780ZM480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544ZM558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780ZM478,798Q478,798 478,798Q478,798 478,798Q478,798 478,798Q478,798 478,798ZM524,628L524,628Q524,628 524,628Q524,628 524,628L524,628L524,628L524,628Q524,628 524,628Q524,628 524,628L524,628Q524,628 524,628Q524,628 524,628ZM480,880L480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880L480,880Q480,880 480,880Q480,880 480,880L480,880Q480,880 480,880Q480,880 480,880L480,880Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f43351a2e456..4e133de7c483 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6969,9 +6969,17 @@ Note that, indefinitely repeating vibrations are not allowed as shutdown vibrations. --> <string name="config_defaultShutdownVibrationFile" /> - <!-- Whether single finger panning is enabled when magnification is on --> + <!-- Whether single finger panning is enabled by default when magnification is on --> <bool name="config_enable_a11y_magnification_single_panning">false</bool> + <!-- Whether the overscroll handler is enabled when fullscreen magnification is on. When true, + the magnification will change the scale if the user pans the magnifier horizontally past + the edge of the screen, or delegate the touch events to the app if the user pans vertically + past the edge. When false, the magnification will delegate the touch events to the app only + when the users uses single finger to pan the magnifier past the edge of the screen, + otherwise there are no extra actions. --> + <bool name="config_enable_a11y_fullscreen_magnification_overscroll_handler">false</bool> + <!-- The file path in which custom vibrations are provided for haptic feedbacks. If the device does not specify any such file path here, if the file path specified here does not exist, or if the contents of the file does not make up a valid customization diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index a0807ca580a2..5fea51599971 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -620,6 +620,9 @@ <!-- width of the border of the magnification thumbnail --> <dimen name="accessibility_magnification_thumbnail_container_stroke_width">4dp</dimen> + <!-- The distance from the edge within which the gesture is considered to be at the edge --> + <dimen name="accessibility_fullscreen_magnification_gesture_edge_slop">12dp</dimen> + <!-- The padding ratio of the Accessibility icon foreground drawable --> <item name="accessibility_icon_foreground_padding_ratio" type="dimen">21.88%</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c16bd241a860..7251d747be79 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5427,6 +5427,8 @@ <java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" /> <java-symbol type="bool" name="config_enable_a11y_magnification_single_panning" /> + <java-symbol type="bool" name="config_enable_a11y_fullscreen_magnification_overscroll_handler" /> + <java-symbol type="dimen" name="accessibility_fullscreen_magnification_gesture_edge_slop" /> <java-symbol type="string" name="config_hapticFeedbackCustomizationFile" /> @@ -5512,4 +5514,16 @@ <java-symbol type="string" name="face_dangling_notification_msg" /> <java-symbol type="string" name="biometric_dangling_notification_action_set_up" /> <java-symbol type="string" name="biometric_dangling_notification_action_not_now" /> + + <!-- Priority Modes icons --> + <java-symbol type="drawable" name="ic_zen_mode_type_bedtime" /> + <java-symbol type="drawable" name="ic_zen_mode_type_driving" /> + <java-symbol type="drawable" name="ic_zen_mode_type_immersive" /> + <java-symbol type="drawable" name="ic_zen_mode_type_managed" /> + <java-symbol type="drawable" name="ic_zen_mode_type_other" /> + <java-symbol type="drawable" name="ic_zen_mode_type_schedule_calendar" /> + <java-symbol type="drawable" name="ic_zen_mode_type_schedule_time" /> + <java-symbol type="drawable" name="ic_zen_mode_type_theater" /> + <java-symbol type="drawable" name="ic_zen_mode_type_unknown" /> + </resources> diff --git a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml index 6e9d4db47c41..94bde68fdf11 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml +++ b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml @@ -25,10 +25,11 @@ <application android:theme="@style/Theme" + android:icon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/ic_launcher_round" android:label="Battery Stats Viewer"> <activity android:name=".BatteryConsumerPickerActivity" android:label="Battery Stats" - android:icon="@mipmap/ic_launcher" android:launchMode="singleTop" android:exported="true"> <intent-filter> diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_background.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9cbf14..000000000000 --- a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="108dp" - android:height="108dp" - android:viewportWidth="108" - android:viewportHeight="108"> - <path - android:fillColor="#3DDC84" - android:pathData="M0,0h108v108h-108z" /> - <path - android:fillColor="#00000000" - android:pathData="M9,0L9,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,0L19,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M29,0L29,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M39,0L39,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M49,0L49,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M59,0L59,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M69,0L69,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M79,0L79,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M89,0L89,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M99,0L99,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,9L108,9" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,19L108,19" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,29L108,29" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,39L108,39" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,49L108,49" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,59L108,59" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,69L108,69" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,79L108,79" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,89L108,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,99L108,99" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,29L89,29" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,39L89,39" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,49L89,49" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,59L89,59" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,69L89,69" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,79L89,79" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M29,19L29,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M39,19L39,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M49,19L49,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M59,19L59,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M69,19L69,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M79,19L79,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> -</vector> diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_foreground.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index fc0c6ab27228..000000000000 --- a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,42 +0,0 @@ -<vector xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:aapt="http://schemas.android.com/aapt" - android:width="108dp" - android:height="108dp" - android:viewportWidth="108" - android:viewportHeight="108"> - <path - android:fillType="evenOdd" - android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14, - 49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" - android:strokeWidth="1" - android:strokeColor="#00000000"> - <aapt:attr name="android:fillColor"> - <gradient - android:endX="78.5885" - android:endY="90.9159" - android:startX="48.7653" - android:startY="61.0927" - android:type="linear"> - <item - android:color="#44000000" - android:offset="0.0" /> - <item - android:color="#00000000" - android:offset="1.0" /> - </gradient> - </aapt:attr> - </path> - <path - android:fillColor="#FFFFFF" - android:fillType="nonZero" - android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56, - 50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05, - 37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78, - 42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44, - 40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08, - 52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94, - 56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06, - 52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" - android:strokeWidth="1" - android:strokeColor="#00000000" /> -</vector> diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml index f35a210cae4b..987de6bcb4f4 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml @@ -17,6 +17,7 @@ <androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/swipe_refresh" + android:paddingTop="?attr/actionBarSize" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml index cf50d2ad1e91..2d276a51a1da 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml @@ -17,11 +17,13 @@ <androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/swipe_refresh" + android:paddingTop="?attr/actionBarSize" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:orientation="vertical" + android:paddingTop="?attr/actionBarSize" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000000..036d09bc5fd5 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@mipmap/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher_round.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000000..036d09bc5fd5 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@mipmap/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher.xml index 6b78462d615b..036d09bc5fd5 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher.xml +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> - <background android:drawable="@drawable/ic_launcher_background" /> - <foreground android:drawable="@drawable/ic_launcher_foreground" /> -</adaptive-icon> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@mipmap/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher_round.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 000000000000..036d09bc5fd5 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@mipmap/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..005798568117 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_foreground.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_foreground.webp Binary files differnew file mode 100644 index 000000000000..085df9d7b8c2 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_foreground.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_round.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..bcb3b7d85f59 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..3d1cf0ed469d --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_foreground.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_foreground.webp Binary files differnew file mode 100644 index 000000000000..bfd456815bae --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_foreground.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_round.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..4cf0d434f4e2 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..ac4f6936ae02 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_foreground.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_foreground.webp Binary files differnew file mode 100644 index 000000000000..cc6b7639a106 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_foreground.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_round.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..1f17221305aa --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..b70e14559dbc --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_foreground.webp Binary files differnew file mode 100644 index 000000000000..6e46bce20904 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_foreground.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_round.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..3fa346c09714 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..8b463f2ba0ff --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_foreground.webp Binary files differnew file mode 100644 index 000000000000..849caff1db0b --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_foreground.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_round.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..bd6e312c8b4a --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/ic_launcher_background.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/ic_launcher_background.xml new file mode 100644 index 000000000000..1e90e07db569 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="ic_launcher_background">#1A7945</color> +</resources>
\ No newline at end of file diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml index 629d729e7b9a..fa30b2c8dc6f 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml @@ -21,6 +21,7 @@ <item name="colorPrimary">#34a853</item> <item name="android:windowActionBar">true</item> <item name="android:windowNoTitle">false</item> + <item name="android:windowDrawsSystemBarBackgrounds">false</item> </style> <style name="LoadTestCardView" parent="Widget.MaterialComponents.CardView"> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 82d2381012e5..5d4139e4be8c 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -23,23 +23,19 @@ applications that come with the platform <!-- Needed for Build.getSerial(), which is used to send a unique number for serial, per HUIG. --> <privapp-permissions package="android.car.usb.handler"> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.angle"> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.apps.tag"> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.backupconfirm"> <permission name="android.permission.BACKUP"/> <permission name="android.permission.CRYPT_KEEPER"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.credentialmanager"> @@ -50,13 +46,11 @@ applications that come with the platform <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.imsserviceentitlement"> <permission name="android.permission.MODIFY_PHONE_STATE" /> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.launcher3"> @@ -68,7 +62,6 @@ applications that come with the platform <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/> <permission name="android.permission.UPDATE_DEVICE_STATS"/> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.managedprovisioning"> @@ -98,7 +91,6 @@ applications that come with the platform <permission name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/> <permission name="android.permission.BIND_CARRIER_SERVICES"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.mtp"> @@ -108,19 +100,16 @@ applications that come with the platform <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.musicfx"> <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.networkrecommendation"> <permission name="android.permission.SCORE_NETWORKS"/> <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.packageinstaller"> @@ -201,7 +190,6 @@ applications that come with the platform <permission name="android.permission.USE_RESERVED_DISK"/> <permission name="android.permission.LOG_COMPAT_CHANGE" /> <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.providers.contacts"> @@ -215,7 +203,6 @@ applications that come with the platform <permission name="android.permission.USE_RESERVED_DISK"/> <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <permission name="android.permission.LOG_COMPAT_CHANGE" /> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.providers.downloads"> @@ -228,7 +215,6 @@ applications that come with the platform <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.UPDATE_DEVICE_STATS"/> <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.providers.telephony"> @@ -238,7 +224,6 @@ applications that come with the platform <!-- Permissions required for reading and logging compat changes --> <permission name="android.permission.LOG_COMPAT_CHANGE" /> <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.server.telecom"> @@ -254,13 +239,11 @@ applications that come with the platform <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.STOP_APP_SWITCHES"/> <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.sharedstoragebackup"> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.shell"> @@ -602,12 +585,10 @@ applications that come with the platform <permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/> <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.soundpicker"> <permission name="android.permission.INTERACT_ACROSS_USERS" /> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.tv"> @@ -619,18 +600,15 @@ applications that come with the platform <permission name="android.permission.READ_CONTENT_RATING_SYSTEMS"/> <permission name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/> <permission name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.vpndialogs"> <permission name="android.permission.CONTROL_VPN"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.wallpaper.livepicker"> <permission name="android.permission.SET_WALLPAPER_COMPONENT"/> <permission name="android.permission.BIND_WALLPAPER"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.wallpaper"> @@ -638,15 +616,14 @@ applications that come with the platform <permission name="android.permission.BIND_WALLPAPER"/> <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/> <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.dynsystem"> <permission name="android.permission.REBOOT"/> <permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/> <permission name="android.permission.READ_OEM_UNLOCK_STATE"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> + <privapp-permissions package="com.android.settings"> <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/> <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/> @@ -657,12 +634,10 @@ applications that come with the platform <privapp-permissions package="com.android.bips"> <permission name="android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.calllogbackup"> <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/> - <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.devicediagnostics"> diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 15f8c328bb56..112eb617e7a6 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -111,3 +111,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "animate_bubble_size_change" + namespace: "multitasking" + description: "Turns on the animation for bubble bar icons size change" + bug: "335575529" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 0fd21f3798ac..7041ea307b0f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -437,7 +437,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private boolean isAppProgressGenerationAllowed() { - return mBackNavigationInfo.getTouchableRegion().equals(mTouchableArea); + return mBackNavigationInfo.isAppProgressGenerationAllowed() + && mBackNavigationInfo.getTouchableRegion().equals(mTouchableArea); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index 9114c7adb6d8..a3111b31a2f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -354,7 +354,7 @@ abstract class CrossActivityBackAnimation( matrix.postScale(scale, scale, scalePivotX, 0f) matrix.postTranslate(tempRectF.left, tempRectF.top) transaction - .setAlpha(leash, keepMinimumAlpha(alpha)) + .setAlpha(leash, alpha) .setMatrix(leash, matrix, tmpFloat9) .setCrop(leash, cropRect) .setCornerRadius(leash, cornerRadius) @@ -562,9 +562,6 @@ abstract class CrossActivityBackAnimation( } } -// The target will loose focus when alpha == 0, so keep a minimum value for it. -private fun keepMinimumAlpha(transAlpha: Float) = max(transAlpha, 0.005f) - private fun isDarkMode(context: Context): Boolean { return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index cee2d92244cc..641952b28bfb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -35,15 +35,16 @@ import androidx.core.util.plus import androidx.core.util.putAll import com.android.internal.logging.InstanceId import com.android.internal.logging.InstanceIdSequence +import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate -import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON -import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT -import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT +import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON +import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT +import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.shared.TransitionUtil @@ -80,6 +81,9 @@ class DesktopModeLoggerTransitionObserver( // animation was cancelled, we restore these tasks to calculate the post-Transition state private val tasksSavedForRecents: SparseArray<TaskInfo> = SparseArray() + // Caching whether the previous transition was exit to overview. + private var wasPreviousTransitionExitToOverview: Boolean = false + // The instanceId for the current logging session private var loggerInstanceId: InstanceId? = null @@ -101,7 +105,7 @@ class DesktopModeLoggerTransitionObserver( finishTransaction: SurfaceControl.Transaction ) { // this was a new recents animation - if (info.isRecentsTransition() && tasksSavedForRecents.isEmpty()) { + if (info.isExitToRecentsTransition() && tasksSavedForRecents.isEmpty()) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Recents animation running, saving tasks for later" @@ -143,6 +147,7 @@ class DesktopModeLoggerTransitionObserver( preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos, postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks ) + wasPreviousTransitionExitToOverview = info.isExitToRecentsTransition() } override fun onTransitionStarting(transition: IBinder) {} @@ -309,21 +314,40 @@ class DesktopModeLoggerTransitionObserver( } /** Get [EnterReason] for this session enter */ - private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason { - return when (transitionInfo.type) { - WindowManager.TRANSIT_WAKE -> EnterReason.SCREEN_ON - Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> EnterReason.APP_HANDLE_DRAG - TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON -> EnterReason.APP_HANDLE_MENU_BUTTON - TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW -> EnterReason.APP_FROM_OVERVIEW - TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT -> EnterReason.KEYBOARD_SHORTCUT_ENTER - WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT - else -> EnterReason.UNKNOWN_ENTER + private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason = + when { + transitionInfo.type == WindowManager.TRANSIT_WAKE -> EnterReason.SCREEN_ON + transitionInfo.type == Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> + EnterReason.APP_HANDLE_DRAG + transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON -> + EnterReason.APP_HANDLE_MENU_BUTTON + transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW -> + EnterReason.APP_FROM_OVERVIEW + transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT -> + EnterReason.KEYBOARD_SHORTCUT_ENTER + // NOTE: the below condition also applies for EnterReason quickswitch + transitionInfo.type == WindowManager.TRANSIT_TO_FRONT -> EnterReason.OVERVIEW + // Enter desktop mode from cancelled recents has no transition. Enter is detected on the + // next transition involving freeform windows. + // TODO(b/346564416): Modify logging for cancelled recents once it transition is + // changed. Also see how to account to time difference between actual enter time and + // time of this log. Also account for the missed session when exit happens just after + // a cancelled recents. + wasPreviousTransitionExitToOverview -> EnterReason.OVERVIEW + transitionInfo.type == WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT + else -> { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "Unknown enter reason for transition type ${transitionInfo.type}", + transitionInfo.type + ) + EnterReason.UNKNOWN_ENTER + } } - } /** Get [ExitReason] for this session exit */ - private fun getExitReason(transitionInfo: TransitionInfo): ExitReason { - return when { + private fun getExitReason(transitionInfo: TransitionInfo): ExitReason = + when { transitionInfo.type == WindowManager.TRANSIT_SLEEP -> ExitReason.SCREEN_OFF transitionInfo.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG -> ExitReason.DRAG_TO_EXIT @@ -331,10 +355,16 @@ class DesktopModeLoggerTransitionObserver( ExitReason.APP_HANDLE_MENU_BUTTON_EXIT transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT -> ExitReason.KEYBOARD_SHORTCUT_EXIT - transitionInfo.isRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW - else -> ExitReason.UNKNOWN_EXIT + transitionInfo.isExitToRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW + else -> { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "Unknown exit reason for transition type ${transitionInfo.type}", + transitionInfo.type + ) + ExitReason.UNKNOWN_EXIT + } } - } /** Adds tasks to the saved copy of freeform taskId, taskInfo. Only used for testing. */ @VisibleForTesting @@ -357,7 +387,7 @@ class DesktopModeLoggerTransitionObserver( return this.windowingMode == WINDOWING_MODE_FREEFORM } - private fun TransitionInfo.isRecentsTransition(): Boolean { + private fun TransitionInfo.isExitToRecentsTransition(): Boolean { return this.type == WindowManager.TRANSIT_TO_FRONT && this.flags == WindowManager.TRANSIT_FLAG_IS_RECENTS } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 1903586fc317..57e469d5cbd2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -550,7 +550,8 @@ public class BackAnimationControllerTest extends ShellTestCase { .setType(type) .setOnBackInvokedCallback(mAppCallback) .setOnBackNavigationDone(new RemoteCallback(result)) - .setTouchableRegion(mTouchableRegion)); + .setTouchableRegion(mTouchableRegion) + .setAppProgressAllowed(true)); triggerBackGesture(); mShellExecutor.flushAll(); releaseBackGesture(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index 0d3cd10e90df..fb03f20f939c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -22,6 +22,7 @@ import android.content.Context import android.os.IBinder import android.testing.AndroidTestingRunner import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS import android.view.WindowManager.TRANSIT_NONE @@ -182,7 +183,7 @@ class DesktopModeLoggerTransitionObserverTest { } @Test - fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonUnknown() { + fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonAppFromOverview() { val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) @@ -218,6 +219,168 @@ class DesktopModeLoggerTransitionObserverTest { } @Test + fun transitToFront_logTaskAddedAndEnterReasonOverview() { + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + val sessionId = transitionObserver.getLoggerSessionId() + + assertThat(sessionId).isNotNull() + verify(desktopModeEventLogger, times(1)) + .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + fun transitToFront_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { + // previous exit to overview transition + val previousSessionId = 1 + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.setLoggerSessionId(previousSessionId) + val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTransitionInfo = + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(previousChange) + .build() + + callOnTransitionReady(previousTransitionInfo) + + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) + verify(desktopModeEventLogger, times(1)) + .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + + // Enter desktop mode from cancelled recents has no transition. Enter is detected on the + // next transition involving freeform windows + + // TRANSIT_TO_FRONT + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + val sessionId = transitionObserver.getLoggerSessionId() + + assertThat(sessionId).isNotNull() + verify(desktopModeEventLogger, times(1)) + .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + fun transitChange_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { + // previous exit to overview transition + val previousSessionId = 1 + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.setLoggerSessionId(previousSessionId) + val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTransitionInfo = + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(previousChange) + .build() + + callOnTransitionReady(previousTransitionInfo) + + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) + verify(desktopModeEventLogger, times(1)) + .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + + // Enter desktop mode from cancelled recents has no transition. Enter is detected on the + // next transition involving freeform windows + + // TRANSIT_CHANGE + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + val sessionId = transitionObserver.getLoggerSessionId() + + assertThat(sessionId).isNotNull() + verify(desktopModeEventLogger, times(1)) + .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + fun transitOpen_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { + // previous exit to overview transition + val previousSessionId = 1 + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.setLoggerSessionId(previousSessionId) + val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTransitionInfo = + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(previousChange) + .build() + + callOnTransitionReady(previousTransitionInfo) + + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) + verify(desktopModeEventLogger, times(1)) + .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + + // Enter desktop mode from cancelled recents has no transition. Enter is detected on the + // next transition involving freeform windows + + // TRANSIT_OPEN + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + val sessionId = transitionObserver.getLoggerSessionId() + + assertThat(sessionId).isNotNull() + verify(desktopModeEventLogger, times(1)) + .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + @Suppress("ktlint:standard:max-line-length") + fun transitEnterDesktopFromAppFromOverview_previousTransitionExitToOverview_logTaskAddedAndEnterReasonAppFromOverview() { + // Tests for AppFromOverview precedence in compared to cancelled Overview + + // previous exit to overview transition + val previousSessionId = 1 + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.setLoggerSessionId(previousSessionId) + val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTransitionInfo = + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(previousChange) + .build() + + callOnTransitionReady(previousTransitionInfo) + + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) + verify(desktopModeEventLogger, times(1)) + .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + + // TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) + .addChange(change) + .build() + + callOnTransitionReady(transitionInfo) + val sessionId = transitionObserver.getLoggerSessionId() + + assertThat(sessionId).isNotNull() + verify(desktopModeEventLogger, times(1)) + .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test fun transitEnterDesktopFromUnknown_logTaskAddedAndEnterReasonUnknown() { val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) val transitionInfo = diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 748ad3182393..35808d9880f0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -484,57 +484,52 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun moveToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val task = setUpFullscreenTask() setUpLandscapeDisplay() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun moveToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) setUpLandscapeDisplay() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun moveToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true) setUpLandscapeDisplay() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun moveToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val task = setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) setUpLandscapeDisplay() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun moveToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val task = setUpFullscreenTask( isResizable = false, @@ -543,26 +538,24 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpLandscapeDisplay() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun moveToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT) setUpPortraitDisplay() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun moveToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val task = setUpFullscreenTask( deviceOrientation = ORIENTATION_PORTRAIT, @@ -570,14 +563,13 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpPortraitDisplay() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun moveToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val task = setUpFullscreenTask( deviceOrientation = ORIENTATION_PORTRAIT, @@ -586,14 +578,13 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpPortraitDisplay() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun moveToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val task = setUpFullscreenTask( isResizable = false, @@ -602,14 +593,13 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpPortraitDisplay() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun moveToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val task = setUpFullscreenTask( isResizable = false, @@ -619,7 +609,7 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpPortraitDisplay() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) } @@ -629,7 +619,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) } @@ -639,7 +629,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_UNDEFINED) } @@ -647,7 +637,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun moveToDesktop_nonExistentTask_doesNothing() { controller.moveToDesktop(999, transitionSource = UNKNOWN) - verifyWCTNotExecuted() + verifyEnterDesktopWCTNotExecuted() } @Test @@ -659,7 +649,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN) - with(getLatestMoveToDesktopWct()) { + with(getLatestEnterDesktopWct()) { assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) } } @@ -674,34 +664,7 @@ class DesktopTasksControllerTest : ShellTestCase() { } controller.moveToDesktop(task, transitionSource = UNKNOWN) - verifyWCTNotExecuted() - } - - @Test - fun moveToDesktop_deviceNotSupported_doesNothing() { - val task = setUpFullscreenTask() - - // Simulate non compatible device - doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - verifyWCTNotExecuted() - } - - @Test - fun moveToDesktop_deviceNotSupported_deviceRestrictionsOverridden_taskIsMovedToDesktop() { - val task = setUpFullscreenTask() - - // Simulate non compatible device - doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } - - // Simulate enforce device restrictions system property overridden to false - whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false) - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - - val wct = getLatestMoveToDesktopWct() - assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + verifyEnterDesktopWCTNotExecuted() } @Test @@ -710,7 +673,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) } @@ -724,7 +687,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToDesktop(fullscreenTask, transitionSource = UNKNOWN) - with(getLatestMoveToDesktopWct()) { + with(getLatestEnterDesktopWct()) { // Operations should include home task, freeform task assertThat(hierarchyOps).hasSize(3) assertReorderSequence(homeTask, freeformTask, fullscreenTask) @@ -742,7 +705,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToDesktop(fullscreenTask, transitionSource = UNKNOWN) - with(getLatestMoveToDesktopWct()) { + with(getLatestEnterDesktopWct()) { // Operations should include wallpaper intent, freeform task, fullscreen task assertThat(hierarchyOps).hasSize(3) assertPendingIntentAt(index = 0, desktopWallpaperIntent) @@ -766,7 +729,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToDesktop(fullscreenTaskDefault, transitionSource = UNKNOWN) - with(getLatestMoveToDesktopWct()) { + with(getLatestEnterDesktopWct()) { // Check that hierarchy operations do not include tasks from second display assertThat(hierarchyOps.map { it.container }).doesNotContain(homeTaskSecond.token.asBinder()) assertThat(hierarchyOps.map { it.container }) @@ -778,7 +741,7 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveToDesktop_splitTaskExitsSplit() { val task = setUpSplitScreenTask() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) verify(splitScreenController) .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)) @@ -788,7 +751,7 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveToDesktop_fullscreenTaskDoesNotExitSplit() { val task = setUpFullscreenTask() controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) verify(splitScreenController, never()) .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)) @@ -803,7 +766,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToDesktop(newTask, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + home wct.assertReorderAt(0, homeTask) for (i in 1..<taskLimit) { // Skipping freeformTasks[0] @@ -837,7 +800,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun moveToFullscreen_nonExistentTask_doesNothing() { controller.moveToFullscreen(999, transitionSource = UNKNOWN) - verifyWCTNotExecuted() + verifyExitDesktopWCTNotExecuted() } @Test @@ -1284,7 +1247,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task1.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) } @@ -1305,7 +1268,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN) - val wct = getLatestMoveToDesktopWct() + val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task4.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) verify(splitScreenController) @@ -1332,7 +1295,6 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val spyController = spy(controller) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) @@ -1349,7 +1311,6 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val spyController = spy(controller) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) @@ -1366,7 +1327,6 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val spyController = spy(controller) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) @@ -1384,7 +1344,6 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val spyController = spy(controller) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) @@ -1402,7 +1361,6 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val spyController = spy(controller) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) @@ -1423,7 +1381,6 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val spyController = spy(controller) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) @@ -1440,7 +1397,6 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val spyController = spy(controller) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) @@ -1460,7 +1416,6 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val spyController = spy(controller) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) @@ -1481,7 +1436,6 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val spyController = spy(controller) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) @@ -1502,7 +1456,6 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() { - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val spyController = spy(controller) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) @@ -1685,7 +1638,6 @@ class DesktopTasksControllerTest : ShellTestCase() { Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG) } } - whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) return task @@ -1703,7 +1655,6 @@ class DesktopTasksControllerTest : ShellTestCase() { private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createSplitScreenTask(displayId) - whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) @@ -1748,7 +1699,7 @@ class DesktopTasksControllerTest : ShellTestCase() { return arg.value } - private fun getLatestMoveToDesktopWct(): WindowContainerTransaction { + private fun getLatestEnterDesktopWct(): WindowContainerTransaction { val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) if (ENABLE_SHELL_TRANSITIONS) { verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any()) @@ -1790,6 +1741,22 @@ class DesktopTasksControllerTest : ShellTestCase() { } } + private fun verifyExitDesktopWCTNotExecuted() { + if (ENABLE_SHELL_TRANSITIONS) { + verify(exitDesktopTransitionHandler, never()).startTransition(any(), any(), any(), any()) + } else { + verify(shellTaskOrganizer, never()).applyTransaction(any()) + } + } + + private fun verifyEnterDesktopWCTNotExecuted() { + if (ENABLE_SHELL_TRANSITIONS) { + verify(enterDesktopTransitionHandler, never()).moveToDesktop(any(), any()) + } else { + verify(shellTaskOrganizer, never()).applyTransaction(any()) + } + } + private fun createTransition( task: RunningTaskInfo?, @WindowManager.TransitionType type: Int = TRANSIT_OPEN diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt index c35721c11741..772e02eb9e29 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt @@ -34,6 +34,7 @@ import com.android.credentialmanager.getflow.ProviderDisplayInfo import com.android.credentialmanager.getflow.RequestDisplayInfo import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode import com.android.credentialmanager.model.BiometricRequestInfo +import com.android.credentialmanager.model.CredentialType import com.android.credentialmanager.model.EntryInfo import com.android.credentialmanager.model.creation.CreateOptionInfo import com.android.credentialmanager.model.get.CredentialEntryInfo @@ -476,7 +477,9 @@ private fun retrieveBiometricGetDisplayValues( return null } val singleEntryType = selectedEntry.credentialType - val username = selectedEntry.userName + val descriptionName = if (singleEntryType == CredentialType.PASSKEY && + !selectedEntry.displayName.isNullOrBlank()) selectedEntry.displayName else + selectedEntry.userName // TODO(b/336362538) : In W, utilize updated localization strings displayTitleText = context.getString( @@ -487,7 +490,7 @@ private fun retrieveBiometricGetDisplayValues( descriptionText = context.getString( R.string.get_dialog_description_single_tap, getRequestDisplayInfo.appName, - username + descriptionName ) return BiometricDisplayInfo(providerIcon = icon, providerName = providerName, diff --git a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt index 79f8b5fc6ecd..16ec1a933d92 100644 --- a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt +++ b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt @@ -17,6 +17,7 @@ package com.android.egg.landroid import android.content.res.Resources +import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity @@ -119,6 +120,26 @@ fun randomSeed(): Long { }.absoluteValue } +fun getDessertCode(): String = + when (Build.VERSION.SDK_INT) { + Build.VERSION_CODES.LOLLIPOP -> "LMP" + Build.VERSION_CODES.LOLLIPOP_MR1 -> "LM1" + Build.VERSION_CODES.M -> "MNC" + Build.VERSION_CODES.N -> "NYC" + Build.VERSION_CODES.N_MR1 -> "NM1" + Build.VERSION_CODES.O -> "OC" + Build.VERSION_CODES.P -> "PIE" + Build.VERSION_CODES.Q -> "QT" + Build.VERSION_CODES.R -> "RVC" + Build.VERSION_CODES.S -> "SC" + Build.VERSION_CODES.S_V2 -> "SC2" + Build.VERSION_CODES.TIRAMISU -> "TM" + Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> "UDC" + Build.VERSION_CODES.VANILLA_ICE_CREAM -> "VIC" + else -> Build.VERSION.RELEASE_OR_CODENAME.replace(Regex("[a-z]*"), "") + } + + val DEBUG_TEXT = mutableStateOf("Hello Universe") const val SHOW_DEBUG_TEXT = false @@ -239,7 +260,8 @@ fun Telemetry(universe: VisibleUniverse) { text = (with(universe.star) { listOf( - " STAR: $name (UDC-${universe.randomSeed % 100_000})", + " STAR: $name (${getDessertCode()}-" + + "${universe.randomSeed % 100_000})", " CLASS: ${cls.name}", "RADIUS: ${radius.toInt()}", " MASS: %.3g".format(mass), diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index 232fa9204517..d6345cec7c30 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.7.0-SNAPSHOT" + extra["jetpackComposeVersion"] = "1.7.0-beta02" } subprojects { diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml index 85ad160f6d66..a842009a09bb 100644 --- a/packages/SettingsLib/Spa/gradle/libs.versions.toml +++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml @@ -15,7 +15,7 @@ # [versions] -agp = "8.3.2" +agp = "8.5.0" compose-compiler = "1.5.11" dexmaker-mockito = "2.28.3" jvm = "17" diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip Binary files differindex 7a9ac5afe013..77e6ad3f2117 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar Binary files differindex d64cd4917707..e6441136f3d4 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties index 182095e76e76..91d2a3ab217d 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties @@ -16,6 +16,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=gradle-8.7-bin.zip +distributionUrl=gradle-8.8-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/packages/SettingsLib/Spa/gradlew b/packages/SettingsLib/Spa/gradlew index 1aa94a426907..b740cf13397a 100755 --- a/packages/SettingsLib/Spa/gradlew +++ b/packages/SettingsLib/Spa/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/packages/SettingsLib/Spa/settings.gradle.kts b/packages/SettingsLib/Spa/settings.gradle.kts index 31f462e5e918..e003c81d7a0c 100644 --- a/packages/SettingsLib/Spa/settings.gradle.kts +++ b/packages/SettingsLib/Spa/settings.gradle.kts @@ -36,9 +36,6 @@ dependencyResolutionManagement { } mavenCentral() maven { - url = uri("https://androidx.dev/snapshots/builds/11846308/artifacts/repository") - } - maven { url = uri("https://jitpack.io") content { includeGroup("com.github.PhilJay") diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index f98695eb2e1d..9b8ecf773661 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -53,21 +53,21 @@ android { dependencies { api(project(":SettingsLibColor")) - api("androidx.appcompat:appcompat:1.7.0-beta01") + api("androidx.appcompat:appcompat:1.7.0-rc01") api("androidx.slice:slice-builders:1.1.0-alpha02") api("androidx.slice:slice-core:1.1.0-alpha02") api("androidx.slice:slice-view:1.1.0-alpha02") - api("androidx.compose.material3:material3:1.3.0-SNAPSHOT") + api("androidx.compose.material3:material3:1.3.0-beta02") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") api("androidx.lifecycle:lifecycle-livedata-ktx") api("androidx.lifecycle:lifecycle-runtime-compose") - api("androidx.navigation:navigation-compose:2.8.0-beta01") + api("androidx.navigation:navigation-compose:2.8.0-beta02") api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha") api("com.google.android.material:material:1.11.0") debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion") - implementation("com.airbnb.android:lottie-compose:5.2.0") + implementation("com.airbnb.android:lottie-compose:6.4.0") androidTestImplementation(project(":testutils")) androidTestImplementation(libs.dexmaker.mockito) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt index 3fdb1d14e667..12d04f95056d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt @@ -33,6 +33,9 @@ data class SettingsPage( // The name of the page provider, who creates this page. It is used to compute the unique id. val sppName: String, + // The category id of the page provider which is the PageId at SettingsEnums. + val metricsCategory: Int = 0, + // The display name of the page, for better readability. val displayName: String, @@ -46,6 +49,7 @@ data class SettingsPage( // TODO: cleanup it once all its usage in Settings are switched to Spp.createSettingsPage fun create( name: String, + metricsCategory: Int = 0, displayName: String? = null, parameter: List<NamedNavArgument> = emptyList(), arguments: Bundle? = null @@ -53,6 +57,7 @@ data class SettingsPage( return SettingsPage( id = genPageId(name, parameter, arguments), sppName = name, + metricsCategory = metricsCategory, displayName = displayName ?: name, parameter = parameter, arguments = arguments diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt index 0281ab817340..95c7d2371b3d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt @@ -34,6 +34,10 @@ interface SettingsPageProvider { /** The page provider name, needs to be *unique* and *stable*. */ val name: String + /** The category id which is the PageId at SettingsEnums.*/ + val metricsCategory: Int + get() = 0 + enum class NavType { Page, Dialog, @@ -79,6 +83,7 @@ fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): Settings return SettingsPage( id = genPageId(name, parameter, arguments), sppName = name, + metricsCategory = metricsCategory, displayName = displayName + parameter.normalizeArgList(arguments, eraseRuntimeValues = true) .joinToString("") { arg -> "/$arg" }, parameter = parameter, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt index 215f6b964ad3..741129738af7 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt @@ -45,6 +45,7 @@ internal const val LOG_DATA_DISPLAY_NAME = "name" internal const val LOG_DATA_SWITCH_STATUS = "switch" const val LOG_DATA_SESSION_NAME = "session" +const val LOG_DATA_METRICS_CATEGORY = "metricsCategory" /** * The interface of logger in Spa diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt index a9e5e393000e..f323e996f5fd 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt @@ -19,6 +19,7 @@ package com.android.settingslib.spa.framework.util import androidx.compose.runtime.Composable import androidx.core.os.bundleOf import com.android.settingslib.spa.framework.common.LOG_DATA_DISPLAY_NAME +import com.android.settingslib.spa.framework.common.LOG_DATA_METRICS_CATEGORY import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME import com.android.settingslib.spa.framework.common.LogCategory import com.android.settingslib.spa.framework.common.LogEvent @@ -45,9 +46,10 @@ private fun SettingsPage.logPageEvent(event: LogEvent, navController: NavControl extraData = bundleOf( LOG_DATA_DISPLAY_NAME to displayName, LOG_DATA_SESSION_NAME to navController.sessionSourceName, + LOG_DATA_METRICS_CATEGORY to metricsCategory, ).apply { val normArguments = parameter.normalize(arguments) if (normArguments != null) putAll(normArguments) } ) -}
\ No newline at end of file +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt index 3dac7dbc005d..ea69eabbe971 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt @@ -61,11 +61,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.rootWidth import com.android.settingslib.spa.testutils.setContentForSizeAssertions import com.google.common.truth.Truth.assertThat +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @OptIn(ExperimentalMaterial3Api::class) +@Ignore("b/346785755") @RunWith(AndroidJUnit4::class) class CustomizedAppBarTest { diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 58c39b477a82..fde7c2caca06 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -785,6 +785,7 @@ android_app { kotlincflags: ["-Xjvm-default=all"], optimize: { shrink_resources: false, + optimized_shrink_resources: false, proguard_flags_files: ["proguard.flags"], }, @@ -921,6 +922,7 @@ systemui_optimized_java_defaults { optimize: true, shrink: true, shrink_resources: true, + optimized_shrink_resources: true, ignore_warnings: false, proguard_compatibility: false, }, diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index d2e5a13adfce..e03ac3de7e4e 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -11,6 +11,7 @@ aioana@google.com alexflo@google.com andonian@google.com amiko@google.com +austindelgado@google.com aroederer@google.com arteiro@google.com asc@google.com @@ -29,11 +30,13 @@ chrisgollner@google.com cinek@google.com cocod@google.com darrellshi@google.com +diyab@google.com dupin@google.com ethibodeau@google.com evanlaird@google.com florenceyang@google.com gallmann@google.com +graciecheng@google.com gwasserman@google.com hwwang@google.com hyunyoungs@google.com @@ -42,10 +45,12 @@ iyz@google.com jbolinger@google.com jdemeulenaere@google.com jeffdq@google.com +jeffpu@google.com jernej@google.com jglazier@google.com jjaggi@google.com jonmiranda@google.com +joshmccloskey@google.com joshtrask@google.com juansmartinez@google.com juliacr@google.com @@ -87,6 +92,7 @@ saff@google.com santie@google.com shanh@google.com snoeberger@google.com +spdonghao@google.com steell@google.com stevenckng@google.com stwu@google.com 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 18e655085ae6..22566e70f409 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 @@ -99,7 +99,7 @@ fun SceneContainer( if (sceneKey == currentSceneKey) { currentDestinations } else { - composableScene.destinationScenes.value + viewModel.resolveSceneFamilies(composableScene.destinationScenes.value) }, ) { with(composableScene) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index f3de463e880e..fa79ea01cf51 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -256,8 +256,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private ViewRootImpl mViewRootImpl; @Mock - private FpsUnlockTracker mFpsUnlockTracker; - @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock private Lazy<DeviceEntryUdfpsTouchOverlayViewModel> mDeviceEntryUdfpsTouchOverlayViewModel; @@ -368,7 +366,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mock(DeviceEntryFaceAuthInteractor.class), mUdfpsKeyguardAccessibilityDelegate, mSelectedUserInteractor, - mFpsUnlockTracker, mKeyguardTransitionInteractor, mDeviceEntryUdfpsTouchOverlayViewModel, mDefaultUdfpsTouchOverlayViewModel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt index 0250c9d99e3a..baeb2dd5aa96 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt @@ -31,7 +31,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -42,13 +41,11 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val communalSceneRepository = kosmos.fakeCommunalSceneRepository - private lateinit var underTest: CommunalTransitionViewModel - - @Before - fun setup() { - underTest = kosmos.communalTransitionViewModel + private val underTest: CommunalTransitionViewModel by lazy { + kosmos.communalTransitionViewModel } @Test @@ -60,11 +57,7 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { enterCommunal(from = KeyguardState.LOCKSCREEN) assertThat(isUmoOnCommunal).isTrue() - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GLANCEABLE_HUB, - to = KeyguardState.LOCKSCREEN, - testScope - ) + exitCommunal(to = KeyguardState.LOCKSCREEN) assertThat(isUmoOnCommunal).isFalse() } @@ -77,11 +70,7 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { enterCommunal(from = KeyguardState.DREAMING) assertThat(isUmoOnCommunal).isTrue() - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GLANCEABLE_HUB, - to = KeyguardState.DREAMING, - testScope - ) + exitCommunal(to = KeyguardState.DREAMING) assertThat(isUmoOnCommunal).isFalse() } @@ -94,11 +83,7 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { enterCommunal(from = KeyguardState.OCCLUDED) assertThat(isUmoOnCommunal).isTrue() - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GLANCEABLE_HUB, - to = KeyguardState.OCCLUDED, - testScope - ) + exitCommunal(to = KeyguardState.OCCLUDED) assertThat(isUmoOnCommunal).isFalse() } @@ -112,20 +97,42 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { assertThat(isUmoOnCommunal).isTrue() // Communal is no longer visible. - kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank) - runCurrent() + communalSceneRepository.changeScene(CommunalScenes.Blank) // isUmoOnCommunal returns false, even without any keyguard transition. assertThat(isUmoOnCommunal).isFalse() } + @Test + fun isUmoOnCommunal_idleOnCommunal_returnsTrue() = + testScope.runTest { + val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal) + assertThat(isUmoOnCommunal).isFalse() + + // Communal is fully visible. + communalSceneRepository.changeScene(CommunalScenes.Communal) + + // isUmoOnCommunal returns true, even without any keyguard transition. + assertThat(isUmoOnCommunal).isTrue() + } + private suspend fun TestScope.enterCommunal(from: KeyguardState) { keyguardTransitionRepository.sendTransitionSteps( from = from, to = KeyguardState.GLANCEABLE_HUB, testScope ) - kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal) + communalSceneRepository.changeScene(CommunalScenes.Communal) + runCurrent() + } + + private suspend fun TestScope.exitCommunal(to: KeyguardState) { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = to, + testScope + ) + communalSceneRepository.changeScene(CommunalScenes.Blank) runCurrent() } } diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 0d23a6da0973..79be2b1f8632 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -330,6 +330,8 @@ <string name="share_to_app_stop_dialog_title">Stop sharing screen?</string> <!-- Text telling a user that they will stop sharing their screen if they click the "Stop sharing" button [CHAR LIMIT=100] --> <string name="share_to_app_stop_dialog_message">You will stop sharing your screen</string> + <!-- Text telling a user that they will stop sharing the contents of the specified [app_name] if they click the "Stop sharing" button. Note that the app name will appear in bold. [CHAR LIMIT=100] --> + <string name="share_to_app_stop_dialog_message_specific_app">You will stop sharing <b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g></b></string> <!-- Button to stop screen sharing [CHAR LIMIT=35] --> <string name="share_to_app_stop_dialog_button">Stop sharing</string> @@ -337,6 +339,8 @@ <string name="cast_to_other_device_stop_dialog_title">Stop casting screen?</string> <!-- Text telling a user that they will stop casting their screen to a different device if they click the "Stop casting" button [CHAR LIMIT=100] --> <string name="cast_to_other_device_stop_dialog_message">You will stop casting your screen</string> + <!-- Text telling a user that they will stop casting the contents of the specified [app_name] to a different device if they click the "Stop casting" button. Note that the app name will appear in bold. [CHAR LIMIT=100] --> + <string name="cast_to_other_device_stop_dialog_message_specific_app">You will stop casting <b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g></b></string> <!-- Button to stop screen casting to a different device [CHAR LIMIT=35] --> <string name="cast_to_other_device_stop_dialog_button">Stop casting</string> diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 4c9af6607afa..e055e7c9683b 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -1250,6 +1250,11 @@ public class ScreenDecorations implements if (mOverlays == null) { return; } + if (mPendingConfigChange) { + // Let RestartingPreDrawListener's onPreDraw call updateConfiguration + // -> updateOverlayProviderViews to redraw with display change synchronously. + return; + } ++mProviderRefreshToken; for (final OverlayWindow overlay: mOverlays) { if (overlay == null) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FpsUnlockTracker.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FpsUnlockTracker.kt deleted file mode 100644 index cf699a2ed349..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FpsUnlockTracker.kt +++ /dev/null @@ -1,208 +0,0 @@ -/* - * 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.biometrics - -import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START -import android.hardware.biometrics.BiometricSourceType -import android.hardware.biometrics.BiometricSourceType.FINGERPRINT -import android.util.Log -import com.android.app.tracing.TraceStateLogger -import com.android.internal.util.LatencyTracker -import com.android.internal.util.LatencyTracker.ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener -import com.android.systemui.plugins.statusbar.StatusBarStateController -import javax.inject.Inject - -private const val TAG = "FpsUnlockTracker" -private const val TRACE_COUNTER_NAME = "FpsUnlockStage" -private const val TRACE_TAG_AOD = "AOD" -private const val TRACE_TAG_KEYGUARD = "KEYGUARD" -private const val DEBUG = true - -/** This is a class for monitoring unlock latency of fps and logging stages in perfetto. */ -@SysUISingleton -class FpsUnlockTracker -@Inject -constructor( - private val statusBarStateController: StatusBarStateController, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val keyguardUnlockAnimationController: KeyguardUnlockAnimationController, - private val latencyTracker: LatencyTracker, -) { - private val fpsTraceStateLogger = TraceStateLogger(TRACE_COUNTER_NAME) - private var fpsAuthenticated: Boolean = false - - private val keyguardUpdateMonitorCallback = - object : KeyguardUpdateMonitorCallback() { - override fun onBiometricAcquired( - biometricSourceType: BiometricSourceType?, - acquireInfo: Int - ) { - if (keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) { - onHalAuthenticationStage(acquireInfo) - } - } - - override fun onBiometricAuthenticated( - userId: Int, - biometricSourceType: BiometricSourceType?, - isStrongBiometric: Boolean - ) { - if (biometricSourceType == FINGERPRINT) { - fpsAuthenticated = true - onExitKeyguard() - } - } - - override fun onBiometricError( - msgId: Int, - errString: String?, - biometricSourceType: BiometricSourceType? - ) { - if (biometricSourceType == FINGERPRINT) { - latencyTracker.onActionCancel(ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME) - } - } - - override fun onBiometricRunningStateChanged( - running: Boolean, - biometricSourceType: BiometricSourceType? - ) { - if (biometricSourceType != FINGERPRINT || !running) { - return - } - onWaitForAuthenticationStage() - } - } - - private val keyguardUnlockAnimationListener = - object : KeyguardUnlockAnimationListener { - override fun onUnlockAnimationFinished() = onUnlockedStage() - } - - /** Start tracking the fps unlock. */ - fun startTracking() { - keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) - keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener( - keyguardUnlockAnimationListener - ) - } - - /** Stop tracking the fps unlock. */ - fun stopTracking() { - keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) - keyguardUnlockAnimationController.removeKeyguardUnlockAnimationListener( - keyguardUnlockAnimationListener - ) - } - - /** - * The stage when the devices is locked and is possible to be unlocked via fps. However, in some - * situations, it might be unlocked only via bouncer. - */ - fun onWaitForAuthenticationStage() { - val stage = - if (keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) - FpsUnlockStage.WAIT_FOR_AUTHENTICATION.name - else FpsUnlockStage.WAIT_FOR_AUTHENTICATION.name + "(Not allowed)" - fpsTraceStateLogger.log(stage) - if (DEBUG) { - Log.d(TAG, "onWaitForAuthenticationStage: stage=$stage") - } - } - - /** - * The stage dedicated to UDFPS, SFPS should not enter this stage. The only place where invokes - * this function is UdfpsController#onFingerDown. - */ - fun onUiReadyStage() { - if (!keyguardUpdateMonitor.isUdfpsSupported || !keyguardUpdateMonitor.isUdfpsEnrolled) { - return - } - fpsTraceStateLogger.log(FpsUnlockStage.UI_READY.name) - startLatencyTracker() - if (DEBUG) { - Log.d(TAG, "onUiReadyStage: dozing=${statusBarStateController.isDozing}") - } - } - - /** The stage when the HAL is authenticating the fingerprint. */ - fun onHalAuthenticationStage(acquire: Int) { - fpsTraceStateLogger.log("${FpsUnlockStage.HAL_AUTHENTICATION.name}($acquire)") - // Start latency tracker here only for SFPS, UDFPS should start at onUiReadyStage. - if ( - keyguardUpdateMonitor.isSfpsSupported && - keyguardUpdateMonitor.isSfpsEnrolled && - acquire == FINGERPRINT_ACQUIRED_START - ) { - startLatencyTracker() - } - if (DEBUG) { - Log.d( - TAG, - "onHalAuthenticationStage: acquire=$acquire" + - ", sfpsSupported=${keyguardUpdateMonitor.isSfpsSupported}" + - ", sfpsEnrolled=${keyguardUpdateMonitor.isSfpsEnrolled}" - ) - } - } - - /** The stage when the authentication is succeeded and is going to exit keyguard. */ - fun onExitKeyguard() { - fpsTraceStateLogger.log(FpsUnlockStage.EXIT_KEYGUARD.name) - if (DEBUG) { - Log.d(TAG, "onExitKeyguard: fpsAuthenticated=$fpsAuthenticated") - } - } - - /** - * The stage when the unlock animation is finished which means the user can start interacting - * with the device. - */ - fun onUnlockedStage() { - fpsTraceStateLogger.log(FpsUnlockStage.UNLOCKED.name) - if (fpsAuthenticated) { - // The device is unlocked successfully via fps, end the instrument. - latencyTracker.onActionEnd(ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME) - } else { - // The device is unlocked but not via fps, maybe bouncer? Cancel the instrument. - latencyTracker.onActionCancel(ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME) - } - if (DEBUG) { - Log.d(TAG, "onUnlockedStage: fpsAuthenticated=$fpsAuthenticated") - } - fpsAuthenticated = false - } - - private fun startLatencyTracker() { - latencyTracker.onActionCancel(ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME) - val tag = if (statusBarStateController.isDozing) TRACE_TAG_AOD else TRACE_TAG_KEYGUARD - latencyTracker.onActionStart(ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME, tag) - } -} - -private enum class FpsUnlockStage { - WAIT_FOR_AUTHENTICATION, - UI_READY, - HAL_AUTHENTICATION, - EXIT_KEYGUARD, - UNLOCKED -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 85b5faf2d556..ad142a86fa03 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -184,7 +184,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final InputManager mInputManager; @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; @NonNull private final SelectedUserInteractor mSelectedUserInteractor; - @NonNull private final FpsUnlockTracker mFpsUnlockTracker; private final boolean mIgnoreRefreshRate; private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -712,7 +711,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor, @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate, @NonNull SelectedUserInteractor selectedUserInteractor, - @NonNull FpsUnlockTracker fpsUnlockTracker, @NonNull KeyguardTransitionInteractor keyguardTransitionInteractor, Lazy<DeviceEntryUdfpsTouchOverlayViewModel> deviceEntryUdfpsTouchOverlayViewModel, Lazy<DefaultUdfpsTouchOverlayViewModel> defaultUdfpsTouchOverlayViewModel, @@ -765,8 +763,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mInputManager = inputManager; mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate; mSelectedUserInteractor = selectedUserInteractor; - mFpsUnlockTracker = fpsUnlockTracker; - mFpsUnlockTracker.startTracking(); mKeyguardTransitionInteractor = keyguardTransitionInteractor; mTouchProcessor = singlePointerTouchProcessor; @@ -1067,9 +1063,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (isOptical()) { mLatencyTracker.onActionStart(ACTION_UDFPS_ILLUMINATE); } - if (getBiometricSessionType() == SESSION_KEYGUARD) { - mFpsUnlockTracker.onUiReadyStage(); - } // Refresh screen timeout and boost process priority if possible. mPowerManager.userActivity(mSystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt index 14d8caf2a5b7..e2a8a691b1fd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt @@ -39,12 +39,14 @@ import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector import com.android.systemui.biometrics.udfps.EllipseOverlapDetector import com.android.systemui.biometrics.udfps.OverlapDetector +import com.android.systemui.biometrics.ui.binder.DeviceEntryUnlockTrackerViewBinder import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import com.android.systemui.util.concurrency.ThreadFactory import dagger.Binds +import dagger.BindsOptionalOf import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey @@ -101,6 +103,9 @@ interface BiometricsModule { @SysUISingleton fun displayStateRepository(impl: DisplayStateRepositoryImpl): DisplayStateRepository + @BindsOptionalOf + fun deviceEntryUnlockTrackerViewBinder(): DeviceEntryUnlockTrackerViewBinder + companion object { /** Background [Executor] for HAL related operations. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 65c5b6b3859c..408e2c2d65a1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -457,17 +457,10 @@ object BiometricViewBinder { // Retry and confirmation when finger on sensor launch { - combine( - viewModel.canTryAgainNow, - viewModel.hasFingerOnSensor, - viewModel.isPendingConfirmation, - ::Triple - ) - .collect { (canRetry, fingerAcquired, pendingConfirmation) -> + combine(viewModel.canTryAgainNow, viewModel.hasFingerOnSensor, ::Pair) + .collect { (canRetry, fingerAcquired) -> if (canRetry && fingerAcquired) { legacyCallback.onButtonTryAgain() - } else if (pendingConfirmation && fingerAcquired) { - viewModel.confirmAuthenticated() } } } @@ -497,13 +490,21 @@ class Spaghetti( @Deprecated("TODO(b/330788871): remove after replacing AuthContainerView") interface Callback { fun onAuthenticated() + fun onUserCanceled() + fun onButtonNegative() + fun onButtonTryAgain() + fun onContentViewMoreOptionsButtonPressed() + fun onError() + fun onUseDeviceCredential() + fun onStartDelayedFingerprintSensor() + fun onAuthenticatedAndConfirmed() } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/DeviceEntryUnlockTrackerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/DeviceEntryUnlockTrackerViewBinder.kt new file mode 100644 index 000000000000..78eaab262370 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/DeviceEntryUnlockTrackerViewBinder.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 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.biometrics.ui.binder + +import com.android.systemui.keyguard.ui.view.KeyguardRootView + +/** ViewBinder for device entry unlock tracker implemented in vendor */ +interface DeviceEntryUnlockTrackerViewBinder { + /** + * Allows vendor binds vendor's view model to the specified view. + * @param view the view to be bound + */ + fun bind(view: KeyguardRootView) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt index ff7ac35ba56b..9cc46506cefb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt @@ -33,7 +33,6 @@ import com.airbnb.lottie.LottieProperty import com.android.app.animation.Interpolators import com.android.keyguard.KeyguardPINView import com.android.systemui.CoreStartable -import com.android.systemui.biometrics.FpsUnlockTracker import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor @@ -65,7 +64,6 @@ constructor( private val biometricStatusInteractor: Lazy<BiometricStatusInteractor>, private val displayStateInteractor: Lazy<DisplayStateInteractor>, private val deviceEntrySideFpsOverlayInteractor: Lazy<DeviceEntrySideFpsOverlayInteractor>, - private val fpsUnlockTracker: Lazy<FpsUnlockTracker>, private val layoutInflater: Lazy<LayoutInflater>, private val sideFpsProgressBarViewModel: Lazy<SideFpsProgressBarViewModel>, private val sfpsSensorInteractor: Lazy<SideFpsSensorInteractor>, @@ -114,7 +112,6 @@ constructor( } } } - .invokeOnCompletion { fpsUnlockTracker.get().stopTracking() } } private var overlayView: View? = null @@ -138,7 +135,7 @@ constructor( displayStateInteractor.get(), sfpsSensorInteractor.get(), ) - bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get()) + bind(overlayView!!, overlayViewModel, windowManager.get()) overlayView!!.visibility = View.INVISIBLE Log.d(TAG, "show(): adding overlayView $overlayView") windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams) @@ -163,12 +160,9 @@ constructor( fun bind( overlayView: View, viewModel: SideFpsOverlayViewModel, - fpsUnlockTracker: FpsUnlockTracker, windowManager: WindowManager ) { overlayView.repeatWhenAttached { - fpsUnlockTracker.startTracking() - val lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation) lottie.addLottieOnCompositionLoadedListener { composition: LottieComposition -> if (overlayView.visibility != View.VISIBLE) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 7d494a5cf229..ac8807dd1b7e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -232,7 +232,6 @@ constructor( val fingerprintStartMode: Flow<FingerprintStartMode> = _fingerprintStartMode.asStateFlow() /** Whether a finger has been acquired by the sensor */ - // TODO(b/331948073): Add support for detecting SFPS finger without authentication running val hasFingerBeenAcquired: Flow<Boolean> = combine(biometricStatusInteractor.fingerprintAcquiredStatus, modalities) { status, @@ -617,7 +616,8 @@ constructor( } /** If the icon can be used as a confirmation button. */ - val isIconConfirmButton: Flow<Boolean> = size.map { it.isNotSmall }.distinctUntilChanged() + val isIconConfirmButton: Flow<Boolean> = + combine(modalities, size) { modalities, size -> modalities.hasUdfps && size.isNotSmall } /** If the negative button should be shown. */ val isNegativeButtonVisible: Flow<Boolean> = @@ -700,6 +700,9 @@ constructor( failedModality: BiometricModality = BiometricModality.None, ) = coroutineScope { if (_isAuthenticated.value.isAuthenticated) { + if (_isAuthenticated.value.needsUserConfirmation && hapticFeedback) { + vibrateOnError() + } return@coroutineScope } @@ -823,6 +826,14 @@ constructor( helpMessage: String = "", ) { if (_isAuthenticated.value.isAuthenticated) { + // Treat second authentication with a different modality as confirmation for the first + if ( + _isAuthenticated.value.needsUserConfirmation && + modality != _isAuthenticated.value.authenticatedModality + ) { + confirmAuthenticated() + return + } // TODO(jbolinger): convert to go/tex-apc? Log.w(TAG, "Cannot show authenticated after authenticated") return diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt index 81fe2a5a5f0d..7f1cb5da474d 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt @@ -97,6 +97,10 @@ constructor( // Server could throw TimeoutException, InterruptedException or ExecutionException Log.e(TAG, "Error calling isAutoOnSupported", e) false + } catch (e: NoSuchMethodError) { + // TODO(b/346716614): Remove this when the flag is cleaned up. + Log.e(TAG, "Non-existed api isAutoOnSupported", e) + false } } @@ -109,6 +113,9 @@ constructor( // Server could throw IllegalStateException, TimeoutException, InterruptedException // or ExecutionException Log.e(TAG, "Error calling setAutoOnEnabled", e) + } catch (e: NoSuchMethodError) { + // TODO(b/346716614): Remove this when the flag is cleaned up. + Log.e(TAG, "Non-existed api setAutoOn", e) } } } @@ -122,6 +129,10 @@ constructor( // or ExecutionException Log.e(TAG, "Error calling isAutoOnEnabled", e) false + } catch (e: NoSuchMethodError) { + // TODO(b/346716614): Remove this when the flag is cleaned up. + Log.e(TAG, "Non-existed api isAutoOnEnabled", e) + false } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt index fce18a263902..e1408a065a37 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.util.CommunalColors import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState @@ -31,13 +32,18 @@ import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTrans import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf +import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn /** View model for transitions related to the communal hub. */ @OptIn(ExperimentalCoroutinesApi::class) @@ -45,6 +51,7 @@ import kotlinx.coroutines.flow.merge class CommunalTransitionViewModel @Inject constructor( + @Application applicationScope: CoroutineScope, communalColors: CommunalColors, glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel, @@ -85,21 +92,32 @@ constructor( * of UMO should be updated. */ val isUmoOnCommunal: Flow<Boolean> = - allOf( - // Only show UMO on the hub if the hub is at least partially visible. This prevents - // the UMO from being missing on the lock screen when going from the hub to lock - // screen in some way other than through a direct transition, such as unlocking from - // the hub, then pressing power twice to go back to the lock screen. - communalSceneInteractor.isCommunalVisible, - merge( - lockscreenToGlanceableHubTransitionViewModel.showUmo, - glanceableHubToLockscreenTransitionViewModel.showUmo, - dreamToGlanceableHubTransitionViewModel.showUmo, - glanceableHubToDreamTransitionViewModel.showUmo, - showUmoFromOccludedToGlanceableHub, - showUmoFromGlanceableHubToOccluded, + anyOf( + communalSceneInteractor.isIdleOnCommunal, + allOf( + // Only show UMO on the hub if the hub is at least partially visible. This + // prevents + // the UMO from being missing on the lock screen when going from the hub to lock + // screen in some way other than through a direct transition, such as unlocking + // from + // the hub, then pressing power twice to go back to the lock screen. + communalSceneInteractor.isCommunalVisible, + merge( + lockscreenToGlanceableHubTransitionViewModel.showUmo, + glanceableHubToLockscreenTransitionViewModel.showUmo, + dreamToGlanceableHubTransitionViewModel.showUmo, + glanceableHubToDreamTransitionViewModel.showUmo, + showUmoFromOccludedToGlanceableHub, + showUmoFromGlanceableHubToOccluded, + ) + .onStart { emit(false) } + ) + ) + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false ) - ) /** Whether to show communal when exiting the occluded state. */ val showCommunalFromOccluded: Flow<Boolean> = communalInteractor.showCommunalFromOccluded diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index cf8358203b6c..00566c1b04e3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -712,7 +712,6 @@ class KeyguardUnlockAnimationController @Inject constructor( // As soon as the shade starts animating out of the way, start the canned unlock animation, // which will finish keyguard exit when it completes. The in-window animations in the // Launcher window will end on their own. - if (fastUnlockTransition()) hideKeyguardViewAfterRemoteAnimation() handler.postDelayed({ if (keyguardViewMediator.get().isShowingAndNotOccluded && !keyguardStateController.isKeyguardGoingAway) { @@ -723,7 +722,7 @@ class KeyguardUnlockAnimationController @Inject constructor( if ((wallpaperTargets?.isNotEmpty() == true)) { fadeInWallpaper() - if (!fastUnlockTransition()) hideKeyguardViewAfterRemoteAnimation() + hideKeyguardViewAfterRemoteAnimation() } else { keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation( false /* cancelled */) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 306f4ffa2a54..608e25a82eff 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -39,6 +39,7 @@ import com.android.keyguard.LegacyLockIconViewController import com.android.keyguard.LockIconView import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable +import com.android.systemui.biometrics.ui.binder.DeviceEntryUnlockTrackerViewBinder import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor @@ -70,6 +71,7 @@ import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import dagger.Lazy +import java.util.Optional import javax.inject.Inject import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -105,6 +107,7 @@ constructor( private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>, private val clockInteractor: KeyguardClockInteractor, private val keyguardViewMediator: KeyguardViewMediator, + private val deviceEntryUnlockTrackerViewBinder: Optional<DeviceEntryUnlockTrackerViewBinder>, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null @@ -157,6 +160,9 @@ constructor( ) } } + if (deviceEntryUnlockTrackerViewBinder.isPresent) { + deviceEntryUnlockTrackerViewBinder.get().bind(keyguardRootView) + } } fun bindIndicationArea() { 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 c98a49bd711d..08175c37f9bb 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 @@ -330,9 +330,16 @@ constructor( * otherwise returns a singleton [Flow] containing [sceneKey]. */ fun resolveSceneFamily(sceneKey: SceneKey): Flow<SceneKey> = flow { - emitAll(sceneFamilyResolvers.get()[sceneKey]?.resolvedScene ?: flowOf(sceneKey)) + emitAll(resolveSceneFamilyOrNull(sceneKey) ?: flowOf(sceneKey)) } + /** + * Returns the [concrete scene][Scenes] for [sceneKey] if it is a [scene family][SceneFamilies], + * otherwise returns `null`. + */ + fun resolveSceneFamilyOrNull(sceneKey: SceneKey): StateFlow<SceneKey>? = + sceneFamilyResolvers.get()[sceneKey]?.resolvedScene + private fun isVisibleInternal( raw: Boolean = repository.isVisible.value, isRemoteUserInteractionOngoing: Boolean = repository.isRemoteUserInteractionOngoing.value, 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 ab24e0bbb690..d380251d8b7e 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 @@ -147,6 +147,20 @@ constructor( } ?: true } + /** + * Immediately resolves any scene families present in [actionResultMap] to their current + * resolution target. + */ + fun resolveSceneFamilies( + actionResultMap: Map<UserAction, UserActionResult>, + ): Map<UserAction, UserActionResult> { + return actionResultMap.mapValues { (_, actionResult) -> + sceneInteractor.resolveSceneFamilyOrNull(actionResult.toScene)?.value?.let { + actionResult.copy(toScene = it) + } ?: actionResult + } + } + private fun replaceSceneFamilies( destinationScenes: Map<UserAction, UserActionResult>, ): Flow<Map<UserAction, UserActionResult>> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt index 6611434b661e..f6fbe38554a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt @@ -30,8 +30,8 @@ import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChi import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChipInteractor.Companion.createDialogLaunchOnClickListener import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndCastToOtherDeviceDialogDelegate +import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndShareToAppDialogDelegate -import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.Utils import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -60,8 +60,8 @@ constructor( private val mediaProjectionRepository: MediaProjectionRepository, private val packageManager: PackageManager, private val systemClock: SystemClock, - private val dialogFactory: SystemUIDialog.Factory, private val dialogTransitionAnimator: DialogTransitionAnimator, + private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper, ) : OngoingActivityChipInteractor { override val chip: StateFlow<OngoingActivityChipModel> = mediaProjectionRepository.mediaProjectionState @@ -70,9 +70,9 @@ constructor( is MediaProjectionState.NotProjecting -> OngoingActivityChipModel.Hidden is MediaProjectionState.Projecting -> { if (isProjectionToOtherDevice(state.hostPackage)) { - createCastToOtherDeviceChip() + createCastToOtherDeviceChip(state) } else { - createShareToAppChip() + createShareToAppChip(state) } } } @@ -97,7 +97,9 @@ constructor( return Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName) } - private fun createCastToOtherDeviceChip(): OngoingActivityChipModel.Shown { + private fun createCastToOtherDeviceChip( + state: MediaProjectionState.Projecting, + ): OngoingActivityChipModel.Shown { return OngoingActivityChipModel.Shown( icon = Icon.Resource( @@ -107,32 +109,39 @@ constructor( // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time. startTimeMs = systemClock.elapsedRealtime(), createDialogLaunchOnClickListener( - castToOtherDeviceDialogDelegate, + createCastToOtherDeviceDialogDelegate(state), dialogTransitionAnimator, ), ) } - private val castToOtherDeviceDialogDelegate = + private fun createCastToOtherDeviceDialogDelegate(state: MediaProjectionState.Projecting) = EndCastToOtherDeviceDialogDelegate( - dialogFactory, + endMediaProjectionDialogHelper, this@MediaProjectionChipInteractor, + state, ) - private fun createShareToAppChip(): OngoingActivityChipModel.Shown { + private fun createShareToAppChip( + state: MediaProjectionState.Projecting, + ): OngoingActivityChipModel.Shown { return OngoingActivityChipModel.Shown( // TODO(b/332662551): Use the right content description. icon = Icon.Resource(SHARE_TO_APP_ICON, contentDescription = null), // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time. startTimeMs = systemClock.elapsedRealtime(), - createDialogLaunchOnClickListener(shareToAppDialogDelegate, dialogTransitionAnimator), + createDialogLaunchOnClickListener( + createShareToAppDialogDelegate(state), + dialogTransitionAnimator + ), ) } - private val shareToAppDialogDelegate = + private fun createShareToAppDialogDelegate(state: MediaProjectionState.Projecting) = EndShareToAppDialogDelegate( - dialogFactory, + endMediaProjectionDialogHelper, this@MediaProjectionChipInteractor, + state, ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt index 33cec9755b1f..596fbf89a10d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt @@ -17,25 +17,33 @@ package com.android.systemui.statusbar.chips.mediaprojection.ui.view import android.os.Bundle +import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.res.R import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor import com.android.systemui.statusbar.phone.SystemUIDialog /** A dialog that lets the user stop an ongoing cast-screen-to-other-device event. */ class EndCastToOtherDeviceDialogDelegate( - private val systemUIDialogFactory: SystemUIDialog.Factory, + private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper, private val interactor: MediaProjectionChipInteractor, + private val state: MediaProjectionState.Projecting, ) : SystemUIDialog.Delegate { override fun createDialog(): SystemUIDialog { - return systemUIDialogFactory.create(this) + return endMediaProjectionDialogHelper.createDialog(this) } override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { with(dialog) { setIcon(MediaProjectionChipInteractor.CAST_TO_OTHER_DEVICE_ICON) setTitle(R.string.cast_to_other_device_stop_dialog_title) - // TODO(b/332662551): Use a different message if they're sharing just a single app. - setMessage(R.string.cast_to_other_device_stop_dialog_message) + setMessage( + endMediaProjectionDialogHelper.getDialogMessage( + state, + genericMessageResId = R.string.cast_to_other_device_stop_dialog_message, + specificAppMessageResId = + R.string.cast_to_other_device_stop_dialog_message_specific_app, + ) + ) // No custom on-click, because the dialog will automatically be dismissed when the // button is clicked anyway. setNegativeButton(R.string.close_dialog_button, /* onClick= */ null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt new file mode 100644 index 000000000000..347be02dbc60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 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.chips.mediaprojection.ui.view + +import android.annotation.StringRes +import android.content.Context +import android.content.pm.PackageManager +import android.text.Html +import android.text.Html.FROM_HTML_MODE_LEGACY +import android.text.TextUtils +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.mediaprojection.data.model.MediaProjectionState +import com.android.systemui.statusbar.phone.SystemUIDialog +import javax.inject.Inject + +/** Helper class for showing dialogs that let users end different types of media projections. */ +@SysUISingleton +class EndMediaProjectionDialogHelper +@Inject +constructor( + private val dialogFactory: SystemUIDialog.Factory, + private val packageManager: PackageManager, + private val context: Context +) { + /** Creates a new [SystemUIDialog] using the given delegate. */ + fun createDialog(delegate: SystemUIDialog.Delegate): SystemUIDialog { + return dialogFactory.create(delegate) + } + + /** + * Returns the message to show in the dialog based on the specific media projection state. + * + * @param genericMessageResId a res ID for a more generic "end projection" message + * @param specificAppMessageResId a res ID for an "end projection" message that also lets us + * specify which app is currently being projected. + */ + fun getDialogMessage( + state: MediaProjectionState.Projecting, + @StringRes genericMessageResId: Int, + @StringRes specificAppMessageResId: Int, + ): CharSequence { + when (state) { + is MediaProjectionState.Projecting.EntireScreen -> + return context.getString(genericMessageResId) + is MediaProjectionState.Projecting.SingleTask -> { + val packageName = + state.task.baseIntent.component?.packageName + ?: return context.getString(genericMessageResId) + try { + val appInfo = packageManager.getApplicationInfo(packageName, 0) + val appName = appInfo.loadLabel(packageManager) + return getSpecificAppMessageText(specificAppMessageResId, appName) + } catch (e: PackageManager.NameNotFoundException) { + // TODO(b/332662551): Log this error. + return context.getString(genericMessageResId) + } + } + } + } + + private fun getSpecificAppMessageText( + @StringRes specificAppMessageResId: Int, + appName: CharSequence, + ): CharSequence { + // https://developer.android.com/guide/topics/resources/string-resource#StylingWithHTML + val escapedAppName = TextUtils.htmlEncode(appName.toString()) + val text = context.getString(specificAppMessageResId, escapedAppName) + return Html.fromHtml(text, FROM_HTML_MODE_LEGACY) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt index 3a863b10ef82..749a11f193c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt @@ -17,25 +17,32 @@ package com.android.systemui.statusbar.chips.mediaprojection.ui.view import android.os.Bundle +import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.res.R import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor import com.android.systemui.statusbar.phone.SystemUIDialog /** A dialog that lets the user stop an ongoing share-screen-to-app event. */ class EndShareToAppDialogDelegate( - private val systemUIDialogFactory: SystemUIDialog.Factory, + private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper, private val interactor: MediaProjectionChipInteractor, + private val state: MediaProjectionState.Projecting, ) : SystemUIDialog.Delegate { override fun createDialog(): SystemUIDialog { - return systemUIDialogFactory.create(this) + return endMediaProjectionDialogHelper.createDialog(this) } override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { with(dialog) { setIcon(MediaProjectionChipInteractor.SHARE_TO_APP_ICON) setTitle(R.string.share_to_app_stop_dialog_title) - // TODO(b/332662551): Use a different message if they're sharing just a single app. - setMessage(R.string.share_to_app_stop_dialog_message) + setMessage( + endMediaProjectionDialogHelper.getDialogMessage( + state, + genericMessageResId = R.string.share_to_app_stop_dialog_message, + specificAppMessageResId = R.string.share_to_app_stop_dialog_message_specific_app + ) + ) // No custom on-click, because the dialog will automatically be dismissed when the // button is clicked anyway. setNegativeButton(R.string.close_dialog_button, /* onClick= */ null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt index 11636bdf0f17..f95e0fb37921 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt @@ -37,6 +37,7 @@ import com.android.systemui.statusbar.phone.BoundsPair import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator import com.android.systemui.statusbar.phone.StatusBarBoundsProvider import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent +import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -224,8 +225,8 @@ constructor( modifiedStatusBarAttributes, isTransientShown, isInFullscreenMode, - ongoingCallRepository.hasOngoingCall, - ) { modifiedAttributes, isTransientShown, isInFullscreenMode, hasOngoingCall -> + ongoingCallRepository.ongoingCallState, + ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallState -> if (modifiedAttributes == null) { null } else { @@ -234,7 +235,7 @@ constructor( modifiedAttributes.appearance, isTransientShown, isInFullscreenMode, - hasOngoingCall, + hasOngoingCall = ongoingCallState is OngoingCallModel.InCall, ) StatusBarAppearance( statusBarMode, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 4bf122dd3b6a..3925bebe55fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -202,6 +202,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements * Gets the touchable region needed for heads up notifications. Returns null if no touchable * region is required (ie: no heads up notification currently exists). */ + // TODO(b/347007367): With scene container enabled this method may report outdated regions @Override public @Nullable Region getTouchableRegion() { NotificationEntry topEntry = getTopEntry(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index d1189e1e5e1c..4b1ee58c2cc6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -29,6 +29,7 @@ import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; import android.view.WindowInsets; +import com.android.compose.animation.scene.ObservableTransitionState; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.Dumpable; import com.android.systemui.ScreenDecorations; @@ -38,7 +39,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlag; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -67,7 +68,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private boolean mIsStatusBarExpanded = false; - private boolean mIsSceneContainerVisible = false; + private boolean mIsIdleOnGone = false; private boolean mShouldAdjustInsets = false; private View mNotificationShadeWindowView; private View mNotificationPanelView; @@ -87,7 +88,6 @@ public final class StatusBarTouchableRegionManager implements Dumpable { NotificationShadeWindowController notificationShadeWindowController, ConfigurationController configurationController, HeadsUpManager headsUpManager, - ShadeExpansionStateManager shadeExpansionStateManager, ShadeInteractor shadeInteractor, Provider<SceneInteractor> sceneInteractor, JavaAdapter javaAdapter, @@ -131,8 +131,8 @@ public final class StatusBarTouchableRegionManager implements Dumpable { if (SceneContainerFlag.isEnabled()) { javaAdapter.alwaysCollectFlow( - sceneInteractor.get().isVisible(), - this::onSceneContainerVisibilityChanged); + sceneInteractor.get().getTransitionState(), + this::onSceneChanged); } else { javaAdapter.alwaysCollectFlow( shadeInteractor.isAnyExpanded(), @@ -167,10 +167,11 @@ public final class StatusBarTouchableRegionManager implements Dumpable { } } - private void onSceneContainerVisibilityChanged(Boolean isVisible) { - if (isVisible != mIsSceneContainerVisible) { - mIsSceneContainerVisible = isVisible; - if (isVisible) { + private void onSceneChanged(ObservableTransitionState transitionState) { + boolean isIdleOnGone = transitionState.isIdle(Scenes.Gone); + if (isIdleOnGone != mIsIdleOnGone) { + mIsIdleOnGone = isIdleOnGone; + if (!isIdleOnGone) { // make sure our state is sensible mForceCollapsedUntilLayout = false; } @@ -281,7 +282,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { // since we don't want stray touches to go through the light reveal scrim to whatever is // underneath. return mIsStatusBarExpanded - || mIsSceneContainerVisible + || !mIsIdleOnGone || mPrimaryBouncerInteractor.isShowing().getValue() || mAlternateBouncerInteractor.isVisibleState() || mUnlockedScreenOffAnimationController.isAnimationPlaying(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index a7d4ce30a191..d128057d2010 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -29,35 +29,36 @@ import androidx.annotation.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.CoreStartable import com.android.systemui.Dumpable -import com.android.systemui.res.R import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.statusbar.chips.ui.view.ChipChronometer +import com.android.systemui.res.R import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer +import com.android.systemui.statusbar.chips.ui.view.ChipChronometer import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.time.SystemClock -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import java.io.PrintWriter import java.util.concurrent.Executor import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch -/** - * A controller to handle the ongoing call chip in the collapsed status bar. - */ +/** A controller to handle the ongoing call chip in the collapsed status bar. */ @SysUISingleton -class OngoingCallController @Inject constructor( +class OngoingCallController +@Inject +constructor( @Application private val scope: CoroutineScope, private val context: Context, private val ongoingCallRepository: OngoingCallRepository, @@ -79,54 +80,61 @@ class OngoingCallController @Inject constructor( private val mListeners: MutableList<OngoingCallListener> = mutableListOf() private val uidObserver = CallAppUidObserver() - private val notifListener = object : NotifCollectionListener { - // Temporary workaround for b/178406514 for testing purposes. - // - // b/178406514 means that posting an incoming call notif then updating it to an ongoing call - // notif does not work (SysUI never receives the update). This workaround allows us to - // trigger the ongoing call chip when an ongoing call notif is *added* rather than - // *updated*, allowing us to test the chip. - // - // TODO(b/183229367): Remove this function override when b/178406514 is fixed. - override fun onEntryAdded(entry: NotificationEntry) { - onEntryUpdated(entry, true) - } + private val notifListener = + object : NotifCollectionListener { + // Temporary workaround for b/178406514 for testing purposes. + // + // b/178406514 means that posting an incoming call notif then updating it to an ongoing + // call notif does not work (SysUI never receives the update). This workaround allows us + // to trigger the ongoing call chip when an ongoing call notif is *added* rather than + // *updated*, allowing us to test the chip. + // + // TODO(b/183229367): Remove this function override when b/178406514 is fixed. + override fun onEntryAdded(entry: NotificationEntry) { + onEntryUpdated(entry, true) + } - override fun onEntryUpdated(entry: NotificationEntry) { - // We have a new call notification or our existing call notification has been updated. - // TODO(b/183229367): This likely won't work if you take a call from one app then - // switch to a call from another app. - if (callNotificationInfo == null && isCallNotification(entry) || - (entry.sbn.key == callNotificationInfo?.key)) { - val newOngoingCallInfo = CallNotificationInfo( - entry.sbn.key, - entry.sbn.notification.getWhen(), - entry.sbn.notification.contentIntent, - entry.sbn.uid, - entry.sbn.notification.extras.getInt( - Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING, - statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false - ) - if (newOngoingCallInfo == callNotificationInfo) { - return + override fun onEntryUpdated(entry: NotificationEntry) { + // We have a new call notification or our existing call notification has been + // updated. + // TODO(b/183229367): This likely won't work if you take a call from one app then + // switch to a call from another app. + if ( + callNotificationInfo == null && isCallNotification(entry) || + (entry.sbn.key == callNotificationInfo?.key) + ) { + val newOngoingCallInfo = + CallNotificationInfo( + entry.sbn.key, + entry.sbn.notification.getWhen(), + entry.sbn.notification.contentIntent, + entry.sbn.uid, + entry.sbn.notification.extras.getInt( + Notification.EXTRA_CALL_TYPE, + -1 + ) == CALL_TYPE_ONGOING, + statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false + ) + if (newOngoingCallInfo == callNotificationInfo) { + return + } + + callNotificationInfo = newOngoingCallInfo + if (newOngoingCallInfo.isOngoing) { + updateChip() + } else { + removeChip() + } } + } - callNotificationInfo = newOngoingCallInfo - if (newOngoingCallInfo.isOngoing) { - updateChip() - } else { + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + if (entry.sbn.key == callNotificationInfo?.key) { removeChip() } } } - override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { - if (entry.sbn.key == callNotificationInfo?.key) { - removeChip() - } - } - } - override fun start() { dumpManager.registerDumpable(this) notifCollection.addCollectionListener(notifListener) @@ -169,8 +177,21 @@ class OngoingCallController @Inject constructor( */ fun hasOngoingCall(): Boolean { return callNotificationInfo?.isOngoing == true && - // When the user is in the phone app, don't show the chip. - !uidObserver.isCallAppVisible + // When the user is in the phone app, don't show the chip. + !uidObserver.isCallAppVisible + } + + /** Creates the right [OngoingCallModel] based on the call state. */ + private fun getOngoingCallModel(): OngoingCallModel { + if (hasOngoingCall()) { + val currentInfo = + callNotificationInfo + // This shouldn't happen, but protect against it in case + ?: return OngoingCallModel.NoCall + return OngoingCallModel.InCall(currentInfo.callStartTime) + } else { + return OngoingCallModel.NoCall + } } override fun addCallback(listener: OngoingCallListener) { @@ -182,9 +203,7 @@ class OngoingCallController @Inject constructor( } override fun removeCallback(listener: OngoingCallListener) { - synchronized(mListeners) { - mListeners.remove(listener) - } + synchronized(mListeners) { mListeners.remove(listener) } } private fun updateChip() { @@ -196,8 +215,8 @@ class OngoingCallController @Inject constructor( if (currentChipView != null && timeView != null) { if (currentCallNotificationInfo.hasValidStartTime()) { timeView.setShouldHideText(false) - timeView.base = currentCallNotificationInfo.callStartTime - - systemClock.currentTimeMillis() + + timeView.base = + currentCallNotificationInfo.callStartTime - systemClock.currentTimeMillis() + systemClock.elapsedRealtime() timeView.start() } else { @@ -218,14 +237,19 @@ class OngoingCallController @Inject constructor( callNotificationInfo = null if (DEBUG) { - Log.w(TAG, "Ongoing call chip view could not be found; " + - "Not displaying chip in status bar") + Log.w( + TAG, + "Ongoing call chip view could not be found; " + + "Not displaying chip in status bar" + ) } } } private fun updateChipClickListener() { - if (callNotificationInfo == null) { return } + if (callNotificationInfo == null) { + return + } val currentChipView = chipView val backgroundView = currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background) @@ -237,7 +261,8 @@ class OngoingCallController @Inject constructor( intent, ActivityTransitionAnimator.Controller.fromView( backgroundView, - InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP) + InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, + ) ) } } @@ -249,9 +274,11 @@ class OngoingCallController @Inject constructor( } private fun updateGestureListening() { - if (callNotificationInfo == null || - callNotificationInfo?.statusBarSwipedAway == true || - !isFullscreen) { + if ( + callNotificationInfo == null || + callNotificationInfo?.statusBarSwipedAway == true || + !isFullscreen + ) { swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG) } else { swipeStatusBarAwayGestureHandler.addOnGestureDetectedCallback(TAG) { _ -> @@ -270,30 +297,31 @@ class OngoingCallController @Inject constructor( } /** Tear down anything related to the chip view to prevent leaks. */ - @VisibleForTesting - fun tearDownChipView() = chipView?.getTimeView()?.stop() + @VisibleForTesting fun tearDownChipView() = chipView?.getTimeView()?.stop() private fun View.getTimeView(): ChipChronometer? { return this.findViewById(R.id.ongoing_activity_chip_time) } /** - * If there's an active ongoing call, then we will force the status bar to always show, even if - * the user is in immersive mode. However, we also want to give users the ability to swipe away - * the status bar if they need to access the area under the status bar. - * - * This method updates the status bar window appropriately when the swipe away gesture is - * detected. - */ + * If there's an active ongoing call, then we will force the status bar to always show, even if + * the user is in immersive mode. However, we also want to give users the ability to swipe away + * the status bar if they need to access the area under the status bar. + * + * This method updates the status bar window appropriately when the swipe away gesture is + * detected. + */ private fun onSwipeAwayGestureDetected() { - if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") } + if (DEBUG) { + Log.d(TAG, "Swipe away gesture detected") + } callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true) statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false) swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG) } private fun sendStateChangeEvent() { - ongoingCallRepository.setHasOngoingCall(hasOngoingCall()) + ongoingCallRepository.setOngoingCallState(getOngoingCallModel()) mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) } } @@ -308,8 +336,8 @@ class OngoingCallController @Inject constructor( val statusBarSwipedAway: Boolean ) { /** - * Returns true if the notification information has a valid call start time. - * See b/192379214. + * Returns true if the notification information has a valid call start time. See + * b/192379214. */ fun hasValidStartTime(): Boolean = callStartTime > 0 } @@ -342,9 +370,10 @@ class OngoingCallController @Inject constructor( callAppUid = uid try { - isCallAppVisible = isProcessVisibleToUser( - iActivityManager.getUidProcessState(uid, context.opPackageName) - ) + isCallAppVisible = + isProcessVisibleToUser( + iActivityManager.getUidProcessState(uid, context.opPackageName) + ) if (isRegistered) { return } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/model/OngoingCallModel.kt new file mode 100644 index 000000000000..aaa52a7b9e1c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/model/OngoingCallModel.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone.ongoingcall.data.model + +/** Represents the state of any ongoing calls. */ +sealed interface OngoingCallModel { + /** There is no ongoing call. */ + data object NoCall : OngoingCallModel + + /** + * There *is* an ongoing call. + * + * @property startTimeMs the time that the phone call started, based on the notification's + * `when` field. Importantly, this time is relative to + * [com.android.systemui.util.time.SystemClock.currentTimeMillis], **not** + * [com.android.systemui.util.time.SystemClock.elapsedRealtime]. + */ + data class InCall(val startTimeMs: Long) : OngoingCallModel +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt index 886481e64dbe..554c47456871 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone.ongoingcall.data.repository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -32,15 +33,15 @@ import kotlinx.coroutines.flow.asStateFlow */ @SysUISingleton class OngoingCallRepository @Inject constructor() { - private val _hasOngoingCall = MutableStateFlow(false) - /** True if there's currently an ongoing call notification and false otherwise. */ - val hasOngoingCall: StateFlow<Boolean> = _hasOngoingCall.asStateFlow() + private val _ongoingCallState = MutableStateFlow<OngoingCallModel>(OngoingCallModel.NoCall) + /** The current ongoing call state. */ + val ongoingCallState: StateFlow<OngoingCallModel> = _ongoingCallState.asStateFlow() /** - * Sets whether there's currently an ongoing call notification. Should only be set from + * Sets the current ongoing call state, based on notifications. Should only be set from * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController]. */ - fun setHasOngoingCall(hasOngoingCall: Boolean) { - _hasOngoingCall.value = hasOngoingCall + fun setOngoingCallState(state: OngoingCallModel) { + _ongoingCallState.value = state } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index f457470af906..4205dd87e747 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -135,6 +135,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.AlphaTintDrawableWrapper; import com.android.systemui.util.RoundedCornerProgressDrawable; import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.volume.domain.interactor.VolumeDialogInteractor; import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor; import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag; import com.android.systemui.volume.ui.binder.VolumeDialogMenuIconBinder; @@ -315,6 +316,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final com.android.systemui.util.time.SystemClock mSystemClock; private final VolumeDialogMenuIconBinder mVolumeDialogMenuIconBinder; private final VolumePanelFlag mVolumePanelFlag; + private final VolumeDialogInteractor mInteractor; public VolumeDialogImpl( Context context, @@ -335,7 +337,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, Lazy<SecureSettings> secureSettings, VibratorHelper vibratorHelper, VolumeDialogMenuIconBinder volumeDialogMenuIconBinder, - com.android.systemui.util.time.SystemClock systemClock) { + com.android.systemui.util.time.SystemClock systemClock, + VolumeDialogInteractor interactor) { mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); mHandler = new H(looper); @@ -370,6 +373,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mVolumeDialogMenuIconBinder = volumeDialogMenuIconBinder; mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS; mVolumePanelFlag = volumePanelFlag; + mInteractor = interactor; dumpManager.registerDumpable("VolumeDialogImpl", this); @@ -1519,6 +1523,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mShowing = true; mIsAnimatingDismiss = false; mDialog.show(); + mInteractor.onDialogShown(); Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked); mController.notifyVisible(true); mController.getCaptionsComponentState(false); @@ -1605,6 +1610,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } mIsAnimatingDismiss = true; mDialogView.animate().cancel(); + mInteractor.onDialogDismissed(); if (mShowing) { mShowing = false; // Only logs when the volume dialog visibility is changed. diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index f8ddc423e7d8..8003f3922b91 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -41,6 +41,7 @@ import com.android.systemui.volume.VolumeDialogImpl; import com.android.systemui.volume.VolumeDialogModule; import com.android.systemui.volume.VolumePanelDialogReceiver; import com.android.systemui.volume.VolumeUI; +import com.android.systemui.volume.domain.interactor.VolumeDialogInteractor; import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor; import com.android.systemui.volume.panel.dagger.VolumePanelComponent; import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory; @@ -118,7 +119,8 @@ public interface VolumeModule { Lazy<SecureSettings> secureSettings, VibratorHelper vibratorHelper, VolumeDialogMenuIconBinder volumeDialogMenuIconBinder, - SystemClock systemClock) { + SystemClock systemClock, + VolumeDialogInteractor interactor) { VolumeDialogImpl impl = new VolumeDialogImpl( context, volumeDialogController, @@ -138,7 +140,8 @@ public interface VolumeModule { secureSettings, vibratorHelper, volumeDialogMenuIconBinder, - systemClock); + systemClock, + interactor); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); impl.setSilentMode(false); diff --git a/packages/SystemUI/src/com/android/systemui/volume/data/repository/VolumeDialogRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/data/repository/VolumeDialogRepository.kt new file mode 100644 index 000000000000..75e1c5acdd60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/data/repository/VolumeDialogRepository.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 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.volume.data.repository + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** A repository that encapsulates the states for Volume Dialog. */ +@SysUISingleton +class VolumeDialogRepository @Inject constructor() { + private val _isDialogVisible: MutableStateFlow<Boolean> = MutableStateFlow(false) + /** Whether the Volume Dialog is visible. */ + val isDialogVisible = _isDialogVisible.asStateFlow() + + /** Sets whether the Volume Dialog is visible. */ + fun setDialogVisibility(isVisible: Boolean) { + _isDialogVisible.value = isVisible + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractor.kt new file mode 100644 index 000000000000..813e7074b911 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractor.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 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.volume.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.volume.data.repository.VolumeDialogRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow + +/** An interactor that propagates the UI states of the Volume Dialog. */ +@SysUISingleton +class VolumeDialogInteractor +@Inject +constructor( + private val repository: VolumeDialogRepository, +) { + /** Whether the Volume Dialog is visible. */ + val isDialogVisible: StateFlow<Boolean> = repository.isDialogVisible + + /** Notifies that the Volume Dialog is shown. */ + fun onDialogShown() { + repository.setDialogVisibility(true) + } + + /** Notifies that the Volume Dialog has been dismissed. */ + fun onDialogDismissed() { + repository.setDialogVisibility(false) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index d267ad449f45..54a14a292ba1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -39,6 +39,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -1182,6 +1183,24 @@ public class ScreenDecorationsTest extends SysuiTestCase { } @Test + public void testUpdateOverlayProviderViews_PendingConfigChange() { + final DecorProvider cutout = new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP); + spyOn(cutout); + doNothing().when(cutout).onReloadResAndMeasure(any(), anyInt(), anyInt(), anyInt(), any()); + mMockCutoutList.add(cutout); + mScreenDecorations.start(); + doCallRealMethod().when(mScreenDecorations).updateOverlayProviderViews(any()); + + mScreenDecorations.mPendingConfigChange = true; + mScreenDecorations.updateOverlayProviderViews(null /* filterIds */); + verify(cutout, never()).onReloadResAndMeasure(any(), anyInt(), anyInt(), anyInt(), any()); + + mScreenDecorations.mPendingConfigChange = false; + mScreenDecorations.updateOverlayProviderViews(null /* filterIds */); + verify(cutout).onReloadResAndMeasure(any(), anyInt(), anyInt(), anyInt(), any()); + } + + @Test public void testSupportHwcLayer_SwitchFrom_NotSupport() { setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index cc7dec56be40..93c6d9ee732c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -847,6 +847,28 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.NO_HAPTICS) } + @Test + fun plays_haptic_on_error_after_auth_when_confirmation_needed() = runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + viewModel.showAuthenticated(testCase.authenticatedModality, 0) + + viewModel.showTemporaryError( + "still sad", + messageAfterError = "", + authenticateAfterError = false, + hapticFeedback = true, + ) + + val haptics by collectLastValue(viewModel.hapticsToPlay) + if (expectConfirmation) { + assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT) + assertThat(haptics?.flag).isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) + } else { + assertThat(haptics?.hapticFeedbackConstant) + .isEqualTo(HapticFeedbackConstants.CONFIRM) + } + } + private suspend fun TestScope.showTemporaryErrors( restart: Boolean, helpAfterError: String = "", @@ -994,7 +1016,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun authenticated_at_most_once() = runGenericTest { + fun authenticated_at_most_once_same_modality() = runGenericTest { val authenticating by collectLastValue(viewModel.isAuthenticating) val authenticated by collectLastValue(viewModel.isAuthenticated) @@ -1056,6 +1078,38 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + fun second_authentication_acts_as_confirmation() = runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + + viewModel.showAuthenticated(testCase.authenticatedModality, 0) + + val authenticating by collectLastValue(viewModel.isAuthenticating) + val authenticated by collectLastValue(viewModel.isAuthenticated) + val message by collectLastValue(viewModel.message) + val size by collectLastValue(viewModel.size) + val canTryAgain by collectLastValue(viewModel.canTryAgainNow) + + assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation) + if (expectConfirmation) { + assertThat(size).isEqualTo(PromptSize.MEDIUM) + assertButtonsVisible( + cancel = true, + confirm = true, + ) + + if (testCase.modalities.hasSfps) { + viewModel.showAuthenticated(BiometricModality.Fingerprint, 0) + assertThat(message).isEqualTo(PromptMessage.Empty) + assertButtonsVisible() + } + } + + assertThat(authenticating).isFalse() + assertThat(authenticated?.isAuthenticated).isTrue() + assertThat(canTryAgain).isFalse() + } + + @Test fun auto_confirm_authentication_when_finger_down() = runGenericTest { val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index 128dd2353042..27b9863e20fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -44,6 +44,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations +import org.mockito.kotlin.clearInvocations import java.util.function.Predicate @RunWith(AndroidJUnit4::class) @@ -336,6 +337,10 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { false /* requestedShowSurfaceBehindKeyguard */ ) + // Cancel the animator so we can verify only the setSurfaceBehind call below. + keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.end() + clearInvocations(surfaceTransactionApplier) + // Set appear to 50%, we'll just verify that we're not applying the identity matrix which // means an animation is in progress. keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f) @@ -377,6 +382,10 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { false /* requestedShowSurfaceBehindKeyguard */ ) + // Cancel the animator so we can verify only the setSurfaceBehind call below. + keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.end() + clearInvocations(surfaceTransactionApplier) + keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f) keyguardUnlockAnimationController.setWallpaperAppearAmount(1f) @@ -409,6 +418,10 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { false /* requestedShowSurfaceBehindKeyguard */ ) + // Stop the animator - we just want to test whether the override is not applied. + keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.end() + clearInvocations(surfaceTransactionApplier) + keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f) keyguardUnlockAnimationController.setWallpaperAppearAmount(1f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt index a4505a99cb77..327eec494b3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository @@ -55,7 +56,7 @@ import org.mockito.kotlin.whenever @SmallTest class MediaProjectionChipInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = Kosmos().also { it.testCase = this } private val testScope = kosmos.testScope private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository private val systemClock = kosmos.fakeSystemClock @@ -178,7 +179,7 @@ class MediaProjectionChipInteractorTest : SysuiTestCase() { } @Test - fun chip_castToOtherDevice_clickListenerShowsCastDialog() = + fun chip_castToOtherDevice_entireScreen_clickListenerShowsCastDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = @@ -200,7 +201,33 @@ class MediaProjectionChipInteractorTest : SysuiTestCase() { } @Test - fun chip_shareToApp_clickListenerShowsShareDialog() = + fun chip_castToOtherDevice_singleTask_clickListenerShowsCastDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask( + CAST_TO_OTHER_DEVICES_PACKAGE, + createTask(taskId = 1) + ) + + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + + // Dialogs must be created on the main thread + context.mainExecutor.execute { + clickListener.onClick(chipView) + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + eq(mockCastDialog), + eq(chipBackgroundView), + eq(null), + anyBoolean(), + ) + } + } + + @Test + fun chip_shareToApp_entireScreen_clickListenerShowsShareDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = @@ -221,6 +248,28 @@ class MediaProjectionChipInteractorTest : SysuiTestCase() { } } + @Test + fun chip_shareToApp_singleTask_clickListenerShowsShareDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask(NORMAL_PACKAGE, createTask(taskId = 1)) + + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + + // Dialogs must be created on the main thread + context.mainExecutor.execute { + clickListener.onClick(chipView) + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + eq(mockShareDialog), + eq(chipBackgroundView), + eq(null), + anyBoolean(), + ) + } + } + companion object { const val CAST_TO_OTHER_DEVICES_PACKAGE = "other.devices.package" const val NORMAL_PACKAGE = "some.normal.package" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt index 9a2f545fa67e..7b676e2b34e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt @@ -16,22 +16,28 @@ package com.android.systemui.statusbar.chips.mediaprojection.ui.view +import android.content.ComponentName import android.content.DialogInterface +import android.content.Intent +import android.content.packageManager +import android.content.pm.ApplicationInfo import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope +import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask import com.android.systemui.res.R import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor import com.android.systemui.statusbar.phone.SystemUIDialog -import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before +import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.mock @@ -41,22 +47,14 @@ import org.mockito.kotlin.whenever @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class EndCastToOtherDeviceDialogDelegateTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = Kosmos().also { it.testCase = this } private val sysuiDialog = mock<SystemUIDialog>() - private val sysuiDialogFactory = kosmos.mockSystemUIDialogFactory - private val underTest = - EndCastToOtherDeviceDialogDelegate( - sysuiDialogFactory, - kosmos.mediaProjectionChipInteractor, - ) - - @Before - fun setUp() { - whenever(sysuiDialogFactory.create(eq(underTest), eq(context))).thenReturn(sysuiDialog) - } + private lateinit var underTest: EndCastToOtherDeviceDialogDelegate @Test fun icon() { + createAndSetDelegate(ENTIRE_SCREEN) + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) verify(sysuiDialog).setIcon(R.drawable.ic_cast_connected) @@ -64,20 +62,52 @@ class EndCastToOtherDeviceDialogDelegateTest : SysuiTestCase() { @Test fun title() { + createAndSetDelegate(SINGLE_TASK) + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) verify(sysuiDialog).setTitle(R.string.cast_to_other_device_stop_dialog_title) } @Test - fun message() { + fun message_entireScreen() { + createAndSetDelegate(ENTIRE_SCREEN) + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) - verify(sysuiDialog).setMessage(R.string.cast_to_other_device_stop_dialog_message) + verify(sysuiDialog) + .setMessage(context.getString(R.string.cast_to_other_device_stop_dialog_message)) + } + + @Test + fun message_singleTask() { + val baseIntent = + Intent().apply { this.component = ComponentName("fake.task.package", "cls") } + val appInfo = mock<ApplicationInfo>() + whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package") + whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>())) + .thenReturn(appInfo) + + createAndSetDelegate( + MediaProjectionState.Projecting.SingleTask( + HOST_PACKAGE, + createTask(taskId = 1, baseIntent = baseIntent) + ) + ) + + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) + + // It'd be nice to use R.string.cast_to_other_device_stop_dialog_message_specific_app + // directly, but it includes the <b> tags which aren't in the returned string. + val result = argumentCaptor<CharSequence>() + verify(sysuiDialog).setMessage(result.capture()) + assertThat(result.firstValue.toString()).isEqualTo("You will stop casting Fake Package") } @Test fun negativeButton() { + createAndSetDelegate(ENTIRE_SCREEN) + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) verify(sysuiDialog).setNegativeButton(R.string.close_dialog_button, null) @@ -86,6 +116,8 @@ class EndCastToOtherDeviceDialogDelegateTest : SysuiTestCase() { @Test fun positiveButton() = kosmos.testScope.runTest { + createAndSetDelegate(SINGLE_TASK) + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) val clickListener = argumentCaptor<DialogInterface.OnClickListener>() @@ -105,4 +137,20 @@ class EndCastToOtherDeviceDialogDelegateTest : SysuiTestCase() { assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue() } + + private fun createAndSetDelegate(state: MediaProjectionState.Projecting) { + underTest = + EndCastToOtherDeviceDialogDelegate( + kosmos.endMediaProjectionDialogHelper, + kosmos.mediaProjectionChipInteractor, + state, + ) + } + + companion object { + private const val HOST_PACKAGE = "fake.host.package" + private val ENTIRE_SCREEN = MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE) + private val SINGLE_TASK = + MediaProjectionState.Projecting.SingleTask(HOST_PACKAGE, createTask(taskId = 1)) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt new file mode 100644 index 000000000000..bbd1109c59e5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2024 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.chips.mediaprojection.ui.view + +import android.content.ComponentName +import android.content.Intent +import android.content.packageManager +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase +import com.android.systemui.mediaprojection.data.model.MediaProjectionState +import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +class EndMediaProjectionDialogHelperTest : SysuiTestCase() { + private val kosmos = Kosmos().also { it.testCase = this } + + private val underTest = kosmos.endMediaProjectionDialogHelper + + @Test + fun createDialog_usesDelegateAndFactory() { + val dialog = mock<SystemUIDialog>() + val delegate = SystemUIDialog.Delegate { dialog } + whenever(kosmos.mockSystemUIDialogFactory.create(eq(delegate))).thenReturn(dialog) + + underTest.createDialog(delegate) + + verify(kosmos.mockSystemUIDialogFactory).create(delegate) + } + + @Test + fun getDialogMessage_entireScreen_isGenericMessage() { + val result = + underTest.getDialogMessage( + MediaProjectionState.Projecting.EntireScreen("host.package"), + R.string.accessibility_home, + R.string.cast_to_other_device_stop_dialog_message_specific_app + ) + + assertThat(result).isEqualTo(context.getString(R.string.accessibility_home)) + } + + @Test + fun getDialogMessage_singleTask_cannotFindPackage_isGenericMessage() { + val baseIntent = + Intent().apply { this.component = ComponentName("fake.task.package", "cls") } + whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>())) + .thenThrow(PackageManager.NameNotFoundException()) + + val projectionState = + MediaProjectionState.Projecting.SingleTask( + "host.package", + createTask(taskId = 1, baseIntent = baseIntent) + ) + + val result = + underTest.getDialogMessage( + projectionState, + R.string.accessibility_home, + R.string.cast_to_other_device_stop_dialog_message_specific_app + ) + + assertThat(result).isEqualTo(context.getString(R.string.accessibility_home)) + } + + @Test + fun getDialogMessage_singleTask_findsPackage_isSpecificMessageWithAppLabel() { + val baseIntent = + Intent().apply { this.component = ComponentName("fake.task.package", "cls") } + val appInfo = mock<ApplicationInfo>() + whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package") + whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>())) + .thenReturn(appInfo) + + val projectionState = + MediaProjectionState.Projecting.SingleTask( + "host.package", + createTask(taskId = 1, baseIntent = baseIntent) + ) + + val result = + underTest.getDialogMessage( + projectionState, + R.string.accessibility_home, + R.string.cast_to_other_device_stop_dialog_message_specific_app + ) + + // It'd be nice to use the R.string resources directly, but they include the <b> tags which + // aren't in the returned string. + assertThat(result.toString()).isEqualTo("You will stop casting Fake Package") + } + + @Test + fun getDialogMessage_appLabelHasSpecialCharacters_isEscaped() { + val baseIntent = + Intent().apply { this.component = ComponentName("fake.task.package", "cls") } + val appInfo = mock<ApplicationInfo>() + whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake & Package <Here>") + whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>())) + .thenReturn(appInfo) + + val projectionState = + MediaProjectionState.Projecting.SingleTask( + "host.package", + createTask(taskId = 1, baseIntent = baseIntent) + ) + + val result = + underTest.getDialogMessage( + projectionState, + R.string.accessibility_home, + R.string.cast_to_other_device_stop_dialog_message_specific_app + ) + + assertThat(result.toString()).isEqualTo("You will stop casting Fake & Package <Here>") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt index 1d6e8669274d..4ddca521abd3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt @@ -16,22 +16,28 @@ package com.android.systemui.statusbar.chips.mediaprojection.ui.view +import android.content.ComponentName import android.content.DialogInterface +import android.content.Intent +import android.content.packageManager +import android.content.pm.ApplicationInfo import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope +import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask import com.android.systemui.res.R import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor import com.android.systemui.statusbar.phone.SystemUIDialog -import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before +import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.mock @@ -41,22 +47,14 @@ import org.mockito.kotlin.whenever @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class EndShareToAppDialogDelegateTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = Kosmos().also { it.testCase = this } private val sysuiDialog = mock<SystemUIDialog>() - private val sysuiDialogFactory = kosmos.mockSystemUIDialogFactory - private val underTest = - EndShareToAppDialogDelegate( - sysuiDialogFactory, - kosmos.mediaProjectionChipInteractor, - ) - - @Before - fun setUp() { - whenever(sysuiDialogFactory.create(eq(underTest), eq(context))).thenReturn(sysuiDialog) - } + private lateinit var underTest: EndShareToAppDialogDelegate @Test fun icon() { + createAndSetDelegate(SINGLE_TASK) + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) verify(sysuiDialog).setIcon(R.drawable.ic_screenshot_share) @@ -64,20 +62,51 @@ class EndShareToAppDialogDelegateTest : SysuiTestCase() { @Test fun title() { + createAndSetDelegate(ENTIRE_SCREEN) + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) verify(sysuiDialog).setTitle(R.string.share_to_app_stop_dialog_title) } @Test - fun message() { + fun message_entireScreen() { + createAndSetDelegate(ENTIRE_SCREEN) + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) - verify(sysuiDialog).setMessage(R.string.share_to_app_stop_dialog_message) + verify(sysuiDialog).setMessage(context.getString(R.string.share_to_app_stop_dialog_message)) + } + + @Test + fun message_singleTask() { + val baseIntent = + Intent().apply { this.component = ComponentName("fake.task.package", "cls") } + val appInfo = mock<ApplicationInfo>() + whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package") + whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>())) + .thenReturn(appInfo) + + createAndSetDelegate( + MediaProjectionState.Projecting.SingleTask( + HOST_PACKAGE, + createTask(taskId = 1, baseIntent = baseIntent) + ) + ) + + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) + + // It'd be nice to use R.string.share_to_app_stop_dialog_message_specific_app directly, but + // it includes the <b> tags which aren't in the returned string. + val result = argumentCaptor<CharSequence>() + verify(sysuiDialog).setMessage(result.capture()) + assertThat(result.firstValue.toString()).isEqualTo("You will stop sharing Fake Package") } @Test fun negativeButton() { + createAndSetDelegate(SINGLE_TASK) + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) verify(sysuiDialog).setNegativeButton(R.string.close_dialog_button, null) @@ -86,6 +115,8 @@ class EndShareToAppDialogDelegateTest : SysuiTestCase() { @Test fun positiveButton() = kosmos.testScope.runTest { + createAndSetDelegate(ENTIRE_SCREEN) + underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) val clickListener = argumentCaptor<DialogInterface.OnClickListener>() @@ -105,4 +136,20 @@ class EndShareToAppDialogDelegateTest : SysuiTestCase() { assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue() } + + private fun createAndSetDelegate(state: MediaProjectionState.Projecting) { + underTest = + EndShareToAppDialogDelegate( + kosmos.endMediaProjectionDialogHelper, + kosmos.mediaProjectionChipInteractor, + state, + ) + } + + companion object { + private const val HOST_PACKAGE = "fake.host.package" + private val ENTIRE_SCREEN = MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE) + private val SINGLE_TASK = + MediaProjectionState.Projecting.SingleTask(HOST_PACKAGE, createTask(taskId = 1)) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index 67129639efaf..65bf0bcf2741 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -22,6 +22,7 @@ import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository @@ -39,8 +40,7 @@ import org.junit.Test @SmallTest class OngoingActivityChipsViewModelTest : SysuiTestCase() { - - private val kosmos = Kosmos() + private val kosmos = Kosmos().also { it.testCase = this } private val testScope = kosmos.testScope private val screenRecordState = kosmos.screenRecordRepository.screenRecordState diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt index 057dcb2a156e..6af14e02ed62 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.phone.LetterboxAppearance import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator import com.android.systemui.statusbar.phone.StatusBarBoundsProvider import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent +import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -390,7 +391,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.statusBarAppearance) - ongoingCallRepository.setHasOngoingCall(true) + ongoingCallRepository.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 34)) onSystemBarAttributesChanged( requestedVisibleTypes = WindowInsets.Type.navigationBars(), ) @@ -403,7 +404,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.statusBarAppearance) - ongoingCallRepository.setHasOngoingCall(true) + ongoingCallRepository.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 789)) onSystemBarAttributesChanged( requestedVisibleTypes = WindowInsets.Type.statusBars(), appearance = APPEARANCE_OPAQUE_STATUS_BARS, @@ -417,7 +418,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.statusBarAppearance) - ongoingCallRepository.setHasOngoingCall(false) + ongoingCallRepository.setOngoingCallState(OngoingCallModel.NoCall) onSystemBarAttributesChanged( requestedVisibleTypes = WindowInsets.Type.navigationBars(), appearance = APPEARANCE_OPAQUE_STATUS_BARS, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index 4d6798be9211..feef9431c28c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -32,16 +32,17 @@ import android.view.View import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R +import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener -import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository +import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.concurrency.FakeExecutor @@ -60,13 +61,13 @@ import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyString import org.mockito.ArgumentMatchers.nullable import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations private const val CALL_UID = 900 @@ -93,8 +94,8 @@ class OngoingCallControllerTest : SysuiTestCase() { private lateinit var controller: OngoingCallController private lateinit var notifCollectionListener: NotifCollectionListener - @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler: - SwipeStatusBarAwayGestureHandler + @Mock + private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler @Mock private lateinit var mockOngoingCallListener: OngoingCallListener @Mock private lateinit var mockActivityStarter: ActivityStarter @Mock private lateinit var mockIActivityManager: IActivityManager @@ -112,21 +113,22 @@ class OngoingCallControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) val notificationCollection = mock(CommonNotifCollection::class.java) - controller = OngoingCallController( - testScope.backgroundScope, - context, - ongoingCallRepository, - notificationCollection, - clock, - mockActivityStarter, - mainExecutor, - mockIActivityManager, - OngoingCallLogger(uiEventLoggerFake), - DumpManager(), - mockStatusBarWindowController, - mockSwipeStatusBarAwayGestureHandler, - statusBarModeRepository, - ) + controller = + OngoingCallController( + testScope.backgroundScope, + context, + ongoingCallRepository, + notificationCollection, + clock, + mockActivityStarter, + mainExecutor, + mockIActivityManager, + OngoingCallLogger(uiEventLoggerFake), + DumpManager(), + mockStatusBarWindowController, + mockSwipeStatusBarAwayGestureHandler, + statusBarModeRepository, + ) controller.start() controller.addCallback(mockOngoingCallListener) controller.setChipView(chipView) @@ -136,7 +138,7 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener = collectionListenerCaptor.value!! `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenReturn(PROC_STATE_INVISIBLE) + .thenReturn(PROC_STATE_INVISIBLE) } @After @@ -146,10 +148,14 @@ class OngoingCallControllerTest : SysuiTestCase() { @Test fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() { - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) + val notification = NotificationEntryBuilder(createOngoingCallNotifEntry()) + notification.modifyNotification(context).setWhen(567) + notifCollectionListener.onEntryUpdated(notification.build()) verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean()) - assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue() + val repoState = ongoingCallRepository.ongoingCallState.value + assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat((repoState as OngoingCallModel.InCall).startTimeMs).isEqualTo(567) } @Test @@ -164,7 +170,8 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener.onEntryUpdated(createNotCallNotifEntry()) verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean()) - assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse() + assertThat(ongoingCallRepository.ongoingCallState.value) + .isInstanceOf(OngoingCallModel.NoCall::class.java) } @Test @@ -172,25 +179,27 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry()) - verify(mockOngoingCallListener, times(2)) - .onOngoingCallStateChanged(anyBoolean()) + verify(mockOngoingCallListener, times(2)).onOngoingCallStateChanged(anyBoolean()) } @Test fun onEntryUpdated_ongoingCallNotifThenScreeningCallNotif_repoUpdated() { notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue() + assertThat(ongoingCallRepository.ongoingCallState.value) + .isInstanceOf(OngoingCallModel.InCall::class.java) notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry()) - assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse() + assertThat(ongoingCallRepository.ongoingCallState.value) + .isInstanceOf(OngoingCallModel.NoCall::class.java) } /** Regression test for b/191472854. */ @Test fun onEntryUpdated_notifHasNullContentIntent_noCrash() { notifCollectionListener.onEntryUpdated( - createCallNotifEntry(ongoingCallStyle, nullContentIntent = true)) + createCallNotifEntry(ongoingCallStyle, nullContentIntent = true) + ) } /** Regression test for b/192379214. */ @@ -202,12 +211,12 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener.onEntryUpdated(notification.build()) chipView.measure( - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) - .isEqualTo(0) + .isEqualTo(0) } @Test @@ -218,12 +227,12 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener.onEntryUpdated(notification.build()) chipView.measure( - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) - .isGreaterThan(0) + .isGreaterThan(0) } @Test @@ -233,12 +242,12 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener.onEntryUpdated(notification.build()) chipView.measure( - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) - .isGreaterThan(0) + .isGreaterThan(0) } /** Regression test for b/194731244. */ @@ -250,15 +259,14 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) } - verify(mockIActivityManager, times(1)) - .registerUidObserver(any(), any(), any(), any()) + verify(mockIActivityManager, times(1)).registerUidObserver(any(), any(), any(), any()) } /** Regression test for b/216248574. */ @Test fun entryUpdated_getUidProcessStateThrowsException_noCrash() { `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenThrow(SecurityException()) + .thenThrow(SecurityException()) // No assert required, just check no crash notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) @@ -267,9 +275,15 @@ class OngoingCallControllerTest : SysuiTestCase() { /** Regression test for b/216248574. */ @Test fun entryUpdated_registerUidObserverThrowsException_noCrash() { - `when`(mockIActivityManager.registerUidObserver( - any(), any(), any(), nullable(String::class.java) - )).thenThrow(SecurityException()) + `when`( + mockIActivityManager.registerUidObserver( + any(), + any(), + any(), + nullable(String::class.java), + ) + ) + .thenThrow(SecurityException()) // No assert required, just check no crash notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) @@ -281,9 +295,8 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) val packageNameCaptor = ArgumentCaptor.forClass(String::class.java) - verify(mockIActivityManager).registerUidObserver( - any(), any(), any(), packageNameCaptor.capture() - ) + verify(mockIActivityManager) + .registerUidObserver(any(), any(), any(), packageNameCaptor.capture()) assertThat(packageNameCaptor.value).isNotNull() } @@ -313,11 +326,13 @@ class OngoingCallControllerTest : SysuiTestCase() { fun onEntryRemoved_callNotifAddedThenRemoved_repoUpdated() { val ongoingCallNotifEntry = createOngoingCallNotifEntry() notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) - assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue() + assertThat(ongoingCallRepository.ongoingCallState.value) + .isInstanceOf(OngoingCallModel.InCall::class.java) notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED) - assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse() + assertThat(ongoingCallRepository.ongoingCallState.value) + .isInstanceOf(OngoingCallModel.NoCall::class.java) } @Test @@ -360,7 +375,8 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED) - assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse() + assertThat(ongoingCallRepository.ongoingCallState.value) + .isInstanceOf(OngoingCallModel.NoCall::class.java) } @Test @@ -379,7 +395,8 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED) - assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue() + assertThat(ongoingCallRepository.ongoingCallState.value) + .isInstanceOf(OngoingCallModel.InCall::class.java) } @Test @@ -404,7 +421,7 @@ class OngoingCallControllerTest : SysuiTestCase() { @Test fun hasOngoingCall_ongoingCallNotifSentAndCallAppNotVisible_returnsTrue() { `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenReturn(PROC_STATE_INVISIBLE) + .thenReturn(PROC_STATE_INVISIBLE) notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) @@ -414,7 +431,7 @@ class OngoingCallControllerTest : SysuiTestCase() { @Test fun hasOngoingCall_ongoingCallNotifSentButCallAppVisible_returnsFalse() { `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenReturn(PROC_STATE_VISIBLE) + .thenReturn(PROC_STATE_VISIBLE) notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) @@ -472,10 +489,8 @@ class OngoingCallControllerTest : SysuiTestCase() { lateinit var newChipView: View TestableLooper.get(this).runWithLooper { - newChipView = LayoutInflater.from(mContext).inflate( - R.layout.ongoing_activity_chip, - null - ) + newChipView = + LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null) } // Change the chip view associated with the controller. @@ -488,13 +503,13 @@ class OngoingCallControllerTest : SysuiTestCase() { fun callProcessChangesToVisible_listenerNotified() { // Start the call while the process is invisible. `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenReturn(PROC_STATE_INVISIBLE) + .thenReturn(PROC_STATE_INVISIBLE) notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) reset(mockOngoingCallListener) val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java) - verify(mockIActivityManager).registerUidObserver( - captor.capture(), any(), any(), nullable(String::class.java)) + verify(mockIActivityManager) + .registerUidObserver(captor.capture(), any(), any(), nullable(String::class.java)) val uidObserver = captor.value // Update the process to visible. @@ -509,13 +524,13 @@ class OngoingCallControllerTest : SysuiTestCase() { fun callProcessChangesToInvisible_listenerNotified() { // Start the call while the process is visible. `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenReturn(PROC_STATE_VISIBLE) + .thenReturn(PROC_STATE_VISIBLE) notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) reset(mockOngoingCallListener) val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java) - verify(mockIActivityManager).registerUidObserver( - captor.capture(), any(), any(), nullable(String::class.java)) + verify(mockIActivityManager) + .registerUidObserver(captor.capture(), any(), any(), nullable(String::class.java)) val uidObserver = captor.value // Update the process to invisible. @@ -534,7 +549,7 @@ class OngoingCallControllerTest : SysuiTestCase() { assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) assertThat(uiEventLoggerFake.eventId(0)) - .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id) + .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id) } /** Regression test for b/212467440. */ @@ -556,8 +571,9 @@ class OngoingCallControllerTest : SysuiTestCase() { assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) assertThat(uiEventLoggerFake.eventId(0)) - .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id) + .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id) } + // Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since // [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class. @@ -621,8 +637,7 @@ class OngoingCallControllerTest : SysuiTestCase() { statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false testScope.runCurrent() - verify(mockSwipeStatusBarAwayGestureHandler) - .removeOnGestureDetectedCallback(anyString()) + verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(anyString()) } @Test @@ -635,8 +650,7 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED) - verify(mockSwipeStatusBarAwayGestureHandler) - .removeOnGestureDetectedCallback(anyString()) + verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(anyString()) } // TODO(b/195839150): Add test @@ -675,5 +689,9 @@ private val person = Person.Builder().setName("name").build() private val hangUpIntent = mock(PendingIntent::class.java) private val ongoingCallStyle = Notification.CallStyle.forOngoingCall(person, hangUpIntent) -private val screeningCallStyle = Notification.CallStyle.forScreeningCall( - person, hangUpIntent, /* answerIntent= */ mock(PendingIntent::class.java))
\ No newline at end of file +private val screeningCallStyle = + Notification.CallStyle.forScreeningCall( + person, + hangUpIntent, + /* answerIntent= */ mock(PendingIntent::class.java), + ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt index 56aa7d6e3ca4..73a86a1b8511 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone.ongoingcall.data.repository import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -27,12 +28,13 @@ class OngoingCallRepositoryTest : SysuiTestCase() { @Test fun hasOngoingCall_matchesSet() { - underTest.setHasOngoingCall(true) + val inCallModel = OngoingCallModel.InCall(startTimeMs = 654) + underTest.setOngoingCallState(inCallModel) - assertThat(underTest.hasOngoingCall.value).isTrue() + assertThat(underTest.ongoingCallState.value).isEqualTo(inCallModel) - underTest.setHasOngoingCall(false) + underTest.setOngoingCallState(OngoingCallModel.NoCall) - assertThat(underTest.hasOngoingCall.value).isFalse() + assertThat(underTest.ongoingCallState.value).isEqualTo(OngoingCallModel.NoCall) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index cdb2b883078a..b8299e5ef3ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.log.assertLogsWtf @@ -63,7 +64,10 @@ import org.junit.Test @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { - private val kosmos = Kosmos().apply { testDispatcher = UnconfinedTestDispatcher() } + private val kosmos = Kosmos().also { + it.testCase = this + it.testDispatcher = UnconfinedTestDispatcher() + } private val testScope = kosmos.testScope diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index fac6a4c22178..57ddcde72643 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -86,6 +86,7 @@ import com.android.systemui.statusbar.policy.FakeConfigurationController; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; +import com.android.systemui.volume.domain.interactor.VolumeDialogInteractor; import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor; import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag; import com.android.systemui.volume.ui.binder.VolumeDialogMenuIconBinder; @@ -153,6 +154,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { private VolumeDialogMenuIconBinder mVolumeDialogMenuIconBinder; @Mock private VolumePanelFlag mVolumePanelFlag; + @Mock + private VolumeDialogInteractor mVolumeDialogInteractor; private final CsdWarningDialog.Factory mCsdWarningDialogFactory = new CsdWarningDialog.Factory() { @@ -218,7 +221,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { mLazySecureSettings, mVibratorHelper, mVolumeDialogMenuIconBinder, - new FakeSystemClock()); + new FakeSystemClock(), + mVolumeDialogInteractor); mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); @@ -778,6 +782,15 @@ public class VolumeDialogImplTest extends SysuiTestCase { assertFalse(foundDnDIcon); } + @Test + public void testInteractor_onShow() { + mDialog.show(SHOW_REASON_UNKNOWN); + mTestableLooper.processAllMessages(); + + verify(mVolumeDialogInteractor).onDialogShown(); + verify(mVolumeDialogInteractor).onDialogDismissed(); // dismiss by timeout + } + /** * @return true if at least one volume row has the DND icon */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryTest.kt new file mode 100644 index 000000000000..dcac85e7fb5b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 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.volume.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class VolumeDialogRepositoryTest : SysuiTestCase() { + private lateinit var underTest: VolumeDialogRepository + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + @Before + fun setUp() { + underTest = kosmos.volumeDialogRepository + } + + @Test + fun isDialogVisible_initialValueFalse() { + testScope.runTest { + val isVisible by collectLastValue(underTest.isDialogVisible) + runCurrent() + + assertThat(isVisible).isFalse() + } + } + + @Test + fun isDialogVisible_onChange() { + testScope.runTest { + val isVisible by collectLastValue(underTest.isDialogVisible) + runCurrent() + + underTest.setDialogVisibility(true) + assertThat(isVisible).isTrue() + + underTest.setDialogVisibility(false) + assertThat(isVisible).isFalse() + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorTest.kt new file mode 100644 index 000000000000..5c735e3c7842 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 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.volume.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class VolumeDialogInteractorTest : SysuiTestCase() { + private lateinit var underTest: VolumeDialogInteractor + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + @Before + fun setUp() { + underTest = kosmos.volumeDialogInteractor + } + + @Test + fun onDialogDismissed() { + testScope.runTest { + val isVisible by collectLastValue(underTest.isDialogVisible) + underTest.onDialogDismissed() + runCurrent() + + assertThat(isVisible).isFalse() + } + } + + @Test + fun onDialogShown() { + testScope.runTest { + val isVisible by collectLastValue(underTest.isDialogVisible) + underTest.onDialogShown() + runCurrent() + + assertThat(isVisible).isTrue() + } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUnlockAnimationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUnlockAnimationControllerKosmos.kt new file mode 100644 index 000000000000..1fa681360070 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUnlockAnimationControllerKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 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.keyguard + +import com.android.systemui.keyguard.KeyguardUnlockAnimationController +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.keyguardUnlockAnimationController by + Kosmos.Fixture { mock<KeyguardUnlockAnimationController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt index 8281984d3b4f..79d58a1d4e40 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt @@ -22,7 +22,6 @@ import android.view.windowManager import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor import com.android.systemui.biometrics.domain.interactor.displayStateInteractor import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor -import com.android.systemui.biometrics.fpsUnlockTracker import com.android.systemui.keyguard.domain.interactor.deviceEntrySideFpsOverlayInteractor import com.android.systemui.keyguard.ui.viewmodel.sideFpsProgressBarViewModel import com.android.systemui.kosmos.Kosmos @@ -38,7 +37,6 @@ val Kosmos.sideFpsOverlayViewBinder by Fixture { { biometricStatusInteractor }, { displayStateInteractor }, { deviceEntrySideFpsOverlayInteractor }, - { fpsUnlockTracker }, { layoutInflater }, { sideFpsProgressBarViewModel }, { sideFpsSensorInteractor }, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt index 3f3840810946..1ae8449d8b4d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt @@ -25,12 +25,14 @@ import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToDreamingTransit import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @OptIn(ExperimentalCoroutinesApi::class) val Kosmos.communalTransitionViewModel by Kosmos.Fixture { CommunalTransitionViewModel( + applicationScope = applicationCoroutineScope, glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel, lockscreenToGlanceableHubTransitionViewModel = diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 67f844340096..31cdbc73f6fa 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -68,6 +68,7 @@ import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.wifiIntera import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor import com.android.systemui.util.time.systemClock +import com.android.systemui.volume.domain.interactor.volumeDialogInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi /** @@ -136,6 +137,7 @@ class KosmosJavaAdapter() { val shadeInteractor by lazy { kosmos.shadeInteractor } val wifiInteractor by lazy { kosmos.wifiInteractor } val fakeWifiRepository by lazy { kosmos.fakeWifiRepository } + val volumeDialogInteractor by lazy { kosmos.volumeDialogInteractor } val ongoingActivityChipsViewModel by lazy { kosmos.ongoingActivityChipsViewModel } val scrimController by lazy { kosmos.scrimController } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt index 062b4484044c..9d2281167fbf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt @@ -21,7 +21,7 @@ import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository -import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper import com.android.systemui.util.time.fakeSystemClock val Kosmos.mediaProjectionChipInteractor: MediaProjectionChipInteractor by @@ -31,7 +31,7 @@ val Kosmos.mediaProjectionChipInteractor: MediaProjectionChipInteractor by mediaProjectionRepository = fakeMediaProjectionRepository, packageManager = packageManager, systemClock = fakeSystemClock, - dialogFactory = mockSystemUIDialogFactory, + endMediaProjectionDialogHelper = endMediaProjectionDialogHelper, dialogTransitionAnimator = mockDialogTransitionAnimator, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt new file mode 100644 index 000000000000..4f82662fa673 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 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.chips.mediaprojection.ui.view + +import android.content.applicationContext +import android.content.packageManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory + +val Kosmos.endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper by + Kosmos.Fixture { + EndMediaProjectionDialogHelper( + dialogFactory = mockSystemUIDialogFactory, + packageManager = packageManager, + context = applicationContext, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FpsUnlockTrackerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryKosmos.kt index 6085a1f3353e..2f4eef53830c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FpsUnlockTrackerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryKosmos.kt @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.android.systemui.biometrics +package com.android.systemui.volume.data.repository import com.android.systemui.kosmos.Kosmos -import com.android.systemui.util.mockito.mock -val Kosmos.fpsUnlockTracker by Kosmos.Fixture { mock<FpsUnlockTracker>() } +val Kosmos.volumeDialogRepository by Kosmos.Fixture { VolumeDialogRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorKosmos.kt new file mode 100644 index 000000000000..2e5d0407d522 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 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.volume.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.data.repository.volumeDialogRepository + +val Kosmos.volumeDialogInteractor by + Kosmos.Fixture { VolumeDialogInteractor(volumeDialogRepository) } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index aa0af7e5075a..b5b998f6cd5e 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -18,6 +18,7 @@ package com.android.server.accessibility.magnification; import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN; +import static android.util.MathUtils.abs; import static android.util.TypedValue.COMPLEX_UNIT_DIP; import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; @@ -263,27 +264,27 @@ public class FullScreenMagnificationController implements @GuardedBy("mLock") boolean isAtEdge() { - return isAtLeftEdge() || isAtRightEdge() || isAtTopEdge() || isAtBottomEdge(); + return isAtLeftEdge(0f) || isAtRightEdge(0f) || isAtTopEdge(0f) || isAtBottomEdge(0f); } @GuardedBy("mLock") - boolean isAtLeftEdge() { - return getOffsetX() == getMaxOffsetXLocked(); + boolean isAtLeftEdge(float slop) { + return abs(getOffsetX() - getMaxOffsetXLocked()) <= slop; } @GuardedBy("mLock") - boolean isAtRightEdge() { - return getOffsetX() == getMinOffsetXLocked(); + boolean isAtRightEdge(float slop) { + return abs(getOffsetX() - getMinOffsetXLocked()) <= slop; } @GuardedBy("mLock") - boolean isAtTopEdge() { - return getOffsetY() == getMaxOffsetYLocked(); + boolean isAtTopEdge(float slop) { + return abs(getOffsetY() - getMaxOffsetYLocked()) <= slop; } @GuardedBy("mLock") - boolean isAtBottomEdge() { - return getOffsetY() == getMinOffsetYLocked(); + boolean isAtBottomEdge(float slop) { + return abs(getOffsetY() - getMinOffsetYLocked()) <= slop; } @GuardedBy("mLock") @@ -1298,7 +1299,7 @@ public class FullScreenMagnificationController implements * Returns whether the user is at one of the edges (left, right, top, bottom) * of the magnification viewport * - * @param displayId + * @param displayId The logical display id. * @return if user is at the edge of the view */ public boolean isAtEdge(int displayId) { @@ -1314,64 +1315,72 @@ public class FullScreenMagnificationController implements /** * Returns whether the user is at the left edge of the viewport * - * @param displayId - * @return if user is at left edge of view + * @param displayId The logical display id. + * @param slop The buffer distance in pixels from the left edge within that will be considered + * to be at the edge. + * @return if user is considered at left edge of view */ - public boolean isAtLeftEdge(int displayId) { + public boolean isAtLeftEdge(int displayId, float slop) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); if (display == null) { return false; } - return display.isAtLeftEdge(); + return display.isAtLeftEdge(slop); } } /** * Returns whether the user is at the right edge of the viewport * - * @param displayId - * @return if user is at right edge of view + * @param displayId The logical display id. + * @param slop The buffer distance in pixels from the right edge within that will be considered + * to be at the edge. + * @return if user is considered at right edge of view */ - public boolean isAtRightEdge(int displayId) { + public boolean isAtRightEdge(int displayId, float slop) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); if (display == null) { return false; } - return display.isAtRightEdge(); + return display.isAtRightEdge(slop); } } /** * Returns whether the user is at the top edge of the viewport * - * @param displayId - * @return if user is at top edge of view + * @param displayId The logical display id. + * @param slop The buffer distance in pixels from the top edge within that will be considered + * to be at the edge. + * @return if user is considered at top edge of view */ - public boolean isAtTopEdge(int displayId) { + public boolean isAtTopEdge(int displayId, float slop) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); if (display == null) { return false; } - return display.isAtTopEdge(); + return display.isAtTopEdge(slop); } } /** * Returns whether the user is at the bottom edge of the viewport * - * @param displayId - * @return if user is at bottom edge of view + * @param displayId The logical display id. + * @param slop The buffer distance in pixels from the bottom edge within that will be considered + * to be at the edge. + * @return if user is considered at bottom edge of view */ - public boolean isAtBottomEdge(int displayId) { + public boolean isAtBottomEdge(int displayId, float slop) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); if (display == null) { return false; } - return display.isAtBottomEdge(); + return display.isAtBottomEdge(slop); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index 6d1ab9f89f78..e9c3fbdf021c 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -171,7 +171,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH private final FullScreenMagnificationVibrationHelper mFullScreenMagnificationVibrationHelper; - @VisibleForTesting final OverscrollHandler mOverscrollHandler; + @VisibleForTesting + @Nullable + final OverscrollHandler mOverscrollHandler; + + private final float mOverscrollEdgeSlop; private final boolean mIsWatch; @@ -308,7 +312,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mSinglePanningState = new SinglePanningState(context); mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper; mOneFingerPanningSettingsProvider = oneFingerPanningSettingsProvider; - mOverscrollHandler = new OverscrollHandler(); + boolean overscrollHandlerSupported = context.getResources().getBoolean( + R.bool.config_enable_a11y_fullscreen_magnification_overscroll_handler); + mOverscrollHandler = overscrollHandlerSupported ? new OverscrollHandler() : null; + mOverscrollEdgeSlop = context.getResources().getDimensionPixelSize( + R.dimen.accessibility_fullscreen_magnification_gesture_edge_slop); mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); if (mDetectShortcutTrigger) { @@ -523,16 +531,14 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (action == ACTION_POINTER_UP && event.getPointerCount() == 2 // includes the pointer currently being released && mPreviousState == mViewportDraggingState) { - // if feature flag is enabled, currently only true on watches - if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) { + if (mOverscrollHandler != null) { mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded(); mOverscrollHandler.clearEdgeState(); } persistScaleAndTransitionTo(mViewportDraggingState); } else if (action == ACTION_UP || action == ACTION_CANCEL) { onPanningFinished(event); - // if feature flag is enabled, currently only true on watches - if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) { + if (mOverscrollHandler != null) { mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded(); mOverscrollHandler.clearEdgeState(); } @@ -540,7 +546,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } - void prepareForState() { checkShouldDetectPassPersistedScale(); } @@ -611,7 +616,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH onPan(second); mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX, distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); - if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) { + if (mOverscrollHandler != null) { mOverscrollHandler.onScrollStateChanged(first, second); } return /* event consumed: */ true; @@ -1000,7 +1005,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH && event.getPointerCount() == 2) { transitionToViewportDraggingStateAndClear(event); } else if (isActivated() && event.getPointerCount() == 2) { - if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled() + if (mOverscrollHandler != null && overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); @@ -1011,9 +1016,13 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } else if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled() && isActivated() && event.getPointerCount() == 1) { - if (overscrollState(event, mFirstPointerDownLocation) + if (mOverscrollHandler != null + && overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); + } else if (overscrollState(event, mFirstPointerDownLocation) + != OVERSCROLL_NONE) { + transitionToDelegatingStateAndClear(); } else { transitToSinglePanningStateAndClear(); } @@ -1255,7 +1264,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) { transitionToViewportDraggingStateAndClear(event); } else if (isActivated() && event.getPointerCount() == 2) { - if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled() + if (mOverscrollHandler != null && overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); @@ -1266,9 +1275,13 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } else if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled() && isActivated() && event.getPointerCount() == 1) { - if (overscrollState(event, mFirstPointerDownLocation) + if (mOverscrollHandler != null + && overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); + } else if (overscrollState(event, mFirstPointerDownLocation) + != OVERSCROLL_NONE) { + transitionToDelegatingStateAndClear(); } else { transitToSinglePanningStateAndClear(); } @@ -1645,22 +1658,36 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } float dX = event.getX() - firstPointerDownLocation.x; float dY = event.getY() - firstPointerDownLocation.y; - if (mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) && dX > 0) { + if (isAtLeftEdge() && dX > 0) { return OVERSCROLL_LEFT_EDGE; - } else if (mFullScreenMagnificationController.isAtRightEdge(mDisplayId) && dX < 0) { + } else if (isAtRightEdge() && dX < 0) { return OVERSCROLL_RIGHT_EDGE; - } else if (mFullScreenMagnificationController.isAtTopEdge(mDisplayId) && dY > 0 - || mFullScreenMagnificationController.isAtBottomEdge(mDisplayId) && dY < 0) { + } else if ((isAtTopEdge() && dY > 0) || (isAtBottomEdge() && dY < 0)) { return OVERSCROLL_VERTICAL_EDGE; } return OVERSCROLL_NONE; } + private boolean isAtLeftEdge() { + return mFullScreenMagnificationController.isAtLeftEdge(mDisplayId, mOverscrollEdgeSlop); + } + + private boolean isAtRightEdge() { + return mFullScreenMagnificationController.isAtRightEdge(mDisplayId, mOverscrollEdgeSlop); + } + + private boolean isAtTopEdge() { + return mFullScreenMagnificationController.isAtTopEdge(mDisplayId, mOverscrollEdgeSlop); + } + + private boolean isAtBottomEdge() { + return mFullScreenMagnificationController.isAtBottomEdge(mDisplayId, mOverscrollEdgeSlop); + } + private boolean pointerValid(PointF pointerDownLocation) { return !(Float.isNaN(pointerDownLocation.x) && Float.isNaN(pointerDownLocation.y)); } - private static final class MotionEventInfo { private static final int MAX_POOL_SIZE = 10; @@ -1845,7 +1872,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH final class SinglePanningState extends SimpleOnGestureListener implements State { - private final GestureDetector mScrollGestureDetector; private MotionEventInfo mEvent; SinglePanningState(Context context) { @@ -1860,8 +1886,10 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH onPanningFinished(event); // fall-through! case ACTION_CANCEL: - mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded(); - mOverscrollHandler.clearEdgeState(); + if (mOverscrollHandler != null) { + mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded(); + mOverscrollHandler.clearEdgeState(); + } transitionTo(mDetectingState); break; } @@ -1889,26 +1917,18 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH + " isAtEdge: " + mFullScreenMagnificationController.isAtEdge(mDisplayId)); } - mOverscrollHandler.onScrollStateChanged(first, second); - return /* event consumed: */ true; - } - - private void vibrateIfNeeded() { - if ((mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) - || mFullScreenMagnificationController.isAtRightEdge(mDisplayId))) { - mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); + if (mOverscrollHandler != null) { + mOverscrollHandler.onScrollStateChanged(first, second); } + return /* event consumed: */ true; } - - @Override public String toString() { return "SinglePanningState{" + "isEdgeOfView=" + mFullScreenMagnificationController.isAtEdge(mDisplayId); } - } /** Overscroll Handler handles the logic when user is at the edge and scrolls past an edge */ @@ -2007,9 +2027,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (mOverscrollState != OVERSCROLL_NONE) { return; } - if ((mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) - || mFullScreenMagnificationController.isAtRightEdge(mDisplayId)) - && !mEdgeCooldown) { + if ((isAtLeftEdge() || isAtRightEdge()) && !mEdgeCooldown) { mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 9ad15b2d431f..2bf319e06efa 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -515,7 +515,10 @@ final class AutofillManagerServiceImpl } final boolean finished = saveResult.isRemoveSession(); - if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished); + if (sVerbose) { + Slog.v(TAG, "finishSessionLocked(): session finished? " + finished + + ", showing save UI? " + saveResult.isLogSaveShown()); + } if (finished) { session.removeFromServiceLocked(); diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 676abd1491d7..d7da2f0052d3 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -295,6 +295,38 @@ public final class PresentationStatsEventLogger { }); } + /** + * Called for inline suggestions - inflated one at + * a time. If InlineSuggestions were filtered, + * reset the count be incrementing + */ + public void maybeIncrementCountShown() { + mEventInternal.ifPresent(event -> { + if (event.shouldResetShownCount) { + event.shouldResetShownCount = false; + event.mCountShown = 0; + } + + if (event.mCountShown == 0) { + // The first time suggestions are rendered + // set time stamp + maybeSetSuggestionPresentedTimestampMs(); + } + + event.mCountShown += 1; + }); + } + + /** + * Call this when UI is hidden. This will set a flag to reset count for inline. We do this + * instead of resetting right away in case there are 0 inline presentations after. + */ + public void markShownCountAsResettable() { + mEventInternal.ifPresent(event -> { + event.shouldResetShownCount = true; + }); + } + public void maybeSetCountShown(@Nullable List<Dataset> datasetList, AutofillId currentViewId) { mEventInternal.ifPresent(event -> { @@ -306,6 +338,13 @@ public final class PresentationStatsEventLogger { }); } + public void maybeSetCountShown(int datasets) { + mEventInternal.ifPresent( + event -> { + event.mCountShown = datasets; + }); + } + private static CountContainer getDatasetCountForAutofillId(@Nullable List<Dataset> datasetList, AutofillId currentViewId) { @@ -567,31 +606,36 @@ public final class PresentationStatsEventLogger { * <p>If the ViewState contains ViewState.STATE_AUTOFILLED, sets field_autofilled_timestamp_ms * else, set field_first_modified_timestamp_ms (if unset) and field_last_modified_timestamp_ms */ - public void onFieldTextUpdated(ViewState state) { - mEventInternal.ifPresent( - event -> { + public void onFieldTextUpdated(ViewState state, int length) { + mEventInternal.ifPresent(event -> { int timestamp = getElapsedTime(); // Focused id should be set before this is called - if (state.id != null && state.id.getViewId() != event.mFocusedId) { + if (state == null || state.id == null || state.id.getViewId() != event.mFocusedId) { // if these don't match, the currently field different than before Slog.w( TAG, - "current id: " - + state.id.getViewId() - + " is different than focused id: " - + event.mFocusedId); + "Bad view state for: " + event.mFocusedId); return; } + // Text changed because filling into form, just log Autofill timestamp if ((state.getState() & ViewState.STATE_AUTOFILLED) != 0) { event.mAutofilledTimestampMs = timestamp; - } else { - if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) { - event.mFieldModifiedFirstTimestampMs = timestamp; - } - event.mFieldModifiedLastTimestampMs = timestamp; + return; } - }); + + // Set length variables + if (event.mFieldFirstLength == DEFAULT_VALUE_INT) { + event.mFieldFirstLength = length; + } + event.mFieldLastLength = length; + + // Set timestamp variables + if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) { + event.mFieldModifiedFirstTimestampMs = timestamp; + } + event.mFieldModifiedLastTimestampMs = timestamp; + }); } public void setPresentationSelectedTimestamp() { @@ -661,15 +705,16 @@ public final class PresentationStatsEventLogger { /** Sets focused_autofill_id using view id */ public void maybeSetFocusedId(AutofillId id) { - maybeSetFocusedId(id.getViewId()); + mEventInternal.ifPresent( + event -> { + event.mFocusedId = id.getViewId(); + if (id.isVirtualInt()) { + event.mFocusedVirtualAutofillId = + id.getVirtualChildIntId() % 100; + } + }); } - /** Sets focused_autofill_id as long as mEventInternal is present */ - public void maybeSetFocusedId(int id) { - mEventInternal.ifPresent(event -> { - event.mFocusedId = id; - }); - } /** * Set views_filled_failure_count using failure count as long as mEventInternal * presents. @@ -823,7 +868,7 @@ public final class PresentationStatsEventLogger { @NotShownReason int mNoPresentationReason = NOT_SHOWN_REASON_UNKNOWN; boolean mIsDatasetAvailable; int mAvailableCount; - int mCountShown; + int mCountShown = 0; int mCountFilteredUserTyping; int mCountNotShownImePresentationNotDrawn; int mCountNotShownImeUserNotSeen; @@ -870,6 +915,9 @@ public final class PresentationStatsEventLogger { ArraySet<AutofillId> mAutofillIdsAttemptedAutofill; ArraySet<AutofillId> mAlreadyFilledAutofillIds = new ArraySet<>(); + + // Not logged - used for internal logic + boolean shouldResetShownCount = false; PresentationStatsEventInternal() {} } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 5b4faa24649c..cdae16b9a3be 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -30,7 +30,6 @@ import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_CREDMAN_BOTTOM_SHEET; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE; -import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE; @@ -2661,19 +2660,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutofillUiCallback @Override - public void onShown(int uiType) { + public void onShown(int uiType, int numDatasetsShown) { synchronized (mLock) { + mPresentationStatsEventLogger.maybeSetDisplayPresentationType(uiType); + if (uiType == UI_TYPE_INLINE) { - if (mLoggedInlineDatasetShown) { + // Inline Suggestions are inflated one at a time + // This number will be reset when filtered + // This will also call maybeSetSuggestionPresentedTimestampMs + mPresentationStatsEventLogger.maybeIncrementCountShown(); + + if (!mLoggedInlineDatasetShown) { // Chip inflation already logged, do not log again. // This is needed because every chip inflation will call this. - return; + mService.logDatasetShown(this.id, mClientState, uiType); + Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown); } mLoggedInlineDatasetShown = true; + } else { + mPresentationStatsEventLogger.maybeSetCountShown(numDatasetsShown); + // Explicitly sets maybeSetSuggestionPresentedTimestampMs + mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs(); + mService.logDatasetShown(this.id, mClientState, uiType); + Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown); } - mService.logDatasetShown(this.id, mClientState, uiType); - mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs(); - Slog.d(TAG, "onShown(): " + uiType); } } @@ -2739,6 +2749,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mInlineSessionController.hideInlineSuggestionsUiLocked(id); + mPresentationStatsEventLogger.markShownCountAsResettable(); } } @@ -4868,7 +4879,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState currentView.maybeCallOnFillReady(flags); } } - mPresentationStatsEventLogger.onFieldTextUpdated(viewState); + if (textValue != null) { + mPresentationStatsEventLogger.onFieldTextUpdated(viewState, textValue.length()); + } if (viewState.id.equals(this.mCurrentViewId) && (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) { @@ -4965,8 +4978,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState synchronized (mLock) { final ViewState currentView = mViewStates.get(mCurrentViewId); currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN); - mPresentationStatsEventLogger.maybeSetCountShown( - response.getDatasets(), mCurrentViewId); mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_DIALOG); } // Just show fill dialog once, so disabled after shown. @@ -4987,10 +4998,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // back a response via callback. final ViewState currentView = mViewStates.get(mCurrentViewId); currentView.setState(ViewState.STATE_INLINE_SHOWN); - // TODO(b/234475358): Log more accurate value of number of inline suggestions - // shown, inflated, and filtered. - mPresentationStatsEventLogger.maybeSetCountShown( - response.getDatasets(), mCurrentViewId); mPresentationStatsEventLogger.maybeSetInlinePresentationAndSuggestionHostUid( mContext, userId); return; @@ -5004,12 +5011,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mService.getMaster().getMaxInputLengthForAutofill()); synchronized (mLock) { - mPresentationStatsEventLogger.maybeSetCountShown( - response.getDatasets(), mCurrentViewId); - mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_MENU); - } - - synchronized (mLock) { if (mUiShownTime == 0) { // Log first time UI is shown. mUiShownTime = SystemClock.elapsedRealtime(); @@ -5249,7 +5250,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void onInflate() { - Session.this.onShown(UI_TYPE_INLINE); + Session.this.onShown(UI_TYPE_INLINE, 1); } }, mService.getMaster().getMaxInputLengthForAutofill()); return mInlineSessionController.setInlineFillUiLocked(inlineFillUi); @@ -5441,9 +5442,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { if (sVerbose) { - Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds - + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish - + " flags: " + flags + " hasSaveInfo: " + (saveInfo != null)); + Slog.v(TAG, "updateTrackedIdsLocked(): trackedViews: " + trackedViews + + " fillableIds: " + fillableIds + " triggerId: " + saveTriggerId + + " saveOnFinish:" + saveOnFinish + " flags: " + flags + + " hasSaveInfo: " + (saveInfo != null)); } mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible, saveOnFinish, toArray(fillableIds), saveTriggerId); diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index 8cc666b538ec..2e9a4dcb45aa 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -102,7 +102,7 @@ public final class AutoFillUI { void cancelSession(); void requestShowSoftInput(AutofillId id); void requestFallbackFromFillDialog(); - void onShown(int uiType); + void onShown(int uiType, int datasetSize); } public AutoFillUI(@NonNull Context context) { @@ -246,9 +246,9 @@ public final class AutoFillUI { } @Override - public void onShown() { + public void onShown(int datasetSize) { if (mCallback != null) { - mCallback.onShown(UI_TYPE_MENU); + mCallback.onShown(UI_TYPE_MENU, datasetSize); } } @@ -462,7 +462,7 @@ public final class AutoFillUI { @Override public void onShown() { - callback.onShown(UI_TYPE_DIALOG); + mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size()); } @Override diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 1831ecdaf77d..1bda70deac06 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -16,6 +16,7 @@ package com.android.server.autofill.ui; import static android.service.autofill.FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE; + import static com.android.server.autofill.Helper.paramsToString; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sFullScreenMode; @@ -90,7 +91,7 @@ final class FillUi { void onDatasetPicked(@NonNull Dataset dataset); void onCanceled(); void onDestroy(); - void onShown(); + void onShown(int datasetSize); void requestShowFillUi(int width, int height, IAutofillWindowPresenter windowPresenter); void requestHideFillUi(); @@ -742,7 +743,8 @@ final class FillUi { mWm.addView(mContentView, params); mOverlayControl.hideOverlays(); mShowing = true; - mCallback.onShown(); + int numShownDatasets = (mAdapter == null) ? 0 : mAdapter.getCount(); + mCallback.onShown(numShownDatasets); } else { mWm.updateViewLayout(mContentView, params); } diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java index 38bbfc4d9108..d3313374243d 100644 --- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -466,12 +466,25 @@ public class ContextualSearchManagerService extends SystemService { issueToken(); Bundle bundle = new Bundle(); bundle.putParcelable(ContextualSearchManager.EXTRA_TOKEN, mToken); - try { - callback.onResult( + // We get take the screenshot with the system server's identity because the system + // server has READ_FRAME_BUFFER permission to get the screenshot. + Binder.withCleanCallingIdentity(() -> { + if (mWmInternal != null) { + bundle.putParcelable(ContextualSearchManager.EXTRA_SCREENSHOT, + mWmInternal.takeAssistScreenshot(Set.of( + TYPE_STATUS_BAR, + TYPE_NAVIGATION_BAR, + TYPE_NAVIGATION_BAR_PANEL, + TYPE_POINTER)) + .asBitmap().asShared()); + } + try { + callback.onResult( new ContextualSearchState(null, null, bundle)); - } catch (RemoteException e) { - Log.e(TAG, "Error invoking ContextualSearchCallback", e); - } + } catch (RemoteException e) { + Log.e(TAG, "Error invoking ContextualSearchCallback", e); + } + }); } synchronized (mLock) { mStateCallback = callback; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index d750065ee96f..d41de38ce2a8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -306,6 +306,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.TestUtilityService; import android.content.pm.UserInfo; +import android.content.pm.UserProperties; import android.content.pm.VersionedPackage; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -14642,7 +14643,7 @@ public class ActivityManagerService extends IActivityManager.Stub final StringBuilder sb = new StringBuilder("registerReceiver: "); sb.append(Binder.getCallingUid()); sb.append('/'); sb.append(receiverId == null ? "null" : receiverId); sb.append('/'); - final int actionsCount = filter.countActions(); + final int actionsCount = filter.safeCountActions(); if (actionsCount > 0) { for (int i = 0; i < actionsCount; ++i) { sb.append(filter.getAction(i)); @@ -18209,7 +18210,9 @@ public class ActivityManagerService extends IActivityManager.Stub /** * Stops user but allow delayed locking. Delayed locking keeps user unlocked even after - * stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true. + * stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true on the + * device or if the user has {@link UserProperties#getAllowStoppingUserWithDelayedLocking()} + * set to true. * * <p>When delayed locking is not enabled through the overlay, this call becomes the same * with {@link #stopUserWithCallback(int, IStopUserCallback)} call. @@ -18221,8 +18224,6 @@ public class ActivityManagerService extends IActivityManager.Stub * other {@code ActivityManager#USER_OP_*} codes for failure. * */ - // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows - // delayed locking behavior once the private space flag is finalized. @Override public int stopUserWithDelayedLocking(@UserIdInt int userId, IStopUserCallback callback) { return mUserController.stopUser(userId, /* allowDelayedLocking= */ true, diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index c6c1f9873a45..fa0e2ca3b2d4 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -384,9 +384,11 @@ class UserController implements Handler.Callback { * postponed until total number of unlocked users in the system reaches mMaxRunningUsers. * Once total number of unlocked users reach mMaxRunningUsers, least recently used user * will be locked. + * + * <p> Note: Even if this is false for the device as a whole, it is possible some users may + * individually allow delayed locking, as specified by + * {@link UserProperties#getAllowStoppingUserWithDelayedLocking()}. */ - // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows - // delayed locking behavior once the private space flag is finalized. @GuardedBy("mLock") private boolean mDelayUserDataLocking; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 5dd1480c2052..684cb245f62b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -13911,9 +13911,8 @@ public class AudioService extends IAudioService.Stub final int stream = AudioAttributes.toLegacyStreamType(aa); final boolean mutingFromVolume = getStreamVolume(stream) == 0; if (mutingFromVolume) { - if (DEBUG_VOL) { - Slog.d(TAG, "notification should not play due to muted stream " + stream); - } + Slog.i(TAG, "shouldNotificationSoundPlay false: muted stream:" + stream + + " attr:" + aa); return false; } @@ -13926,10 +13925,8 @@ public class AudioService extends IAudioService.Stub // is the owner of GAIN_TRANSIENT_EXCLUSIVE focus also recording? final boolean mutingFromFocusAndRecording = mRecordMonitor.isRecordingActiveForUid(uid); if (mutingFromFocusAndRecording) { - if (DEBUG_VOL) { - Slog.d(TAG, "notification should not play due to exclusive focus owner recording " - + " uid:" + uid); - } + Slog.i(TAG, "shouldNotificationSoundPlay false: exclusive focus owner recording " + + " uid:" + uid + " attr:" + aa); return false; } return true; diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index 469e8b7f26ad..276ab03d9f4b 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -108,6 +108,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } private final Context mContext; + private final BiometricManager mBiometricManager; @NonNull private final BiometricContext mBiometricContext; private final IStatusBarService mStatusBarService; @VisibleForTesting final IBiometricSysuiReceiver mSysuiReceiver; @@ -131,6 +132,7 @@ public final class AuthSession implements IBinder.DeathRecipient { private final String mOpPackageName; private final boolean mDebugEnabled; private final List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties; + private final List<Integer> mSfpsSensorIds; // The current state, which can be either idle, called, or started private @SessionState int mState = STATE_AUTH_IDLE; @@ -220,6 +222,11 @@ public final class AuthSession implements IBinder.DeathRecipient { mCancelled = false; mBiometricFrameworkStatsLogger = logger; mOperationContext = new OperationContextExt(true /* isBP */); + mBiometricManager = mContext.getSystemService(BiometricManager.class); + + mSfpsSensorIds = mFingerprintSensorProperties.stream().filter( + FingerprintSensorPropertiesInternal::isAnySidefpsType).map( + prop -> prop.sensorId).toList(); try { mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */); @@ -316,7 +323,7 @@ public final class AuthSession implements IBinder.DeathRecipient { Slog.w(TAG, "Received cookie but already cancelled (ignoring): " + cookie); return; } - if (hasAuthenticated()) { + if (hasAuthenticatedAndConfirmed()) { Slog.d(TAG, "onCookieReceived after successful auth"); return; } @@ -494,6 +501,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } case STATE_AUTH_STARTED: + case STATE_AUTH_PENDING_CONFIRM: case STATE_AUTH_STARTED_UI_SHOWING: { if (isAllowDeviceCredential() && errorLockout) { // SystemUI handles transition from biometric to device credential. @@ -539,7 +547,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onAcquired(int sensorId, int acquiredInfo, int vendorCode) { - if (hasAuthenticated()) { + if (hasAuthenticatedAndConfirmed()) { Slog.d(TAG, "onAcquired after successful auth"); return; } @@ -562,7 +570,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onSystemEvent(int event) { - if (hasAuthenticated()) { + if (hasAuthenticatedAndConfirmed()) { Slog.d(TAG, "onSystemEvent after successful auth"); return; } @@ -579,12 +587,15 @@ public final class AuthSession implements IBinder.DeathRecipient { void onDialogAnimatedIn(boolean startFingerprintNow) { if (mState != STATE_AUTH_STARTED && mState != STATE_ERROR_PENDING_SYSUI - && mState != STATE_AUTH_PAUSED) { + && mState != STATE_AUTH_PAUSED && mState != STATE_AUTH_PENDING_CONFIRM) { Slog.e(TAG, "onDialogAnimatedIn, unexpected state: " + mState); return; } - mState = STATE_AUTH_STARTED_UI_SHOWING; + if (mState != STATE_AUTH_PENDING_CONFIRM) { + mState = STATE_AUTH_STARTED_UI_SHOWING; + } + if (startFingerprintNow) { startAllPreparedFingerprintSensors(); } else { @@ -600,6 +611,7 @@ public final class AuthSession implements IBinder.DeathRecipient { if (mState != STATE_AUTH_STARTED && mState != STATE_AUTH_STARTED_UI_SHOWING && mState != STATE_AUTH_PAUSED + && mState != STATE_AUTH_PENDING_CONFIRM && mState != STATE_ERROR_PENDING_SYSUI) { Slog.w(TAG, "onStartFingerprint, started from unexpected state: " + mState); } @@ -608,7 +620,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onTryAgainPressed() { - if (hasAuthenticated()) { + if (hasAuthenticatedAndConfirmed()) { Slog.d(TAG, "onTryAgainPressed after successful auth"); return; } @@ -625,7 +637,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onAuthenticationSucceeded(int sensorId, boolean strong, byte[] token) { - if (hasAuthenticated()) { + if (hasAuthenticatedAndConfirmed()) { Slog.d(TAG, "onAuthenticationSucceeded after successful auth"); return; } @@ -656,11 +668,17 @@ public final class AuthSession implements IBinder.DeathRecipient { Slog.e(TAG, "RemoteException", e); } - cancelAllSensors(sensor -> sensor.id != sensorId); + if (mState == STATE_AUTH_PENDING_CONFIRM) { + // Do not cancel Sfps sensors so auth can continue running + cancelAllSensors( + sensor -> sensor.id != sensorId && !mSfpsSensorIds.contains(sensor.id)); + } else { + cancelAllSensors(sensor -> sensor.id != sensorId); + } } void onAuthenticationRejected(int sensorId) { - if (hasAuthenticated()) { + if (hasAuthenticatedAndConfirmed()) { Slog.d(TAG, "onAuthenticationRejected after successful auth"); return; } @@ -678,7 +696,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onAuthenticationTimedOut(int sensorId, int cookie, int error, int vendorCode) { - if (hasAuthenticated()) { + if (hasAuthenticatedAndConfirmed()) { Slog.d(TAG, "onAuthenticationTimedOut after successful auth"); return; } @@ -703,7 +721,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onDeviceCredentialPressed() { - if (hasAuthenticated()) { + if (hasAuthenticatedAndConfirmed()) { Slog.d(TAG, "onDeviceCredentialPressed after successful auth"); return; } @@ -739,6 +757,10 @@ public final class AuthSession implements IBinder.DeathRecipient { return mAuthenticatedSensorId != -1; } + private boolean hasAuthenticatedAndConfirmed() { + return mAuthenticatedSensorId != -1 && mState == STATE_AUTHENTICATED_PENDING_SYSUI; + } + private void logOnDialogDismissed(@BiometricPrompt.DismissedReason int reason) { if (reason == BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED) { // Explicit auth, authentication confirmed. @@ -828,6 +850,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } else { Slog.e(TAG, "mTokenEscrow is null"); } + mClientReceiver.onAuthenticationSucceeded( Utils.getAuthenticationTypeForResult(reason)); break; @@ -861,6 +884,16 @@ public final class AuthSession implements IBinder.DeathRecipient { } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } finally { + if (mTokenEscrow != null && mBiometricManager != null) { + final byte[] byteToken = new byte[mTokenEscrow.length]; + for (int i = 0; i < mTokenEscrow.length; i++) { + byteToken[i] = mTokenEscrow[i]; + } + mBiometricManager.resetLockoutTimeBound(mToken, + mContext.getOpPackageName(), + mAuthenticatedSensorId, mUserId, byteToken); + } + // ensure everything is cleaned up when dismissed cancelAllSensors(); } @@ -874,7 +907,7 @@ public final class AuthSession implements IBinder.DeathRecipient { * @return true if this AuthSession is finished, e.g. should be set to null */ boolean onCancelAuthSession(boolean force) { - if (hasAuthenticated()) { + if (hasAuthenticatedAndConfirmed()) { Slog.d(TAG, "onCancelAuthSession after successful auth"); return true; } diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 1e2451c2f916..daaafcb61bc5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -242,14 +242,14 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions> byteToken[i] = hardwareAuthToken.get(i); } - if (mIsStrongBiometric) { - mBiometricManager.resetLockoutTimeBound(getToken(), - getContext().getOpPackageName(), - getSensorId(), getTargetUserId(), byteToken); - } - // For BP, BiometricService will add the authToken to Keystore. - if (!isBiometricPrompt() && mIsStrongBiometric) { + if (!isBiometricPrompt()) { + if (mIsStrongBiometric) { + mBiometricManager.resetLockoutTimeBound(getToken(), + getContext().getOpPackageName(), + getSensorId(), getTargetUserId(), byteToken); + } + final int result = KeyStoreAuthorization.getInstance().addAuthToken(byteToken); if (result != 0) { Slog.d(TAG, "Error adding auth token : " + result); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 266093229186..72d92b974c1a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -209,7 +209,7 @@ public class FingerprintAuthenticationClient Slog.d(TAG, "Lockout is implemented by the HAL"); return; } - if (authenticated) { + if (authenticated && !isBiometricPrompt()) { getLockoutTracker().resetFailedAttemptsForUser(true /* clearAttemptCounter */, getTargetUserId()); } else { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 9e8bf0e2a060..69452310f6cc 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -195,6 +195,18 @@ final class InputMethodBindingController { } /** + * Returns {@link InputMethodInfo} that is queried from {@link #getSelectedMethodId()}. + * + * @return {@link InputMethodInfo} whose IME ID is the same as {@link #getSelectedMethodId()}. + * {@code null} otherwise + */ + @GuardedBy("ImfLock.class") + @Nullable + InputMethodInfo getSelectedMethod() { + return InputMethodSettingsRepository.get(mUserId).getMethodMap().get(mSelectedMethodId); + } + + /** * The token we have made for the currently active input method, to * identify it in the future. */ diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 223d54842b16..c4b1f4010af5 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -270,6 +270,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000; private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; + + private static final int INVALID_SUBTYPE_HASHCODE = + InputMethodSettings.INVALID_SUBTYPE_HASHCODE; + private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; private static final String HANDLER_THREAD_NAME = "android.imms"; private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2"; @@ -322,7 +326,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private final Handler mHandler; @NonNull - private final Handler mPackageMonitorHandler; + private final Handler mIoHandler; @MultiUserUnawareField @UserIdInt @@ -1237,7 +1241,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Context context, boolean experimentalConcurrentMultiUserModeEnabled, @Nullable ServiceThread serviceThreadForTesting, - @Nullable ServiceThread packageMonitorThreadForTesting, + @Nullable ServiceThread ioThreadForTesting, @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) { synchronized (ImfLock.class) { mExperimentalConcurrentMultiUserModeEnabled = @@ -1258,15 +1262,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. thread.start(); mHandler = Handler.createAsync(thread.getLooper(), this); { - final ServiceThread packageMonitorThread = - packageMonitorThreadForTesting != null - ? packageMonitorThreadForTesting + final ServiceThread ioThread = + ioThreadForTesting != null + ? ioThreadForTesting : new ServiceThread( PACKAGE_MONITOR_THREAD_NAME, Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); - packageMonitorThread.start(); - mPackageMonitorHandler = Handler.createAsync(packageMonitorThread.getLooper()); + ioThread.start(); + mIoHandler = Handler.createAsync(ioThread.getLooper()); } SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler); mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null @@ -1537,7 +1541,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes"); - mMyPackageMonitor.register(mContext, UserHandle.ALL, mPackageMonitorHandler); + mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler); mSettingsObserver.registerContentObserverLocked(currentUserId); final IntentFilter broadcastFilterForAllUsers = new IntentFilter(); @@ -2005,17 +2009,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") @NonNull InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) { + final int userId = mCurrentUserId; + final var bindingController = getInputMethodBindingController(userId); if (!mBoundToMethod) { - getCurMethodLocked().bindInput(mCurClient.mBinding); + bindingController.getCurMethod().bindInput(mCurClient.mBinding); mBoundToMethod = true; } final boolean restarting = !initial; final Binder startInputToken = new Binder(); - final var bindingController = getInputMethodBindingController(mCurrentUserId); final StartInputInfo info = new StartInputInfo(mCurrentUserId, - getCurTokenLocked(), - getCurTokenDisplayIdLocked(), bindingController.getCurId(), startInputReason, + bindingController.getCurToken(), bindingController.getCurTokenDisplayId(), + bindingController.getCurId(), startInputReason, restarting, UserHandle.getUserId(mCurClient.mUid), mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo, mImeBindingState.mFocusedWindowSoftInputMode, @@ -2028,9 +2033,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // same-user scenarios. // That said ignoring cross-user scenario will never affect IMEs that do not have // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case. - if (mCurrentUserId == UserHandle.getUserId( - mCurClient.mUid)) { - mPackageManagerInternal.grantImplicitAccess(mCurrentUserId, null /* intent */, + if (userId == UserHandle.getUserId(mCurClient.mUid)) { + mPackageManagerInternal.grantImplicitAccess(userId, null /* intent */, UserHandle.getAppId(bindingController.getCurMethodUid()), mCurClient.mUid, true /* direct */); } @@ -2060,7 +2064,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } final var curId = bindingController.getCurId(); - final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId) + final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(userId) .getMethodMap().get(curId); final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); @@ -3106,6 +3110,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // If subtype is null, try to find the most applicable one from // getCurrentInputMethodSubtype. subtypeId = NOT_A_SUBTYPE_ID; + // TODO(b/347083680): The method below has questionable behaviors. newSubtype = getCurrentInputMethodSubtypeLocked(); if (newSubtype != null) { for (int i = 0; i < subtypeCount; ++i) { @@ -4192,10 +4197,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) { - final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final int userId = mCurrentUserId; + final var currentImi = getInputMethodBindingController(userId).getSelectedMethod(); final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( - onlyCurrentIme, settings.getMethodMap().get(getSelectedMethodIdLocked()), - mCurrentSubtype); + onlyCurrentIme, currentImi, mCurrentSubtype); if (nextSubtype == null) { return false; } @@ -4210,10 +4215,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!calledWithValidTokenLocked(token)) { return false; } - final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final int userId = mCurrentUserId; + final var currentImi = getInputMethodBindingController(userId).getSelectedMethod(); final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( - false /* onlyCurrentIme */, - settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype); + false /* onlyCurrentIme */, currentImi, mCurrentSubtype); return nextSubtype != null; } } @@ -4648,8 +4653,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mCurrentUserId != mSwitchingController.getUserId()) { return; } - final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId) - .getMethodMap().get(getSelectedMethodIdLocked()); + final var imi = getInputMethodBindingController(mCurrentUserId).getSelectedMethod(); if (imi != null) { mSwitchingController.onUserActionLocked(imi, mCurrentSubtype); } @@ -5443,20 +5447,26 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mCurrentSubtype); // Set Subtype here + final int newSubtypeHashcode; + final InputMethodSubtype newSubtype; if (imi == null || subtypeId < 0) { - settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); - mCurrentSubtype = null; + newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE; + newSubtype = null; } else { if (subtypeId < imi.getSubtypeCount()) { InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId); - settings.putSelectedSubtype(subtype.hashCode()); - mCurrentSubtype = subtype; + newSubtypeHashcode = subtype.hashCode(); + newSubtype = subtype; } else { - settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + // TODO(b/347093491): Probably this should be determined from the new subtype. + newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE; // If the subtype is not specified, choose the most applicable one - mCurrentSubtype = getCurrentInputMethodSubtypeLocked(); + // TODO(b/347083680): The method below has questionable behaviors. + newSubtype = getCurrentInputMethodSubtypeLocked(); } } + mCurrentSubtype = newSubtype; + settings.putSelectedSubtype(newSubtypeHashcode); notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, mCurrentSubtype); if (!setSubtypeOnly) { @@ -5506,6 +5516,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } synchronized (ImfLock.class) { if (mCurrentUserId == userId) { + // TODO(b/347083680): The method below has questionable behaviors. return getCurrentInputMethodSubtypeLocked(); } @@ -5523,6 +5534,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * * <p>TODO: Address code duplication between this and * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}.</p> + * + * <p>Also this method has had questionable behaviors:</p> + * <ul> + * <li>Calling this method can update {@link #mCurrentSubtype}.</li> + * <li>This method may return {@link #mCurrentSubtype} as-is, even if it does not belong + * to the current IME.</li> + * </ul> + * <p>TODO(b/347083680): Address above issues.</p> */ @GuardedBy("ImfLock.class") InputMethodSubtype getCurrentInputMethodSubtypeLocked() { @@ -5906,27 +5925,36 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. synchronized (ImfLock.class) { final int uid = Binder.getCallingUid(); - if (getSelectedMethodIdLocked() == null) { + final int imeUserId = UserHandle.getUserId(uid); + if (imeUserId != mCurrentUserId) { + // Currently concurrent multi-user is not supported here due to the remaining + // dependency on mCurEditorInfo and mCurClient. + // TODO(b/341558132): Remove this early-exit once it becomes multi-user ready. + Slog.i(TAG, "Ignoring createInputContentUriToken due to user ID mismatch." + + " imeUserId=" + imeUserId + " mCurrentUserId=" + mCurrentUserId); return null; } - if (getCurTokenLocked() != token) { - Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + getCurTokenLocked() - + " token=" + token); + final var bindingController = getInputMethodBindingController(imeUserId); + if (bindingController.getSelectedMethodId() == null) { + return null; + } + if (bindingController.getCurToken() != token) { + Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + + bindingController.getCurToken() + " token=" + token); return null; } // We cannot simply distinguish a bad IME that reports an arbitrary package name from // an unfortunate IME whose internal state is already obsolete due to the asynchronous // nature of our system. Let's compare it with our internal record. - final var curPackageName = mCurEditorInfo != null - ? mCurEditorInfo.packageName : null; + // TODO(b/341558132): Use "imeUserId" to query per-user "curEditorInfo" + final var curPackageName = mCurEditorInfo != null ? mCurEditorInfo.packageName : null; if (!TextUtils.equals(curPackageName, packageName)) { Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName=" + curPackageName + " packageName=" + packageName); return null; } - // This user ID can never bee spoofed. - final int imeUserId = UserHandle.getUserId(uid); - // This user ID can never bee spoofed. + // This user ID can never be spoofed. + // TODO(b/341558132): Use "imeUserId" to query per-user "curClient" final int appUserId = UserHandle.getUserId(mCurClient.mUid); // This user ID may be invalid if "contentUri" embedded an invalid user ID. final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri, diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java index 7ce4074bb1d0..5569e1e5211e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java @@ -58,7 +58,7 @@ final class InputMethodSettings { * used {@code -1} here. We cannot change this value as it's already saved into secure settings. * </p> */ - private static final int INVALID_SUBTYPE_HASHCODE = -1; + static final int INVALID_SUBTYPE_HASHCODE = -1; /** * A string code that represents "no subtype" when a subtype hashcode is used. * diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java index 60b9a4cfe840..68924b5f370f 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java @@ -78,7 +78,7 @@ final class InputMethodSettingsRepository { userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); - sPerUserMap.put(userId, settings); + put(userId, settings); } } }); diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java index 880787e8664c..12495bb4f2cc 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java +++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java @@ -619,7 +619,18 @@ class GnssNetworkConnectivityHandler { ServiceState state = telephonyManager.getServiceState(); if (state != null && state.isUsingNonTerrestrialNetwork()) { networkRequestBuilder.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); - networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE); + try { + networkRequestBuilder.addTransportType(NetworkCapabilities + .TRANSPORT_SATELLITE); + networkRequestBuilder.removeCapability(NetworkCapabilities + .NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED); + } catch (IllegalArgumentException ignored) { + // In case TRANSPORT_SATELLITE or NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED + // are not recognized, meaning an old connectivity module runs on new + // android in which case no network with such capabilities will be brought + // up, so it's safe to ignore the exception. + // TODO: Can remove the try-catch in next quarter release. + } } } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java b/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java index 6578853909c9..b532d5a8f8fc 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java +++ b/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java @@ -21,9 +21,9 @@ import android.os.Bundle; import android.os.PersistableBundle; import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService; import android.util.Slog; +import android.util.Base64; import java.io.IOException; -import java.util.Base64; import java.util.Comparator; import java.util.List; import java.util.TreeSet; @@ -53,7 +53,7 @@ public class InferenceInfoStore { String infoBytesBase64String = pb.getString( OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY); if (infoBytesBase64String != null) { - byte[] infoBytes = Base64.getDecoder().decode(infoBytesBase64String); + byte[] infoBytes = Base64.decode(infoBytesBase64String, Base64.DEFAULT); com.android.server.ondeviceintelligence.nano.InferenceInfo inferenceInfo = com.android.server.ondeviceintelligence.nano.InferenceInfo.parseFrom( infoBytes); @@ -84,7 +84,9 @@ public class InferenceInfoStore { } private synchronized void add(com.android.server.ondeviceintelligence.nano.InferenceInfo info) { - while (System.currentTimeMillis() - inferenceInfos.first().getStartTimeMs() > maxAgeMs) { + while (!inferenceInfos.isEmpty() + && System.currentTimeMillis() - inferenceInfos.first().getStartTimeMs() + > maxAgeMs) { inferenceInfos.pollFirst(); } inferenceInfos.add(toInferenceInfo(info)); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 06065638dc11..00e9d8d595f1 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -33,6 +33,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE; import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT; import static android.content.pm.PackageManager.INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE; +import static android.content.pm.PackageManager.INSTALL_FAILED_SESSION_INVALID; import static android.content.pm.PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_STAGED; @@ -3459,11 +3460,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - if (mHasAppMetadataFile && !getStagedAppMetadataFile().exists()) { - throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE, - "App metadata file expected but not found in " + stageDir.getAbsolutePath()); - } - final List<ApkLite> addedFiles = getAddedApkLitesLocked(); if (addedFiles.isEmpty() && (removeSplitList.size() == 0 || mHasAppMetadataFile)) { @@ -3593,6 +3589,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + File stagedAppMetadataFile = isIncrementalInstallation() + ? getTmpAppMetadataFile() : getStagedAppMetadataFile(); + if (mHasAppMetadataFile && !stagedAppMetadataFile.exists()) { + throw new PackageManagerException(INSTALL_FAILED_SESSION_INVALID, + "App metadata file expected but not found in " + stageDir.getAbsolutePath()); + } + if (isIncrementalInstallation()) { if (!isIncrementalInstallationAllowed(existingPkgSetting)) { throw new PackageManagerException( @@ -3601,8 +3604,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // Since we moved the staged app metadata file so that incfs can be initialized, lets // now move it back. - File appMetadataFile = getTmpAppMetadataFile(); - if (appMetadataFile.exists()) { + if (mHasAppMetadataFile) { + File appMetadataFile = getTmpAppMetadataFile(); final IncrementalFileStorages incrementalFileStorages = getIncrementalFileStorages(); try { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 66a93d7ffc47..c0b8034b9a56 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3999,7 +3999,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService final PackageMetrics.ComponentStateMetrics componentStateMetrics = new PackageMetrics.ComponentStateMetrics(setting, UserHandle.getUid(userId, packageSetting.getAppId()), - packageSetting.getEnabled(userId), callingUid); + setting.isComponent() ? computer.getComponentEnabledSettingInternal( + setting.getComponentName(), callingUid, userId) + : packageSetting.getEnabled(userId), callingUid); if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId, callingPackage)) { continue; diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java index 0052d4f28b93..7f2476979635 100644 --- a/services/core/java/com/android/server/power/ThermalManagerService.java +++ b/services/core/java/com/android/server/power/ThermalManagerService.java @@ -543,7 +543,7 @@ public class ThermalManagerService extends SystemService { if (!mHalReady.get()) { FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, getCallingUid(), FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__HAL_NOT_READY, - Float.NaN); + Float.NaN, forecastSeconds); return Float.NaN; } @@ -553,7 +553,7 @@ public class ThermalManagerService extends SystemService { } FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, getCallingUid(), FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__INVALID_ARGUMENT, - Float.NaN); + Float.NaN, forecastSeconds); return Float.NaN; } @@ -1778,7 +1778,7 @@ public class ThermalManagerService extends SystemService { FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, Binder.getCallingUid(), FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE, - Float.NaN); + Float.NaN, forecastSeconds); return Float.NaN; } @@ -1789,7 +1789,7 @@ public class ThermalManagerService extends SystemService { FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, Binder.getCallingUid(), THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE_THRESHOLD, - Float.NaN); + Float.NaN, forecastSeconds); return Float.NaN; } @@ -1828,12 +1828,12 @@ public class ThermalManagerService extends SystemService { FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, Binder.getCallingUid(), THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE_THRESHOLD, - Float.NaN); + Float.NaN, forecastSeconds); } else { FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, Binder.getCallingUid(), FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__SUCCESS, - maxNormalized); + maxNormalized, forecastSeconds); } return maxNormalized; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5159fc4009f7..d9dc7ba9ad12 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -37,6 +37,7 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; import static android.app.CameraCompatTaskInfo.cameraCompatControlStateToString; import static android.app.WaitResult.INVALID_DELAY; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; @@ -46,6 +47,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; @@ -855,7 +857,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @CameraCompatControlState private int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN; - // The callback that allows to ask the calling View to apply the treatment for stretched // issues affecting camera viewfinders when the user clicks on the camera compat control. @Nullable @@ -6562,10 +6563,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Schedule an idle timeout in case the app doesn't do it for us. mTaskSupervisor.scheduleIdleTimeout(this); - mTaskSupervisor.mStoppingActivities.remove(this); - if (getDisplayArea().allResumedActivitiesComplete()) { - mRootWindowContainer.executeAppTransitionForAllDisplay(); - } + mTaskSupervisor.reportResumedActivityLocked(this); resumeKeyDispatchingLocked(); final Task rootTask = getRootTask(); @@ -8559,11 +8557,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // and back which can cause visible issues (see b/184078928). final int parentWindowingMode = newParentConfiguration.windowConfiguration.getWindowingMode(); + final boolean isInCameraCompatFreeform = parentWindowingMode == WINDOWING_MODE_FREEFORM + && mLetterboxUiController.getFreeformCameraCompatMode() + != CAMERA_COMPAT_FREEFORM_NONE; // Bubble activities should always fill their parent and should not be letterboxed. final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble() && (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW || parentWindowingMode == WINDOWING_MODE_FULLSCREEN + || isInCameraCompatFreeform // When starting to switch between PiP and fullscreen, the task is pinned // and the activity is fullscreen. But only allow to apply letterbox if the // activity is exiting PiP because an entered PiP should fill the task. @@ -8701,9 +8703,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig || getCompatDisplayInsets() != null || (isFloating(parentWindowingMode) - // Check the windowing mode of activity as well in case it is switching - // between PiP and fullscreen. - && isFloating(inOutConfig.windowConfiguration.getWindowingMode())) + // Check the requested windowing mode of activity as well in case it is + // switching between PiP and fullscreen. + && (inOutConfig.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_UNDEFINED + || isFloating(inOutConfig.windowConfiguration.getWindowingMode()))) || rotation == ROTATION_UNDEFINED)) { // If the insets configuration decoupled logic is not enabled for the app, or the app // already has a compat override, or the context doesn't contain enough info to diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index b6e6991656f2..3867d2d229ea 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2064,6 +2064,21 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } } + boolean reportResumedActivityLocked(ActivityRecord r) { + // A resumed activity cannot be stopping. remove from list + mStoppingActivities.remove(r); + + final Task rootTask = r.getRootTask(); + if (rootTask.getDisplayArea().allResumedActivitiesComplete()) { + mRootWindowContainer.ensureActivitiesVisible(); + // Make sure activity & window visibility should be identical + // for all displays in this stage. + mRootWindowContainer.executeAppTransitionForAllDisplay(); + return true; + } + return false; + } + // Called when WindowManager has finished animating the launchingBehind activity to the back. private void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) { final Task task = r.getTask(); diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 0febec9169c0..1ce324f68569 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; @@ -201,6 +202,8 @@ class BackNavigationController { infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback()); infoBuilder.setAnimationCallback(callbackInfo.isAnimationCallback()); infoBuilder.setTouchableRegion(window.getFrame()); + infoBuilder.setAppProgressAllowed((window.getAttrs().privateFlags + & PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED) != 0); mNavigationMonitor.startMonitor(window, navigationObserver); ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, " @@ -1366,8 +1369,6 @@ class BackNavigationController { ? task.getSurfaceControl() : mAdaptors[0].mTarget.getSurfaceControl()); } - // remove starting surface. - mStartingSurface = null; } } @@ -1384,7 +1385,10 @@ class BackNavigationController { .removeWindowlessStartingSurface(mRequestedStartingSurfaceId, !openTransitionMatch); mRequestedStartingSurfaceId = INVALID_TASK_ID; - mStartingSurface = null; + if (mStartingSurface != null && mStartingSurface.isValid()) { + mStartingSurface.release(); + mStartingSurface = null; + } } } diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java new file mode 100644 index 000000000000..0c751cfe4f46 --- /dev/null +++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2024 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.wm; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; + +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.annotation.NonNull; +import android.app.CameraCompatTaskInfo; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.ProtoLogGroup; +import com.android.internal.protolog.common.ProtoLog; +import com.android.window.flags.Flags; + +/** + * Policy for camera compatibility freeform treatment. + * + * <p>The treatment is applied to a fixed-orientation camera activity in freeform windowing mode. + * The treatment letterboxes or pillarboxes the activity to the expected orientation and provides + * changes to the camera and display orientation signals to match those expected on a portrait + * device in that orientation (for example, on a standard phone). + */ +final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompatStateListener, + ActivityRefresher.Evaluator { + private static final String TAG = TAG_WITH_CLASS_NAME ? "CameraCompatFreeformPolicy" : TAG_WM; + + @NonNull + private final DisplayContent mDisplayContent; + @NonNull + private final ActivityRefresher mActivityRefresher; + @NonNull + private final CameraStateMonitor mCameraStateMonitor; + + private boolean mIsCameraCompatTreatmentPending = false; + + CameraCompatFreeformPolicy(@NonNull DisplayContent displayContent, + @NonNull CameraStateMonitor cameraStateMonitor, + @NonNull ActivityRefresher activityRefresher) { + mDisplayContent = displayContent; + mCameraStateMonitor = cameraStateMonitor; + mActivityRefresher = activityRefresher; + } + + void start() { + mCameraStateMonitor.addCameraStateListener(this); + mActivityRefresher.addEvaluator(this); + } + + /** Releases camera callback listener. */ + void dispose() { + mCameraStateMonitor.removeCameraStateListener(this); + mActivityRefresher.removeEvaluator(this); + } + + // Refreshing only when configuration changes after rotation or camera split screen aspect ratio + // treatment is enabled. + @Override + public boolean shouldRefreshActivity(@NonNull ActivityRecord activity, + @NonNull Configuration newConfig, + @NonNull Configuration lastReportedConfig) { + return isTreatmentEnabledForActivity(activity) && mIsCameraCompatTreatmentPending; + } + + /** + * Whether activity is eligible for camera compatibility free-form treatment. + * + * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing + * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and + * provides changes to the camera and display orientation signals to match those expected on a + * portrait device in that orientation (for example, on a standard phone). + * + * <p>The treatment is enabled when the following conditions are met: + * <ul> + * <li>Property gating the camera compatibility free-form treatment is enabled. + * <li>Activity isn't opted out by the device manufacturer with override. + * </ul> + */ + @VisibleForTesting + boolean shouldApplyFreeformTreatmentForCameraCompat(@NonNull ActivityRecord activity) { + return Flags.cameraCompatForFreeform() && !activity.info.isChangeEnabled( + ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT); + } + + @Override + public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, + @NonNull String cameraId) { + if (!isTreatmentEnabledForActivity(cameraActivity)) { + return false; + } + final int existingCameraCompatMode = + cameraActivity.mLetterboxUiController.getFreeformCameraCompatMode(); + final int newCameraCompatMode = getCameraCompatMode(cameraActivity); + if (newCameraCompatMode != existingCameraCompatMode) { + mIsCameraCompatTreatmentPending = true; + cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode(newCameraCompatMode); + forceUpdateActivityAndTask(cameraActivity); + return true; + } else { + mIsCameraCompatTreatmentPending = false; + } + return false; + } + + @Override + public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity, + @NonNull String cameraId) { + if (isActivityForCameraIdRefreshing(cameraId)) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_STATES, + "Display id=%d is notified that Camera %s is closed but activity is" + + " still refreshing. Rescheduling an update.", + mDisplayContent.mDisplayId, cameraId); + return false; + } + cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode( + CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE); + forceUpdateActivityAndTask(cameraActivity); + mIsCameraCompatTreatmentPending = false; + return true; + } + + private void forceUpdateActivityAndTask(ActivityRecord cameraActivity) { + cameraActivity.recomputeConfiguration(); + cameraActivity.updateReportedConfigurationAndSend(); + Task cameraTask = cameraActivity.getTask(); + if (cameraTask != null) { + cameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true); + } + } + + private static int getCameraCompatMode(@NonNull ActivityRecord topActivity) { + return switch (topActivity.getRequestedConfigurationOrientation()) { + case ORIENTATION_PORTRAIT -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT; + case ORIENTATION_LANDSCAPE -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE; + default -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; + }; + } + + /** + * Whether camera compat treatment is applicable for the given activity, ignoring its windowing + * mode. + * + * <p>Conditions that need to be met: + * <ul> + * <li>Treatment is enabled. + * <li>Camera is active for the package. + * <li>The app has a fixed orientation. + * <li>The app is in freeform windowing mode. + * </ul> + */ + private boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) { + int orientation = activity.getRequestedConfigurationOrientation(); + return shouldApplyFreeformTreatmentForCameraCompat(activity) + && mCameraStateMonitor.isCameraRunningForActivity(activity) + && orientation != ORIENTATION_UNDEFINED + && activity.inFreeformWindowingMode() + // "locked" and "nosensor" values are often used by camera apps that can't + // handle dynamic changes so we shouldn't force-letterbox them. + && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR + && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED + // TODO(b/332665280): investigate whether we can support activity embedding. + && !activity.isEmbedded(); + } + + private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) { + final ActivityRecord topActivity = mDisplayContent.topRunningActivity( + /* considerKeyguardState= */ true); + if (topActivity == null || !isTreatmentEnabledForActivity(topActivity) + || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) { + return false; + } + return topActivity.mLetterboxUiController.isRefreshRequested(); + } +} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 87ee5d8f7f13..ad711cb2af31 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -263,6 +263,7 @@ import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.RegionUtils; import com.android.server.wm.utils.RotationCache; import com.android.server.wm.utils.WmDisplayCutout; +import com.android.window.flags.Flags; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -477,6 +478,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy; @Nullable + final CameraCompatFreeformPolicy mCameraCompatFreeformPolicy; + @Nullable final CameraStateMonitor mCameraStateMonitor; @Nullable final ActivityRefresher mActivityRefresher; @@ -683,7 +686,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private InputTarget mLastImeInputTarget; - /** * Tracks the windowToken of the input method input target and the corresponding * {@link WindowContainerListener} for monitoring changes (e.g. the requested visibility @@ -1233,11 +1235,26 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // without the need to restart the device. final boolean shouldCreateDisplayRotationCompatPolicy = mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime(); - if (shouldCreateDisplayRotationCompatPolicy) { + final boolean shouldCreateCameraCompatFreeformPolicy = Flags.cameraCompatForFreeform() + && DesktopModeLaunchParamsModifier.canEnterDesktopMode(mWmService.mContext); + if (shouldCreateDisplayRotationCompatPolicy || shouldCreateCameraCompatFreeformPolicy) { mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH); mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH); - mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy( - this, mCameraStateMonitor, mActivityRefresher); + if (shouldCreateDisplayRotationCompatPolicy) { + mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(this, + mCameraStateMonitor, mActivityRefresher); + mDisplayRotationCompatPolicy.start(); + } else { + mDisplayRotationCompatPolicy = null; + } + + if (shouldCreateCameraCompatFreeformPolicy) { + mCameraCompatFreeformPolicy = new CameraCompatFreeformPolicy(this, + mCameraStateMonitor, mActivityRefresher); + mCameraCompatFreeformPolicy.start(); + } else { + mCameraCompatFreeformPolicy = null; + } mCameraStateMonitor.startListeningToCameraState(); } else { @@ -1245,9 +1262,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mCameraStateMonitor = null; mActivityRefresher = null; mDisplayRotationCompatPolicy = null; + mCameraCompatFreeformPolicy = null; } - mRotationReversionController = new DisplayRotationReversionController(this); mInputMonitor = new InputMonitor(mWmService, this); @@ -3350,6 +3367,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (mDisplayRotationCompatPolicy != null) { mDisplayRotationCompatPolicy.dispose(); } + + if (mCameraCompatFreeformPolicy != null) { + mCameraCompatFreeformPolicy.dispose(); + } + if (mCameraStateMonitor != null) { mCameraStateMonitor.dispose(); } diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index e0cc064fcacc..6ecafdb03d20 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -80,8 +80,11 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp mDisplayContent = displayContent; mWmService = displayContent.mWmService; mCameraStateMonitor = cameraStateMonitor; - mCameraStateMonitor.addCameraStateListener(this); mActivityRefresher = activityRefresher; + } + + void start() { + mCameraStateMonitor.addCameraStateListener(this); mActivityRefresher.addEvaluator(this); } @@ -365,7 +368,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp } // TODO(b/336474959): Do we need cameraId here? - private boolean isActivityForCameraIdRefreshing(String cameraId) { + private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) { final ActivityRecord topActivity = mDisplayContent.topRunningActivity( /* considerKeyguardState= */ true); if (!isTreatmentEnabledForActivity(topActivity) diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 5e93e8930bab..9a513752dee2 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; @@ -103,6 +104,7 @@ import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTy import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.TaskDescription; +import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode; import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.pm.PackageManager; import android.content.res.Configuration; @@ -231,6 +233,9 @@ final class LetterboxUiController { private boolean mDoubleTapEvent; + @FreeformCameraCompatMode + private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE; + LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) { mLetterboxConfiguration = wmService.mLetterboxConfiguration; // Given activityRecord may not be fully constructed since LetterboxUiController @@ -711,6 +716,15 @@ final class LetterboxUiController { .isTreatmentEnabledForActivity(mActivityRecord); } + @FreeformCameraCompatMode + int getFreeformCameraCompatMode() { + return mFreeformCameraCompatMode; + } + + void setFreeformCameraCompatMode(@FreeformCameraCompatMode int freeformCameraCompatMode) { + mFreeformCameraCompatMode = freeformCameraCompatMode; + } + private boolean isCompatChangeEnabled(long overrideChangeId) { return mActivityRecord.info.isChangeEnabled(overrideChangeId); } @@ -871,12 +885,14 @@ final class LetterboxUiController { // Check if we are in the given pose and in fullscreen mode. // Note that we check the task rather than the parent as with ActivityEmbedding the parent might // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is - // actually fullscreen. + // actually fullscreen. If display is still in transition e.g. unfolding, don't return true + // for HALF_FOLDED state or app will flicker. private boolean isDisplayFullScreenAndInPosture(boolean isTabletop) { Task task = mActivityRecord.getTask(); return mActivityRecord.mDisplayContent != null && task != null && mActivityRecord.mDisplayContent.getDisplayRotation().isDeviceInPosture( DeviceStateController.DeviceState.HALF_FOLDED, isTabletop) + && !mActivityRecord.mDisplayContent.inTransition() && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 54ba47eeb441..f5ab38f72b54 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1905,7 +1905,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Don't do recursive work. return; } - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RWC_ensureActivitiesVisible"); mTaskSupervisor.beginActivityVisibilityUpdate(); try { // First the front root tasks. In case any are not fullscreen and are in front of home. @@ -1915,7 +1914,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } finally { mTaskSupervisor.endActivityVisibilityUpdate(); - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 22f718ddbd22..787c5d6f7699 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3515,7 +3515,10 @@ class Task extends TaskFragment { && !appCompatTaskInfo.topActivityInSizeCompat && top.mLetterboxUiController.shouldEnableUserAspectRatioSettings() && !info.isTopActivityTransparent; - appCompatTaskInfo.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed(); + appCompatTaskInfo.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed(); + appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top == null + ? CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE + : top.mLetterboxUiController.getFreeformCameraCompatMode(); } /** diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index b19de189af5a..1f0c827083ac 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -362,6 +362,7 @@ public: void notifyDropWindow(const sp<IBinder>& token, float x, float y) override; void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, const std::set<gui::Uid>& uids) override; + void notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) override; /* --- PointerControllerPolicyInterface implementation --- */ @@ -1108,6 +1109,10 @@ void NativeInputManager::notifyVibratorState(int32_t deviceId, bool isOn) { checkAndClearExceptionFromCallback(env, "notifyVibratorState"); } +void NativeInputManager::notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) { + mInputManager->getChoreographer().setFocusedDisplay(displayId); +} + void NativeInputManager::displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId) { mInputManager->getDispatcher().displayRemoved(displayId); } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index 3b25cb13e66c..42bd75a7a67e 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -127,7 +127,7 @@ public class InputMethodManagerServiceTestBase { protected IInputMethodInvoker mMockInputMethodInvoker; protected InputMethodManagerService mInputMethodManagerService; protected ServiceThread mServiceThread; - protected ServiceThread mPackageMonitorThread; + protected ServiceThread mIoThread; protected boolean mIsLargeScreen; private InputManagerGlobal.TestSession mInputManagerGlobalSession; @@ -226,14 +226,14 @@ public class InputMethodManagerServiceTestBase { "immstest1", Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); - mPackageMonitorThread = + mIoThread = new ServiceThread( "immstest2", Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); mInputMethodManagerService = new InputMethodManagerService(mContext, InputMethodManagerService.shouldEnableExperimentalConcurrentMultiUserMode(mContext), - mServiceThread, mPackageMonitorThread, + mServiceThread, mIoThread, unusedUserId -> mMockInputMethodBindingController); spyOn(mInputMethodManagerService); @@ -267,8 +267,8 @@ public class InputMethodManagerServiceTestBase { mInputMethodManagerService.mInputMethodDeviceConfigs.destroy(); } - if (mPackageMonitorThread != null) { - mPackageMonitorThread.quitSafely(); + if (mIoThread != null) { + mIoThread.quitSafely(); } if (mServiceThread != null) { diff --git a/services/tests/ondeviceintelligencetests/Android.bp b/services/tests/ondeviceintelligencetests/Android.bp new file mode 100644 index 000000000000..aa859422f54f --- /dev/null +++ b/services/tests/ondeviceintelligencetests/Android.bp @@ -0,0 +1,62 @@ +// Copyright (C) 2024 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "FrameworksOnDeviceIntelligenceTests", + team: "trendy_team_machine_learning", + defaults: [ + "modules-utils-testable-device-config-defaults", + ], + + srcs: [ + "src/**/*.java", + ], + + static_libs: [ + "androidx.test.core", + "androidx.test.runner", + "androidx.test.ext.truth", + "mockito-target-extended-minus-junit4", + "platform-test-annotations", + "services.core", + "servicestests-core-utils", + "servicestests-utils-mockito-extended", + "truth", + "frameworks-base-testutils", + "androidx.test.rules", + ], + + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], + + optimize: { + enabled: false, + }, +} diff --git a/services/tests/ondeviceintelligencetests/AndroidManifest.xml b/services/tests/ondeviceintelligencetests/AndroidManifest.xml new file mode 100644 index 000000000000..8bd111e96638 --- /dev/null +++ b/services/tests/ondeviceintelligencetests/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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.ondeviceintelligencetests"> + + <application android:testOnly="true" + android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.ondeviceintelligencetests" + android:label="Frameworks OnDeviceIntelligence Services Tests" /> + +</manifest> diff --git a/services/tests/ondeviceintelligencetests/AndroidTest.xml b/services/tests/ondeviceintelligencetests/AndroidTest.xml new file mode 100644 index 000000000000..3ae96c5039b2 --- /dev/null +++ b/services/tests/ondeviceintelligencetests/AndroidTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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 Frameworks OnDeviceIntelligence Services Tests."> + <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="FrameworksOnDeviceIntelligenceTests.apk" /> + </target_preparer> + + <option name="test-tag" value="FrameworksOnDeviceIntelligenceTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.ondeviceintelligencetests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java b/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java new file mode 100644 index 000000000000..d3943e32ef07 --- /dev/null +++ b/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024 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.ondeviceintelligence; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Bundle; +import android.os.PersistableBundle; +import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService; +import android.app.ondeviceintelligence.InferenceInfo; +import android.util.Base64; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.framework.protobuf.nano.MessageNano; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class InferenceInfoStoreTest { + InferenceInfoStore inferenceInfoStore; + + @Before + public void setUp() { + inferenceInfoStore = new InferenceInfoStore(1000); + } + + @Test + public void testInferenceInfoParsesFromBundleSuccessfully() throws Exception { + Bundle bundle = new Bundle(); + bundle.putByteArray(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY, + getInferenceInfoBytes(1, 1, 100)); + inferenceInfoStore.addInferenceInfoFromBundle(bundle); + List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0); + assertThat(inferenceInfos).hasSize(1); + assertThat(inferenceInfos.get(0).getUid()).isEqualTo(1); + assertThat(inferenceInfos.get(0).getStartTimeMs()).isEqualTo(1); + assertThat(inferenceInfos.get(0).getEndTimeMs()).isEqualTo(100); + } + + @Test + public void testInferenceInfoParsesFromPersistableBundleSuccessfully() throws Exception { + PersistableBundle bundle = new PersistableBundle(); + bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY, + Base64.encodeToString(getInferenceInfoBytes(1, 1, 100), Base64.DEFAULT)); + inferenceInfoStore.addInferenceInfoFromBundle(bundle); + List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0); + assertThat(inferenceInfos).hasSize(1); + assertThat(inferenceInfos.get(0).getUid()).isEqualTo(1); + assertThat(inferenceInfos.get(0).getStartTimeMs()).isEqualTo(1); + assertThat(inferenceInfos.get(0).getEndTimeMs()).isEqualTo(100); + } + + + @Test + public void testEvictionAfterMaxAge() throws Exception { + PersistableBundle bundle = new PersistableBundle(); + long testStartTime = System.currentTimeMillis(); + bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY, + Base64.encodeToString(getInferenceInfoBytes(1, testStartTime - 10, + testStartTime + 100), Base64.DEFAULT)); + inferenceInfoStore.addInferenceInfoFromBundle(bundle); + bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY, + Base64.encodeToString(getInferenceInfoBytes(1, testStartTime - 5, + testStartTime + 100), Base64.DEFAULT)); + inferenceInfoStore.addInferenceInfoFromBundle(bundle); + Thread.sleep(1020); + List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0); + assertThat(inferenceInfos).hasSize(2); + assertThat(inferenceInfos.get(0).getUid()).isEqualTo(1); + assertThat(inferenceInfos.get(0).getStartTimeMs()).isEqualTo(testStartTime - 10); + assertThat(inferenceInfos.get(0).getEndTimeMs()).isEqualTo(testStartTime + 100); + inferenceInfoStore.addInferenceInfoFromBundle(bundle); + List<InferenceInfo> inferenceInfos2 = inferenceInfoStore.getLatestInferenceInfo(0); + assertThat(inferenceInfos2).hasSize(1); //previous entries should have been evicted + } + + private byte[] getInferenceInfoBytes(int uid, long startTime, long endTime) { + com.android.server.ondeviceintelligence.nano.InferenceInfo inferenceInfo = + new com.android.server.ondeviceintelligence.nano.InferenceInfo(); + inferenceInfo.uid = uid; + inferenceInfo.startTimeMs = startTime; + inferenceInfo.endTimeMs = endTime; + return MessageNano.toByteArray(inferenceInfo); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index f482ddc7e03c..3ef81fde6506 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -28,8 +28,11 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.server.testutils.TestUtils.strictMock; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; import static org.mockito.AdditionalMatchers.gt; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -198,6 +201,7 @@ public class FullScreenMagnificationGestureHandlerTest { private final Scroller mMockScroller = spy(new Scroller(mContext)); private boolean mMockMagnificationConnectionState; + private boolean mMockOneFingerPanningEnabled; private OffsettableClock mClock; private FullScreenMagnificationGestureHandler mMgh; @@ -209,8 +213,6 @@ public class FullScreenMagnificationGestureHandlerTest { static final Rect INITIAL_MAGNIFICATION_BOUNDS = new Rect(0, 0, 800, 800); - static final Region INITIAL_MAGNIFICATION_REGION = new Region(INITIAL_MAGNIFICATION_BOUNDS); - @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -282,6 +284,8 @@ public class FullScreenMagnificationGestureHandlerTest { @NonNull private FullScreenMagnificationGestureHandler newInstance(boolean detectSingleFingerTripleTap, boolean detectTwoFingerTripleTap, boolean detectShortcutTrigger) { + enableOneFingerPanning( + isWatch() || Flags.enableMagnificationOneFingerPanningGesture()); FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler( mContext, @@ -297,8 +301,12 @@ public class FullScreenMagnificationGestureHandlerTest { mMockMagnificationLogger, ViewConfiguration.get(mContext), mMockOneFingerPanningSettingsProvider); + // OverscrollHandler is only supported on watches. + // @See config_enable_a11y_fullscreen_magnification_overscroll_handler if (isWatch()) { - enableOneFingerPanning(true); + assertNotNull(h.mOverscrollHandler); + } else { + assertNull(h.mOverscrollHandler); } mHandler = new TestHandler(h.mDetectingState, mClock) { @Override @@ -836,7 +844,7 @@ public class FullScreenMagnificationGestureHandlerTest { @Test public void testActionUpNotAtEdge_singlePanningState_detectingState() { - assumeTrue(isWatch()); + assumeTrue(isOneFingerPanningEnabled()); goFromStateIdleTo(STATE_SINGLE_PANNING); send(upEvent()); @@ -846,8 +854,7 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test - public void testScroll_SinglePanningDisabled_delegatingState() { - assumeTrue(isWatch()); + public void testScroll_singlePanningDisabled_delegatingState() { enableOneFingerPanning(false); goFromStateIdleTo(STATE_ACTIVATED); @@ -858,8 +865,54 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + public void testSingleFingerOverscrollAtLeftEdge_isNotWatch_transitionToDelegatingState() { + assumeTrue(isOneFingerPanningEnabled()); + assumeFalse(isWatch()); + goFromStateIdleTo(STATE_ACTIVATED); + float centerY = + (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f; + mFullScreenMagnificationController.setCenter( + DISPLAY_0, INITIAL_MAGNIFICATION_BOUNDS.left, centerY, false, 1); + final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; + PointF initCoords = + new PointF(INITIAL_MAGNIFICATION_BOUNDS.centerX(), + INITIAL_MAGNIFICATION_BOUNDS.centerY()); + PointF edgeCoords = new PointF(initCoords.x, initCoords.y); + edgeCoords.offset(swipeMinDistance, 0); + + allowEventDelegation(); + swipeAndHold(initCoords, edgeCoords); + + assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState); + assertTrue(isZoomed()); + } + + @Test + public void testSingleFingerOverscrollAtBottomEdge_isNotWatch_transitionToDelegatingState() { + assumeTrue(isOneFingerPanningEnabled()); + assumeFalse(isWatch()); + goFromStateIdleTo(STATE_ACTIVATED); + float centerX = + (INITIAL_MAGNIFICATION_BOUNDS.right + INITIAL_MAGNIFICATION_BOUNDS.left) / 2.0f; + mFullScreenMagnificationController.setCenter( + DISPLAY_0, centerX, INITIAL_MAGNIFICATION_BOUNDS.bottom, false, 1); + final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; + PointF initCoords = + new PointF(INITIAL_MAGNIFICATION_BOUNDS.centerX(), + INITIAL_MAGNIFICATION_BOUNDS.centerY()); + PointF edgeCoords = new PointF(initCoords.x, initCoords.y); + edgeCoords.offset(0, -swipeMinDistance); + + allowEventDelegation(); + swipeAndHold(initCoords, edgeCoords); + + assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState); + assertTrue(isZoomed()); + } + + @Test @FlakyTest - public void testScroll_singleHorizontalPanningAndAtEdge_leftEdgeOverscroll() { + public void testSingleFingerOverscrollAtLeftEdge_isWatch_expectedOverscrollState() { assumeTrue(isWatch()); goFromStateIdleTo(STATE_SINGLE_PANNING); float centerY = @@ -883,7 +936,7 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @FlakyTest - public void testScroll_singleHorizontalPanningAndAtEdge_rightEdgeOverscroll() { + public void testSingleFingerOverscrollAtRightEdge_isWatch_expectedOverscrollState() { assumeTrue(isWatch()); goFromStateIdleTo(STATE_SINGLE_PANNING); float centerY = @@ -907,7 +960,7 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @FlakyTest - public void testScroll_singleVerticalPanningAndAtEdge_verticalOverscroll() { + public void testSingleFingerOverscrollAtTopEdge_isWatch_expectedOverscrollState() { assumeTrue(isWatch()); goFromStateIdleTo(STATE_SINGLE_PANNING); float centerX = @@ -929,7 +982,7 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test - public void testScroll_singlePanningAndAtEdge_noOverscroll() { + public void testSingleFingerScrollAtEdge_isWatch_noOverscroll() { assumeTrue(isWatch()); goFromStateIdleTo(STATE_SINGLE_PANNING); float centerY = @@ -951,7 +1004,7 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test - public void testScroll_singleHorizontalPanningAndAtEdge_vibrate() { + public void testSingleFingerHorizontalScrollAtEdge_isWatch_vibrate() { assumeTrue(isWatch()); goFromStateIdleTo(STATE_SINGLE_PANNING); mFullScreenMagnificationController.setCenter( @@ -975,7 +1028,7 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test - public void testScroll_singleVerticalPanningAndAtEdge_doNotVibrate() { + public void testSingleFingerVerticalScrollAtEdge_isWatch_doNotVibrate() { assumeTrue(isWatch()); goFromStateIdleTo(STATE_SINGLE_PANNING); mFullScreenMagnificationController.setCenter( @@ -1000,7 +1053,7 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE) - public void singleFinger_testScrollAfterMagnified_startsFling() { + public void testSingleFingerScrollAfterMagnified_startsFling() { assumeTrue(isWatch()); goFromStateIdleTo(STATE_ACTIVATED); @@ -1282,9 +1335,14 @@ public class FullScreenMagnificationGestureHandlerTest { } private void enableOneFingerPanning(boolean enable) { + mMockOneFingerPanningEnabled = enable; when(mMockOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()).thenReturn(enable); } + private boolean isOneFingerPanningEnabled() { + return mMockOneFingerPanningEnabled; + } + private void assertActionsInOrder(List<MotionEvent> actualEvents, List<Integer> expectedActions) { assertTrue(actualEvents.size() == expectedActions.size()); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index 1eaa170e169b..bc2fd73f22ae 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -22,10 +22,11 @@ import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_NEG import static android.hardware.biometrics.BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED; import static android.hardware.biometrics.BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED; import static android.hardware.biometrics.BiometricPrompt.DISMISSED_REASON_NEGATIVE; +import static android.hardware.biometrics.BiometricPrompt.DISMISSED_REASON_USER_CANCEL; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED; -import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_PAUSED; +import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI; @@ -50,9 +51,9 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; -import android.content.Context; import android.content.res.Resources; import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ComponentInfoInternal; @@ -70,9 +71,12 @@ import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.security.KeyStoreAuthorization; +import android.testing.TestableContext; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.R; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.log.BiometricContext; @@ -80,6 +84,7 @@ import com.android.server.biometrics.log.BiometricFrameworkStatsLogger; import com.android.server.biometrics.log.OperationContextExt; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -95,8 +100,12 @@ public class AuthSessionTest { private static final String TEST_PACKAGE = "test_package"; private static final long TEST_REQUEST_ID = 22; + private static final String ACQUIRED_STRING = "test_acquired_info_callback"; + private static final String ACQUIRED_STRING_VENDOR = "test_acquired_info_callback_vendor"; - @Mock private Context mContext; + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @Mock private Resources mResources; @Mock private BiometricContext mBiometricContext; @Mock private ITrustManager mTrustManager; @@ -110,6 +119,7 @@ public class AuthSessionTest { @Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver; @Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger; @Mock private BiometricCameraManager mBiometricCameraManager; + @Mock private BiometricManager mBiometricManager; private Random mRandom; private IBinder mToken; @@ -121,7 +131,11 @@ public class AuthSessionTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - when(mContext.getResources()).thenReturn(mResources); + mContext.addMockSystemService(BiometricManager.class, mBiometricManager); + mContext.getOrCreateTestableResources().addOverride(R.string.fingerprint_acquired_partial, + ACQUIRED_STRING); + mContext.getOrCreateTestableResources().addOverride(R.array.fingerprint_acquired_vendor, + new String[]{ACQUIRED_STRING_VENDOR}); when(mClientReceiver.asBinder()).thenReturn(mock(Binder.class)); when(mBiometricContext.updateContext(any(), anyBoolean())) .thenAnswer(invocation -> invocation.getArgument(0)); @@ -499,8 +513,6 @@ public class AuthSessionTest { @Test public void testCallbackOnAcquired() throws RemoteException { - final String acquiredStr = "test_acquired_info_callback"; - final String acquiredStrVendor = "test_acquired_info_callback_vendor"; setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR); final AuthSession session = createAuthSession(mSensors, @@ -510,18 +522,15 @@ public class AuthSessionTest { 0 /* operationId */, 0 /* userId */); - when(mContext.getString(com.android.internal.R.string.fingerprint_acquired_partial)) - .thenReturn(acquiredStr); session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL, 0); - verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStr)); - verify(mClientReceiver).onAcquired(eq(1), eq(acquiredStr)); + verify(mStatusBarService).onBiometricHelp(anyInt(), eq(ACQUIRED_STRING)); + verify(mClientReceiver).onAcquired(eq(1), eq(ACQUIRED_STRING)); - when(mResources.getStringArray(com.android.internal.R.array.fingerprint_acquired_vendor)) - .thenReturn(new String[]{acquiredStrVendor}); session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 0); - verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStrVendor)); + verify(mStatusBarService).onBiometricHelp(anyInt(), eq(ACQUIRED_STRING_VENDOR)); verify(mClientReceiver).onAcquired( - eq(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR_BASE), eq(acquiredStrVendor)); + eq(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR_BASE), + eq(ACQUIRED_STRING_VENDOR)); } @Test @@ -665,6 +674,87 @@ public class AuthSessionTest { verify(mStatusBarService, never()).onBiometricError(anyInt(), anyInt(), anyInt()); } + @Test + public void onAuthReceivedWhileWaitingForConfirmation_SFPS() throws Exception { + setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_POWER_BUTTON); + setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class)); + final long operationId = 123; + final int userId = 10; + final AuthSession session = createAuthSession(mSensors, + false /* checkDevicePolicyManager */, + Authenticators.BIOMETRIC_STRONG, + TEST_REQUEST_ID, + operationId, + userId); + session.goToInitialState(); + for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) { + session.onCookieReceived( + session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie()); + } + session.onDialogAnimatedIn(true /* startFingerprintNow */); + + // Face succeeds + session.onAuthenticationSucceeded(1, true, null); + verify(mStatusBarService).onBiometricAuthenticated(TYPE_FACE); + for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) { + if (sensor.modality == FingerprintSensorProperties.TYPE_POWER_BUTTON) { + assertEquals(BiometricSensor.STATE_AUTHENTICATING, sensor.getSensorState()); + } + } + + // SFPS succeeds + session.onAuthenticationSucceeded(0, true, null); + verify(mStatusBarService).onBiometricAuthenticated(TYPE_FINGERPRINT); + } + + @Test + public void onDialogDismissedResetLockout_Confirmed() throws Exception { + setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_POWER_BUTTON); + setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class)); + final long operationId = 123; + final int userId = 10; + final AuthSession session = createAuthSession(mSensors, + false /* checkDevicePolicyManager */, + Authenticators.BIOMETRIC_STRONG, + TEST_REQUEST_ID, + operationId, + userId); + session.goToInitialState(); + session.onDialogAnimatedIn(true /* startFingerprintNow */); + + // Face succeeds + session.onAuthenticationSucceeded(1, true, new byte[1]); + + // Dismiss through confirmation + session.onDialogDismissed(DISMISSED_REASON_BIOMETRIC_CONFIRMED, null); + + verify(mBiometricManager).resetLockoutTimeBound(any(), any(), anyInt(), anyInt(), any()); + } + + @Test + public void onDialogDismissedResetLockout_Cancelled() throws Exception { + setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_POWER_BUTTON); + setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class)); + final long operationId = 123; + final int userId = 10; + final AuthSession session = createAuthSession(mSensors, + false /* checkDevicePolicyManager */, + Authenticators.BIOMETRIC_STRONG, + TEST_REQUEST_ID, + operationId, + userId); + session.goToInitialState(); + session.onDialogAnimatedIn(true /* startFingerprintNow */); + + // Face succeeds + session.onAuthenticationSucceeded(1, true, new byte[1]); + + // User cancel after success + session.onDialogDismissed(DISMISSED_REASON_USER_CANCEL, null); + + verify(mBiometricManager).resetLockoutTimeBound(any(), any(), anyInt(), anyInt(), any()); + } + // TODO (b/208484275) : Enable these tests // @Test // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java index 98738052b1de..2d4dbb77343e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java @@ -92,6 +92,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -445,21 +446,61 @@ public class FaceAuthenticationClientTest { ); } + @Test + public void testResetLockoutOnAuthSuccess_nonBiometricPrompt() throws RemoteException { + FaceAuthenticationClient client = createClient(false); + client.start(mCallback); + client.onAuthenticated(new Face("friendly", 1 /* faceId */, + 2 /* deviceId */), true /* authenticated */, createHardwareAuthToken()); + + verify(mBiometricManager).resetLockoutTimeBound(eq(mToken), eq(mContext.getOpPackageName()), + anyInt(), anyInt(), any()); + } + + @Test + public void testNoResetLockoutOnAuthFailure_nonBiometricPrompt() throws RemoteException { + FaceAuthenticationClient client = createClient(false); + client.start(mCallback); + client.onAuthenticated(new Face("friendly", 1 /* faceId */, + 2 /* deviceId */), false /* authenticated */, createHardwareAuthToken()); + + verify(mBiometricManager, never()).resetLockoutTimeBound(eq(mToken), + eq(mContext.getOpPackageName()), anyInt(), anyInt(), any()); + } + + @Test + public void testNoResetLockoutOnAuthSuccess_BiometricPrompt() throws RemoteException { + FaceAuthenticationClient client = createClient(true); + client.start(mCallback); + client.onAuthenticated(new Face("friendly", 1 /* faceId */, + 2 /* deviceId */), true /* authenticated */, createHardwareAuthToken()); + + verify(mBiometricManager, never()).resetLockoutTimeBound(eq(mToken), + eq(mContext.getOpPackageName()), anyInt(), anyInt(), any()); + } + private FaceAuthenticationClient createClient() throws RemoteException { return createClient(2 /* version */, mClientMonitorCallbackConverter, - false /* allowBackgroundAuthentication */, + false /* allowBackgroundAuthentication */, true /* isBiometricPrompt */, + null /* lockoutTracker */); + } + + private FaceAuthenticationClient createClient(boolean isBiometricPrompt) + throws RemoteException { + return createClient(2 /* version */, mClientMonitorCallbackConverter, + true /* allowBackgroundAuthentication */, isBiometricPrompt, null /* lockoutTracker */); } private FaceAuthenticationClient createClientWithNullListener() throws RemoteException { return createClient(2 /* version */, null /* listener */, - true /* allowBackgroundAuthentication */, + false /* allowBackgroundAuthentication */, true /* isBiometricPrompt */, null /* lockoutTracker */); } private FaceAuthenticationClient createClient(int version) throws RemoteException { return createClient(version, mClientMonitorCallbackConverter, - false /* allowBackgroundAuthentication */, + false /* allowBackgroundAuthentication */, true /* isBiometricPrompt */, null /* lockoutTracker */); } @@ -468,12 +509,14 @@ public class FaceAuthenticationClientTest { return createClient(0 /* version */, mClientMonitorCallbackConverter, true /* allowBackgroundAuthentication */, + true /* isBiometricPrompt */, lockoutTracker); } private FaceAuthenticationClient createClient(int version, ClientMonitorCallbackConverter listener, boolean allowBackgroundAuthentication, + boolean isBiometricPrompt, LockoutTracker lockoutTracker) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); @@ -488,7 +531,7 @@ public class FaceAuthenticationClientTest { .build(); return new FaceAuthenticationClient(mContext, () -> aidl, mToken, 2 /* requestId */, listener, OP_ID, - false /* restricted */, options, 4 /* cookie */, + false /* restricted */, options, isBiometricPrompt ? 4 : 0 /* cookie */, false /* requireConfirmation */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, mUsageStats, lockoutTracker, allowBackgroundAuthentication, @@ -500,4 +543,8 @@ public class FaceAuthenticationClientTest { } }; } + + private ArrayList<Byte> createHardwareAuthToken() { + return new ArrayList<>(Collections.nCopies(69, Byte.valueOf("0"))); + } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 182d60328440..ecd799f44552 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -98,6 +98,7 @@ import org.mockito.junit.MockitoRule; import java.time.Clock; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -608,6 +609,45 @@ public class FingerprintAuthenticationClientTest { } @Test + public void testResetLockoutOnAuthSuccess_nonBiometricPrompt() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1 /* version */, + true /* allowBackgroundAuthentication */, false /* isBiometricPrompt */, + mClientMonitorCallbackConverter, mLockoutTracker); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, + 2 /* deviceId */), true /* authenticated */, createHardwareAuthToken()); + + verify(mBiometricManager).resetLockoutTimeBound(eq(mToken), eq(mContext.getOpPackageName()), + anyInt(), anyInt(), any()); + } + + @Test + public void testNoResetLockoutOnAuthFailure_nonBiometricPrompt() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1 /* version */, + true /* allowBackgroundAuthentication */, false /* isBiometricPrompt */, + mClientMonitorCallbackConverter, mLockoutTracker); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, + 2 /* deviceId */), false /* authenticated */, createHardwareAuthToken()); + + verify(mBiometricManager, never()).resetLockoutTimeBound(eq(mToken), + eq(mContext.getOpPackageName()), anyInt(), anyInt(), any()); + } + + @Test + public void testNoResetLockoutOnAuthSuccess_BiometricPrompt() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1 /* version */, + true /* allowBackgroundAuthentication */, true /* isBiometricPrompt */, + mClientMonitorCallbackConverter, mLockoutTracker); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, + 2 /* deviceId */), true /* authenticated */, createHardwareAuthToken()); + + verify(mBiometricManager, never()).resetLockoutTimeBound(eq(mToken), + eq(mContext.getOpPackageName()), anyInt(), anyInt(), any()); + } + + @Test public void testOnAuthenticatedFalseWhenListenerIsNull() throws RemoteException { final FingerprintAuthenticationClient client = createClientWithNullListener(); client.start(mCallback); @@ -630,11 +670,11 @@ public class FingerprintAuthenticationClientTest { @Test public void testLockoutTracker_authSuccess() throws RemoteException { final FingerprintAuthenticationClient client = createClient(1 /* version */, - true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter, - mLockoutTracker); + true /* allowBackgroundAuthentication */, false /* isBiometricPrompt */, + mClientMonitorCallbackConverter, mLockoutTracker); client.start(mCallback); client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, - 2 /* deviceId */), true /* authenticated */, new ArrayList<>()); + 2 /* deviceId */), true /* authenticated */, createHardwareAuthToken()); verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID); verify(mLockoutTracker, never()).addFailedAttemptForUser(anyInt()); @@ -643,11 +683,11 @@ public class FingerprintAuthenticationClientTest { @Test public void testLockoutTracker_authFailed() throws RemoteException { final FingerprintAuthenticationClient client = createClient(1 /* version */, - true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter, - mLockoutTracker); + true /* allowBackgroundAuthentication */, false /* isBiometricPrompt */, + mClientMonitorCallbackConverter, mLockoutTracker); client.start(mCallback); client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, - 2 /* deviceId */), false /* authenticated */, new ArrayList<>()); + 2 /* deviceId */), false /* authenticated */, createHardwareAuthToken()); verify(mLockoutTracker, never()).resetFailedAttemptsForUser(anyBoolean(), anyInt()); verify(mLockoutTracker).addFailedAttemptForUser(USER_ID); @@ -655,27 +695,31 @@ public class FingerprintAuthenticationClientTest { private FingerprintAuthenticationClient createClient() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */, + true /* isBiometricPrompt */, mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClientWithoutBackgroundAuth() throws RemoteException { return createClient(100 /* version */, false /* allowBackgroundAuthentication */, - mClientMonitorCallbackConverter, null); + true /* isBiometricPrompt */, mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClient(int version) throws RemoteException { return createClient(version, true /* allowBackgroundAuthentication */, + true /* isBiometricPrompt */, mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClientWithNullListener() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */, - null, /* listener */null); + true /* isBiometricPrompt */, + /* listener */null, null); } private FingerprintAuthenticationClient createClient(int version, - boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener, + boolean allowBackgroundAuthentication, boolean isBiometricPrompt, + ClientMonitorCallbackConverter listener, LockoutTracker lockoutTracker) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); @@ -687,7 +731,8 @@ public class FingerprintAuthenticationClientTest { .setSensorId(SENSOR_ID) .build(); return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken, REQUEST_ID, - listener, OP_ID, false /* restricted */, options, 4 /* cookie */, + listener, OP_ID, false /* restricted */, options, + isBiometricPrompt ? 4 : 0 /* cookie */, false /* requireConfirmation */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, null /* taskStackListener */, mUdfpsOverlayController, mAuthenticationStateListeners, allowBackgroundAuthentication, mSensorProps, @@ -698,4 +743,8 @@ public class FingerprintAuthenticationClientTest { } }; } + + private ArrayList<Byte> createHardwareAuthToken() { + return new ArrayList<>(Collections.nCopies(69, Byte.valueOf("0"))); + } } diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java index f221b75564a3..148c96850d34 100644 --- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java @@ -18,6 +18,20 @@ package com.android.server.statusbar; import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; +import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS; +import static android.app.StatusBarManager.DISABLE2_MASK; +import static android.app.StatusBarManager.DISABLE2_NONE; +import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE; +import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; +import static android.app.StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS; +import static android.app.StatusBarManager.DISABLE_BACK; +import static android.app.StatusBarManager.DISABLE_CLOCK; +import static android.app.StatusBarManager.DISABLE_HOME; +import static android.app.StatusBarManager.DISABLE_MASK; +import static android.app.StatusBarManager.DISABLE_NONE; +import static android.app.StatusBarManager.DISABLE_RECENT; +import static android.app.StatusBarManager.DISABLE_SEARCH; +import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -137,6 +151,7 @@ public class StatusBarManagerServiceTest { LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); when(mMockStatusBar.asBinder()).thenReturn(mMockStatusBar); + when(mMockStatusBar.isBinderAlive()).thenReturn(true); when(mApplicationInfo.loadLabel(any())).thenReturn(APP_NAME); mockHandleIncomingUser(); @@ -722,6 +737,369 @@ public class StatusBarManagerServiceTest { verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt()); } + @Test + public void testGetDisableFlags() throws Exception { + String packageName = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + mStatusBarManagerService.disable(DISABLE_NONE, mMockStatusBar, packageName); + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + } + + @Test + public void testSetHomeDisabled() throws Exception { + int expectedFlags = DISABLE_MASK & DISABLE_HOME; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + // disable + mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg); + // check that disable works + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + } + + @Test + public void testSetSystemInfoDisabled() throws Exception { + int expectedFlags = DISABLE_MASK & DISABLE_SYSTEM_INFO; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + // disable + mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + } + + @Test + public void testSetRecentDisabled() throws Exception { + int expectedFlags = DISABLE_MASK & DISABLE_RECENT; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + // disable + mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + } + + @Test + public void testSetBackDisabled() throws Exception { + int expectedFlags = DISABLE_MASK & DISABLE_BACK; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + // disable + mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + } + + @Test + public void testSetClockDisabled() throws Exception { + int expectedFlags = DISABLE_MASK & DISABLE_CLOCK; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + // disable home + mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + } + + @Test + public void testSetSearchDisabled() throws Exception { + int expectedFlags = DISABLE_MASK & DISABLE_SEARCH; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar, + userId)[0]); + // disable + mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + } + + @Test + public void testSetQuickSettingsDisabled2() throws Exception { + int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE2_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + // disable + mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + } + + @Test + public void testSetSystemIconsDisabled2() throws Exception { + int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar, + userId)[1]); + // disable + mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + } + + @Test + public void testSetNotificationShadeDisabled2() throws Exception { + int expectedFlags = DISABLE2_MASK & DISABLE2_NOTIFICATION_SHADE; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar, + userId)[1]); + // disable + mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + } + + + @Test + public void testSetGlobalActionsDisabled2() throws Exception { + int expectedFlags = DISABLE2_MASK & DISABLE2_GLOBAL_ACTIONS; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar, + userId)[1]); + // disable + mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + } + + @Test + public void testSetRotateSuggestionsDisabled2() throws Exception { + int expectedFlags = DISABLE2_MASK & DISABLE2_ROTATE_SUGGESTIONS; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + // disable + mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + } + + @Test + public void testSetTwoDisable2Flags() throws Exception { + int expectedFlags = DISABLE2_MASK & DISABLE2_ROTATE_SUGGESTIONS & DISABLE2_QUICK_SETTINGS; + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + // disable + mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + } + + @Test + public void testSetTwoDisableFlagsRemoveOne() throws Exception { + int twoFlags = DISABLE_MASK & DISABLE_HOME & DISABLE_BACK; + int expectedFlag = DISABLE_MASK & DISABLE_HOME; + + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + // disable + mStatusBarManagerService.disable(twoFlags, mMockStatusBar, pkg); + mStatusBarManagerService.disable(DISABLE_NONE, mMockStatusBar, pkg); + mStatusBarManagerService.disable(expectedFlag, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlag, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + } + + @Test + public void testSetTwoDisable2FlagsRemoveOne() throws Exception { + int twoFlags = DISABLE2_MASK & DISABLE2_ROTATE_SUGGESTIONS & DISABLE2_QUICK_SETTINGS; + int expectedFlag = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS; + + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar, + userId)[1]); + // disable + mStatusBarManagerService.disable2(twoFlags, mMockStatusBar, pkg); + mStatusBarManagerService.disable2(DISABLE2_NONE, mMockStatusBar, pkg); + mStatusBarManagerService.disable2(expectedFlag, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(expectedFlag, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + } + + @Test + public void testDisableBothFlags() throws Exception { + int disableFlags = DISABLE_MASK & DISABLE_BACK & DISABLE_HOME; + int disable2Flags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS & DISABLE2_ROTATE_SUGGESTIONS; + + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar, + userId)[0]); + assertEquals(DISABLE2_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + // disable + mStatusBarManagerService.disable(disableFlags, mMockStatusBar, pkg); + mStatusBarManagerService.disable2(disable2Flags, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(disableFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + assertEquals(disable2Flags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + } + + @Test + public void testDisableBothFlagsEnable1Flags() throws Exception { + int disableFlags = DISABLE_MASK & DISABLE_BACK & DISABLE_HOME; + int disable2Flags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS & DISABLE2_ROTATE_SUGGESTIONS; + + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + assertEquals(DISABLE2_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + // disable + mStatusBarManagerService.disable(disableFlags, mMockStatusBar, pkg); + mStatusBarManagerService.disable2(disable2Flags, mMockStatusBar, pkg); + // re-enable one + mStatusBarManagerService.disable(DISABLE_NONE, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + assertEquals(disable2Flags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + } + + @Test + public void testDisableBothFlagsEnable2Flags() throws Exception { + int disableFlags = DISABLE_MASK & DISABLE_BACK & DISABLE_HOME; + int disable2Flags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS & DISABLE2_ROTATE_SUGGESTIONS; + + String pkg = mContext.getPackageName(); + int userId = 0; + mockUidCheck(); + mockCurrentUserCheck(userId); + // before disabling + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + assertEquals(DISABLE2_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + // disable + mStatusBarManagerService.disable(disableFlags, mMockStatusBar, pkg); + mStatusBarManagerService.disable2(disable2Flags, mMockStatusBar, pkg); + // re-enable one + mStatusBarManagerService.disable2(DISABLE_NONE, mMockStatusBar, pkg); + // check that right flag is disabled + assertEquals(disableFlags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]); + assertEquals(DISABLE2_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]); + } + + @Test + public void testDifferentUsersDisable() throws Exception { + int user1Id = 0; + mockUidCheck(); + mockCurrentUserCheck(user1Id); + int user2Id = 14; + mockComponentInfo(user2Id); + mockEverything(user2Id); + + int expectedUser1Flags = DISABLE_MASK & DISABLE_BACK; + int expectedUser2Flags = DISABLE_MASK & DISABLE_HOME; + String pkg = mContext.getPackageName(); + + // before disabling + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]); + assertEquals(DISABLE_NONE, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, user2Id)[0]); + // disable + mStatusBarManagerService.disableForUser(expectedUser1Flags, mMockStatusBar, pkg, user1Id); + mStatusBarManagerService.disableForUser(expectedUser2Flags, mMockStatusBar, pkg, user2Id); + // check that right flag is disabled + assertEquals(expectedUser1Flags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]); + assertEquals(expectedUser2Flags, + mStatusBarManagerService.getDisableFlags(mMockStatusBar, user2Id)[0]); + } + + private void mockUidCheck() { mockUidCheck(TEST_PACKAGE); } diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java new file mode 100644 index 000000000000..b3f150241115 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2024 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.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; +import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.CameraCompatTaskInfo; +import android.app.WindowConfiguration.WindowingMode; +import android.app.servertransaction.RefreshCallbackItem; +import android.app.servertransaction.ResumeActivityItem; +import android.compat.testing.PlatformCompatChangeRule; +import android.content.ComponentName; +import android.content.pm.ActivityInfo.ScreenOrientation; +import android.content.res.Configuration; +import android.content.res.Configuration.Orientation; +import android.graphics.Rect; +import android.hardware.camera2.CameraManager; +import android.os.Handler; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +import java.util.concurrent.Executor; + +/** + * Tests for {@link CameraCompatFreeformPolicy}. + * + * Build/Install/Run: + * atest WmTests:CameraCompatFreeformPolicyTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class CameraCompatFreeformPolicyTests extends WindowTestsBase { + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + // Main activity package name needs to be the same as the process to test overrides. + private static final String TEST_PACKAGE_1 = "com.android.frameworks.wmtests"; + private static final String TEST_PACKAGE_2 = "com.test.package.two"; + private static final String CAMERA_ID_1 = "camera-1"; + private static final String CAMERA_ID_2 = "camera-2"; + private CameraManager mMockCameraManager; + private Handler mMockHandler; + private LetterboxConfiguration mLetterboxConfiguration; + + private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; + private CameraCompatFreeformPolicy mCameraCompatFreeformPolicy; + private ActivityRecord mActivity; + private Task mTask; + private ActivityRefresher mActivityRefresher; + + @Before + public void setUp() throws Exception { + mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration; + spyOn(mLetterboxConfiguration); + when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled()) + .thenReturn(true); + when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()) + .thenReturn(true); + when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + .thenReturn(true); + + mMockCameraManager = mock(CameraManager.class); + doAnswer(invocation -> { + mCameraAvailabilityCallback = invocation.getArgument(1); + return null; + }).when(mMockCameraManager).registerAvailabilityCallback( + any(Executor.class), any(CameraManager.AvailabilityCallback.class)); + + when(mContext.getSystemService(CameraManager.class)).thenReturn(mMockCameraManager); + + mDisplayContent.setIgnoreOrientationRequest(true); + + mMockHandler = mock(Handler.class); + + when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }); + + mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler); + mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM); + CameraStateMonitor cameraStateMonitor = + new CameraStateMonitor(mDisplayContent, mMockHandler); + mCameraCompatFreeformPolicy = + new CameraCompatFreeformPolicy(mDisplayContent, cameraStateMonitor, + mActivityRefresher); + + mCameraCompatFreeformPolicy.start(); + cameraStateMonitor.startListeningToCameraState(); + } + + @Test + public void testFullscreen_doesNotActivateCameraCompatMode() { + configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); + doReturn(false).when(mActivity).inFreeformWindowingMode(); + + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + + assertNotInCameraCompatMode(); + } + + @Test + public void testOrientationUnspecified_doesNotActivateCameraCompatMode() { + configureActivity(SCREEN_ORIENTATION_UNSPECIFIED); + + assertNotInCameraCompatMode(); + } + + @Test + public void testNoCameraConnection_doesNotActivateCameraCompatMode() { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + assertNotInCameraCompatMode(); + } + + @Test + public void testCameraConnected_activatesCameraCompatMode() throws Exception { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + + assertInCameraCompatMode(); + assertActivityRefreshRequested(/* refreshRequested */ false); + } + + @Test + public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + callOnActivityConfigurationChanging(mActivity); + + assertInCameraCompatMode(); + assertActivityRefreshRequested(/* refreshRequested */ true); + } + + @Test + public void testReconnectedToDifferentCamera_activatesCameraCompatModeAndRefresh() + throws Exception { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1); + callOnActivityConfigurationChanging(mActivity); + + assertInCameraCompatMode(); + assertActivityRefreshRequested(/* refreshRequested */ true); + } + + @Test + public void testCameraDisconnected_deactivatesCameraCompatMode() { + configureActivityAndDisplay(SCREEN_ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE, + WINDOWING_MODE_FREEFORM); + // Open camera and test for compat treatment + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + assertInCameraCompatMode(); + + // Close camera and test for revert + mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); + + assertNotInCameraCompatMode(); + } + + @Test + public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2); + + assertNotInCameraCompatMode(); + } + + @Test + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT}) + public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + + assertTrue(mActivity.info + .isChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT)); + assertFalse(mCameraCompatFreeformPolicy + .shouldApplyFreeformTreatmentForCameraCompat(mActivity)); + } + + @Test + public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + + assertTrue(mCameraCompatFreeformPolicy + .shouldApplyFreeformTreatmentForCameraCompat(mActivity)); + } + + @Test + public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() + throws Exception { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + + doReturn(false).when( + mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat(); + + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + callOnActivityConfigurationChanging(mActivity); + + assertActivityRefreshRequested(/* refreshRequested */ false); + } + + @Test + public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception { + when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + .thenReturn(false); + + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + callOnActivityConfigurationChanging(mActivity); + + assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); + } + + @Test + public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() + throws Exception { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + doReturn(true).when(mActivity.mLetterboxUiController) + .shouldRefreshActivityViaPauseForCameraCompat(); + + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + callOnActivityConfigurationChanging(mActivity); + + assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); + } + + private void configureActivity(@ScreenOrientation int activityOrientation) { + configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM); + } + + private void configureActivity(@ScreenOrientation int activityOrientation, + @WindowingMode int windowingMode) { + configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT, windowingMode); + } + + private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation, + @Orientation int naturalOrientation, @WindowingMode int windowingMode) { + mTask = new TaskBuilder(mSupervisor) + .setDisplay(mDisplayContent) + .setWindowingMode(windowingMode) + .build(); + + mActivity = new ActivityBuilder(mAtm) + // Set the component to be that of the test class in order to enable compat changes + .setComponent(ComponentName.createRelative(mContext, + com.android.server.wm.CameraCompatFreeformPolicyTests.class.getName())) + .setScreenOrientation(activityOrientation) + .setTask(mTask) + .build(); + + spyOn(mActivity.mLetterboxUiController); + spyOn(mActivity.info); + + doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean()); + doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation(); + + doReturn(true).when(mActivity).inFreeformWindowingMode(); + } + + private void assertInCameraCompatMode() { + assertNotEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE, + mActivity.mLetterboxUiController.getFreeformCameraCompatMode()); + } + + private void assertNotInCameraCompatMode() { + assertEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE, + mActivity.mLetterboxUiController.getFreeformCameraCompatMode()); + } + + private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception { + assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true); + } + + private void assertActivityRefreshRequested(boolean refreshRequested, + boolean cycleThroughStop) throws Exception { + verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0)) + .setIsRefreshRequested(true); + + final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token, + cycleThroughStop ? ON_STOP : ON_PAUSE); + final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token, + /* isForward */ false, /* shouldSendCompatFakeFocus */ false); + + verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0)) + .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(), + refreshCallbackItem, resumeActivityItem); + } + + private void callOnActivityConfigurationChanging(ActivityRecord activity) { + mActivityRefresher.onActivityConfigurationChanging(activity, + /* newConfig */ createConfiguration(/*letterbox=*/ true), + /* lastReportedConfig */ createConfiguration(/*letterbox=*/ false)); + } + + private Configuration createConfiguration(boolean letterbox) { + final Configuration configuration = new Configuration(); + Rect bounds = letterbox ? new Rect(300, 0, 700, 600) : new Rect(0, 0, 1000, 600); + configuration.windowConfiguration.setAppBounds(bounds); + return configuration; + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 417ee6be17bc..695750217cac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -83,6 +83,8 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFO import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; +import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM; +import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE; import static com.google.common.truth.Truth.assertThat; @@ -115,6 +117,8 @@ import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.Presubmit; import android.util.ArraySet; import android.view.Display; @@ -2822,6 +2826,31 @@ public class DisplayContentTests extends WindowTestsBase { verify(mWm.mUmInternal, never()).isUserVisible(userId2, displayId); } + @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @Test + public void cameraCompatFreeformFlagEnabled_cameraCompatFreeformPolicyNotNull() { + doReturn(true).when(() -> + DesktopModeLaunchParamsModifier.canEnterDesktopMode(any())); + + assertNotNull(createNewDisplay().mCameraCompatFreeformPolicy); + } + + @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @Test + public void cameraCompatFreeformFlagNotEnabled_cameraCompatFreeformPolicyIsNull() { + doReturn(true).when(() -> + DesktopModeLaunchParamsModifier.canEnterDesktopMode(any())); + + assertNull(createNewDisplay().mCameraCompatFreeformPolicy); + } + + @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + @Test + public void desktopWindowingFlagNotEnabled_cameraCompatFreeformPolicyIsNull() { + assertNull(createNewDisplay().mCameraCompatFreeformPolicy); + } + private void removeRootTaskTests(Runnable runnable) { final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java index c76acd7e1d6b..c65371fc3320 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java @@ -142,6 +142,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { doNothing().when(mDisplayRotationCompatPolicy).showToast(anyInt()); doNothing().when(mDisplayRotationCompatPolicy).showToast(anyInt(), anyString()); + mDisplayRotationCompatPolicy.start(); cameraStateMonitor.startListeningToCameraState(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 1a366b3e3a4f..ac1aa20b322f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4121,6 +4121,35 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testUpdateResolvedBoundsVerticalPosition_unfoldDisplay_notTabletop() { + // Set up a display in portrait with a fixed-orientation LANDSCAPE app. + setUpDisplaySizeWithApp(1000, 2000); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier( + 1.0f /*letterboxVerticalPositionMultiplier*/); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); + + // Make the activity full-screen. + mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + // Simulate display unfolding. + setFoldablePosture(true /* isHalfFolded */, true /* isTabletop */); + doReturn(true).when(mActivity.mDisplayContent).inTransition(); + resizeDisplay(mTask.mDisplayContent, 1400, 2800); + + // Make sure app doesn't jump to top (default tabletop position) when unfolding. + assertEquals(1.0f, mActivity.mLetterboxUiController.getVerticalPositionMultiplier( + mActivity.getParent().getConfiguration()), 0); + + // Simulate display fully open after unfolding. + setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */); + doReturn(false).when(mActivity.mDisplayContent).inTransition(); + + assertEquals(1.0f, mActivity.mLetterboxUiController.getVerticalPositionMultiplier( + mActivity.getParent().getConfiguration()), 0); + } + + @Test public void testGetFixedOrientationLetterboxAspectRatio_tabletop_centered() { // Set up a display in portrait with a fixed-orientation LANDSCAPE app setUpDisplaySizeWithApp(1400, 2800); diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index c45c86cec5a7..c962a3f9ea4d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -445,6 +445,9 @@ public class SystemServicesTestRule implements TestRule { if (dc.mDisplayRotationCompatPolicy != null) { dc.mDisplayRotationCompatPolicy.dispose(); } + if (dc.mCameraCompatFreeformPolicy != null) { + dc.mCameraCompatFreeformPolicy.dispose(); + } if (dc.mCameraStateMonitor != null) { dc.mCameraStateMonitor.dispose(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 6ecaea90b85b..e01cea3d62f8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -69,6 +69,7 @@ import static org.mockito.Mockito.never; import android.app.ActivityManager; import android.app.ActivityOptions; +import android.app.CameraCompatTaskInfo; import android.app.TaskInfo; import android.app.WindowConfiguration; import android.content.ComponentName; @@ -1986,6 +1987,17 @@ public class TaskTests extends WindowTestsBase { assertNotEquals(activityDifferentPackage, task.getBottomMostActivityInSamePackage()); } + @Test + public void getTaskInfoPropagatesCameraCompatMode() { + final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); + final ActivityRecord activity = task.getTopMostActivity(); + activity.mLetterboxUiController + .setFreeformCameraCompatMode(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT); + + assertEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT, + task.getTaskInfo().appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode); + } + private Task getTestTask() { return new TaskBuilder(mSupervisor).setCreateActivity(true).build(); } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java index d551953477d8..c16d18b34360 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java @@ -23,6 +23,7 @@ import com.android.asllib.util.XmlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -40,6 +41,11 @@ public class DataCategory implements AslMarshallable { this.mDataTypes = dataTypes; } + public DataCategory(String categoryName) { + this.mCategoryName = categoryName; + this.mDataTypes = new LinkedHashMap<String, DataType>(); + } + public String getCategoryName() { return mCategoryName; } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java index 90424fe00504..724416285acd 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java @@ -26,33 +26,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -public class DataCategoryFactory implements AslMarshallableFactory<DataCategory> { - @Override - public DataCategory createFromHrElements(List<Element> elements) throws MalformedXmlException { - String categoryName = null; - Map<String, DataType> dataTypeMap = new LinkedHashMap<String, DataType>(); - for (Element ele : elements) { - categoryName = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_DATA_CATEGORY, true); - String dataTypeName = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_DATA_TYPE, true); - if (!DataTypeConstants.getValidDataTypes().containsKey(categoryName)) { - throw new MalformedXmlException( - String.format("Unrecognized data category %s", categoryName)); - } - if (!DataTypeConstants.getValidDataTypes().get(categoryName).contains(dataTypeName)) { - throw new MalformedXmlException( - String.format( - "Unrecognized data type name %s for category %s", - dataTypeName, categoryName)); - } - dataTypeMap.put( - dataTypeName, new DataTypeFactory().createFromHrElements(XmlUtils.listOf(ele))); - } - - return new DataCategory(categoryName, dataTypeMap); - } - +public class DataCategoryFactory { /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ - @Override public DataCategory createFromOdElements(List<Element> elements) throws MalformedXmlException { Element dataCategoryEle = XmlUtils.getSingleElement(elements); Map<String, DataType> dataTypeMap = new LinkedHashMap<String, DataType>(); diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java index 4a0d75977d78..ba0e3db52027 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java @@ -30,28 +30,17 @@ import java.util.Map; * DataCategory} */ public class DataLabels implements AslMarshallable { - private final Map<String, DataCategory> mDataAccessed; private final Map<String, DataCategory> mDataCollected; private final Map<String, DataCategory> mDataShared; public DataLabels( - Map<String, DataCategory> dataAccessed, Map<String, DataCategory> dataCollected, Map<String, DataCategory> dataShared) { - mDataAccessed = dataAccessed; mDataCollected = dataCollected; mDataShared = dataShared; } /** - * Returns the data accessed {@link Map} of {@link DataCategoryConstants} to {@link - * DataCategory} - */ - public Map<String, DataCategory> getDataAccessed() { - return mDataAccessed; - } - - /** * Returns the data collected {@link Map} of {@link DataCategoryConstants} to {@link * DataCategory} */ @@ -72,7 +61,6 @@ public class DataLabels implements AslMarshallable { Element dataLabelsEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DATA_LABELS); - maybeAppendDataUsages(doc, dataLabelsEle, mDataAccessed, XmlUtils.OD_NAME_DATA_ACCESSED); maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_COLLECTED); maybeAppendDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.OD_NAME_DATA_SHARED); @@ -83,9 +71,12 @@ public class DataLabels implements AslMarshallable { @Override public List<Element> toHrDomElements(Document doc) { Element dataLabelsEle = doc.createElement(XmlUtils.HR_TAG_DATA_LABELS); - maybeAppendHrDataUsages(doc, dataLabelsEle, mDataAccessed, XmlUtils.HR_TAG_DATA_ACCESSED); - maybeAppendHrDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED); - maybeAppendHrDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.HR_TAG_DATA_SHARED); + maybeAppendHrDataUsages( + doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED, false); + maybeAppendHrDataUsages( + doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED_EPHEMERAL, true); + maybeAppendHrDataUsages( + doc, dataLabelsEle, mDataShared, XmlUtils.HR_TAG_DATA_SHARED, false); return XmlUtils.listOf(dataLabelsEle); } @@ -115,7 +106,8 @@ public class DataLabels implements AslMarshallable { Document doc, Element dataLabelsEle, Map<String, DataCategory> dataCategoriesMap, - String dataUsageTypeName) { + String dataUsageTypeName, + boolean ephemeral) { if (dataCategoriesMap.isEmpty()) { return; } @@ -123,10 +115,15 @@ public class DataLabels implements AslMarshallable { DataCategory dataCategory = dataCategoriesMap.get(dataCategoryName); for (String dataTypeName : dataCategory.getDataTypes().keySet()) { DataType dataType = dataCategory.getDataTypes().get(dataTypeName); - // XmlUtils.appendChildren(dataLabelsEle, dataType.toHrDomElements(doc)); + if (ephemeral + != (dataType.getEphemeral() != null ? dataType.getEphemeral() : false)) { + continue; + } + Element hrDataTypeEle = doc.createElement(dataUsageTypeName); - hrDataTypeEle.setAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY, dataCategoryName); - hrDataTypeEle.setAttribute(XmlUtils.HR_ATTR_DATA_TYPE, dataTypeName); + hrDataTypeEle.setAttribute( + XmlUtils.HR_ATTR_DATA_TYPE, + dataCategoryName + XmlUtils.DATA_TYPE_SEPARATOR + dataTypeName); XmlUtils.maybeSetHrBoolAttr( hrDataTypeEle, XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL, @@ -135,8 +132,6 @@ public class DataLabels implements AslMarshallable { hrDataTypeEle, XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL, dataType.getIsSharingOptional()); - XmlUtils.maybeSetHrBoolAttr( - hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL, dataType.getEphemeral()); hrDataTypeEle.setAttribute( XmlUtils.HR_ATTR_PURPOSES, String.join( diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java index 5473e010cc65..c4d88761835a 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java @@ -18,16 +18,15 @@ package com.android.asllib.marshallable; import com.android.asllib.util.AslgenUtil; import com.android.asllib.util.DataCategoryConstants; +import com.android.asllib.util.DataTypeConstants; import com.android.asllib.util.MalformedXmlException; import com.android.asllib.util.XmlUtils; import org.w3c.dom.Element; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { @@ -39,13 +38,46 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { AslgenUtil.logI("Found no DataLabels in hr format."); return null; } - Map<String, DataCategory> dataAccessed = - getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_ACCESSED); Map<String, DataCategory> dataCollected = - getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED); + getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED, false); + Map<String, DataCategory> dataCollectedEphemeral = + getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED_EPHEMERAL, true); Map<String, DataCategory> dataShared = - getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED); - DataLabels dataLabels = new DataLabels(dataAccessed, dataCollected, dataShared); + getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED, null); + + for (String dataCollectedEphemeralDataCategoryKey : dataCollectedEphemeral.keySet()) { + DataCategory dataCategoryEphemeral = + dataCollectedEphemeral.get(dataCollectedEphemeralDataCategoryKey); + for (String dataCollectedEphemeralDataTypeKey : + dataCategoryEphemeral.getDataTypes().keySet()) { + if (dataCollected.containsKey(dataCollectedEphemeralDataCategoryKey) + && dataCollected + .get(dataCollectedEphemeralDataCategoryKey) + .getDataTypes() + .containsKey(dataCollectedEphemeralDataTypeKey)) { + throw new MalformedXmlException( + String.format( + "Duplicate entries in data-collected and" + + " data-collected-ephemeral: %s %s", + dataCollectedEphemeralDataCategoryKey, + dataCollectedEphemeralDataTypeKey)); + } + + if (!dataCollected.containsKey(dataCollectedEphemeralDataCategoryKey)) { + dataCollected.put( + dataCollectedEphemeralDataCategoryKey, + new DataCategory(dataCollectedEphemeralDataCategoryKey)); + } + DataType dataTypeEphemeral = + dataCategoryEphemeral.getDataTypes().get(dataCollectedEphemeralDataTypeKey); + dataCollected + .get(dataCollectedEphemeralDataCategoryKey) + .getDataTypes() + .put(dataCollectedEphemeralDataTypeKey, dataTypeEphemeral); + } + } + DataLabels dataLabels = new DataLabels(dataCollected, dataShared); + validateIsXOptional(dataLabels); return dataLabels; } @@ -58,13 +90,11 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { AslgenUtil.logI("Found no DataLabels in od format."); return null; } - Map<String, DataCategory> dataAccessed = - getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_ACCESSED); Map<String, DataCategory> dataCollected = getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_COLLECTED); Map<String, DataCategory> dataShared = getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_SHARED); - DataLabels dataLabels = new DataLabels(dataAccessed, dataCollected, dataShared); + DataLabels dataLabels = new DataLabels(dataCollected, dataShared); validateIsXOptional(dataLabels); return dataLabels; } @@ -88,56 +118,56 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { } private static Map<String, DataCategory> getDataCategoriesWithTag( - Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException { + Element dataLabelsEle, String dataCategoryUsageTypeTag, Boolean ephemeral) + throws MalformedXmlException { List<Element> dataUsedElements = XmlUtils.getChildrenByTagName(dataLabelsEle, dataCategoryUsageTypeTag); Map<String, DataCategory> dataCategoryMap = new LinkedHashMap<String, DataCategory>(); - Set<String> dataCategoryNames = new HashSet<String>(); for (int i = 0; i < dataUsedElements.size(); i++) { Element dataUsedEle = dataUsedElements.get(i); - String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY); + String dataCategoryAndTypeCombinedStr = + dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE); + String[] strs = dataCategoryAndTypeCombinedStr.split(XmlUtils.DATA_TYPE_SEPARATOR); + if (strs.length != 2) { + throw new MalformedXmlException( + String.format( + "Could not parse human-readable data type string (expecting" + + " substring of _data_type_): %s", + dataCategoryAndTypeCombinedStr)); + } + String dataCategoryName = strs[0]; + String dataTypeName = strs[1]; + if (!DataCategoryConstants.getValidDataCategories().contains(dataCategoryName)) { throw new MalformedXmlException( String.format("Unrecognized category name: %s", dataCategoryName)); } - dataCategoryNames.add(dataCategoryName); - } - for (String dataCategoryName : dataCategoryNames) { - var dataCategoryElements = - dataUsedElements.stream() - .filter( - ele -> - ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY) - .equals(dataCategoryName)) - .toList(); - DataCategory dataCategory = - new DataCategoryFactory().createFromHrElements(dataCategoryElements); - dataCategoryMap.put(dataCategoryName, dataCategory); + if (!DataTypeConstants.getValidDataTypes() + .get(dataCategoryName) + .contains(dataTypeName)) { + throw new MalformedXmlException( + String.format( + "Unrecognized data type name %s for category %s", + dataTypeName, dataCategoryName)); + } + + if (!dataCategoryMap.containsKey(dataCategoryName)) { + dataCategoryMap.put(dataCategoryName, new DataCategory(dataCategoryName)); + } + dataCategoryMap + .get(dataCategoryName) + .getDataTypes() + .put( + dataTypeName, + new DataTypeFactory().createFromHrElements(dataUsedEle, ephemeral)); } + return dataCategoryMap; } private void validateIsXOptional(DataLabels dataLabels) throws MalformedXmlException { // Validate booleans such as isCollectionOptional, isSharingOptional. - for (DataCategory dataCategory : dataLabels.getDataAccessed().values()) { - for (DataType dataType : dataCategory.getDataTypes().values()) { - if (dataType.getIsSharingOptional() != null) { - throw new MalformedXmlException( - String.format( - "isSharingOptional was unexpectedly defined on a DataType" - + " belonging to data accessed: %s", - dataType.getDataTypeName())); - } - if (dataType.getIsCollectionOptional() != null) { - throw new MalformedXmlException( - String.format( - "isCollectionOptional was unexpectedly defined on a DataType" - + " belonging to data accessed: %s", - dataType.getDataTypeName())); - } - } - } for (DataCategory dataCategory : dataLabels.getDataCollected().values()) { for (DataType dataType : dataCategory.getDataTypes().values()) { if (dataType.getIsSharingOptional() != null) { diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java index 488c2595912a..a5559d801349 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java @@ -25,12 +25,22 @@ import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; -public class DataTypeFactory implements AslMarshallableFactory<DataType> { +public class DataTypeFactory { /** Creates a {@link DataType} from the human-readable DOM element. */ - @Override - public DataType createFromHrElements(List<Element> elements) throws MalformedXmlException { - Element hrDataTypeEle = XmlUtils.getSingleElement(elements); - String dataTypeName = hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE); + public DataType createFromHrElements(Element hrDataTypeEle, Boolean ephemeral) + throws MalformedXmlException { + String dataCategoryAndTypeCombinedStr = + hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE); + String[] strs = dataCategoryAndTypeCombinedStr.split(XmlUtils.DATA_TYPE_SEPARATOR); + if (strs.length != 2) { + throw new MalformedXmlException( + String.format( + "Could not parse human-readable data type string (expecting substring" + + " of _data_type_): %s", + dataCategoryAndTypeCombinedStr)); + } + String dataTypeName = strs[1]; + List<DataType.Purpose> purposes = XmlUtils.getPipelineSplitAttr(hrDataTypeEle, XmlUtils.HR_ATTR_PURPOSES, true) .stream() @@ -47,13 +57,13 @@ public class DataTypeFactory implements AslMarshallableFactory<DataType> { XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL, false); Boolean isSharingOptional = XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL, false); - Boolean ephemeral = XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL, false); + // Boolean ephemeral = XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL, + // false); return new DataType( dataTypeName, purposes, isCollectionOptional, isSharingOptional, ephemeral); } /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ - @Override public DataType createFromOdElements(List<Element> elements) throws MalformedXmlException { Element odDataTypeEle = XmlUtils.getSingleElement(elements); String dataTypeName = odDataTypeEle.getAttribute(XmlUtils.OD_ATTR_NAME); diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java index 854c0d0ac3e1..242e7be66d76 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java @@ -26,15 +26,10 @@ import java.util.List; /** Safety Label representation containing zero or more {@link DataCategory} for data shared */ public class SystemAppSafetyLabel implements AslMarshallable { - private final String mUrl; + private final Boolean mDeclaration; - public SystemAppSafetyLabel(String url) { - this.mUrl = url; - } - - /** Returns the system app safety label URL. */ - public String getUrl() { - return mUrl; + public SystemAppSafetyLabel(Boolean d) { + this.mDeclaration = d; } /** Creates an on-device DOM element from the {@link SystemAppSafetyLabel}. */ @@ -43,7 +38,7 @@ public class SystemAppSafetyLabel implements AslMarshallable { Element systemAppSafetyLabelEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL); systemAppSafetyLabelEle.appendChild( - XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl)); + XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_DECLARATION, mDeclaration)); return XmlUtils.listOf(systemAppSafetyLabelEle); } @@ -52,7 +47,8 @@ public class SystemAppSafetyLabel implements AslMarshallable { public List<Element> toHrDomElements(Document doc) { Element systemAppSafetyLabelEle = doc.createElement(XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL); - systemAppSafetyLabelEle.setAttribute(XmlUtils.HR_ATTR_URL, mUrl); + XmlUtils.maybeSetHrBoolAttr( + systemAppSafetyLabelEle, XmlUtils.HR_ATTR_DECLARATION, mDeclaration); return XmlUtils.listOf(systemAppSafetyLabelEle); } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java index c8e22b6c42cd..7f4aa7a3b690 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java @@ -36,8 +36,9 @@ public class SystemAppSafetyLabelFactory implements AslMarshallableFactory<Syste return null; } - String url = XmlUtils.getStringAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_URL, true); - return new SystemAppSafetyLabel(url); + Boolean declaration = + XmlUtils.getBoolAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_DECLARATION, true); + return new SystemAppSafetyLabel(declaration); } /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ @@ -49,7 +50,8 @@ public class SystemAppSafetyLabelFactory implements AslMarshallableFactory<Syste AslgenUtil.logI("No SystemAppSafetyLabel found in od format."); return null; } - String url = XmlUtils.getOdStringEle(systemAppSafetyLabelEle, XmlUtils.OD_NAME_URL, true); - return new SystemAppSafetyLabel(url); + Boolean declaration = + XmlUtils.getOdBoolEle(systemAppSafetyLabelEle, XmlUtils.OD_NAME_DECLARATION, true); + return new SystemAppSafetyLabel(declaration); } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java index 1d54ead0a28d..97cbc3950490 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java @@ -27,6 +27,8 @@ import java.util.Arrays; import java.util.List; public class XmlUtils { + public static final String DATA_TYPE_SEPARATOR = "_data_type_"; + public static final String HR_TAG_APP_METADATA_BUNDLES = "app-metadata-bundles"; public static final String HR_TAG_SYSTEM_APP_SAFETY_LABEL = "system-app-safety-label"; public static final String HR_TAG_SAFETY_LABELS = "safety-labels"; @@ -38,6 +40,7 @@ public class XmlUtils { public static final String HR_TAG_THIRD_PARTY_VERIFICATION = "third-party-verification"; public static final String HR_TAG_DATA_ACCESSED = "data-accessed"; public static final String HR_TAG_DATA_COLLECTED = "data-collected"; + public static final String HR_TAG_DATA_COLLECTED_EPHEMERAL = "data-collected-ephemeral"; public static final String HR_TAG_DATA_SHARED = "data-shared"; public static final String HR_ATTR_NAME = "name"; public static final String HR_ATTR_EMAIL = "email"; @@ -52,10 +55,11 @@ public class XmlUtils { public static final String HR_ATTR_IS_SHARING_OPTIONAL = "isSharingOptional"; public static final String HR_ATTR_IS_DATA_DELETABLE = "isDataDeletable"; public static final String HR_ATTR_IS_DATA_ENCRYPTED = "isDataEncrypted"; - public static final String HR_ATTR_EPHEMERAL = "ephemeral"; + // public static final String HR_ATTR_EPHEMERAL = "ephemeral"; public static final String HR_ATTR_PURPOSES = "purposes"; public static final String HR_ATTR_VERSION = "version"; public static final String HR_ATTR_URL = "url"; + public static final String HR_ATTR_DECLARATION = "declaration"; public static final String HR_ATTR_TITLE = "title"; public static final String HR_ATTR_DESCRIPTION = "description"; public static final String HR_ATTR_CONTAINS_ADS = "containsAds"; @@ -103,6 +107,7 @@ public class XmlUtils { public static final String OD_NAME_CATEGORY = "category"; public static final String OD_NAME_VERSION = "version"; public static final String OD_NAME_URL = "url"; + public static final String OD_NAME_DECLARATION = "declaration"; public static final String OD_NAME_SYSTEM_APP_SAFETY_LABEL = "system_app_safety_label"; public static final String OD_NAME_SECURITY_LABELS = "security_labels"; public static final String OD_NAME_THIRD_PARTY_VERIFICATION = "third_party_verification"; @@ -299,12 +304,13 @@ public class XmlUtils { .toList(); if (boolEles.size() > 1) { throw new MalformedXmlException( - String.format("Found more than one %s in %s.", nameName, ele.getTagName())); + String.format( + "Found more than one boolean %s in %s.", nameName, ele.getTagName())); } if (boolEles.isEmpty()) { if (required) { throw new MalformedXmlException( - String.format("Found no %s in %s.", nameName, ele.getTagName())); + String.format("Found no boolean %s in %s.", nameName, ele.getTagName())); } return null; } @@ -329,12 +335,13 @@ public class XmlUtils { .toList(); if (longEles.size() > 1) { throw new MalformedXmlException( - String.format("Found more than one %s in %s.", nameName, ele.getTagName())); + String.format( + "Found more than one long %s in %s.", nameName, ele.getTagName())); } if (longEles.isEmpty()) { if (required) { throw new MalformedXmlException( - String.format("Found no %s in %s.", nameName, ele.getTagName())); + String.format("Found no long %s in %s.", nameName, ele.getTagName())); } return null; } @@ -359,12 +366,13 @@ public class XmlUtils { .toList(); if (eles.size() > 1) { throw new MalformedXmlException( - String.format("Found more than one %s in %s.", nameName, ele.getTagName())); + String.format( + "Found more than one string %s in %s.", nameName, ele.getTagName())); } if (eles.isEmpty()) { if (required) { throw new MalformedXmlException( - String.format("Found no %s in %s.", nameName, ele.getTagName())); + String.format("Found no string %s in %s.", nameName, ele.getTagName())); } return null; } @@ -386,12 +394,13 @@ public class XmlUtils { .toList(); if (eles.size() > 1) { throw new MalformedXmlException( - String.format("Found more than one %s in %s.", nameName, ele.getTagName())); + String.format( + "Found more than one pbundle %s in %s.", nameName, ele.getTagName())); } if (eles.isEmpty()) { if (required) { throw new MalformedXmlException( - String.format("Found no %s in %s.", nameName, ele.getTagName())); + String.format("Found no pbundle %s in %s.", nameName, ele.getTagName())); } return null; } @@ -456,12 +465,15 @@ public class XmlUtils { .toList(); if (arrayEles.size() > 1) { throw new MalformedXmlException( - String.format("Found more than one %s in %s.", nameName, ele.getTagName())); + String.format( + "Found more than one string array %s in %s.", + nameName, ele.getTagName())); } if (arrayEles.isEmpty()) { if (required) { throw new MalformedXmlException( - String.format("Found no %s in %s.", nameName, ele.getTagName())); + String.format( + "Found no string array %s in %s.", nameName, ele.getTagName())); } return null; } diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java index f156484485ec..dbeeb496144a 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java @@ -18,7 +18,6 @@ package com.android.asllib; import com.android.asllib.marshallable.AndroidSafetyLabelTest; import com.android.asllib.marshallable.AppInfoTest; -import com.android.asllib.marshallable.DataCategoryTest; import com.android.asllib.marshallable.DataLabelsTest; import com.android.asllib.marshallable.DataTypeEqualityTest; import com.android.asllib.marshallable.DeveloperInfoTest; @@ -36,7 +35,7 @@ import org.junit.runners.Suite; AslgenTests.class, AndroidSafetyLabelTest.class, AppInfoTest.class, - DataCategoryTest.class, + // DataCategoryTest.class, DataLabelsTest.class, DataTypeEqualityTest.class, DeveloperInfoTest.class, diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java index d2e0fc338243..5d1d45a9a29b 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java @@ -34,8 +34,7 @@ import java.util.List; @RunWith(JUnit4.class) public class AslgenTests { private static final String VALID_MAPPINGS_PATH = "com/android/asllib/validmappings"; - private static final List<String> VALID_MAPPINGS_SUBDIRS = - List.of("location", "contacts", "general"); + private static final List<String> VALID_MAPPINGS_SUBDIRS = List.of("general"); private static final String HR_XML_FILENAME = "hr.xml"; private static final String OD_XML_FILENAME = "od.xml"; diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java deleted file mode 100644 index ebb31865843f..000000000000 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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 - * - * 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.asllib.marshallable; - -import com.android.asllib.testutils.TestUtils; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class DataCategoryTest { - private static final String DATA_CATEGORY_HR_PATH = "com/android/asllib/datacategory/hr"; - private static final String DATA_CATEGORY_OD_PATH = "com/android/asllib/datacategory/od"; - - private static final String VALID_PERSONAL_FILE_NAME = "data-category-personal.xml"; - private static final String VALID_PARTIAL_PERSONAL_FILE_NAME = - "data-category-personal-partial.xml"; - private static final String VALID_FINANCIAL_FILE_NAME = "data-category-financial.xml"; - private static final String VALID_LOCATION_FILE_NAME = "data-category-location.xml"; - private static final String VALID_EMAIL_TEXT_MESSAGE_FILE_NAME = - "data-category-email-text-message.xml"; - private static final String VALID_PHOTO_VIDEO_FILE_NAME = "data-category-photo-video.xml"; - private static final String VALID_AUDIO_FILE_NAME = "data-category-audio.xml"; - private static final String VALID_STORAGE_FILE_NAME = "data-category-storage.xml"; - private static final String VALID_HEALTH_FITNESS_FILE_NAME = "data-category-health-fitness.xml"; - private static final String VALID_CONTACTS_FILE_NAME = "data-category-contacts.xml"; - private static final String VALID_CALENDAR_FILE_NAME = "data-category-calendar.xml"; - private static final String VALID_IDENTIFIERS_FILE_NAME = "data-category-identifiers.xml"; - private static final String VALID_APP_PERFORMANCE_FILE_NAME = - "data-category-app-performance.xml"; - private static final String VALID_ACTIONS_IN_APP_FILE_NAME = "data-category-actions-in-app.xml"; - private static final String VALID_SEARCH_AND_BROWSING_FILE_NAME = - "data-category-search-and-browsing.xml"; - - private static final String EMPTY_PURPOSE_PERSONAL_FILE_NAME = - "data-category-personal-empty-purpose.xml"; - private static final String MISSING_PURPOSE_PERSONAL_FILE_NAME = - "data-category-personal-missing-purpose.xml"; - private static final String UNRECOGNIZED_TYPE_PERSONAL_FILE_NAME = - "data-category-personal-unrecognized-type.xml"; - private static final String UNRECOGNIZED_CATEGORY_FILE_NAME = "data-category-unrecognized.xml"; - - /** Logic for setting up tests (empty if not yet needed). */ - public static void main(String[] params) throws Exception {} - - @Before - public void setUp() throws Exception { - System.out.println("set up."); - } - - /** Test for data category personal. */ - @Test - public void testDataCategoryPersonal() throws Exception { - System.out.println("starting testDataCategoryPersonal."); - testHrToOdDataCategory(VALID_PERSONAL_FILE_NAME); - } - - /** Test for data category financial. */ - @Test - public void testDataCategoryFinancial() throws Exception { - System.out.println("starting testDataCategoryFinancial."); - testHrToOdDataCategory(VALID_FINANCIAL_FILE_NAME); - } - - /** Test for data category location. */ - @Test - public void testDataCategoryLocation() throws Exception { - System.out.println("starting testDataCategoryLocation."); - testHrToOdDataCategory(VALID_LOCATION_FILE_NAME); - } - - /** Test for data category email text message. */ - @Test - public void testDataCategoryEmailTextMessage() throws Exception { - System.out.println("starting testDataCategoryEmailTextMessage."); - testHrToOdDataCategory(VALID_EMAIL_TEXT_MESSAGE_FILE_NAME); - } - - /** Test for data category photo video. */ - @Test - public void testDataCategoryPhotoVideo() throws Exception { - System.out.println("starting testDataCategoryPhotoVideo."); - testHrToOdDataCategory(VALID_PHOTO_VIDEO_FILE_NAME); - } - - /** Test for data category audio. */ - @Test - public void testDataCategoryAudio() throws Exception { - System.out.println("starting testDataCategoryAudio."); - testHrToOdDataCategory(VALID_AUDIO_FILE_NAME); - } - - /** Test for data category storage. */ - @Test - public void testDataCategoryStorage() throws Exception { - System.out.println("starting testDataCategoryStorage."); - testHrToOdDataCategory(VALID_STORAGE_FILE_NAME); - } - - /** Test for data category health fitness. */ - @Test - public void testDataCategoryHealthFitness() throws Exception { - System.out.println("starting testDataCategoryHealthFitness."); - testHrToOdDataCategory(VALID_HEALTH_FITNESS_FILE_NAME); - } - - /** Test for data category contacts. */ - @Test - public void testDataCategoryContacts() throws Exception { - System.out.println("starting testDataCategoryContacts."); - testHrToOdDataCategory(VALID_CONTACTS_FILE_NAME); - } - - /** Test for data category calendar. */ - @Test - public void testDataCategoryCalendar() throws Exception { - System.out.println("starting testDataCategoryCalendar."); - testHrToOdDataCategory(VALID_CALENDAR_FILE_NAME); - } - - /** Test for data category identifiers. */ - @Test - public void testDataCategoryIdentifiers() throws Exception { - System.out.println("starting testDataCategoryIdentifiers."); - testHrToOdDataCategory(VALID_IDENTIFIERS_FILE_NAME); - } - - /** Test for data category app performance. */ - @Test - public void testDataCategoryAppPerformance() throws Exception { - System.out.println("starting testDataCategoryAppPerformance."); - testHrToOdDataCategory(VALID_APP_PERFORMANCE_FILE_NAME); - } - - /** Test for data category actions in app. */ - @Test - public void testDataCategoryActionsInApp() throws Exception { - System.out.println("starting testDataCategoryActionsInApp."); - testHrToOdDataCategory(VALID_ACTIONS_IN_APP_FILE_NAME); - } - - /** Test for data category search and browsing. */ - @Test - public void testDataCategorySearchAndBrowsing() throws Exception { - System.out.println("starting testDataCategorySearchAndBrowsing."); - testHrToOdDataCategory(VALID_SEARCH_AND_BROWSING_FILE_NAME); - } - - /** Test for data category search and browsing. */ - @Test - public void testMissingOptionalsAllowed() throws Exception { - System.out.println("starting testMissingOptionalsAllowed."); - testHrToOdDataCategory(VALID_PARTIAL_PERSONAL_FILE_NAME); - } - - /** Test for empty purposes. */ - @Test - public void testEmptyPurposesNotAllowed() throws Exception { - System.out.println("starting testEmptyPurposesNotAllowed."); - hrToOdExpectException(EMPTY_PURPOSE_PERSONAL_FILE_NAME); - } - - /** Test for missing purposes. */ - @Test - public void testMissingPurposesNotAllowed() throws Exception { - System.out.println("starting testMissingPurposesNotAllowed."); - hrToOdExpectException(MISSING_PURPOSE_PERSONAL_FILE_NAME); - } - - /** Test for unrecognized type. */ - @Test - public void testUnrecognizedTypeNotAllowed() throws Exception { - System.out.println("starting testUnrecognizedTypeNotAllowed."); - hrToOdExpectException(UNRECOGNIZED_TYPE_PERSONAL_FILE_NAME); - } - - /** Test for unrecognized category. */ - @Test - public void testUnrecognizedCategoryNotAllowed() throws Exception { - System.out.println("starting testUnrecognizedCategoryNotAllowed."); - hrToOdExpectException(UNRECOGNIZED_CATEGORY_FILE_NAME); - } - - private void hrToOdExpectException(String fileName) { - TestUtils.hrToOdExpectException(new DataCategoryFactory(), DATA_CATEGORY_HR_PATH, fileName); - } - - private void testHrToOdDataCategory(String fileName) throws Exception { - TestUtils.testHrToOd( - TestUtils.document(), - new DataCategoryFactory(), - DATA_CATEGORY_HR_PATH, - DATA_CATEGORY_OD_PATH, - fileName); - } -} diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java index 26617264b2e9..ff4374166dd3 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java @@ -34,6 +34,10 @@ public class DataLabelsTest { "data-labels-accessed-invalid-bool.xml"; private static final String COLLECTED_VALID_BOOL_FILE_NAME = "data-labels-collected-valid-bool.xml"; + private static final String COLLECTED_EPHEMERAL_FILE_NAME = + "data-labels-collected-ephemeral.xml"; + private static final String COLLECTED_EPHEMERAL_COLLISION_FILE_NAME = + "data-labels-collected-ephemeral-collision.xml"; private static final String COLLECTED_INVALID_BOOL_FILE_NAME = "data-labels-collected-invalid-bool.xml"; private static final String SHARED_VALID_BOOL_FILE_NAME = "data-labels-shared-valid-bool.xml"; @@ -69,27 +73,27 @@ public class DataLabelsTest { System.out.println("set up."); } - /** Test for data labels accessed valid bool. */ + /** Test for data labels collected valid bool. */ @Test - public void testDataLabelsAccessedValidBool() throws Exception { - System.out.println("starting testDataLabelsAccessedValidBool."); - testHrToOdDataLabels(ACCESSED_VALID_BOOL_FILE_NAME); - testOdToHrDataLabels(ACCESSED_VALID_BOOL_FILE_NAME); + public void testDataLabelsCollectedValidBool() throws Exception { + System.out.println("starting testDataLabelsCollectedValidBool."); + testHrToOdDataLabels(COLLECTED_VALID_BOOL_FILE_NAME); + testOdToHrDataLabels(COLLECTED_VALID_BOOL_FILE_NAME); } - /** Test for data labels accessed invalid bool. */ + /** Test for data labels collected ephemeral. */ @Test - public void testDataLabelsAccessedInvalidBool() throws Exception { - System.out.println("starting testDataLabelsAccessedInvalidBool."); - hrToOdExpectException(ACCESSED_INVALID_BOOL_FILE_NAME); + public void testDataLabelsCollectedEphemeral() throws Exception { + System.out.println("starting testDataLabelsCollectedEphemeral."); + testHrToOdDataLabels(COLLECTED_EPHEMERAL_FILE_NAME); + testOdToHrDataLabels(COLLECTED_EPHEMERAL_FILE_NAME); } - /** Test for data labels collected valid bool. */ + /** Test for data labels ephemeral collision. */ @Test - public void testDataLabelsCollectedValidBool() throws Exception { - System.out.println("starting testDataLabelsCollectedValidBool."); - testHrToOdDataLabels(COLLECTED_VALID_BOOL_FILE_NAME); - testOdToHrDataLabels(COLLECTED_VALID_BOOL_FILE_NAME); + public void testDataLabelsCollectedEphemeralCollision() throws Exception { + System.out.println("starting testDataLabelsCollectedEphemeralCollision."); + hrToOdExpectException(COLLECTED_EPHEMERAL_COLLISION_FILE_NAME); } /** Test for data labels collected invalid bool. */ diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java index 33c276487c64..87d3e4499f5c 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java @@ -31,7 +31,7 @@ public class SystemAppSafetyLabelTest { "com/android/asllib/systemappsafetylabel/od"; private static final String VALID_FILE_NAME = "valid.xml"; - private static final String MISSING_URL_FILE_NAME = "missing-url.xml"; + private static final String MISSING_BOOL_FILE_NAME = "missing-bool.xml"; /** Logic for setting up tests (empty if not yet needed). */ public static void main(String[] params) throws Exception {} @@ -49,12 +49,12 @@ public class SystemAppSafetyLabelTest { testOdToHrSystemAppSafetyLabel(VALID_FILE_NAME); } - /** Tests missing url. */ + /** Tests missing bool. */ @Test - public void testMissingUrl() throws Exception { - System.out.println("starting testMissingUrl."); - hrToOdExpectException(MISSING_URL_FILE_NAME); - odToHrExpectException(MISSING_URL_FILE_NAME); + public void testMissingBool() throws Exception { + System.out.println("starting testMissingBool."); + hrToOdExpectException(MISSING_BOOL_FILE_NAME); + odToHrExpectException(MISSING_BOOL_FILE_NAME); } private void hrToOdExpectException(String fileName) { diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml index 7bcde4547933..afb048632bc6 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml @@ -1,4 +1,4 @@ <app-metadata-bundles version="123456"> -<system-app-safety-label url="www.example.com"> +<system-app-safety-label declaration="true"> </system-app-safety-label> </app-metadata-bundles>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml index ef0f549fc46b..e8640c4f0934 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml @@ -1,6 +1,6 @@ <bundle> <long name="version" value="123456"/> <pbundle_as_map name="system_app_safety_label"> - <string name="url" value="www.example.com"/> + <boolean name="declaration" value="true"/> </pbundle_as_map> </bundle>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml index 68e191e8ebe3..680e01a97af3 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml @@ -1,17 +1,12 @@ <data-labels> - <data-shared dataCategory="actions_in_app" - dataType="user_interaction" + <data-shared dataType="actions_in_app_data_type_user_interaction" purposes="analytics" /> - <data-shared dataCategory="actions_in_app" - dataType="in_app_search_history" + <data-shared dataType="actions_in_app_data_type_in_app_search_history" purposes="analytics" /> - <data-shared dataCategory="actions_in_app" - dataType="installed_apps" + <data-shared dataType="actions_in_app_data_type_installed_apps" purposes="analytics" /> - <data-shared dataCategory="actions_in_app" - dataType="user_generated_content" + <data-shared dataType="actions_in_app_data_type_user_generated_content" purposes="analytics" /> - <data-shared dataCategory="actions_in_app" - dataType="other" + <data-shared dataType="actions_in_app_data_type_other" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml index a6bd17d63704..db114bfe15db 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml @@ -1,11 +1,8 @@ <data-labels> - <data-shared dataCategory="app_performance" - dataType="crash_logs" + <data-shared dataType="app_performance_data_type_crash_logs" purposes="analytics" /> - <data-shared dataCategory="app_performance" - dataType="performance_diagnostics" + <data-shared dataType="app_performance_data_type_performance_diagnostics" purposes="analytics" /> - <data-shared dataCategory="app_performance" - dataType="other" + <data-shared dataType="app_performance_data_type_other" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml index 6274604f3431..cf273f4dada0 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml @@ -1,11 +1,8 @@ <data-labels> - <data-shared dataCategory="audio" - dataType="sound_recordings" + <data-shared dataType="audio_data_type_sound_recordings" purposes="analytics" /> - <data-shared dataCategory="audio" - dataType="music_files" + <data-shared dataType="audio_data_type_music_files" purposes="analytics" /> - <data-shared dataCategory="audio" - dataType="other" + <data-shared dataType="audio_data_type_other" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml index f7201f625629..16f9d9b699e4 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml @@ -1,5 +1,4 @@ <data-labels> - <data-shared dataCategory="calendar" - dataType="calendar" + <data-shared dataType="calendar_data_type_calendar" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml index e8d40be91d90..6d7a4e8c976d 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml @@ -1,5 +1,4 @@ <data-labels> - <data-shared dataCategory="contacts" - dataType="contacts" + <data-shared dataType="contacts_data_type_contacts" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml index 69e9b87db287..7a9e978afd10 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml @@ -1,11 +1,8 @@ <data-labels> - <data-shared dataCategory="email_text_message" - dataType="emails" + <data-shared dataType="email_text_message_data_type_emails" purposes="analytics" /> - <data-shared dataCategory="email_text_message" - dataType="text_messages" + <data-shared dataType="email_text_message_data_type_text_messages" purposes="analytics" /> - <data-shared dataCategory="email_text_message" - dataType="other" + <data-shared dataType="email_text_message_data_type_other" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml index fdd84569a5df..24385b64fe6c 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml @@ -1,14 +1,10 @@ <data-labels> - <data-shared dataCategory="financial" - dataType="card_bank_account" + <data-shared dataType="financial_data_type_card_bank_account" purposes="analytics" /> - <data-shared dataCategory="financial" - dataType="purchase_history" + <data-shared dataType="financial_data_type_purchase_history" purposes="analytics" /> - <data-shared dataCategory="financial" - dataType="credit_score" + <data-shared dataType="financial_data_type_credit_score" purposes="analytics" /> - <data-shared dataCategory="financial" - dataType="other" + <data-shared dataType="financial_data_type_other" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml index bac58e6fb7df..faf30b059eef 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml @@ -1,8 +1,6 @@ <data-labels> - <data-shared dataCategory="health_fitness" - dataType="health" + <data-shared dataType="health_fitness_data_type_health" purposes="analytics" /> - <data-shared dataCategory="health_fitness" - dataType="fitness" + <data-shared dataType="health_fitness_data_type_fitness" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml index ee45f269eb7f..510190660bef 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml @@ -1,5 +1,4 @@ <data-labels> - <data-shared dataCategory="identifiers" - dataType="other" + <data-shared dataType="identifiers_data_type_other" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml index e8e59118f69c..72cda7e40deb 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml @@ -1,8 +1,6 @@ <data-labels> - <data-shared dataCategory="location" - dataType="approx_location" + <data-shared dataType="location_data_type_approx_location" purposes="analytics" /> - <data-shared dataCategory="location" - dataType="precise_location" + <data-shared dataType="location_data_type_precise_location" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml index 0b220f43e5af..25586814c966 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml @@ -1,5 +1,4 @@ <data-labels> - <data-shared dataCategory="personal" - dataType="email_address" + <data-shared dataType="personal_data_type_email_address" purposes="" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml index ac221f208577..c5a54755387b 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml @@ -1,4 +1,3 @@ <data-labels> - <data-shared dataCategory="personal" - dataType="email_address" /> + <data-shared dataType="personal_data_type_email_address" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml index 11b73689230b..6ccf336f8556 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml @@ -1,8 +1,6 @@ <data-labels> - <data-shared dataCategory="personal" - dataType="name" + <data-shared dataType="personal_data_type_name" purposes="analytics|developer_communications" /> - <data-shared dataCategory="personal" - dataType="email_address" + <data-shared dataType="personal_data_type_email_address" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml index f1fbd56571b2..bd88adafea60 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml @@ -1,5 +1,4 @@ <data-labels> - <data-shared dataCategory="personal" - dataType="unrecognized" + <data-shared dataType="personal_data_type_unrecognized" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml index 59074628de70..742ed86ed4e5 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml @@ -1,31 +1,21 @@ <data-labels> - <data-shared dataCategory="personal" - dataType="name" - ephemeral="true" + <data-shared dataType="personal_data_type_name" isSharingOptional="true" purposes="analytics|developer_communications" /> - <data-shared dataCategory="personal" - dataType="email_address" + <data-shared dataType="personal_data_type_email_address" purposes="analytics" /> - <data-shared dataCategory="personal" - dataType="physical_address" + <data-shared dataType="personal_data_type_physical_address" purposes="analytics" /> - <data-shared dataCategory="personal" - dataType="phone_number" + <data-shared dataType="personal_data_type_phone_number" purposes="analytics" /> - <data-shared dataCategory="personal" - dataType="race_ethnicity" + <data-shared dataType="personal_data_type_race_ethnicity" purposes="analytics" /> - <data-shared dataCategory="personal" - dataType="political_or_religious_beliefs" + <data-shared dataType="personal_data_type_political_or_religious_beliefs" purposes="analytics" /> - <data-shared dataCategory="personal" - dataType="sexual_orientation_or_gender_identity" + <data-shared dataType="personal_data_type_sexual_orientation_or_gender_identity" purposes="analytics" /> - <data-shared dataCategory="personal" - dataType="personal_identifiers" + <data-shared dataType="personal_data_type_personal_identifiers" purposes="analytics" /> - <data-shared dataCategory="personal" - dataType="other" + <data-shared dataType="personal_data_type_other" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml index 05fe159a1d7c..d416063b87da 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml @@ -1,8 +1,6 @@ <data-labels> - <data-shared dataCategory="photo_video" - dataType="photos" + <data-shared dataType="photo_video_data_type_photos" purposes="analytics" /> - <data-shared dataCategory="photo_video" - dataType="videos" + <data-shared dataType="photo_video_data_type_videos" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml index a5de7bed4152..3d932d6d01bf 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml @@ -1,5 +1,4 @@ <data-labels> - <data-shared dataCategory="search_and_browsing" - dataType="web_browsing_history" + <data-shared dataType="search_and_browsing_data_type_web_browsing_history" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml index f01e2df8d99a..704cb1c4674e 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml @@ -1,5 +1,4 @@ <data-labels> - <data-shared dataCategory="storage" - dataType="files_docs" + <data-shared dataType="storage_data_type_files_docs" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml index f1fbd56571b2..bd88adafea60 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml @@ -1,5 +1,4 @@ <data-labels> - <data-shared dataCategory="personal" - dataType="unrecognized" + <data-shared dataType="personal_data_type_unrecognized" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml index c5be68424226..a578d7343f28 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml @@ -1,5 +1,4 @@ <data-labels> - <data-shared dataCategory="unrecognized" - dataType="email_address" + <data-shared dataType="unrecognized_data_type_email_address" purposes="analytics" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml index 161057a51b79..c0bd6529eac0 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml @@ -1,11 +1,8 @@ <data-labels> - <data-accessed dataCategory="location" - dataType="approx_location" + <data-accessed dataType="location_data_type_approx_location" purposes="app_functionality" /> - <data-collected dataCategory="location" - dataType="precise_location" + <data-collected dataType="location_data_type_precise_location" purposes="app_functionality" /> - <data-shared dataCategory="personal" - dataType="name" + <data-shared dataType="personal_data_type_name" purposes="app_functionality" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml index bb45f426e083..d09fc3b48f51 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml @@ -1,7 +1,5 @@ <data-labels> - <data-accessed dataCategory="location" - dataType="approx_location" - ephemeral="false" + <data-accessed dataType="location_data_type_approx_location" isSharingOptional="false" purposes="app_functionality" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml index f927bba838fd..6e7f81257116 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml @@ -1,6 +1,4 @@ <data-labels> - <data-accessed dataCategory="location" - dataType="approx_location" - ephemeral="false" + <data-accessed dataType="location_data_type_approx_location" purposes="app_functionality" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral-collision.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral-collision.xml new file mode 100644 index 000000000000..ee362feb2ca5 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral-collision.xml @@ -0,0 +1,14 @@ +<data-labels> + <data-collected dataType="photo_video_data_type_photos" + isCollectionOptional="true" + purposes="app_functionality" /> + <data-collected-ephemeral dataType="location_data_type_approx_location" + isCollectionOptional="false" + purposes="app_functionality" /> + <data-collected dataType="location_data_type_approx_location" + isCollectionOptional="true" + purposes="app_functionality" /> + <data-collected-ephemeral dataType="contacts_data_type_contacts" + isCollectionOptional="true" + purposes="app_functionality" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral.xml new file mode 100644 index 000000000000..79c900027666 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral.xml @@ -0,0 +1,14 @@ +<data-labels> + <data-collected dataType="photo_video_data_type_photos" + isCollectionOptional="true" + purposes="app_functionality" /> + <data-collected dataType="location_data_type_precise_location" + isCollectionOptional="true" + purposes="app_functionality" /> + <data-collected-ephemeral dataType="location_data_type_approx_location" + isCollectionOptional="false" + purposes="app_functionality" /> + <data-collected-ephemeral dataType="contacts_data_type_contacts" + isCollectionOptional="true" + purposes="app_functionality" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml index ba11afbdce5f..801fadaadcdd 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml @@ -1,7 +1,5 @@ <data-labels> - <data-collected dataCategory="location" - dataType="approx_location" - ephemeral="false" + <data-collected dataType="location_data_type_approx_location" isSharingOptional="false" purposes="app_functionality" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml index 4b6d39776aeb..1ada12db0af8 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml @@ -1,7 +1,5 @@ <data-labels> - <data-collected dataCategory="location" - dataType="approx_location" - ephemeral="false" + <data-collected dataType="location_data_type_approx_location" isCollectionOptional="false" purposes="app_functionality" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml index 7840b9876ad8..b327d88b2b8a 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml @@ -1,7 +1,5 @@ <data-labels> - <data-shared dataCategory="location" - dataType="approx_location" - ephemeral="false" + <data-shared dataType="location_data_type_approx_location" isCollectionOptional="false" purposes="app_functionality" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml index ccf77b0e03be..34bd0de2bf51 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml @@ -1,7 +1,5 @@ <data-labels> - <data-shared dataCategory="location" - dataType="approx_location" - ephemeral="false" + <data-shared dataType="location_data_type_approx_location" isSharingOptional="false" purposes="app_functionality" /> </data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml index 14f9ef2b74ad..974ea6922fce 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml @@ -1,17 +1,17 @@ <pbundle_as_map name="data_labels"> <pbundle_as_map name="data_shared"> <pbundle_as_map name="personal"> - <pbundle_as_map name="name"> - <int-array name="purposes" num="2"> - <item value="2" /> - <item value="3" /> - </int-array> - </pbundle_as_map> - <pbundle_as_map name="email_address"> - <int-array name="purposes" num="1"> - <item value="2" /> - </int-array> - </pbundle_as_map> -</pbundle_as_map> + <pbundle_as_map name="name"> + <int-array name="purposes" num="2"> + <item value="2" /> + <item value="3" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="email_address"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + </pbundle_as_map> </pbundle_as_map> </pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml index 1c87de9e67a7..62c26ab20c68 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml @@ -7,7 +7,6 @@ <item value="3" /> </int-array> <boolean name="is_sharing_optional" value="true" /> - <boolean name="ephemeral" value="true" /> </pbundle_as_map> <pbundle_as_map name="email_address"> <int-array name="purposes" num="1"> diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml index ddefc18f62e3..df000aa2a882 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml @@ -5,7 +5,6 @@ <int-array name="purposes" num="1"> <item value="1"/> </int-array> - <boolean name="ephemeral" value="false"/> </pbundle_as_map> </pbundle_as_map> </pbundle_as_map> diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-ephemeral.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-ephemeral.xml new file mode 100644 index 000000000000..c671c4be0f5a --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-ephemeral.xml @@ -0,0 +1,38 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_collected"> + <pbundle_as_map name="photo_video"> + <pbundle_as_map name="photos"> + <int-array name="purposes" num="1"> + <item value="1"/> + </int-array> + <boolean name="is_collection_optional" value="true"/> + <boolean name="ephemeral" value="false"/> + </pbundle_as_map> + </pbundle_as_map> + <pbundle_as_map name="location"> + <pbundle_as_map name="precise_location"> + <int-array name="purposes" num="1"> + <item value="1"/> + </int-array> + <boolean name="is_collection_optional" value="true"/> + <boolean name="ephemeral" value="false"/> + </pbundle_as_map> + <pbundle_as_map name="approx_location"> + <int-array name="purposes" num="1"> + <item value="1"/> + </int-array> + <boolean name="is_collection_optional" value="false"/> + <boolean name="ephemeral" value="true"/> + </pbundle_as_map> + </pbundle_as_map> + <pbundle_as_map name="contacts"> + <pbundle_as_map name="contacts"> + <int-array name="purposes" num="1"> + <item value="1"/> + </int-array> + <boolean name="is_collection_optional" value="true"/> + <boolean name="ephemeral" value="true"/> + </pbundle_as_map> + </pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml index 3864f986d20d..0edd8fa264c7 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml @@ -6,7 +6,6 @@ <item value="1"/> </int-array> <boolean name="is_sharing_optional" value="false"/> - <boolean name="ephemeral" value="false"/> </pbundle_as_map> </pbundle_as_map> </pbundle_as_map> diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml index 8997f4f30c33..84456daedbd5 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml @@ -1,9 +1,7 @@ <safety-labels version="12345"> <data-labels> - <data-shared dataCategory="location" - dataType="approx_location" + <data-shared dataType="location_data_type_approx_location" isSharingOptional="false" - ephemeral="false" purposes="app_functionality" /> </data-labels> </safety-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml index a966fdaf9fe0..fa2a3f841e09 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml @@ -8,7 +8,6 @@ <item value="1"/> </int-array> <boolean name="is_sharing_optional" value="false"/> - <boolean name="ephemeral" value="false"/> </pbundle_as_map> </pbundle_as_map> </pbundle_as_map> diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-bool.xml index ff26c05abdb0..ff26c05abdb0 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-bool.xml diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml index 6fe86c33523b..f01d7d299d4a 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml @@ -1 +1 @@ -<system-app-safety-label url="www.example.com"></system-app-safety-label>
\ No newline at end of file +<system-app-safety-label declaration="true"></system-app-safety-label>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-url.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-bool.xml index 33b796552463..33b796552463 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-url.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-bool.xml diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml index f96535b4b49b..fad631b37e38 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml @@ -1,3 +1,3 @@ <pbundle_as_map name="system_app_safety_label"> - <string name="url" value="www.example.com"/> + <boolean name="declaration" value="true"/> </pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml index 8f854ad1107e..41b32b5ed7b0 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml @@ -1,15 +1,13 @@ <app-metadata-bundles version="123"> <safety-labels version="12345"> <data-labels> - <data-shared dataCategory="location" - dataType="approx_location" + <data-shared + dataType="location_data_type_approx_location" isSharingOptional="false" - ephemeral="false" purposes="app_functionality" /> - <data-shared dataCategory="location" - dataType="precise_location" + <data-shared + dataType="location_data_type_precise_location" isSharingOptional="true" - ephemeral="true" purposes="app_functionality|analytics" /> </data-labels> <security-labels @@ -19,7 +17,7 @@ <third-party-verification url="www.example.com"> </third-party-verification> </safety-labels> - <system-app-safety-label url="www.example.com"> + <system-app-safety-label declaration="true"> </system-app-safety-label> <transparency-info> <developer-info diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml index 8f1dc6475b78..c11ac4359ecd 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml @@ -10,7 +10,6 @@ <item value="1"/> </int-array> <boolean name="is_sharing_optional" value="false"/> - <boolean name="ephemeral" value="false"/> </pbundle_as_map> <pbundle_as_map name="precise_location"> <int-array name="purposes" num="2"> @@ -18,7 +17,6 @@ <item value="2"/> </int-array> <boolean name="is_sharing_optional" value="true"/> - <boolean name="ephemeral" value="true"/> </pbundle_as_map> </pbundle_as_map> </pbundle_as_map> @@ -32,7 +30,7 @@ </pbundle_as_map> </pbundle_as_map> <pbundle_as_map name="system_app_safety_label"> - <string name="url" value="www.example.com"/> + <boolean name="declaration" value="true"/> </pbundle_as_map> <pbundle_as_map name="transparency_info"> <pbundle_as_map name="developer_info"> |