Merge "Ignore public volume state change event when user is null" into sc-dev
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 7ca77d4..3f6e8a5 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.appsearch;
import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
+import static android.os.Process.INVALID_UID;
import static android.os.UserHandle.USER_NULL;
import android.annotation.ElapsedRealtimeLong;
@@ -53,7 +54,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
@@ -128,6 +128,17 @@
mContext.registerReceiverAsUser(new UserActionReceiver(), UserHandle.ALL,
new IntentFilter(Intent.ACTION_USER_REMOVED), /*broadcastPermission=*/ null,
/*scheduler=*/ null);
+
+ //TODO(b/145759910) Add a direct callback when user clears the data instead of relying on
+ // broadcasts
+ IntentFilter packageChangedFilter = new IntentFilter();
+ packageChangedFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+ packageChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
+ packageChangedFilter.addDataScheme("package");
+ packageChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ mContext.registerReceiverAsUser(new PackageChangedReceiver(), UserHandle.ALL,
+ packageChangedFilter, /*broadcastPermission=*/ null,
+ /*scheduler=*/ null);
}
private class UserActionReceiver extends BroadcastReceiver {
@@ -135,15 +146,15 @@
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
switch (intent.getAction()) {
case Intent.ACTION_USER_REMOVED:
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
if (userId == USER_NULL) {
- Slog.e(TAG, "userId is missing in the intent: " + intent);
+ Log.e(TAG, "userId is missing in the intent: " + intent);
return;
}
handleUserRemoved(userId);
break;
default:
- Slog.e(TAG, "Received unknown intent: " + intent);
+ Log.e(TAG, "Received unknown intent: " + intent);
}
}
}
@@ -163,9 +174,52 @@
try {
mImplInstanceManager.removeAppSearchImplForUser(userId);
mLoggerInstanceManager.removePlatformLoggerForUser(userId);
- Slog.i(TAG, "Removed AppSearchImpl instance for user: " + userId);
+ Log.i(TAG, "Removed AppSearchImpl instance for user: " + userId);
} catch (Throwable t) {
- Slog.e(TAG, "Unable to remove data for user: " + userId, t);
+ Log.e(TAG, "Unable to remove data for user: " + userId, t);
+ }
+ }
+
+ private class PackageChangedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+ switch (intent.getAction()) {
+ case Intent.ACTION_PACKAGE_FULLY_REMOVED:
+ case Intent.ACTION_PACKAGE_DATA_CLEARED:
+ String packageName = intent.getData().getSchemeSpecificPart();
+ if (packageName == null) {
+ Log.e(TAG, "Package name is missing in the intent: " + intent);
+ return;
+ }
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
+ if (uid == INVALID_UID) {
+ Log.e(TAG, "uid is missing in the intent: " + intent);
+ return;
+ }
+ handlePackageRemoved(packageName, uid);
+ break;
+ default:
+ Log.e(TAG, "Received unknown intent: " + intent);
+ }
+ }
+ }
+
+ private void handlePackageRemoved(String packageName, int uid) {
+ int userId = UserHandle.getUserId(uid);
+ try {
+ if (isUserLocked(userId)) {
+ //TODO(b/186151459) clear the uninstalled package data when user is unlocked.
+ return;
+ }
+ if (ImplInstanceManager.getAppSearchDir(userId).exists()) {
+ // Only clear the package's data if AppSearch exists for this user.
+ AppSearchImpl impl = mImplInstanceManager.getOrCreateAppSearchImpl(mContext,
+ userId);
+ //TODO(b/145759910) clear visibility setting for package.
+ impl.clearPackageData(packageName);
+ }
+ } catch (Throwable t) {
+ Log.e(TAG, "Unable to remove data for package: " + packageName, t);
}
}
@@ -177,17 +231,20 @@
}
private void verifyUserUnlocked(int callingUserId) {
+ if (isUserLocked(callingUserId)) {
+ throw new IllegalStateException("User " + callingUserId + " is locked or not running.");
+ }
+ }
+
+ private boolean isUserLocked(int callingUserId) {
synchronized (mUnlockedUserIdsLocked) {
// First, check the local copy.
if (mUnlockedUserIdsLocked.contains(callingUserId)) {
- return;
+ return false;
}
// If the local copy says the user is locked, check with UM for the actual state,
// since the user might just have been unlocked.
- if (!mUserManager.isUserUnlockingOrUnlocked(UserHandle.of(callingUserId))) {
- throw new IllegalStateException(
- "User " + callingUserId + " is locked or not running.");
- }
+ return !mUserManager.isUserUnlockingOrUnlocked(UserHandle.of(callingUserId));
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index af39b79..45023f9 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -73,6 +73,15 @@
}
/**
+ * Returns AppSearch directory in the credential encrypted system directory for the given user.
+ *
+ * <p>This folder should only be accessed after unlock.
+ */
+ public static File getAppSearchDir(@UserIdInt int userId) {
+ return new File(Environment.getDataSystemCeDirectory(userId), APP_SEARCH_DIR);
+ }
+
+ /**
* Gets an instance of AppSearchImpl for the given user, or creates one if none exists.
*
* <p>If no AppSearchImpl instance exists for the unlocked user, Icing will be initialized and
@@ -139,15 +148,10 @@
private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId)
throws AppSearchException {
- File appSearchDir = getAppSearchDir(context, userId);
+ File appSearchDir = getAppSearchDir(userId);
return AppSearchImpl.create(appSearchDir, context, userId, mGlobalQuerierPackage);
}
- private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) {
- // See com.android.internal.app.ChooserActivity::getPinnedSharedPrefs
- return new File(Environment.getDataSystemCeDirectory(userId), APP_SEARCH_DIR);
- }
-
/**
* Returns the global querier package if it's a system package. Otherwise, empty string.
*
diff --git a/core/api/current.txt b/core/api/current.txt
index c3c7919..4cdc519 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -56795,6 +56795,7 @@
public interface SplashScreen {
method public void clearOnExitAnimationListener();
method public void setOnExitAnimationListener(@NonNull android.window.SplashScreen.OnExitAnimationListener);
+ method public void setSplashScreenTheme(@StyleRes int);
}
public static interface SplashScreen.OnExitAnimationListener {
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 7fe2a41..5e72325 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -796,6 +796,10 @@
void setMimeGroup(String packageName, String group, in List<String> mimeTypes);
+ String getSplashScreenTheme(String packageName, int userId);
+
+ void setSplashScreenTheme(String packageName, String themeName, int userId);
+
List<String> getMimeGroup(String packageName, String group);
boolean isAutoRevokeWhitelisted(String packageName);
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 55a6ab7..84317b3 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -81,6 +81,7 @@
public int installReason;
public @PackageManager.UninstallReason int uninstallReason;
public String harmfulAppWarning;
+ public String splashScreenTheme;
public ArraySet<String> disabledComponents;
public ArraySet<String> enabledComponents;
@@ -130,6 +131,7 @@
if (o.componentLabelIconOverrideMap != null) {
this.componentLabelIconOverrideMap = new ArrayMap<>(o.componentLabelIconOverrideMap);
}
+ splashScreenTheme = o.splashScreenTheme;
}
@Nullable
@@ -242,6 +244,7 @@
return componentLabelIconOverrideMap.get(componentName);
}
+
/**
* Test if this package is installed.
*/
@@ -479,7 +482,11 @@
}
if (harmfulAppWarning == null && oldState.harmfulAppWarning != null
|| (harmfulAppWarning != null
- && !harmfulAppWarning.equals(oldState.harmfulAppWarning))) {
+ && !harmfulAppWarning.equals(oldState.harmfulAppWarning))) {
+ return false;
+ }
+
+ if (!Objects.equals(splashScreenTheme, oldState.splashScreenTheme)) {
return false;
}
return true;
@@ -505,6 +512,7 @@
hashCode = 31 * hashCode + Objects.hashCode(disabledComponents);
hashCode = 31 * hashCode + Objects.hashCode(enabledComponents);
hashCode = 31 * hashCode + Objects.hashCode(harmfulAppWarning);
+ hashCode = 31 * hashCode + Objects.hashCode(splashScreenTheme);
return hashCode;
}
diff --git a/core/java/android/hardware/face/OWNERS b/core/java/android/hardware/face/OWNERS
index be10df1..0b4d9d9 100644
--- a/core/java/android/hardware/face/OWNERS
+++ b/core/java/android/hardware/face/OWNERS
@@ -1,7 +1,3 @@
# Bug component: 879035
-curtislb@google.com
-ilyamaty@google.com
-jaggies@google.com
-joshmccloskey@google.com
-kchyn@google.com
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/java/android/hardware/fingerprint/OWNERS b/core/java/android/hardware/fingerprint/OWNERS
index e55b8c56..5c93672 100644
--- a/core/java/android/hardware/fingerprint/OWNERS
+++ b/core/java/android/hardware/fingerprint/OWNERS
@@ -1,8 +1,3 @@
# Bug component: 114777
-curtislb@google.com
-ilyamaty@google.com
-jaggies@google.com
-joshmccloskey@google.com
-kchyn@google.com
-
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/java/android/hardware/iris/OWNERS b/core/java/android/hardware/iris/OWNERS
index 33527f8..0b4d9d9 100644
--- a/core/java/android/hardware/iris/OWNERS
+++ b/core/java/android/hardware/iris/OWNERS
@@ -1,3 +1,3 @@
# Bug component: 879035
-jaggies@google.com
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index d6292ca..d15aee0 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -266,6 +266,14 @@
private static final int NOT_A_SUBTYPE_ID = -1;
/**
+ * {@code true} to try to avoid blocking apps' UI thread by sending
+ * {@link StartInputReason#WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION} and
+ * {@link StartInputReason#WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION} in a truly asynchronous
+ * way. {@code false} to go back to the previous synchronous semantics.
+ */
+ private static final boolean USE_REPORT_WINDOW_GAINED_FOCUS_ASYNC = false;
+
+ /**
* A constant that represents Voice IME.
*
* @see InputMethodSubtype#getMode()
@@ -689,20 +697,29 @@
Log.v(TAG, "Reporting focus gain, without startInput"
+ ", nextFocusIsServedView=" + nextFocusHasConnection);
}
- final int startInputReason =
- nextFocusHasConnection ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
- : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
- final Completable.InputBindResult value = Completable.createInputBindResult();
- mService.startInputOrWindowGainedFocus(
- startInputReason, mClient,
- focusedView.getWindowToken(), startInputFlags, softInputMode,
- windowFlags,
- null,
- null,
- 0 /* missingMethodFlags */,
- mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
- ResultCallbacks.of(value));
- Completable.getResult(value); // ignore the result
+
+ if (USE_REPORT_WINDOW_GAINED_FOCUS_ASYNC) {
+ mService.reportWindowGainedFocusAsync(
+ nextFocusHasConnection, mClient, focusedView.getWindowToken(),
+ startInputFlags, softInputMode, windowFlags,
+ mCurRootView.mContext.getApplicationInfo().targetSdkVersion);
+ } else {
+ final int startInputReason = nextFocusHasConnection
+ ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
+ : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+ final Completable.InputBindResult value =
+ Completable.createInputBindResult();
+ mService.startInputOrWindowGainedFocus(
+ startInputReason, mClient,
+ focusedView.getWindowToken(), startInputFlags, softInputMode,
+ windowFlags,
+ null,
+ null,
+ 0 /* missingMethodFlags */,
+ mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
+ ResultCallbacks.of(value));
+ Completable.getResult(value); // ignore the result
+ }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1087,6 +1104,11 @@
public void setImeTraceEnabled(boolean enabled) {
ImeTracing.getInstance().setEnabled(enabled);
}
+
+ @Override
+ public void throwExceptionFromSystem(String message) {
+ throw new RuntimeException(message);
+ }
};
final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
index 18f29ae..7d222db 100644
--- a/core/java/android/window/SplashScreen.java
+++ b/core/java/android/window/SplashScreen.java
@@ -17,12 +17,17 @@
package android.window;
import android.annotation.NonNull;
+import android.annotation.StyleRes;
import android.annotation.SuppressLint;
import android.annotation.UiThread;
import android.app.Activity;
import android.app.ActivityThread;
+import android.app.AppGlobals;
import android.content.Context;
+import android.content.res.Resources;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
import android.util.Singleton;
import android.util.Slog;
@@ -60,6 +65,17 @@
*/
void clearOnExitAnimationListener();
+
+ /**
+ * Overrides the theme used for the {@link SplashScreen}s of this application.
+ * <p>
+ * By default, the {@link SplashScreen} uses the theme set in the manifest. This method
+ * overrides and persists the theme used for the {@link SplashScreen} of this application.
+ * <p>
+ * To reset to the default theme, set this the themeId to {@link Resources#ID_NULL}.
+ */
+ void setSplashScreenTheme(@StyleRes int themeId);
+
/**
* Listens for the splash screen exit event.
*/
@@ -84,6 +100,8 @@
* @hide
*/
class SplashScreenImpl implements SplashScreen {
+ private static final String TAG = "SplashScreenImpl";
+
private OnExitAnimationListener mExitAnimationListener;
private final IBinder mActivityToken;
private final SplashScreenManagerGlobal mGlobal;
@@ -119,6 +137,29 @@
mGlobal.removeImpl(this);
}
}
+
+ public void setSplashScreenTheme(@StyleRes int themeId) {
+ if (mActivityToken == null) {
+ Log.w(TAG, "Couldn't persist the starting theme. This instance is not an Activity");
+ return;
+ }
+
+ Activity activity = ActivityThread.currentActivityThread().getActivity(
+ mActivityToken);
+ if (activity == null) {
+ return;
+ }
+ String themeName = themeId != Resources.ID_NULL
+ ? activity.getResources().getResourceName(themeId) : null;
+
+ try {
+ AppGlobals.getPackageManager().setSplashScreenTheme(
+ activity.getComponentName().getPackageName(),
+ themeName, activity.getUserId());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Couldn't persist the starting theme", e);
+ }
+ }
}
/**
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index 49dbbaa..a61e86b 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -30,4 +30,5 @@
void reportFullscreenMode(boolean fullscreen);
void updateActivityViewToScreenMatrix(int bindSequence, in float[] matrixValues);
void setImeTraceEnabled(boolean enabled);
+ void throwExceptionFromSystem(String message);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 93cd4e9..772e344 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -68,6 +68,12 @@
int unverifiedTargetSdkVersion,
in IInputBindResultResultCallback inputBindResult);
+ oneway void reportWindowGainedFocusAsync(
+ boolean nextFocusHasConnection, in IInputMethodClient client, in IBinder windowToken,
+ /* @StartInputFlags */ int startInputFlags,
+ /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
+ int windowFlags, int unverifiedTargetSdkVersion);
+
oneway void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode, in IVoidResultCallback resultCallback);
oneway void showInputMethodPickerFromSystem(in IInputMethodClient client,
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 9298d9fc..4065bd1 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -349,15 +349,19 @@
private final Rect mTmpBounds = new Rect();
public VectorDrawable() {
- this(new VectorDrawableState(null), null);
+ this(null, null);
}
/**
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
- private VectorDrawable(@NonNull VectorDrawableState state, @Nullable Resources res) {
- mVectorState = state;
+ private VectorDrawable(@Nullable VectorDrawableState state, @Nullable Resources res) {
+ // As the mutable, not-thread-safe native instance is stored in VectorDrawableState, we
+ // need to always do a defensive copy even if mutate() isn't called. Otherwise
+ // draw() being called on 2 different VectorDrawable instances could still hit the same
+ // underlying native object.
+ mVectorState = new VectorDrawableState(state);
updateLocalState(res);
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index 4142e51..b75252b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -58,7 +58,6 @@
/** Returns true if the gesture should be rejected. */
boolean isFalseTouch(int interactionType);
-
/**
* Does basic checking to see if gesture looks like a tap.
*
@@ -127,8 +126,19 @@
/** Removes a {@link FalsingBeliefListener}. */
void removeFalsingBeliefListener(FalsingBeliefListener listener);
+ /** Adds a {@link FalsingTapListener}. */
+ void addTapListener(FalsingTapListener falsingTapListener);
+
+ /** Removes a {@link FalsingTapListener}. */
+ void removeTapListener(FalsingTapListener falsingTapListener);
+
/** Listener that is alerted when falsing belief level crosses a predfined threshold. */
interface FalsingBeliefListener {
void onFalse();
}
+
+ /** Listener that is alerted when a double tap is required to confirm a single tap. */
+ interface FalsingTapListener {
+ void onDoubleTapRequired();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
index 8765c9a..947466f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
@@ -1,7 +1,3 @@
set noparent
-kchyn@google.com
-jaggies@google.com
-curtislb@google.com
-ilyamaty@google.com
-joshmccloskey@google.com
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 01d5959..9e621b3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -79,6 +79,7 @@
private final Collection<FalsingClassifier> mClassifiers;
private final List<FalsingBeliefListener> mFalsingBeliefListeners = new ArrayList<>();
+ private List<FalsingTapListener> mFalsingTapListeners = new ArrayList<>();
private final SessionListener mSessionListener = new SessionListener() {
@Override
@@ -277,6 +278,7 @@
FalsingClassifier.Result.falsed(
0, getClass().getSimpleName(), "bad history"));
logDebug("False Single Tap: true (bad history)");
+ mFalsingTapListeners.forEach(FalsingTapListener::onDoubleTapRequired);
return true;
} else {
mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(0.1));
@@ -356,6 +358,16 @@
}
@Override
+ public void addTapListener(FalsingTapListener listener) {
+ mFalsingTapListeners.add(listener);
+ }
+
+ @Override
+ public void removeTapListener(FalsingTapListener listener) {
+ mFalsingTapListeners.remove(listener);
+ }
+
+ @Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.println("BRIGHTLINE FALSING MANAGER");
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
index e557773..e8445d4 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -146,4 +146,14 @@
public void removeFalsingBeliefListener(FalsingBeliefListener listener) {
mFalsingBeliefListeners.remove(listener);
}
+
+ @Override
+ public void addTapListener(FalsingTapListener falsingTapListener) {
+
+ }
+
+ @Override
+ public void removeTapListener(FalsingTapListener falsingTapListener) {
+
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 1723291..6b819fb 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -176,6 +176,16 @@
}
@Override
+ public void addTapListener(FalsingTapListener listener) {
+ mInternalFalsingManager.addTapListener(listener);
+ }
+
+ @Override
+ public void removeTapListener(FalsingTapListener listener) {
+ mInternalFalsingManager.removeTapListener(listener);
+ }
+
+ @Override
public void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
mInternalFalsingManager.onProximityEvent(proximityEvent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 8264a9c..fb109f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -84,7 +84,6 @@
private int mCutoutHeight;
private int mGapHeight;
private int mIndexOfFirstViewInShelf = -1;
- private int mIndexOfFirstViewInOverflowingSection = -1;
private NotificationShelfController mController;
@@ -180,7 +179,6 @@
viewState.xTranslation = getTranslationX();
viewState.hasItemsInStableShelf = lastViewState.inShelf;
viewState.firstViewInShelf = algorithmState.firstViewInShelf;
- viewState.firstViewInOverflowSection = algorithmState.firstViewInOverflowSection;
if (mNotGoneIndex != -1) {
viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex);
}
@@ -268,17 +266,6 @@
// TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
notificationClipEnd = stackEnd;
- } else if (mAmbientState.isExpansionChanging()) {
- if (mIndexOfFirstViewInOverflowingSection != -1
- && i >= mIndexOfFirstViewInOverflowingSection) {
- // Clip notifications in (section overflowing into shelf) to shelf start.
- notificationClipEnd = shelfStart - mPaddingBetweenElements;
- } else {
- // Clip notifications before the section overflowing into shelf
- // to stackEnd because we do not show the shelf if the section right before the
- // shelf is still hidden.
- notificationClipEnd = stackEnd;
- }
} else {
notificationClipEnd = shelfStart - mPaddingBetweenElements;
}
@@ -831,11 +818,6 @@
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
}
- public void setFirstViewInOverflowingSection(ExpandableView firstViewInOverflowingSection) {
- mIndexOfFirstViewInOverflowingSection =
- mHostLayoutController.indexOfChild(firstViewInOverflowingSection);
- }
-
private class ShelfState extends ExpandableViewState {
private boolean hasItemsInStableShelf;
private ExpandableView firstViewInShelf;
@@ -849,7 +831,6 @@
super.applyToView(view);
setIndexOfFirstViewInShelf(firstViewInShelf);
- setFirstViewInOverflowingSection(firstViewInOverflowSection);
updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
@@ -863,7 +844,6 @@
super.animateTo(child, properties);
setIndexOfFirstViewInShelf(firstViewInShelf);
- setFirstViewInOverflowingSection(firstViewInOverflowSection);
updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 6cacec7..e3c5546 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -33,9 +33,7 @@
import com.android.systemui.statusbar.notification.row.FooterView;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
/**
* The Algorithm of the {@link com.android.systemui.statusbar.notification.stack
@@ -255,16 +253,16 @@
}
}
- state.firstViewInShelf = null;
- // Save y, sectionStart, sectionEnd from when shade is fully expanded.
- // Consider updating these states in updateContentView instead so that we don't have to
- // recalculate in every frame.
+ // Save (height of view before shelf, index of first view in shelf) from when shade is fully
+ // expanded. Consider updating these states in updateContentView instead so that we don't
+ // have to recalculate in every frame.
float currentY = -scrollY;
- int sectionStartIndex = 0;
- int sectionEndIndex = 0;
+ float previousY = 0;
+ state.firstViewInShelf = null;
+ state.viewHeightBeforeShelf = -1;
for (int i = 0; i < state.visibleChildren.size(); i++) {
final ExpandableView view = state.visibleChildren.get(i);
- // Add space between sections.
+
final boolean applyGapHeight = childNeedsGapHeight(
ambientState.getSectionProvider(), i,
view, getPreviousView(i, state));
@@ -273,88 +271,27 @@
}
if (ambientState.getShelf() != null) {
- // Save index of first view in the shelf
final float shelfStart = ambientState.getStackEndHeight()
- ambientState.getShelf().getIntrinsicHeight();
if (currentY >= shelfStart
&& !(view instanceof FooterView)
&& state.firstViewInShelf == null) {
state.firstViewInShelf = view;
+ // There might be a section gap right before the shelf.
+ // Limit the height of the view before the shelf so that it does not include
+ // a gap and become taller than it normally is.
+ state.viewHeightBeforeShelf = Math.min(getMaxAllowedChildHeight(view),
+ ambientState.getStackEndHeight()
+ - ambientState.getShelf().getIntrinsicHeight()
+ - mPaddingBetweenElements
+ - previousY);
}
}
-
- // Record y position when fully expanded
- ExpansionData expansionData = new ExpansionData();
- expansionData.fullyExpandedY = currentY;
- state.expansionData.put(view, expansionData);
-
- if (ambientState.getSectionProvider()
- .beginsSection(view, getPreviousView(i, state))) {
-
- // Save section start/end for views in the section before this new section
- ExpandableView sectionStartView = state.visibleChildren.get(sectionStartIndex);
- final float sectionStart =
- state.expansionData.get(sectionStartView).fullyExpandedY;
-
- ExpandableView sectionEndView = state.visibleChildren.get(sectionEndIndex);
- float sectionEnd = state.expansionData.get(sectionEndView).fullyExpandedY
- + sectionEndView.getIntrinsicHeight();
-
- if (ambientState.getShelf() != null) {
- // If we show the shelf, trim section end to shelf start
- // This means section end > start for views in the shelf
- final float shelfStart = ambientState.getStackEndHeight()
- - ambientState.getShelf().getIntrinsicHeight();
- if (state.firstViewInShelf != null && sectionEnd > shelfStart) {
- sectionEnd = shelfStart;
- }
- }
-
- // Update section bounds of every view in the previous section
- // Consider using shared SectionInfo for views in same section to avoid looping back
- for (int j = sectionStartIndex; j < i; j++) {
- ExpandableView sectionView = state.visibleChildren.get(j);
- ExpansionData viewExpansionData =
- state.expansionData.get(sectionView);
- viewExpansionData.sectionStart = sectionStart;
- viewExpansionData.sectionEnd = sectionEnd;
- state.expansionData.put(sectionView, viewExpansionData);
- }
- sectionStartIndex = i;
-
- if (view instanceof FooterView) {
- // Also record section bounds for FooterView (same as its own)
- // because it is the last view and we won't get to this point again
- // after the loop ends
- ExpansionData footerExpansionData = state.expansionData.get(view);
- footerExpansionData.sectionStart = expansionData.fullyExpandedY;
- footerExpansionData.sectionEnd = expansionData.fullyExpandedY
- + view.getIntrinsicHeight();
- state.expansionData.put(view, footerExpansionData);
- }
- }
- sectionEndIndex = i;
+ previousY = currentY;
currentY = currentY
+ getMaxAllowedChildHeight(view)
+ mPaddingBetweenElements;
}
-
- // Which view starts the section of the view right before the shelf?
- // Save it for later when we clip views in that section to shelf start.
- state.firstViewInOverflowSection = null;
- if (state.firstViewInShelf != null) {
- ExpandableView nextView = null;
- final int startIndex = state.visibleChildren.indexOf(state.firstViewInShelf);
- for (int i = startIndex - 1; i >= 0; i--) {
- ExpandableView view = state.visibleChildren.get(i);
- if (nextView != null && ambientState.getSectionProvider()
- .beginsSection(nextView, view)) {
- break;
- }
- nextView = view;
- }
- state.firstViewInOverflowSection = nextView;
- }
}
private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
@@ -394,6 +331,26 @@
}
}
+ /**
+ * @return Fraction to apply to view height and gap between views.
+ * Does not include shelf height even if shelf is showing.
+ */
+ private float getExpansionFractionWithoutShelf(
+ StackScrollAlgorithmState algorithmState,
+ AmbientState ambientState) {
+
+ final boolean isShowingShelf = ambientState.getShelf() != null
+ && algorithmState.firstViewInShelf != null;
+
+ final float stackHeight = ambientState.getStackHeight()
+ - (isShowingShelf ? ambientState.getShelf().getIntrinsicHeight() : 0f);
+
+ float stackEndHeight = ambientState.getStackEndHeight()
+ - (isShowingShelf ? ambientState.getShelf().getIntrinsicHeight() : 0f);
+
+ return stackHeight / stackEndHeight;
+ }
+
// TODO(b/172289889) polish shade open from HUN
/**
* Populates the {@link ExpandableViewState} for a single child.
@@ -426,22 +383,8 @@
viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
}
- // TODO(b/172289889) move sectionFraction and showSection to initAlgorithmState
- // Get fraction of section showing, and later apply it to view height and gaps between views
- float sectionFraction = 1f;
- boolean showSection = true;
-
- if (!ambientState.isOnKeyguard()
- && !ambientState.isPulseExpanding()
- && ambientState.isExpansionChanging()) {
-
- final ExpansionData expansionData = algorithmState.expansionData.get(view);
- final float sectionHeight = expansionData.sectionEnd - expansionData.sectionStart;
- sectionFraction = MathUtils.constrain(
- (ambientState.getStackHeight() - expansionData.sectionStart) / sectionHeight,
- 0f, 1f);
- showSection = expansionData.sectionStart < ambientState.getStackHeight();
- }
+ final float expansionFraction = getExpansionFractionWithoutShelf(
+ algorithmState, ambientState);
// Add gap between sections.
final boolean applyGapHeight =
@@ -449,46 +392,58 @@
ambientState.getSectionProvider(), i,
view, getPreviousView(i, algorithmState));
if (applyGapHeight) {
- currentYPosition += sectionFraction * mGapHeight;
+ currentYPosition += expansionFraction * mGapHeight;
}
viewState.yTranslation = currentYPosition;
-
if (view instanceof SectionHeaderView) {
// Add padding before sections for overscroll effect.
- viewState.yTranslation += ambientState.getSectionPadding();
+ viewState.yTranslation += expansionFraction * ambientState.getSectionPadding();
}
- if (view != ambientState.getTrackedHeadsUpRow()) {
+ if (view instanceof FooterView) {
+ viewState.yTranslation = Math.min(viewState.yTranslation,
+ ambientState.getStackHeight());
+ // Hide footer if shelf is showing
+ viewState.hidden = algorithmState.firstViewInShelf != null;
+ } else if (view != ambientState.getTrackedHeadsUpRow()) {
if (ambientState.isExpansionChanging()) {
- viewState.hidden = !showSection;
+ // Show all views. Views below the shelf will later be clipped (essentially hidden)
+ // in NotificationShelf.
+ viewState.hidden = false;
viewState.inShelf = algorithmState.firstViewInShelf != null
&& i >= algorithmState.visibleChildren.indexOf(
- algorithmState.firstViewInShelf)
- && !(view instanceof FooterView);
+ algorithmState.firstViewInShelf);
} else if (ambientState.getShelf() != null) {
// When pulsing (incoming notification on AOD), innerHeight is 0; clamp all
// to shelf start, thereby hiding all notifications (except the first one, which we
// later unhide in updatePulsingState)
final int shelfStart = ambientState.getInnerHeight()
- ambientState.getShelf().getIntrinsicHeight();
- if (!(view instanceof FooterView)) {
- viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
- }
+ viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
if (viewState.yTranslation >= shelfStart) {
viewState.hidden = !view.isExpandAnimationRunning()
- && !view.hasExpandingChild()
- && !(view instanceof FooterView);
+ && !view.hasExpandingChild();
viewState.inShelf = true;
// Notifications in the shelf cannot be visible HUNs.
viewState.headsUpIsVisible = false;
}
}
- viewState.height = (int) MathUtils.lerp(
- 0, getMaxAllowedChildHeight(view), sectionFraction);
+
+ // Clip height of view right before shelf.
+ float maxViewHeight = getMaxAllowedChildHeight(view);
+ if (ambientState.isExpansionChanging()
+ && algorithmState.viewHeightBeforeShelf != -1) {
+ final int indexOfFirstViewInShelf = algorithmState.visibleChildren.indexOf(
+ algorithmState.firstViewInShelf);
+ if (i == indexOfFirstViewInShelf - 1) {
+ maxViewHeight = algorithmState.viewHeightBeforeShelf;
+ }
+ }
+ viewState.height = (int) MathUtils.lerp(0, maxViewHeight, expansionFraction);
}
- currentYPosition += viewState.height + sectionFraction * mPaddingBetweenElements;
+ currentYPosition += viewState.height + expansionFraction * mPaddingBetweenElements;
setLocation(view.getViewState(), currentYPosition, i);
viewState.yTranslation += ambientState.getStackY();
return currentYPosition;
@@ -743,35 +698,6 @@
this.mIsExpanded = isExpanded;
}
- /**
- * Data used to layout views while shade expansion changes.
- */
- public class ExpansionData {
-
- /**
- * Y position of top of first view in section.
- */
- public float sectionStart;
-
- /**
- * Y position of bottom of last view in section.
- */
- public float sectionEnd;
-
- /**
- * Y position of view when shade is fully expanded.
- * Does not include distance between top notifications panel and top of screen.
- */
- public float fullyExpandedY;
-
- /**
- * Whether this notification is in the same section as the notification right before the
- * shelf. Used to determine which notification should be clipped to shelf start while
- * shade expansion changes.
- */
- public boolean inOverflowingSection;
- }
-
public class StackScrollAlgorithmState {
/**
@@ -785,16 +711,9 @@
public ExpandableView firstViewInShelf;
/**
- * First view in section overflowing into shelf while shade expansion changes.
+ * Height of view right before the shelf.
*/
- public ExpandableView firstViewInOverflowSection;
-
- /**
- * Map of view to ExpansionData used for layout during shade expansion.
- * Use view instead of index as key, because visibleChildren indices do not match the ones
- * used in the shelf.
- */
- public Map<ExpandableView, ExpansionData> expansionData = new HashMap<>();
+ public float viewHeightBeforeShelf;
/**
* The children from the host view which are not gone.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index e2af940..9449836 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -53,6 +53,7 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.UserManager;
+import android.os.VibrationEffect;
import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.util.Log;
import android.util.MathUtils;
@@ -101,6 +102,7 @@
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -206,6 +208,7 @@
private final ExpansionCallback mExpansionCallback = new ExpansionCallback();
private final BiometricUnlockController mBiometricUnlockController;
private final NotificationPanelView mView;
+ private final VibratorHelper mVibratorHelper;
private final MetricsLogger mMetricsLogger;
private final ActivityManager mActivityManager;
private final ConfigurationController mConfigurationController;
@@ -545,6 +548,14 @@
}
};
+ private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() {
+ @Override
+ public void onDoubleTapRequired() {
+ showTransientIndication(R.string.notification_tap_again);
+ mVibratorHelper.vibrate(VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ }
+ };
+
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@Main Resources resources,
@@ -592,6 +603,7 @@
statusBarKeyguardViewManager, latencyTracker, flingAnimationUtilsBuilder.get(),
statusBarTouchableRegionManager, ambientState);
mView = view;
+ mVibratorHelper = vibratorHelper;
mMetricsLogger = metricsLogger;
mActivityManager = activityManager;
mConfigurationController = configurationController;
@@ -4015,6 +4027,7 @@
// window, so
// force a call to onThemeChanged
mConfigurationListener.onThemeChanged();
+ mFalsingManager.addTapListener(mFalsingTapListener);
}
@Override
@@ -4023,6 +4036,7 @@
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mConfigurationController.removeCallback(mConfigurationListener);
mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
+ mFalsingManager.removeTapListener(mFalsingTapListener);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 088f947..2b5caf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -364,7 +364,6 @@
MetricsEvent.ACTION_LS_NOTE,
0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_NOTIFICATION_FALSE_TOUCH);
- mNotificationPanel.showTransientIndication(R.string.notification_tap_again);
ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild();
if (previousView != null) {
previousView.makeInactive(true /* animate */);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3424821..c7c681b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3355,68 +3355,100 @@
@NonNull
@Override
+ public void reportWindowGainedFocusAsync(
+ boolean nextFocusHasConnection, IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
+ int windowFlags, int unverifiedTargetSdkVersion) {
+ final int startInputReason = nextFocusHasConnection
+ ? StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
+ : StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+ try {
+ startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
+ startInputFlags, softInputMode, windowFlags, null /* attribute */,
+ null /* inputContext */, 0 /* missingMethods */, unverifiedTargetSdkVersion);
+ } catch (Throwable t) {
+ if (client != null) {
+ try {
+ client.throwExceptionFromSystem(t.getMessage());
+ } catch (RemoteException ignore) { }
+ }
+ }
+ }
+
+ @NonNull
+ @Override
public void startInputOrWindowGainedFocus(
@StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
@StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
@MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion,
IInputBindResultResultCallback resultCallback) {
- CallbackUtils.onResult(resultCallback, (Supplier<InputBindResult>) () -> {
- if (windowToken == null) {
- Slog.e(TAG, "windowToken cannot be null.");
+ CallbackUtils.onResult(resultCallback, (Supplier<InputBindResult>) () ->
+ startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
+ startInputFlags, softInputMode, windowFlags, attribute, inputContext,
+ missingMethods, unverifiedTargetSdkVersion));
+ }
+
+ @NonNull
+ private InputBindResult startInputOrWindowGainedFocusInternal(
+ @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo attribute, @Nullable IInputContext inputContext,
+ @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion) {
+ if (windowToken == null) {
+ Slog.e(TAG, "windowToken cannot be null.");
+ return InputBindResult.NULL;
+ }
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+ "IMMS.startInputOrWindowGainedFocus");
+ ImeTracing.getInstance().triggerManagerServiceDump(
+ "InputMethodManagerService#startInputOrWindowGainedFocus");
+ final int callingUserId = UserHandle.getCallingUserId();
+ final int userId;
+ if (attribute != null && attribute.targetInputMethodUser != null
+ && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "Using EditorInfo.targetInputMethodUser requires"
+ + " INTERACT_ACROSS_USERS_FULL.");
+ userId = attribute.targetInputMethodUser.getIdentifier();
+ if (!mUserManagerInternal.isUserRunning(userId)) {
+ // There is a chance that we hit here because of race condition. Let's just
+ // return an error code instead of crashing the caller process, which at
+ // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
+ // important process.
+ Slog.e(TAG, "User #" + userId + " is not running.");
+ return InputBindResult.INVALID_USER;
+ }
+ } else {
+ userId = callingUserId;
+ }
+ final InputBindResult result;
+ synchronized (mMethodMap) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
+ client, windowToken, startInputFlags, softInputMode, windowFlags,
+ attribute, inputContext, missingMethods, unverifiedTargetSdkVersion,
+ userId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ if (result == null) {
+ // This must never happen, but just in case.
+ Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
+ + InputMethodDebug.startInputReasonToString(startInputReason)
+ + " windowFlags=#" + Integer.toHexString(windowFlags)
+ + " editorInfo=" + attribute);
return InputBindResult.NULL;
}
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
- "IMMS.startInputOrWindowGainedFocus");
- ImeTracing.getInstance().triggerManagerServiceDump(
- "InputMethodManagerService#startInputOrWindowGainedFocus");
- final int callingUserId = UserHandle.getCallingUserId();
- final int userId;
- if (attribute != null && attribute.targetInputMethodUser != null
- && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
- mContext.enforceCallingPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "Using EditorInfo.targetInputMethodUser requires"
- + " INTERACT_ACROSS_USERS_FULL.");
- userId = attribute.targetInputMethodUser.getIdentifier();
- if (!mUserManagerInternal.isUserRunning(userId)) {
- // There is a chance that we hit here because of race condition. Let's just
- // return an error code instead of crashing the caller process, which at
- // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
- // important process.
- Slog.e(TAG, "User #" + userId + " is not running.");
- return InputBindResult.INVALID_USER;
- }
- } else {
- userId = callingUserId;
- }
- final InputBindResult result;
- synchronized (mMethodMap) {
- final long ident = Binder.clearCallingIdentity();
- try {
- result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
- client, windowToken, startInputFlags, softInputMode, windowFlags,
- attribute, inputContext, missingMethods, unverifiedTargetSdkVersion,
- userId);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- if (result == null) {
- // This must never happen, but just in case.
- Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
- + InputMethodDebug.startInputReasonToString(startInputReason)
- + " windowFlags=#" + Integer.toHexString(windowFlags)
- + " editorInfo=" + attribute);
- return InputBindResult.NULL;
- }
- return result;
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- });
+ return result;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
}
@NonNull
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index fcaf6d8..0fc91ba 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1627,6 +1627,33 @@
@BinderThread
@Override
+ public void reportWindowGainedFocusAsync(
+ boolean nextFocusHasConnection,
+ @Nullable IInputMethodClient client,
+ @Nullable IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @SoftInputModeFlags int softInputMode,
+ int windowFlags,
+ int unverifiedTargetSdkVersion) {
+ final int startInputReason = nextFocusHasConnection
+ ? StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
+ : StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+ try {
+ startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
+ startInputFlags, softInputMode, windowFlags, null /* editorInfo */,
+ null /* inputContext */, 0 /* missingMethods */,
+ unverifiedTargetSdkVersion);
+ } catch (Throwable t) {
+ if (client != null) {
+ try {
+ client.throwExceptionFromSystem(t.getMessage());
+ } catch (RemoteException ignore) { }
+ }
+ }
+ }
+
+ @BinderThread
+ @Override
public void startInputOrWindowGainedFocus(
@StartInputReason int startInputReason,
@Nullable IInputMethodClient client,
@@ -1641,8 +1668,8 @@
IInputBindResultResultCallback resultCallback) {
CallbackUtils.onResult(resultCallback, (Supplier<InputBindResult>) () ->
startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
- startInputFlags, softInputMode, windowFlags, editorInfo, inputContext,
- missingMethods, unverifiedTargetSdkVersion));
+ startInputFlags, softInputMode, windowFlags, editorInfo, inputContext,
+ missingMethods, unverifiedTargetSdkVersion));
}
@BinderThread
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f5f0ce1..8d6c145 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -21627,7 +21627,8 @@
null /*disabledComponents*/,
PackageManager.INSTALL_REASON_UNKNOWN,
PackageManager.UNINSTALL_REASON_UNKNOWN,
- null /*harmfulAppWarning*/);
+ null /*harmfulAppWarning*/,
+ null /*splashScreenTheme*/);
}
mSettings.writeKernelMappingLPr(ps);
}
@@ -27701,6 +27702,23 @@
return mSettings.getPackageLPr(packageName).getMimeGroup(mimeGroup);
}
+ @Override
+ public void setSplashScreenTheme(@NonNull String packageName, @Nullable String themeId,
+ int userId) {
+ int callingUid = Binder.getCallingUid();
+ PackageSetting packageSetting = getPackageSettingForUser(packageName, callingUid, userId);
+ if (packageSetting != null) {
+ packageSetting.setSplashScreenTheme(userId, themeId);
+ }
+ }
+
+ @Override
+ public String getSplashScreenTheme(@NonNull String packageName, int userId) {
+ int callingUid = Binder.getCallingUid();
+ PackageSetting packageSetting = getPackageSettingForUser(packageName, callingUid, userId);
+ return packageSetting != null ? packageSetting.getSplashScreenTheme(userId) : null;
+ }
+
/**
* Temporary method that wraps mSettings.writeLPr() and calls mPermissionManager's
* writeLegacyPermissionsTEMP() beforehand.
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 19b56b7..731d41c 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -493,7 +493,8 @@
ArrayMap<String, PackageUserState.SuspendParams> suspendParams, boolean instantApp,
boolean virtualPreload, String lastDisableAppCaller,
ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
- int installReason, int uninstallReason, String harmfulAppWarning) {
+ int installReason, int uninstallReason, String harmfulAppWarning,
+ String splashScreenTheme) {
PackageUserState state = modifyUserState(userId);
state.ceDataInode = ceDataInode;
state.enabled = enabled;
@@ -512,6 +513,7 @@
state.instantApp = instantApp;
state.virtualPreload = virtualPreload;
state.harmfulAppWarning = harmfulAppWarning;
+ state.splashScreenTheme = splashScreenTheme;
onChanged();
}
@@ -522,7 +524,8 @@
otherState.instantApp,
otherState.virtualPreload, otherState.lastDisableAppCaller,
otherState.enabledComponents, otherState.disabledComponents,
- otherState.installReason, otherState.uninstallReason, otherState.harmfulAppWarning);
+ otherState.installReason, otherState.uninstallReason, otherState.harmfulAppWarning,
+ otherState.splashScreenTheme);
}
ArraySet<String> getEnabledComponents(int userId) {
@@ -723,6 +726,26 @@
}
/**
+ * @param userId the specified user to modify the theme for
+ * @param themeName the theme name to persist
+ * @see android.window.SplashScreen#setSplashScreenTheme(int)
+ */
+ public void setSplashScreenTheme(@UserIdInt int userId, @Nullable String themeName) {
+ modifyUserState(userId).splashScreenTheme = themeName;
+ }
+
+ /**
+ * @param userId the specified user to get the theme setting from
+ * @return the theme name previously persisted for the user or null
+ * if no splashscreen theme is persisted.
+ * @see android.window.SplashScreen#setSplashScreenTheme(int)
+ */
+ @Nullable
+ public String getSplashScreenTheme(@UserIdInt int userId) {
+ return readUserState(userId).splashScreenTheme;
+ }
+
+ /**
* @return True if package is still being loaded, false if the package is fully loaded.
*/
public boolean isPackageLoading() {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e409019..b6d4a5b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -340,6 +340,7 @@
private static final String ATTR_INSTANT_APP = "instant-app";
private static final String ATTR_VIRTUAL_PRELOAD = "virtual-preload";
private static final String ATTR_HARMFUL_APP_WARNING = "harmful-app-warning";
+ private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme";
private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_FINGERPRINT = "fingerprint";
@@ -939,7 +940,9 @@
null /*disabledComponents*/,
PackageManager.INSTALL_REASON_UNKNOWN,
PackageManager.UNINSTALL_REASON_UNKNOWN,
- null /*harmfulAppWarning*/);
+ null, /*harmfulAppWarning*/
+ null /*splashscreenTheme*/
+ );
}
}
}
@@ -1578,7 +1581,8 @@
null /*disabledComponents*/,
PackageManager.INSTALL_REASON_UNKNOWN,
PackageManager.UNINSTALL_REASON_UNKNOWN,
- null /*harmfulAppWarning*/);
+ null /*harmfulAppWarning*/,
+ null /* splashScreenTheme*/);
}
return;
}
@@ -1666,6 +1670,8 @@
PackageManager.INSTALL_REASON_UNKNOWN);
final int uninstallReason = parser.getAttributeInt(null, ATTR_UNINSTALL_REASON,
PackageManager.UNINSTALL_REASON_UNKNOWN);
+ final String splashScreenTheme = parser.getAttributeValue(null,
+ ATTR_SPLASH_SCREEN_THEME);
ArraySet<String> enabledComponents = null;
ArraySet<String> disabledComponents = null;
@@ -1738,7 +1744,8 @@
ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
hidden, distractionFlags, suspended, suspendParamsMap,
instantApp, virtualPreload, enabledCaller, enabledComponents,
- disabledComponents, installReason, uninstallReason, harmfulAppWarning);
+ disabledComponents, installReason, uninstallReason, harmfulAppWarning,
+ splashScreenTheme);
mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
} else if (tagName.equals("preferred-activities")) {
@@ -1995,6 +2002,10 @@
serializer.attribute(null, ATTR_HARMFUL_APP_WARNING,
ustate.harmfulAppWarning);
}
+ if (ustate.splashScreenTheme != null) {
+ serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME,
+ ustate.splashScreenTheme);
+ }
if (ustate.suspended) {
for (int i = 0; i < ustate.suspendParams.size(); i++) {
final String suspendingPackage = ustate.suspendParams.keyAt(i);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 721432e..d4707d6f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -176,12 +176,14 @@
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
@@ -6677,8 +6679,30 @@
prev = null;
}
}
- final int splashScreenThemeResId = options != null
+
+ // TODO(185200798): Persist theme name instead of theme if
+ int splashScreenThemeResId = options != null
? options.getSplashScreenThemeResId() : 0;
+
+ // User can override the splashscreen theme. The theme name is used to persist
+ // the setting, so if no theme is set in the ActivityOptions, we check if has
+ // been persisted here.
+ if (splashScreenThemeResId == 0) {
+ try {
+ String themeName = mAtmService.getPackageManager()
+ .getSplashScreenTheme(r.packageName, r.mUserId);
+ if (themeName != null) {
+ Context packageContext = mAtmService.mContext
+ .createPackageContext(r.packageName, 0);
+ splashScreenThemeResId = packageContext.getResources()
+ .getIdentifier(themeName, null, null);
+ }
+ } catch (RemoteException | PackageManager.NameNotFoundException
+ | Resources.NotFoundException ignore) {
+ // Just use the default theme
+ }
+ }
+
r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity),
splashScreenThemeResId, samePackage);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d65a28d..8274e38 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -16771,7 +16771,9 @@
provisioningParams.isKeepAccountMigrated(), callerPackage);
if (provisioningParams.isOrganizationOwnedProvisioning()) {
- setProfileOwnerOnOrgOwnedDeviceState(admin, userInfo.id, caller.getUserId());
+ synchronized (getLockObject()) {
+ markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(admin, userInfo.id);
+ }
}
return userInfo.getUserHandle();
@@ -17003,22 +17005,6 @@
}
}
- private void setProfileOwnerOnOrgOwnedDeviceState(
- ComponentName admin, @UserIdInt int profileId, @UserIdInt int parentUserId) {
- synchronized (getLockObject()) {
- markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(admin, profileId);
- }
- restrictRemovalOfManagedProfile(parentUserId);
- }
-
- private void restrictRemovalOfManagedProfile(@UserIdInt int parentUserId) {
- final UserHandle parentUserHandle = UserHandle.of(parentUserId);
- mUserManager.setUserRestriction(
- UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
- /* value= */ true,
- parentUserHandle);
- }
-
@Override
public void provisionFullyManagedDevice(
@NonNull FullyManagedDeviceProvisioningParams provisioningParams,
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 4ce336d..9b42799 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -2660,9 +2660,6 @@
}
switch (targetStatus) {
- case IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE:
- // Do nothing, this is a reset state.
- break;
case IDataLoaderStatusListener::DATA_LOADER_DESTROYED: {
switch (currentStatus) {
case IDataLoaderStatusListener::DATA_LOADER_BINDING:
@@ -2683,8 +2680,12 @@
}
case IDataLoaderStatusListener::DATA_LOADER_CREATED:
switch (currentStatus) {
- case IDataLoaderStatusListener::DATA_LOADER_DESTROYED:
case IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE:
+ case IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE:
+ // Before binding need to make sure we are unbound.
+ // Otherwise we'll get stuck binding.
+ return destroy();
+ case IDataLoaderStatusListener::DATA_LOADER_DESTROYED:
case IDataLoaderStatusListener::DATA_LOADER_BINDING:
return bind();
case IDataLoaderStatusListener::DATA_LOADER_BOUND:
@@ -2709,7 +2710,8 @@
<< ", but got: " << mountId;
return binder::Status::fromServiceSpecificError(-EPERM, "Mount ID mismatch.");
}
- if (newStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) {
+ if (newStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE ||
+ newStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) {
// User-provided status, let's postpone the handling to avoid possible deadlocks.
mService.addTimedJob(*mService.mTimedQueue, id(), Constants::userStatusDelay,
[this, newStatus]() { setCurrentStatus(newStatus); });
@@ -2721,7 +2723,7 @@
}
void IncrementalService::DataLoaderStub::setCurrentStatus(int newStatus) {
- int targetStatus, oldStatus;
+ int oldStatus, oldTargetStatus, newTargetStatus;
DataLoaderStatusListener listener;
{
std::unique_lock lock(mMutex);
@@ -2730,22 +2732,31 @@
}
oldStatus = mCurrentStatus;
- targetStatus = mTargetStatus;
+ oldTargetStatus = mTargetStatus;
listener = mStatusListener;
// Change the status.
mCurrentStatus = newStatus;
mCurrentStatusTs = mService.mClock->now();
- if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE ||
- mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) {
- // For unavailable, unbind from DataLoader to ensure proper re-commit.
- setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+ switch (mCurrentStatus) {
+ case IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE:
+ // Unavailable, retry.
+ setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_STARTED);
+ break;
+ case IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE:
+ // Unrecoverable, just unbind.
+ setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+ break;
+ default:
+ break;
}
+
+ newTargetStatus = mTargetStatus;
}
LOG(DEBUG) << "Current status update for DataLoader " << id() << ": " << oldStatus << " -> "
- << newStatus << " (target " << targetStatus << ")";
+ << newStatus << " (target " << oldTargetStatus << " -> " << newTargetStatus << ")";
if (listener) {
listener->onStatusChanged(id(), newStatus);
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 6a3d953..cdf2e89 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -126,7 +126,7 @@
class MockDataLoader : public IDataLoader {
public:
MockDataLoader() {
- ON_CALL(*this, create(_, _, _, _)).WillByDefault(Invoke(this, &MockDataLoader::createOk));
+ initializeCreateOk();
ON_CALL(*this, start(_)).WillByDefault(Invoke(this, &MockDataLoader::startOk));
ON_CALL(*this, stop(_)).WillByDefault(Invoke(this, &MockDataLoader::stopOk));
ON_CALL(*this, destroy(_)).WillByDefault(Invoke(this, &MockDataLoader::destroyOk));
@@ -145,6 +145,10 @@
binder::Status(int32_t id, const std::vector<InstallationFileParcel>& addedFiles,
const std::vector<std::string>& removedFiles));
+ void initializeCreateOk() {
+ ON_CALL(*this, create(_, _, _, _)).WillByDefault(Invoke(this, &MockDataLoader::createOk));
+ }
+
void initializeCreateOkNoStatus() {
ON_CALL(*this, create(_, _, _, _))
.WillByDefault(Invoke(this, &MockDataLoader::createOkNoStatus));
@@ -275,6 +279,14 @@
}
return binder::Status::ok();
}
+ binder::Status bindToDataLoaderOkWithNoDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(bindDelayMs == 0) << bindDelayMs;
+ return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+ }
binder::Status bindToDataLoaderOkWith1sDelay(int32_t mountId,
const DataLoaderParamsParcel& params,
int bindDelayMs,
@@ -338,14 +350,13 @@
mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE);
}
binder::Status unbindFromDataLoaderOk(int32_t id) {
+ mBindDelayMs = -1;
if (mDataLoader) {
if (auto status = mDataLoader->destroy(id); !status.isOk()) {
return status;
}
mDataLoader = nullptr;
- }
- mBindDelayMs = -1;
- if (mListener) {
+ } else if (mListener) {
mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
}
return binder::Status::ok();
@@ -1156,12 +1167,81 @@
ASSERT_GE(storageId, 0);
ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
{}, {}));
- mDataLoaderManager->setDataLoaderStatusUnavailable();
+ mDataLoaderManager->setDataLoaderStatusUnrecoverable();
+
+ // Timed callback present.
+ ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_GE(mTimedQueue->mAfter, 10ms);
+ auto timedCallback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
+
+ // First callback call to propagate unrecoverable.
+ timedCallback();
+
+ // And second call to trigger recreation.
ASSERT_NE(nullptr, mLooper->mCallback);
ASSERT_NE(nullptr, mLooper->mCallbackData);
mLooper->mCallback(-1, -1, mLooper->mCallbackData);
}
+TEST_F(IncrementalServiceTest, testStartDataLoaderUnavailable) {
+ mIncFs->openMountSuccess();
+ mDataLoader->initializeCreateOkNoStatus();
+
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(3);
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(3);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(3);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(2);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ EXPECT_CALL(*mLooper, addFd(MockIncFs::kPendingReadsFd, _, _, _, _)).Times(1);
+ EXPECT_CALL(*mLooper, removeFd(MockIncFs::kPendingReadsFd)).Times(1);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWithNoDelay));
+
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+ {}, {}));
+
+ // Unavailable.
+ mDataLoaderManager->setDataLoaderStatusUnavailable();
+
+ // Timed callback present.
+ ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_GE(mTimedQueue->mAfter, 10ms);
+ auto timedCallback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
+
+ // Propagating unavailable and expecting it to trigger rebind with 1s retry delay.
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith1sDelay));
+ timedCallback();
+
+ // Unavailable #2.
+ mDataLoaderManager->setDataLoaderStatusUnavailable();
+
+ // Timed callback present.
+ ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_GE(mTimedQueue->mAfter, 10ms);
+ timedCallback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
+
+ // Propagating unavailable and expecting it to trigger rebind with 10s retry delay.
+ // This time succeed.
+ mDataLoader->initializeCreateOk();
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith10sDelay));
+ timedCallback();
+}
+
TEST_F(IncrementalServiceTest, testStartDataLoaderUnhealthyStorage) {
mIncFs->openMountSuccess();