Merge "Try closing PFDs sent in map after binder call" into main
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 807fa48..c8e1e4d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1150,6 +1150,11 @@
*
* <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 @@
}
/**
- * 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 @@
/**
* 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 86d061c..e895d7b 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -823,6 +823,16 @@
}
/**
+ * 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 57ee622..bf51f00 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -304,6 +304,7 @@
* <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 @@
*
* <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 20522fa..7926afe 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5577,8 +5577,8 @@
}
/**
- * 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 @@
* 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 @@
* @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 @@
/**
* 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 9589785..8271caf 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -29,6 +29,7 @@
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 @@
@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 7023ef7..8836c8a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -211,3 +211,12 @@
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 815fc58..0714285 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -220,7 +220,7 @@
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 a11bb78..2377b86 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -25,6 +25,7 @@
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.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 @@
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 7b7ead4..ae051f9 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3307,6 +3307,11 @@
@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 @@
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 58a3c80..0d4c556 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -4326,14 +4326,16 @@
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 57bded7..59639d0 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -117,6 +117,8 @@
@NonNull
private final Rect mTouchableRegion;
+ private final boolean mAppProgressGenerationAllowed;
+
/**
* Create a new {@link BackNavigationInfo} instance.
*
@@ -132,7 +134,8 @@
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 @@
mCustomAnimationInfo = customAnimationInfo;
mLetterboxColor = letterboxColor;
mTouchableRegion = new Rect(touchableRegion);
+ mAppProgressGenerationAllowed = appProgressGenerationAllowed;
}
private BackNavigationInfo(@NonNull Parcel in) {
@@ -152,6 +156,7 @@
mCustomAnimationInfo = in.readTypedObject(CustomAnimationInfo.CREATOR);
mLetterboxColor = in.readInt();
mTouchableRegion = in.readTypedObject(Rect.CREATOR);
+ mAppProgressGenerationAllowed = in.readBoolean();
}
/** @hide */
@@ -165,6 +170,7 @@
dest.writeTypedObject(mCustomAnimationInfo, flags);
dest.writeInt(mLetterboxColor);
dest.writeTypedObject(mTouchableRegion, flags);
+ dest.writeBoolean(mAppProgressGenerationAllowed);
}
/**
@@ -224,6 +230,14 @@
}
/**
+ * @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 @@
private int mLetterboxColor = Color.TRANSPARENT;
private Rect mTouchableRegion;
+ private boolean mAppProgressGenerationAllowed;
/**
* @see BackNavigationInfo#getType()
@@ -502,6 +517,15 @@
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 @@
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 150b04e..01c78a0 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -31,4 +31,11 @@
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 2194c89..40d760e 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -537,8 +537,13 @@
}
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 @@
}
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 @@
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 6832825..ff57fd4 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -898,6 +898,13 @@
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 ac524f9..8d6cfd1 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.util.SparseArray;
import android.view.Menu;
import android.view.Window;
+
import com.android.internal.view.menu.MenuPresenter;
/**
@@ -49,4 +50,5 @@
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 b6bf617..89fdeeb 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -36,41 +36,43 @@
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 0000000..7428a71
--- /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 0000000..3cc0066
--- /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 0000000..7091357
--- /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 0000000..5e224eb
--- /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 0000000..d236b0d
--- /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 0000000..4000489
--- /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 0000000..57d596a
--- /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 0000000..cc66b32
--- /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 0000000..c1afd44
--- /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 f43351a..4e133de 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 a0807ca..5fea515 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 c16bd24..7251d74 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 6e9d4db..94bde68 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 07d5da9..0000000
--- 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 fc0c6ab..0000000
--- 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 f35a210..987de6b 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 cf50d2a..2d276a5 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 0000000..036d09b
--- /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 0000000..036d09b
--- /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 6b78462..036d09b 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 0000000..036d09b
--- /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
new file mode 100644
index 0000000..0057985
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
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
new file mode 100644
index 0000000..085df9d
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_foreground.webp
Binary files differ
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
new file mode 100644
index 0000000..bcb3b7d
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..3d1cf0e
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
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
new file mode 100644
index 0000000..bfd4568
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_foreground.webp
Binary files differ
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
new file mode 100644
index 0000000..4cf0d43
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..ac4f693
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
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
new file mode 100644
index 0000000..cc6b763
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_foreground.webp
Binary files differ
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
new file mode 100644
index 0000000..1f17221
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..b70e145
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
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
new file mode 100644
index 0000000..6e46bce
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Binary files differ
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
new file mode 100644
index 0000000..3fa346c
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..8b463f2
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
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
new file mode 100644
index 0000000..849caff
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Binary files differ
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
new file mode 100644
index 0000000..bd6e312
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
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 0000000..1e90e07
--- /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 629d729..fa30b2c 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 82d2381..5d4139e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -23,23 +23,19 @@
<!-- 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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<!-- 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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 15f8c32..112eb61 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -111,3 +111,13 @@
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 0fd21f3..7041ea3 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 @@
}
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 9114c7a..a3111b3 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 @@
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 @@
}
}
-// 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 cee2d92..641952b 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.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 @@
// 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 @@
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 @@
preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks
)
+ wasPreviousTransitionExitToOverview = info.isExitToRecentsTransition()
}
override fun onTransitionStarting(transition: IBinder) {}
@@ -309,21 +314,40 @@
}
/** 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 @@
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 @@
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 1903586..57e469d 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 @@
.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 0d3cd10..fb03f20 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.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 @@
}
@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 @@
}
@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 748ad31..35808d9 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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
setUpPortraitDisplay()
controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestMoveToDesktopWct()
+ val wct = getLatestEnterDesktopWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
}
@@ -629,7 +619,7 @@
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 @@
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 @@
@Test
fun moveToDesktop_nonExistentTask_doesNothing() {
controller.moveToDesktop(999, transitionSource = UNKNOWN)
- verifyWCTNotExecuted()
+ verifyEnterDesktopWCTNotExecuted()
}
@Test
@@ -659,7 +649,7 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
controller.moveToFullscreen(999, transitionSource = UNKNOWN)
- verifyWCTNotExecuted()
+ verifyExitDesktopWCTNotExecuted()
}
@Test
@@ -1284,7 +1247,7 @@
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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 @@
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 @@
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 @@
}
}
+ 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 c35721c..772e02e 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.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 @@
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 @@
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 79f8b5fc..16ec1a9 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 @@
}.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 @@
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 232fa92..d6345ce 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
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 85ad160..a842009 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.7-bin.zip
deleted file mode 100644
index 7a9ac5a..0000000
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip
new file mode 100644
index 0000000..77e6ad3
--- /dev/null
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
index d64cd49..e644113 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 182095e..91d2a3a 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 1aa94a4..b740cf1 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 31f462e..e003c81 100644
--- a/packages/SettingsLib/Spa/settings.gradle.kts
+++ b/packages/SettingsLib/Spa/settings.gradle.kts
@@ -36,9 +36,6 @@
}
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 f98695e..9b8ecf7 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -53,21 +53,21 @@
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 3fdb1d1..12d04f9 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 @@
// 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 @@
// 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 @@
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 0281ab8..95c7d23 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 @@
/** 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 @@
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 215f6b9..7411297 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_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 a9e5e39..f323e99 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 @@
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 @@
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 3dac7db..ea69eab 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 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 58c39b4..fde7c2c 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -785,6 +785,7 @@
kotlincflags: ["-Xjvm-default=all"],
optimize: {
shrink_resources: false,
+ optimized_shrink_resources: false,
proguard_flags_files: ["proguard.flags"],
},
@@ -921,6 +922,7 @@
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 d2e5a13..e03ac3d 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -11,6 +11,7 @@
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 @@
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 @@
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 @@
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 18e65508..22566e7 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 @@
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 f3de463e..fa79ea0 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 @@
@Mock
private ViewRootImpl mViewRootImpl;
@Mock
- private FpsUnlockTracker mFpsUnlockTracker;
- @Mock
private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock
private Lazy<DeviceEntryUdfpsTouchOverlayViewModel> mDeviceEntryUdfpsTouchOverlayViewModel;
@@ -368,7 +366,6 @@
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 0250c9d..baeb2dd 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 0d23a6d..79be2b1 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 4c9af66..e055e7c 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -1250,6 +1250,11 @@
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 cf699a2..0000000
--- 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 85b5faf..ad142a8 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 @@
@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 @@
@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 @@
mInputManager = inputManager;
mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
mSelectedUserInteractor = selectedUserInteractor;
- mFpsUnlockTracker = fpsUnlockTracker;
- mFpsUnlockTracker.startTracking();
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mTouchProcessor = singlePointerTouchProcessor;
@@ -1067,9 +1063,6 @@
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 14d8caf..e2a8a69 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.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 @@
@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 65c5b6b..408e2c2 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 @@
// 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 @@
@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/tests/utils/src/com/android/systemui/biometrics/FpsUnlockTrackerKosmos.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/DeviceEntryUnlockTrackerViewBinder.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FpsUnlockTrackerKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/DeviceEntryUnlockTrackerViewBinder.kt
index 6085a1f..78eaab2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FpsUnlockTrackerKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/DeviceEntryUnlockTrackerViewBinder.kt
@@ -14,9 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics
+package com.android.systemui.biometrics.ui.binder
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
-val Kosmos.fpsUnlockTracker by Kosmos.Fixture { mock<FpsUnlockTracker>() }
+/** 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 ff7ac35..9cc4650 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.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 @@
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 @@
}
}
}
- .invokeOnCompletion { fpsUnlockTracker.get().stopTracking() }
}
private var overlayView: View? = null
@@ -138,7 +135,7 @@
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 @@
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 7d494a5..ac8807d 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 @@
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 @@
}
/** 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 @@
failedModality: BiometricModality = BiometricModality.None,
) = coroutineScope {
if (_isAuthenticated.value.isAuthenticated) {
+ if (_isAuthenticated.value.needsUserConfirmation && hapticFeedback) {
+ vibrateOnError()
+ }
return@coroutineScope
}
@@ -823,6 +826,14 @@
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 81fe2a5..7f1cb5d 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 @@
// 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 @@
// 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 @@
// 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 fce18a26..e1408a0 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.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.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 @@
class CommunalTransitionViewModel
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
communalColors: CommunalColors,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
@@ -85,21 +92,32 @@
* 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 cf83582..00566c1 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 @@
// 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 @@
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 306f4ff..608e25a 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.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.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 @@
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 @@
)
}
}
+ 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 c98a49b..08175c3 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 @@
* 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 ab24e0b..d380251 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 @@
} ?: 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 6611434..f6fbe38 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.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 @@
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 @@
is MediaProjectionState.NotProjecting -> OngoingActivityChipModel.Hidden
is MediaProjectionState.Projecting -> {
if (isProjectionToOtherDevice(state.hostPackage)) {
- createCastToOtherDeviceChip()
+ createCastToOtherDeviceChip(state)
} else {
- createShareToAppChip()
+ createShareToAppChip(state)
}
}
}
@@ -97,7 +97,9 @@
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 @@
// 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 33cec97..596fbf8 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 0000000..347be02
--- /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 3a863b1..749a11f 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 11636bd..f95e0fb 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.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 @@
modifiedStatusBarAttributes,
isTransientShown,
isInFullscreenMode,
- ongoingCallRepository.hasOngoingCall,
- ) { modifiedAttributes, isTransientShown, isInFullscreenMode, hasOngoingCall ->
+ ongoingCallRepository.ongoingCallState,
+ ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallState ->
if (modifiedAttributes == null) {
null
} else {
@@ -234,7 +235,7 @@
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 4bf122d..3925beb 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 @@
* 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 d1189e1..4b1ee58 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.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.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 @@
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 @@
NotificationShadeWindowController notificationShadeWindowController,
ConfigurationController configurationController,
HeadsUpManager headsUpManager,
- ShadeExpansionStateManager shadeExpansionStateManager,
ShadeInteractor shadeInteractor,
Provider<SceneInteractor> sceneInteractor,
JavaAdapter javaAdapter,
@@ -131,8 +131,8 @@
if (SceneContainerFlag.isEnabled()) {
javaAdapter.alwaysCollectFlow(
- sceneInteractor.get().isVisible(),
- this::onSceneContainerVisibilityChanged);
+ sceneInteractor.get().getTransitionState(),
+ this::onSceneChanged);
} else {
javaAdapter.alwaysCollectFlow(
shadeInteractor.isAnyExpanded(),
@@ -167,10 +167,11 @@
}
}
- 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 @@
// 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 a7d4ce3..d128057 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 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 @@
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 @@
*/
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 @@
}
override fun removeCallback(listener: OngoingCallListener) {
- synchronized(mListeners) {
- mListeners.remove(listener)
- }
+ synchronized(mListeners) { mListeners.remove(listener) }
}
private fun updateChip() {
@@ -196,8 +215,8 @@
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 @@
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 @@
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 @@
}
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 @@
}
/** 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 @@
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 @@
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 0000000..aaa52a7b
--- /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 886481e..554c474 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 @@
*/
@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 f457470..4205dd8 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.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 @@
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 @@
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 @@
mVolumeDialogMenuIconBinder = volumeDialogMenuIconBinder;
mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS;
mVolumePanelFlag = volumePanelFlag;
+ mInteractor = interactor;
dumpManager.registerDumpable("VolumeDialogImpl", this);
@@ -1519,6 +1523,7 @@
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 @@
}
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 f8ddc42..8003f39 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.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 @@
Lazy<SecureSettings> secureSettings,
VibratorHelper vibratorHelper,
VolumeDialogMenuIconBinder volumeDialogMenuIconBinder,
- SystemClock systemClock) {
+ SystemClock systemClock,
+ VolumeDialogInteractor interactor) {
VolumeDialogImpl impl = new VolumeDialogImpl(
context,
volumeDialogController,
@@ -138,7 +140,8 @@
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 0000000..75e1c5ac
--- /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 0000000..813e707
--- /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 d267ad4..54a14a2 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.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 @@
}
@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 cc7dec56..93c6d9e 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 @@
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 @@
}
@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 @@
}
@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 128dd23..27b9863 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.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 @@
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 @@
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 @@
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 a4505a9..327eec4 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.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 @@
@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 @@
}
@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 @@
}
@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 @@
}
}
+ @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 9a2f545..7b676e2 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 @@
@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 @@
@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 @@
@Test
fun positiveButton() =
kosmos.testScope.runTest {
+ createAndSetDelegate(SINGLE_TASK)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
val clickListener = argumentCaptor<DialogInterface.OnClickListener>()
@@ -105,4 +137,20 @@
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 0000000..bbd1109
--- /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 1d6e866..4ddca52 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 @@
@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 @@
@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 @@
@Test
fun positiveButton() =
kosmos.testScope.runTest {
+ createAndSetDelegate(ENTIRE_SCREEN)
+
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
val clickListener = argumentCaptor<DialogInterface.OnClickListener>()
@@ -105,4 +136,20 @@
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 6712963..65bf0bc 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.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 @@
@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 057dcb2..6af14e0 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.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 @@
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 @@
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 @@
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 4d6798b..feef943 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.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.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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
/** 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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
testScope.runCurrent()
- verify(mockSwipeStatusBarAwayGestureHandler)
- .removeOnGestureDetectedCallback(anyString())
+ verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(anyString())
}
@Test
@@ -635,8 +650,7 @@
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 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 56aa7d6..73a86a1 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 @@
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 @@
@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 cdb2b88..b8299e5 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.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 @@
@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 fac6a4c..57ddcde 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.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 @@
private VolumeDialogMenuIconBinder mVolumeDialogMenuIconBinder;
@Mock
private VolumePanelFlag mVolumePanelFlag;
+ @Mock
+ private VolumeDialogInteractor mVolumeDialogInteractor;
private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
new CsdWarningDialog.Factory() {
@@ -218,7 +221,8 @@
mLazySecureSettings,
mVibratorHelper,
mVolumeDialogMenuIconBinder,
- new FakeSystemClock());
+ new FakeSystemClock(),
+ mVolumeDialogInteractor);
mDialog.init(0, null);
State state = createShellState();
mDialog.onStateChangedH(state);
@@ -778,6 +782,15 @@
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 0000000..dcac85e
--- /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 0000000..5c735e3
--- /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/systemui/biometrics/FpsUnlockTrackerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUnlockAnimationControllerKosmos.kt
similarity index 76%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FpsUnlockTrackerKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUnlockAnimationControllerKosmos.kt
index 6085a1f..1fa6813 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FpsUnlockTrackerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUnlockAnimationControllerKosmos.kt
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics
+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.fpsUnlockTracker by Kosmos.Fixture { mock<FpsUnlockTracker>() }
+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 8281984..79d58a1 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 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 @@
{ 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 3f38408..1ae8449 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.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 67f8443..31cdbc7 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.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 @@
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 062b448..9d22811 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.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 @@
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 0000000..4f82662
--- /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
similarity index 80%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FpsUnlockTrackerKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryKosmos.kt
index 6085a1f..2f4eef5 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/biometrics/FpsUnlockTrackerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FpsUnlockTrackerKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorKosmos.kt
index 6085a1f..2e5d040 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FpsUnlockTrackerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorKosmos.kt
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics
+package com.android.systemui.volume.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.volume.data.repository.volumeDialogRepository
-val Kosmos.fpsUnlockTracker by Kosmos.Fixture { mock<FpsUnlockTracker>() }
+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 aa0af7e..b5b998f 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 @@
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 @@
@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 @@
* 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 @@
/**
* 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 6d1ab9f..e9c3fbd 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 @@
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 @@
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 @@
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 @@
}
}
-
void prepareForState() {
checkShouldDetectPassPersistedScale();
}
@@ -611,7 +616,7 @@
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 @@
&& 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 @@
} 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 @@
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 @@
} 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 @@
}
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 @@
final class SinglePanningState extends SimpleOnGestureListener implements State {
-
private final GestureDetector mScrollGestureDetector;
private MotionEventInfo mEvent;
SinglePanningState(Context context) {
@@ -1860,8 +1886,10 @@
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 @@
+ " isAtEdge: "
+ mFullScreenMagnificationController.isAtEdge(mDisplayId));
}
- mOverscrollHandler.onScrollStateChanged(first, second);
+ if (mOverscrollHandler != null) {
+ mOverscrollHandler.onScrollStateChanged(first, second);
+ }
return /* event consumed: */ true;
}
- private void vibrateIfNeeded() {
- if ((mFullScreenMagnificationController.isAtLeftEdge(mDisplayId)
- || mFullScreenMagnificationController.isAtRightEdge(mDisplayId))) {
- mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled();
- }
- }
-
-
-
@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 @@
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 9ad15b2..2bf319e 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 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 676abd1..d7da2f0 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 @@
});
}
+ /**
+ * 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 void maybeSetCountShown(int datasets) {
+ mEventInternal.ifPresent(
+ event -> {
+ event.mCountShown = datasets;
+ });
+ }
+
private static CountContainer getDatasetCountForAutofillId(@Nullable List<Dataset> datasetList,
AutofillId currentViewId) {
@@ -567,31 +606,36 @@
* <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 @@
/** 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 @@
@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 @@
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 5b4faa2..cdae16b 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.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 @@
// 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 @@
}
mInlineSessionController.hideInlineSuggestionsUiLocked(id);
+ mPresentationStatsEventLogger.markShownCountAsResettable();
}
}
@@ -4868,7 +4879,9 @@
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 @@
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 @@
// 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 @@
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 @@
@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 @@
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 8cc666b..2e9a4dc 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 @@
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 @@
}
@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 @@
@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 1831ecd..1bda70d 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 @@
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 @@
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 38bbfc4..d331337 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 @@
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 d750065..d41de38 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.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 @@
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 @@
/**
* 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 @@
* 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 c6c1f98..fa0e2ca 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 @@
* 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 5dd1480..684cb24 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 @@
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 @@
// 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 469e8b7..276ab03 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 @@
}
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
void onAcquired(int sensorId, int acquiredInfo, int vendorCode) {
- if (hasAuthenticated()) {
+ if (hasAuthenticatedAndConfirmed()) {
Slog.d(TAG, "onAcquired after successful auth");
return;
}
@@ -562,7 +570,7 @@
}
void onSystemEvent(int event) {
- if (hasAuthenticated()) {
+ if (hasAuthenticatedAndConfirmed()) {
Slog.d(TAG, "onSystemEvent after successful auth");
return;
}
@@ -579,12 +587,15 @@
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 @@
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 @@
}
void onTryAgainPressed() {
- if (hasAuthenticated()) {
+ if (hasAuthenticatedAndConfirmed()) {
Slog.d(TAG, "onTryAgainPressed after successful auth");
return;
}
@@ -625,7 +637,7 @@
}
void onAuthenticationSucceeded(int sensorId, boolean strong, byte[] token) {
- if (hasAuthenticated()) {
+ if (hasAuthenticatedAndConfirmed()) {
Slog.d(TAG, "onAuthenticationSucceeded after successful auth");
return;
}
@@ -656,11 +668,17 @@
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 @@
}
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 @@
}
void onDeviceCredentialPressed() {
- if (hasAuthenticated()) {
+ if (hasAuthenticatedAndConfirmed()) {
Slog.d(TAG, "onDeviceCredentialPressed after successful auth");
return;
}
@@ -739,6 +757,10 @@
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 @@
} else {
Slog.e(TAG, "mTokenEscrow is null");
}
+
mClientReceiver.onAuthenticationSucceeded(
Utils.getAuthenticationTypeForResult(reason));
break;
@@ -861,6 +884,16 @@
} 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 @@
* @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 1e2451c..daaafcb 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 @@
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 2660932..72d92b9 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 @@
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 9e8bf0e..69452310 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 @@
}
/**
+ * 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 223d548..c4b1f40 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 @@
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 @@
private final Handler mHandler;
@NonNull
- private final Handler mPackageMonitorHandler;
+ private final Handler mIoHandler;
@MultiUserUnawareField
@UserIdInt
@@ -1237,7 +1241,7 @@
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 @@
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 @@
}
}, "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 @@
@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 @@
// 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 @@
}
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 @@
// 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 @@
@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 @@
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 @@
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 @@
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 @@
}
synchronized (ImfLock.class) {
if (mCurrentUserId == userId) {
+ // TODO(b/347083680): The method below has questionable behaviors.
return getCurrentInputMethodSubtypeLocked();
}
@@ -5523,6 +5534,14 @@
*
* <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 @@
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 7ce4074..5569e1e 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 @@
* 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 60b9a4c..68924b5 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 @@
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 880787e..12495bb 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 @@
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 6578853..b532d5a 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.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 @@
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 @@
}
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 0606563..00e9d8d 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_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 @@
}
}
- 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 @@
}
}
+ 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 @@
}
// 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 66a93d7..c0b8034 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 @@
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 0052d4f..7f24769 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 5159fc4..d9dc7ba 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_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_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 @@
@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 @@
// 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 @@
// 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 @@
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 b6e6991..3867d2d 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 @@
}
}
+ 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 0febec9..1ce324f 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 @@
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 @@
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 @@
? task.getSurfaceControl()
: mAdaptors[0].mTarget.getSurfaceControl());
}
- // remove starting surface.
- mStartingSurface = null;
}
}
@@ -1384,7 +1385,10 @@
.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 0000000..0c751cf
--- /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 87ee5d8..ad711cb 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.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 @@
@Nullable
final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
@Nullable
+ final CameraCompatFreeformPolicy mCameraCompatFreeformPolicy;
+ @Nullable
final CameraStateMonitor mCameraStateMonitor;
@Nullable
final ActivityRefresher mActivityRefresher;
@@ -683,7 +686,6 @@
*/
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 @@
// 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 @@
mCameraStateMonitor = null;
mActivityRefresher = null;
mDisplayRotationCompatPolicy = null;
+ mCameraCompatFreeformPolicy = null;
}
-
mRotationReversionController = new DisplayRotationReversionController(this);
mInputMonitor = new InputMonitor(mWmService, this);
@@ -3350,6 +3367,11 @@
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 e0cc064..6ecafdb 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 @@
mDisplayContent = displayContent;
mWmService = displayContent.mWmService;
mCameraStateMonitor = cameraStateMonitor;
- mCameraStateMonitor.addCameraStateListener(this);
mActivityRefresher = activityRefresher;
+ }
+
+ void start() {
+ mCameraStateMonitor.addCameraStateListener(this);
mActivityRefresher.addEvaluator(this);
}
@@ -365,7 +368,7 @@
}
// 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 5e93e89..9a51375 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 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 @@
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 @@
.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 @@
// 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 54ba47e..f5ab38f 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 @@
// 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 @@
}
} 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 22f718d..787c5d6 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 @@
&& !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 b19de18..1f0c827 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 @@
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 @@
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 3b25cb1..42bd75a 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 @@
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 @@
"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 @@
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 0000000..aa85942
--- /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 0000000..8bd111e
--- /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 0000000..3ae96c5
--- /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 0000000..d3943e3
--- /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 f482ddc..3ef81fd 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 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 @@
private final Scroller mMockScroller = spy(new Scroller(mContext));
private boolean mMockMagnificationConnectionState;
+ private boolean mMockOneFingerPanningEnabled;
private OffsettableClock mClock;
private FullScreenMagnificationGestureHandler mMgh;
@@ -209,8 +213,6 @@
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 @@
@NonNull
private FullScreenMagnificationGestureHandler newInstance(boolean detectSingleFingerTripleTap,
boolean detectTwoFingerTripleTap, boolean detectShortcutTrigger) {
+ enableOneFingerPanning(
+ isWatch() || Flags.enableMagnificationOneFingerPanningGesture());
FullScreenMagnificationGestureHandler h =
new FullScreenMagnificationGestureHandler(
mContext,
@@ -297,8 +301,12 @@
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 @@
@Test
public void testActionUpNotAtEdge_singlePanningState_detectingState() {
- assumeTrue(isWatch());
+ assumeTrue(isOneFingerPanningEnabled());
goFromStateIdleTo(STATE_SINGLE_PANNING);
send(upEvent());
@@ -846,8 +854,7 @@
}
@Test
- public void testScroll_SinglePanningDisabled_delegatingState() {
- assumeTrue(isWatch());
+ public void testScroll_singlePanningDisabled_delegatingState() {
enableOneFingerPanning(false);
goFromStateIdleTo(STATE_ACTIVATED);
@@ -858,8 +865,54 @@
}
@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 @@
@Test
@FlakyTest
- public void testScroll_singleHorizontalPanningAndAtEdge_rightEdgeOverscroll() {
+ public void testSingleFingerOverscrollAtRightEdge_isWatch_expectedOverscrollState() {
assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
float centerY =
@@ -907,7 +960,7 @@
@Test
@FlakyTest
- public void testScroll_singleVerticalPanningAndAtEdge_verticalOverscroll() {
+ public void testSingleFingerOverscrollAtTopEdge_isWatch_expectedOverscrollState() {
assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
float centerX =
@@ -929,7 +982,7 @@
}
@Test
- public void testScroll_singlePanningAndAtEdge_noOverscroll() {
+ public void testSingleFingerScrollAtEdge_isWatch_noOverscroll() {
assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
float centerY =
@@ -951,7 +1004,7 @@
}
@Test
- public void testScroll_singleHorizontalPanningAndAtEdge_vibrate() {
+ public void testSingleFingerHorizontalScrollAtEdge_isWatch_vibrate() {
assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
mFullScreenMagnificationController.setCenter(
@@ -975,7 +1028,7 @@
}
@Test
- public void testScroll_singleVerticalPanningAndAtEdge_doNotVibrate() {
+ public void testSingleFingerVerticalScrollAtEdge_isWatch_doNotVibrate() {
assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
mFullScreenMagnificationController.setCenter(
@@ -1000,7 +1053,7 @@
@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 @@
}
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 1eaa170..bc2fd73 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.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 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.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.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 @@
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 @@
@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 @@
@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 @@
@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 @@
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 @@
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 9873805..2d4dbb7 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.MockitoRule;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -445,21 +446,61 @@
);
}
+ @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 @@
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 @@
.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 @@
}
};
}
+
+ 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 182d603..ecd799f 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 java.time.Clock;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -608,6 +609,45 @@
}
@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 @@
@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 @@
@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 @@
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 @@
.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 @@
}
};
}
+
+ 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 f221b75..148c968 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 @@
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 @@
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 @@
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 0000000..b3f1502
--- /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 417ee6b..6957502 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.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.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 @@
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 c76acd7..c65371f 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 @@
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 1a366b3..ac1aa20 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 @@
}
@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 c45c86c..c962a3f 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 @@
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 6ecaea9..e01cea3 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 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 @@
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 d551953..c16d18b 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 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 @@
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 90424fe..7244162 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.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 4a0d759..ba0e3db 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 @@
* 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 @@
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 @@
@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 @@
Document doc,
Element dataLabelsEle,
Map<String, DataCategory> dataCategoriesMap,
- String dataUsageTypeName) {
+ String dataUsageTypeName,
+ boolean ephemeral) {
if (dataCategoriesMap.isEmpty()) {
return;
}
@@ -123,10 +115,15 @@
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 @@
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 5473e01..c4d8876 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 @@
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 @@
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 @@
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 @@
}
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);
+ 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));
}
- 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);
- }
+
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 488c259..a5559d8 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.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 @@
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 854c0d0..242e7be 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 @@
/** 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 @@
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 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 c8e22b6..7f4aa7a 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 @@
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 @@
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 1d54ead..97cbc39 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.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 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 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 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 @@
.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 @@
.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 @@
.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 @@
.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 @@
.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 f156484..dbeeb49 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 @@
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 @@
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 d2e0fc3..5d1d45a 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 @@
@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 ebb3186..0000000
--- 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 2661726..ff43741 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 @@
"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,21 +73,6 @@
System.out.println("set up.");
}
- /** Test for data labels accessed 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);
- }
-
- /** Test for data labels accessed invalid bool. */
- @Test
- public void testDataLabelsAccessedInvalidBool() throws Exception {
- System.out.println("starting testDataLabelsAccessedInvalidBool.");
- hrToOdExpectException(ACCESSED_INVALID_BOOL_FILE_NAME);
- }
-
/** Test for data labels collected valid bool. */
@Test
public void testDataLabelsCollectedValidBool() throws Exception {
@@ -92,6 +81,21 @@
testOdToHrDataLabels(COLLECTED_VALID_BOOL_FILE_NAME);
}
+ /** Test for data labels collected ephemeral. */
+ @Test
+ public void testDataLabelsCollectedEphemeral() throws Exception {
+ System.out.println("starting testDataLabelsCollectedEphemeral.");
+ testHrToOdDataLabels(COLLECTED_EPHEMERAL_FILE_NAME);
+ testOdToHrDataLabels(COLLECTED_EPHEMERAL_FILE_NAME);
+ }
+
+ /** Test for data labels ephemeral collision. */
+ @Test
+ public void testDataLabelsCollectedEphemeralCollision() throws Exception {
+ System.out.println("starting testDataLabelsCollectedEphemeralCollision.");
+ hrToOdExpectException(COLLECTED_EPHEMERAL_COLLISION_FILE_NAME);
+ }
+
/** Test for data labels collected invalid bool. */
@Test
public void testDataLabelsCollectedInvalidBool() throws Exception {
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 33c2764..87d3e44 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 @@
"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 @@
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 7bcde45..afb0486 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 ef0f549..e8640c4 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 68e191e..680e01a 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 a6bd17d..db114bf 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 6274604..cf273f4 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 f7201f6..16f9d9b6 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 e8d40be..6d7a4e8 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 69e9b87..7a9e978 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 fdd8456..24385b6 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 bac58e6..faf30b0 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 ee45f26..5101906 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 e8e5911..72cda7e 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 0b220f4..2558681 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 ac221f2..c5a5475 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 11b7368..6ccf336 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 f1fbd56..bd88ada 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 5907462..742ed86 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 05fe159..d416063 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 a5de7be..3d932d6 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 f01e2df..704cb1c 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 f1fbd56..bd88ada 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 c5be684..a578d73 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 161057a..c0bd652 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 bb45f42..d09fc3b 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 f927bba..6e7f812 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 0000000..ee362fe
--- /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 0000000..79c9000
--- /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 ba11afb..801fada 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 4b6d3977..1ada12d 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 7840b98..b327d88 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 ccf77b0..34bd0de 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 14f9ef2..974ea69 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 1c87de9..62c26ab 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 ddefc18..df000aa 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 0000000..c671c4b
--- /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 3864f98..0edd8fa26 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 8997f4f..84456da 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 a966fda..fa2a3f8 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
similarity index 100%
rename from tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml
rename to 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 6fe86c3..f01d7d2 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
similarity index 100%
rename from tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-url.xml
rename to 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 f96535b..fad631b 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 8f854ad..41b32b5 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 8f1dc64..c11ac43 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">