diff options
40 files changed, 628 insertions, 156 deletions
diff --git a/apex/appsearch/Android.bp b/apex/appsearch/Android.bp index b014fdcb3df3..ab44dd92b657 100644 --- a/apex/appsearch/Android.bp +++ b/apex/appsearch/Android.bp @@ -21,6 +21,7 @@ apex { ], key: "com.android.appsearch.key", certificate: ":com.android.appsearch.certificate", + updatable: false, } apex_key { diff --git a/core/api/current.txt b/core/api/current.txt index db5d143662e4..5b639ff0b00f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4735,6 +4735,13 @@ package android.app { field @NonNull public static final android.os.Parcelable.Creator<android.app.AutomaticZenRule> CREATOR; } + public final class BackgroundServiceStartNotAllowedException extends android.app.ServiceStartNotAllowedException implements android.os.Parcelable { + ctor public BackgroundServiceStartNotAllowedException(@NonNull String); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.BackgroundServiceStartNotAllowedException> CREATOR; + } + public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener { ctor public DatePickerDialog(@NonNull android.content.Context); ctor public DatePickerDialog(@NonNull android.content.Context, @StyleRes int); @@ -4980,6 +4987,13 @@ package android.app { method @Deprecated public void setSelectedGroup(int); } + public final class ForegroundServiceStartNotAllowedException extends android.app.ServiceStartNotAllowedException implements android.os.Parcelable { + ctor public ForegroundServiceStartNotAllowedException(@NonNull String); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.ForegroundServiceStartNotAllowedException> CREATOR; + } + @Deprecated public class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener { ctor @Deprecated public Fragment(); method @Deprecated public void dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]); @@ -6563,6 +6577,9 @@ package android.app { field public static final int STOP_FOREGROUND_REMOVE = 1; // 0x1 } + public abstract class ServiceStartNotAllowedException extends java.lang.IllegalStateException { + } + public abstract class SharedElementCallback { ctor public SharedElementCallback(); method public android.os.Parcelable onCaptureSharedElementSnapshot(android.view.View, android.graphics.Matrix, android.graphics.RectF); @@ -51684,6 +51701,7 @@ package android.view.inputmethod { method public int describeContents(); method public void dump(android.util.Printer, String); method public android.content.ComponentName getComponent(); + method public int getConfigChanges(); method public String getId(); method public int getIsDefaultResourceId(); method public String getPackageName(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index fc1edf115625..a02320dbb70a 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2548,6 +2548,10 @@ package android.view.inputmethod { method @NonNull public static android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(@NonNull java.util.List<android.view.inputmethod.InlineSuggestion>); } + public final class InputMethodInfo implements android.os.Parcelable { + ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int); + } + public final class InputMethodManager { method public int getDisplayId(); method public boolean hasActiveInputConnection(@Nullable android.view.View); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index b8735c731817..2f3b50b17d51 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2551,9 +2551,9 @@ public class AppOpsManager { false, // READ_MEDIA_AUDIO false, // WRITE_MEDIA_AUDIO false, // READ_MEDIA_VIDEO - false, // WRITE_MEDIA_VIDEO + true, // WRITE_MEDIA_VIDEO false, // READ_MEDIA_IMAGES - false, // WRITE_MEDIA_IMAGES + true, // WRITE_MEDIA_IMAGES true, // LEGACY_STORAGE false, // ACCESS_ACCESSIBILITY false, // READ_DEVICE_IDENTIFIERS diff --git a/core/java/android/app/BackgroundServiceStartNotAllowedException.java b/core/java/android/app/BackgroundServiceStartNotAllowedException.java new file mode 100644 index 000000000000..f6361b52bf9d --- /dev/null +++ b/core/java/android/app/BackgroundServiceStartNotAllowedException.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Exception thrown when an app tries to start a background {@link Service} when it's not allowed to + * do so. + */ +public final class BackgroundServiceStartNotAllowedException + extends ServiceStartNotAllowedException implements Parcelable { + /** + * Constructor. + */ + public BackgroundServiceStartNotAllowedException(@NonNull String message) { + super(message); + } + + BackgroundServiceStartNotAllowedException(@NonNull Parcel source) { + super(source.readString()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(getMessage()); + } + + public static final @NonNull Creator<android.app.BackgroundServiceStartNotAllowedException> + CREATOR = new Creator<android.app.BackgroundServiceStartNotAllowedException>() { + @NonNull + public android.app.BackgroundServiceStartNotAllowedException createFromParcel( + Parcel source) { + return new android.app.BackgroundServiceStartNotAllowedException(source); + } + + @NonNull + public android.app.BackgroundServiceStartNotAllowedException[] newArray(int size) { + return new android.app.BackgroundServiceStartNotAllowedException[size]; + } + }; +} diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 85fb543a3967..bc798136f2e3 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1796,7 +1796,7 @@ class ContextImpl extends Context { "Unable to start service " + service + ": " + cn.getClassName()); } else if (cn.getPackageName().equals("?")) { - throw new IllegalStateException( + throw ServiceStartNotAllowedException.newInstance(requireForeground, "Not allowed to start service " + service + ": " + cn.getClassName()); } } diff --git a/core/java/android/app/ForegroundServiceStartNotAllowedException.java b/core/java/android/app/ForegroundServiceStartNotAllowedException.java new file mode 100644 index 000000000000..41eeada2df6b --- /dev/null +++ b/core/java/android/app/ForegroundServiceStartNotAllowedException.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Exception thrown when an app tries to start a foreground {@link Service} when it's not allowed to + * do so. + */ +public final class ForegroundServiceStartNotAllowedException + extends ServiceStartNotAllowedException implements Parcelable { + /** + * Constructor. + */ + public ForegroundServiceStartNotAllowedException(@NonNull String message) { + super(message); + } + + ForegroundServiceStartNotAllowedException(@NonNull Parcel source) { + super(source.readString()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(getMessage()); + } + + public static final @NonNull Creator<android.app.ForegroundServiceStartNotAllowedException> + CREATOR = new Creator<android.app.ForegroundServiceStartNotAllowedException>() { + @NonNull + public android.app.ForegroundServiceStartNotAllowedException createFromParcel( + Parcel source) { + return new android.app.ForegroundServiceStartNotAllowedException(source); + } + + @NonNull + public android.app.ForegroundServiceStartNotAllowedException[] newArray(int size) { + return new android.app.ForegroundServiceStartNotAllowedException[size]; + } + }; +} diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 3798de921dc7..2ceea7f1a6a8 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -697,7 +697,8 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * service element of manifest file. The value of attribute * {@link android.R.attr#foregroundServiceType} can be multiple flags ORed together.</p> * - * @throws IllegalStateException If the app targeting API is + * @throws ForegroundServiceStartNotAllowedException + * If the app targeting API is * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from * becoming foreground service due to background restriction. * @@ -738,8 +739,14 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * @param notification The Notification to be displayed. * @param foregroundServiceType must be a subset flags of manifest attribute * {@link android.R.attr#foregroundServiceType} flags. + * * @throws IllegalArgumentException if param foregroundServiceType is not subset of manifest * attribute {@link android.R.attr#foregroundServiceType}. + * @throws ForegroundServiceStartNotAllowedException + * If the app targeting API is + * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from + * becoming foreground service due to background restriction. + * * @see android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST */ public final void startForeground(int id, @NonNull Notification notification, diff --git a/core/java/android/app/ServiceStartNotAllowedException.java b/core/java/android/app/ServiceStartNotAllowedException.java new file mode 100644 index 000000000000..33285b2190eb --- /dev/null +++ b/core/java/android/app/ServiceStartNotAllowedException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.NonNull; + +/** + * Exception thrown when an app tries to start a {@link Service} when it's not allowed to do so. + */ +public abstract class ServiceStartNotAllowedException extends IllegalStateException { + ServiceStartNotAllowedException(@NonNull String message) { + super(message); + } + + /** + * Return either {@link ForegroundServiceStartNotAllowedException} or + * {@link BackgroundServiceStartNotAllowedException} + * @hide + */ + @NonNull + public static ServiceStartNotAllowedException newInstance(boolean foreground, + @NonNull String message) { + if (foreground) { + return new ForegroundServiceStartNotAllowedException(message); + } else { + return new BackgroundServiceStartNotAllowedException(message); + } + } +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 4284dc2117c5..e20f706c2c35 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3118,8 +3118,18 @@ public abstract class Context { * * @throws SecurityException If the caller does not have permission to access the service * or the service can not be found. - * @throws IllegalStateException If the application is in a state where the service - * can not be started (such as not in the foreground in a state when services are allowed). + * @throws IllegalStateException + * Before Android {@link android.os.Build.VERSION_CODES#S}, + * if the application is in a state where the service + * can not be started (such as not in the foreground in a state when services are allowed), + * {@link IllegalStateException} was thrown. + * @throws android.app.BackgroundServiceStartNotAllowedException + * On Android {@link android.os.Build.VERSION_CODES#S} and later, + * if the application is in a state where the service + * can not be started (such as not in the foreground in a state when services are allowed), + * {@link android.app.BackgroundServiceStartNotAllowedException} is thrown + * This excemption extends {@link IllegalStateException}, so apps can + * use {@code catch (IllegalStateException)} to catch both. * * @see #stopService * @see #bindService @@ -3150,7 +3160,8 @@ public abstract class Context { * @throws SecurityException If the caller does not have permission to access the service * or the service can not be found. * - * @throws IllegalStateException If the caller app's targeting API is + * @throws android.app.ForegroundServiceStartNotAllowedException + * If the caller app's targeting API is * {@link android.os.Build.VERSION_CODES#S} or later, and the foreground service is restricted * from start due to background restriction. * diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 5cfcd667632b..9198eb74d1f8 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -171,7 +171,7 @@ class IInputMethodWrapper extends IInputMethod.Stub SomeArgs args = (SomeArgs) msg.obj; try { inputMethod.initializeInternal((IBinder) args.arg1, msg.arg1, - (IInputMethodPrivilegedOperations) args.arg2); + (IInputMethodPrivilegedOperations) args.arg2, (int) args.arg3); } finally { args.recycle(); } @@ -280,9 +280,10 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void initializeInternal(IBinder token, int displayId, - IInputMethodPrivilegedOperations privOps) { + IInputMethodPrivilegedOperations privOps, int configChanges) { mCaller.executeOrSendMessage( - mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps)); + mCaller.obtainMessageIOOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps, + configChanges)); } @BinderThread diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 7e2be01feb01..03dd3067e64e 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -70,6 +70,7 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -131,6 +132,7 @@ import android.widget.TextView; import android.window.WindowMetricsHelper; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodPrivilegedOperations; @@ -513,6 +515,8 @@ public class InputMethodService extends AbstractInputMethodService { private boolean mIsAutomotive; private Handler mHandler; private boolean mImeSurfaceScheduledForRemoval; + private Configuration mLastKnownConfig; + private int mHandledConfigChanges; /** * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput} @@ -588,12 +592,14 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public final void initializeInternal(@NonNull IBinder token, int displayId, - IInputMethodPrivilegedOperations privilegedOperations) { + IInputMethodPrivilegedOperations privilegedOperations, + int configChanges) { if (InputMethodPrivilegedOperationsRegistry.isRegistered(token)) { Log.w(TAG, "The token has already registered, ignore this initialization."); return; } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal"); + mHandledConfigChanges = configChanges; mPrivOps.set(privilegedOperations); InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps); updateInputMethodDisplay(displayId); @@ -821,6 +827,9 @@ public class InputMethodService extends AbstractInputMethodService { setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); } final boolean isVisible = isInputViewShown(); + if (isVisible && getResources() != null) { + mLastKnownConfig = getResources().getConfiguration(); + } final boolean visibilityChanged = isVisible != wasVisible; if (resultReceiver != null) { resultReceiver.send(visibilityChanged @@ -1428,10 +1437,37 @@ public class InputMethodService extends AbstractInputMethodService { * state: {@link #onStartInput} if input is active, and * {@link #onCreateInputView} and {@link #onStartInputView} and related * appropriate functions if the UI is displayed. + * <p>Starting with {@link Build.VERSION_CODES#S}, IMEs can opt into handling configuration + * changes themselves instead of being restarted with + * {@link android.R.styleable#InputMethod_configChanges}. */ @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - resetStateForNewConfiguration(); + if (shouldImeRestartForConfig(newConfig)) { + resetStateForNewConfiguration(); + } + } + + /** + * @return {@code true} if {@link InputMethodService} needs to restart to handle + * .{@link #onConfigurationChanged(Configuration)} + */ + @VisibleForTesting + boolean shouldImeRestartForConfig(@NonNull Configuration newConfig) { + if (mLastKnownConfig == null) { + return true; + } + // If the new config is the same as the config this Service is already running with, + // then don't bother calling resetStateForNewConfiguration. + int diff = mLastKnownConfig.diffPublicOnly(newConfig); + if (diff != 0) { + // remove attrs not-relevant to IME service. + diff &= ActivityInfo.CONFIG_KEYBOARD_HIDDEN; + diff &= ActivityInfo.CONFIG_KEYBOARD; + diff &= ActivityInfo.CONFIG_NAVIGATION; + } + int unhandledDiff = (diff & ~mHandledConfigChanges); + return unhandledDiff != 0; } private void resetStateForNewConfiguration() { @@ -3181,7 +3217,17 @@ public class InputMethodService extends AbstractInputMethodService { requestHideSelf(InputMethodManager.HIDE_NOT_ALWAYS); } } - + + @VisibleForTesting + void setLastKnownConfig(@NonNull Configuration config) { + mLastKnownConfig = config; + } + + @VisibleForTesting + void setHandledConfigChanges(int configChanges) { + mHandledConfigChanges = configChanges; + } + void startExtractingText(boolean inputChanged) { final ExtractEditText eet = mExtractEditText; if (eet != null && getCurrentInputStarted() diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java index a078e0434867..73520e07d118 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -59,7 +59,6 @@ public final class IncrementalFileStorages { /** * Set up files and directories used in an installation session. Only used by Incremental. * All the files will be created in defaultStorage. - * TODO(b/133435829): code clean up * * @throws IllegalStateException the session is not an Incremental installation session. * @throws IOException if fails to setup files or directories. @@ -73,12 +72,10 @@ public final class IncrementalFileStorages { @Nullable IStorageHealthListener healthListener, @NonNull List<InstallationFileParcel> addedFiles, @NonNull PerUidReadTimeouts[] perUidReadTimeouts, - IPackageLoadingProgressCallback progressCallback) throws IOException { - // TODO(b/136132412): validity check if session should not be incremental + @Nullable IPackageLoadingProgressCallback progressCallback) throws IOException { IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService( Context.INCREMENTAL_SERVICE); if (incrementalManager == null) { - // TODO(b/146080380): add incremental-specific error code throw new IOException("Failed to obtain incrementalManager."); } @@ -89,7 +86,6 @@ public final class IncrementalFileStorages { try { result.addApkFile(file); } catch (IOException e) { - // TODO(b/146080380): add incremental-specific error code throw new IOException( "Failed to add file to IncFS: " + file.name + ", reason: ", e); } @@ -203,7 +199,6 @@ public final class IncrementalFileStorages { /** * Resets the states and unbinds storage instances for an installation session. - * TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept */ public void cleanUp() { if (mDefaultStorage == null) { @@ -211,8 +206,8 @@ public final class IncrementalFileStorages { } try { + mIncrementalManager.unregisterLoadingProgressCallbacks(mStageDir.getAbsolutePath()); mDefaultStorage.unBind(mStageDir.getAbsolutePath()); - mDefaultStorage.unregisterLoadingProgressListener(); } catch (IOException ignored) { } mDefaultStorage = null; diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 05899947c3df..cec6a1fb271d 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -342,7 +342,6 @@ public final class IncrementalManager { storage.unregisterLoadingProgressListener(); } - // TODO(b/165841827): handle reboot and app update public boolean registerCallback(@NonNull IncrementalStorage storage, @NonNull IPackageLoadingProgressCallback callback) { final int storageId = storage.getId(); @@ -364,30 +363,6 @@ public final class IncrementalManager { return storage.registerLoadingProgressListener(this); } - public boolean unregisterCallback(@NonNull IncrementalStorage storage, - @NonNull IPackageLoadingProgressCallback callback) { - final int storageId = storage.getId(); - final RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage; - synchronized (mCallbacks) { - callbacksForStorage = mCallbacks.get(storageId); - if (callbacksForStorage == null) { - // no callback has ever been registered on this storage - return false; - } - if (!callbacksForStorage.unregister(callback)) { - // the callback was not registered - return false; - } - if (callbacksForStorage.getRegisteredCallbackCount() > 0) { - // other callbacks are still listening on this storage - return true; - } - mCallbacks.delete(storageId); - } - // stop listening for this storage - return storage.unregisterLoadingProgressListener(); - } - @Override public void onStorageLoadingProgressChanged(int storageId, float progress) { final RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage; diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index de4554b9e624..6ade5e622eab 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -105,7 +105,7 @@ public interface InputMethod { */ @MainThread default void initializeInternal(IBinder token, int displayId, - IInputMethodPrivilegedOperations privilegedOperations) { + IInputMethodPrivilegedOperations privilegedOperations, int configChanges) { updateInputMethodDisplay(displayId); attachToken(token); } diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 5d876a6f62d3..25712f8bf9b8 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -18,19 +18,23 @@ package android.view.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; +import android.inputmethodservice.InputMethodService; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; @@ -60,6 +64,7 @@ import java.util.List; * @attr ref android.R.styleable#InputMethod_isDefault * @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod * @attr ref android.R.styleable#InputMethod_supportsInlineSuggestions + * @attr ref android.R.styleable#InputMethod_configChanges */ public final class InputMethodInfo implements Parcelable { static final String TAG = "InputMethodInfo"; @@ -118,6 +123,12 @@ public final class InputMethodInfo implements Parcelable { private final boolean mInlineSuggestionsEnabled; /** + * The flag for configurations IME assumes the responsibility for handling in + * {@link InputMethodService#onConfigurationChanged(Configuration)}}. + */ + private final int mHandledConfigChanges; + + /** * @param service the {@link ResolveInfo} corresponds in which the IME is implemented. * @return a unique ID to be returned by {@link #getId()}. We have used * {@link ComponentName#flattenToShortString()} for this purpose (and it is already @@ -203,6 +214,8 @@ public final class InputMethodInfo implements Parcelable { false); inlineSuggestionsEnabled = sa.getBoolean( com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions, false); + mHandledConfigChanges = sa.getInt( + com.android.internal.R.styleable.InputMethod_configChanges, 0); sa.recycle(); final int depth = parser.getDepth(); @@ -287,6 +300,7 @@ public final class InputMethodInfo implements Parcelable { mIsVrOnly = source.readBoolean(); mService = ResolveInfo.CREATOR.createFromParcel(source); mSubtypes = new InputMethodSubtypeArray(source); + mHandledConfigChanges = source.readInt(); mForceDefault = false; } @@ -298,7 +312,22 @@ public final class InputMethodInfo implements Parcelable { this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, settingsActivity, null /* subtypes */, 0 /* isDefaultResId */, false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, - false /* inlineSuggestionsEnabled */, false /* isVrOnly */); + false /* inlineSuggestionsEnabled */, false /* isVrOnly */, + 0 /* handledConfigChanges */); + } + + /** + * Temporary API for creating a built-in input method for test. + * @hide + */ + @TestApi + public InputMethodInfo(@NonNull String packageName, @NonNull String className, + @NonNull CharSequence label, @NonNull String settingsActivity, + int handledConfigChanges) { + this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, + settingsActivity, null /* subtypes */, 0 /* isDefaultResId */, + false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, + false /* inlineSuggestionsEnabled */, false /* isVrOnly */, handledConfigChanges); } /** @@ -310,7 +339,7 @@ public final class InputMethodInfo implements Parcelable { boolean forceDefault) { this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault, true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */, - false /* isVrOnly */); + false /* isVrOnly */, 0 /* handledconfigChanges */); } /** @@ -321,7 +350,8 @@ public final class InputMethodInfo implements Parcelable { List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault, boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) { this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault, - supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly); + supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly, + 0 /* handledConfigChanges */); } /** @@ -331,7 +361,7 @@ public final class InputMethodInfo implements Parcelable { public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault, boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled, - boolean isVrOnly) { + boolean isVrOnly, int handledConfigChanges) { final ServiceInfo si = ri.serviceInfo; mService = ri; mId = new ComponentName(si.packageName, si.name).flattenToShortString(); @@ -343,6 +373,7 @@ public final class InputMethodInfo implements Parcelable { mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; mInlineSuggestionsEnabled = inlineSuggestionsEnabled; mIsVrOnly = isVrOnly; + mHandledConfigChanges = handledConfigChanges; } private static ResolveInfo buildFakeResolveInfo(String packageName, String className, @@ -489,6 +520,17 @@ public final class InputMethodInfo implements Parcelable { } } + /** + * Returns the bit mask of kinds of configuration changes that this IME + * can handle itself (without being restarted by the system). + * + * @attr ref android.R.styleable#InputMethod_configChanges + */ + @ActivityInfo.Config + public int getConfigChanges() { + return mHandledConfigChanges; + } + public void dump(Printer pw, String prefix) { pw.println(prefix + "mId=" + mId + " mSettingsActivityName=" + mSettingsActivityName @@ -579,6 +621,7 @@ public final class InputMethodInfo implements Parcelable { dest.writeBoolean(mIsVrOnly); mService.writeToParcel(dest, flags); mSubtypes.writeToParcel(dest); + dest.writeInt(mHandledConfigChanges); } /** diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index c33637353984..8d82e33dc29f 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -35,7 +35,8 @@ import com.android.internal.view.InlineSuggestionsRequestInfo; * {@hide} */ oneway interface IInputMethod { - void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps); + void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps, + int configChanges); void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo, in IInlineSuggestionsRequestCallback cb); diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 69bb20cf9c1e..99f58ee1f8a6 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3564,6 +3564,16 @@ <attr name="__removed2" format="boolean" /> <!-- Specifies whether the IME supports showing inline suggestions. --> <attr name="supportsInlineSuggestions" format="boolean" /> + <!-- Specify one or more configuration changes that the IME will handle itself. If not + specified, the IME will be restarted if any of these configuration changes happen in + the system. Otherwise, the IME will remain running and its + {@link android.inputmethodservice.InputMethodService#onConfigurationChanged} + method is called with the new configuration. + <p>Note that all of these configuration changes can impact the + resource values seen by the application, so you will generally need + to re-retrieve all resources (including view layouts, drawables, etc) + to correctly handle any configuration change.--> + <attr name="configChanges" /> </declare-styleable> <!-- This is the subtype of InputMethod. Subtype can describe locales (for example, en_US and diff --git a/core/tests/coretests/src/android/inputmethodservice/InputMethodServiceTest.java b/core/tests/coretests/src/android/inputmethodservice/InputMethodServiceTest.java new file mode 100644 index 000000000000..890614982ba5 --- /dev/null +++ b/core/tests/coretests/src/android/inputmethodservice/InputMethodServiceTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + + +import static junit.framework.Assert.assertFalse; + +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.os.Build; + +import androidx.test.filters.SmallTest; +import androidx.test.rule.ServiceTestRule; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeoutException; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class InputMethodServiceTest { + private InputMethodService mService; + private Context mContext; + @Rule + public final ServiceTestRule serviceRule = new ServiceTestRule(); + + @Before + public void setUp() throws TimeoutException { + mContext = getInstrumentation().getContext(); + mService = new InputMethodService(); + } + + @Test + public void testShouldImeRestartForConfig() throws Exception { + // Make sure we preserve Pre-S behavior i.e. Service restarts. + mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.R; + Configuration config = mContext.getResources().getConfiguration(); + mService.setLastKnownConfig(config); + assertTrue("IME should restart for Pre-S", + mService.shouldImeRestartForConfig(config)); + + // IME shouldn't restart on targetSdk S+ (with no config changes). + mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.S; + assertFalse("IME shouldn't restart for S+", + mService.shouldImeRestartForConfig(config)); + + // Screen density changed but IME doesn't handle congfigChanges + config.densityDpi = 99; + assertTrue("IME should restart for unhandled configChanges", + mService.shouldImeRestartForConfig(config)); + + // opt-in IME to handle config changes. + mService.setHandledConfigChanges(ActivityInfo.CONFIG_DENSITY); + assertFalse("IME shouldn't restart for S+ since it handles configChanges", + mService.shouldImeRestartForConfig(config)); + } +} diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 6e458681bbab..be49e1f8c71e 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -170,7 +170,6 @@ <declare-styleable name="AlphaTintDrawableWrapper"> <attr name="android:tint" /> - <attr name="android:drawable" /> <attr name="android:alpha" /> </declare-styleable> @@ -191,9 +190,5 @@ <attr name="borderThickness" format="dimension" /> <attr name="borderColor" format="color" /> </declare-styleable> - - <declare-styleable name="RoundedCornerProgressDrawable"> - <attr name="android:drawable" /> - </declare-styleable> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java index b188acbf30f3..3df264421d75 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java @@ -23,6 +23,7 @@ import android.util.Log; import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.people.PeopleSpaceActivity; import com.android.systemui.statusbar.FeatureFlags; import javax.inject.Inject; @@ -54,6 +55,12 @@ public class PeopleSpaceWidgetEnabler extends SystemUI { ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); + mContext.getPackageManager().setComponentEnabledSetting( + new ComponentName(mContext, PeopleSpaceActivity.class), + showPeopleSpace + ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); } catch (Exception e) { Log.w(TAG, "Error enabling People Space widget:", e); } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java index a6aec3b7b1b7..0b40e225fb2b 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java @@ -17,8 +17,6 @@ package com.android.systemui.settings.brightness; import android.content.Context; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -29,10 +27,8 @@ import android.widget.SeekBar; import androidx.annotation.Nullable; import com.android.settingslib.RestrictedLockUtils; -import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.statusbar.policy.BrightnessMirrorController; -import com.android.systemui.util.RoundedCornerProgressDrawable; import com.android.systemui.util.ViewController; import javax.inject.Inject; @@ -274,9 +270,6 @@ public class BrightnessSlider private BrightnessSlider fromTree(ViewGroup root, boolean useMirror) { BrightnessSliderView v = root.requireViewById(R.id.brightness_slider); - // TODO(175026098) Workaround. Remove when b/175026098 is fixed - applyTheme(v); - return new BrightnessSlider(root, v, useMirror); } @@ -286,32 +279,5 @@ public class BrightnessSlider ? R.layout.quick_settings_brightness_dialog_thick : R.layout.quick_settings_brightness_dialog; } - - private LayerDrawable findProgressClippableDrawable(BrightnessSliderView v) { - SeekBar b = v.requireViewById(R.id.slider); - if (b.getProgressDrawable() instanceof LayerDrawable) { - Drawable progress = ((LayerDrawable) b.getProgressDrawable()) - .findDrawableByLayerId(com.android.internal.R.id.progress); - if (progress instanceof RoundedCornerProgressDrawable) { - Drawable inner = ((RoundedCornerProgressDrawable) progress).getDrawable(); - if (inner instanceof LayerDrawable) { - return (LayerDrawable) inner; - } - } - } - return null; - } - - private void applyTheme(BrightnessSliderView v) { - LayerDrawable layer = findProgressClippableDrawable(v); - if (layer != null) { - layer.findDrawableByLayerId(R.id.slider_foreground).setTintList( - Utils.getColorAttr(v.getContext(), - com.android.internal.R.attr.colorControlActivated)); - layer.findDrawableByLayerId(R.id.slider_icon).setTintList( - Utils.getColorAttr(v.getContext(), - com.android.internal.R.attr.colorBackground)); - } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java b/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java index 79a197d9d409..a22793b05070 100644 --- a/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java @@ -16,15 +16,18 @@ package com.android.systemui.util; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; import android.graphics.drawable.DrawableWrapper; +import android.graphics.drawable.InsetDrawable; import android.util.AttributeSet; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.systemui.R; import org.xmlpull.v1.XmlPullParser; @@ -45,13 +48,18 @@ import java.io.IOException; * @attr ref R.styleable#AlphaTintDrawableWrapper_tint * @attr ref R.styleable#AlphaTintDrawableWrapper_alpha */ -public class AlphaTintDrawableWrapper extends DrawableWrapper { +public class AlphaTintDrawableWrapper extends InsetDrawable { private ColorStateList mTint; private int[] mThemeAttrs; /** No-arg constructor used by drawable inflation. */ public AlphaTintDrawableWrapper() { - super(null); + super(null, 0); + } + + AlphaTintDrawableWrapper(Drawable drawable, int[] themeAttrs) { + super(drawable, 0); + mThemeAttrs = themeAttrs; } @Override @@ -74,7 +82,7 @@ public class AlphaTintDrawableWrapper extends DrawableWrapper { public void applyTheme(Theme t) { super.applyTheme(t); - if (mThemeAttrs != null) { + if (mThemeAttrs != null && t != null) { final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.AlphaTintDrawableWrapper); updateStateFromTypedArray(a); @@ -92,9 +100,6 @@ public class AlphaTintDrawableWrapper extends DrawableWrapper { } private void updateStateFromTypedArray(@NonNull TypedArray a) { - if (a.hasValue(R.styleable.AlphaTintDrawableWrapper_android_drawable)) { - setDrawable(a.getDrawable(R.styleable.AlphaTintDrawableWrapper_android_drawable)); - } if (a.hasValue(R.styleable.AlphaTintDrawableWrapper_android_tint)) { mTint = a.getColorStateList(R.styleable.AlphaTintDrawableWrapper_android_tint); } @@ -109,4 +114,57 @@ public class AlphaTintDrawableWrapper extends DrawableWrapper { getDrawable().mutate().setTintList(mTint); } } + + @Nullable + @Override + public ConstantState getConstantState() { + return new AlphaTintState(super.getConstantState(), mThemeAttrs, getAlpha(), mTint); + } + + static class AlphaTintState extends Drawable.ConstantState { + + private ConstantState mWrappedState; + private int[] mThemeAttrs; + private int mAlpha; + private ColorStateList mColorStateList; + + AlphaTintState( + ConstantState wrappedState, + int[] themeAttrs, + int alpha, + ColorStateList colorStateList + ) { + mWrappedState = wrappedState; + mThemeAttrs = themeAttrs; + mAlpha = alpha; + mColorStateList = colorStateList; + } + + @NonNull + @Override + public Drawable newDrawable() { + return newDrawable(null, null); + } + + @NonNull + @Override + public Drawable newDrawable(Resources res, Theme theme) { + DrawableWrapper wrapper = (DrawableWrapper) mWrappedState.newDrawable(res, theme); + AlphaTintDrawableWrapper alphaTintDrawableWrapper = + new AlphaTintDrawableWrapper(wrapper.getDrawable(), mThemeAttrs); + alphaTintDrawableWrapper.setTintList(mColorStateList); + alphaTintDrawableWrapper.setAlpha(mAlpha); + return alphaTintDrawableWrapper; + } + + @Override + public boolean canApplyTheme() { + return true; + } + + @Override + public int getChangingConfigurations() { + return mWrappedState.getChangingConfigurations(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt index 1af2c9f46373..6aadd1020bce 100644 --- a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt @@ -17,15 +17,12 @@ package com.android.systemui.util import android.content.res.Resources -import android.content.res.TypedArray import android.graphics.Canvas import android.graphics.Path import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.DrawableWrapper -import android.util.AttributeSet -import com.android.systemui.R -import org.xmlpull.v1.XmlPullParser +import android.graphics.drawable.InsetDrawable /** * [DrawableWrapper] to use in the progress of a slider. @@ -38,9 +35,9 @@ import org.xmlpull.v1.XmlPullParser * is meant to be smaller than the rounded corner. The background should have rounded corners that * are half of the height. */ -class RoundedCornerProgressDrawable(drawable: Drawable?) : DrawableWrapper(drawable) { - - constructor() : this(null) +class RoundedCornerProgressDrawable @JvmOverloads constructor( + drawable: Drawable? = null +) : InsetDrawable(drawable, 0) { companion object { private const val MAX_LEVEL = 10000 // Taken from Drawable @@ -52,35 +49,11 @@ class RoundedCornerProgressDrawable(drawable: Drawable?) : DrawableWrapper(drawa setClipPath(Rect()) } - override fun inflate( - r: Resources, - parser: XmlPullParser, - attrs: AttributeSet, - theme: Resources.Theme? - ) { - val a = obtainAttributes(r, theme, attrs, R.styleable.RoundedCornerProgressDrawable) - - // Inflation will advance the XmlPullParser and AttributeSet. - super.inflate(r, parser, attrs, theme) - - updateStateFromTypedArray(a) - if (drawable == null) { - throw IllegalStateException("${this::class.java.simpleName} needs a drawable") - } - a.recycle() - } - override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean { onLevelChange(level) return super.onLayoutDirectionChanged(layoutDirection) } - private fun updateStateFromTypedArray(a: TypedArray) { - if (a.hasValue(R.styleable.RoundedCornerProgressDrawable_android_drawable)) { - setDrawable(a.getDrawable(R.styleable.RoundedCornerProgressDrawable_android_drawable)) - } - } - override fun onBoundsChange(bounds: Rect) { setClipPath(bounds) super.onBoundsChange(bounds) @@ -115,4 +88,24 @@ class RoundedCornerProgressDrawable(drawable: Drawable?) : DrawableWrapper(drawa super.draw(canvas) canvas.restore() } + + override fun getConstantState(): ConstantState? { + // This should not be null as it was created with a state in the constructor. + return RoundedCornerState(super.getConstantState()!!) + } + + private class RoundedCornerState(private val wrappedState: ConstantState) : ConstantState() { + override fun newDrawable(): Drawable { + return newDrawable(null, null) + } + + override fun newDrawable(res: Resources?, theme: Resources.Theme?): Drawable { + val wrapper = wrappedState.newDrawable(res, theme) as DrawableWrapper + return RoundedCornerProgressDrawable(wrapper.drawable) + } + + override fun getChangingConfigurations(): Int { + return wrappedState.changingConfigurations + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java index 06806d0e6ab6..6a648bdf8cd4 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java @@ -347,6 +347,7 @@ public class ProximitySensor implements ThresholdSensor { public void check(long timeoutMs, Consumer<Boolean> callback) { if (!mSensor.isLoaded()) { callback.accept(null); + return; } mCallbacks.add(callback); if (!mRegistered.getAndSet(true)) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java index c5a197eef2d4..242fe9f5fffe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java @@ -16,6 +16,8 @@ package com.android.systemui.util.sensors; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -86,6 +88,29 @@ public class ProximityCheckTest extends SysuiTestCase { } @Test + public void testNotLoaded() { + mFakeProximitySensor.setSensorAvailable(false); + + assertThat(mTestableCallback.mLastResult).isNull(); + assertThat(mTestableCallback.mNumCalls).isEqualTo(0); + + mProximityCheck.check(100, mTestableCallback); + + assertThat(mTestableCallback.mLastResult).isNull(); + assertThat(mTestableCallback.mNumCalls).isEqualTo(1); + + mFakeProximitySensor.setSensorAvailable(true); + + mProximityCheck.check(100, mTestableCallback); + + mFakeProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 0)); + mFakeProximitySensor.alertListeners(); + + assertThat(mTestableCallback.mLastResult).isNotNull(); + assertThat(mTestableCallback.mNumCalls).isEqualTo(2); + } + + @Test public void testProxDoesntCancelOthers() { assertFalse(mFakeProximitySensor.isRegistered()); // We don't need our "other" listener to do anything. Just ensure our sensor is registered. diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 5550999b2405..2efc83c4af06 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -52,6 +52,7 @@ import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.BroadcastOptions; +import android.app.ForegroundServiceStartNotAllowedException; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.Notification; @@ -693,7 +694,7 @@ public final class ActiveServices { + "could not resolve client package " + callingPackage); } if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, aInfo.uid)) { - throw new IllegalStateException(msg); + throw new ForegroundServiceStartNotAllowedException(msg); } return null; } @@ -1778,7 +1779,7 @@ public final class ActiveServices { ignoreForeground = true; if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, r.appInfo.uid)) { - throw new IllegalStateException(msg); + throw new ForegroundServiceStartNotAllowedException(msg); } } } diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index cd17cfef2726..b3070b7cf1ba 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -16,6 +16,7 @@ package com.android.server.display; +import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayViewport; import android.os.IBinder; @@ -38,12 +39,14 @@ abstract class DisplayDevice { private final IBinder mDisplayToken; private final String mUniqueId; + protected DisplayDeviceConfig mDisplayDeviceConfig; // The display device does not manage these properties itself, they are set by // the display manager service. The display device shouldn't really be looking at these. private int mCurrentLayerStack = -1; private int mCurrentOrientation = -1; private Rect mCurrentLayerStackRect; private Rect mCurrentDisplayRect; + private final Context mContext; // The display device owns its surface, but it should only set it // within a transaction from performTraversalLocked. @@ -53,10 +56,13 @@ abstract class DisplayDevice { // Do not use for any other purpose. DisplayDeviceInfo mDebugLastLoggedDeviceInfo; - public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId) { + public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId, + Context context) { mDisplayAdapter = displayAdapter; mDisplayToken = displayToken; mUniqueId = uniqueId; + mDisplayDeviceConfig = null; + mContext = context; } /** @@ -74,7 +80,10 @@ abstract class DisplayDevice { * @return The DisplayDeviceConfig; {@code null} if not overridden. */ public DisplayDeviceConfig getDisplayDeviceConfig() { - return null; + if (mDisplayDeviceConfig == null) { + mDisplayDeviceConfig = loadDisplayDeviceConfig(); + } + return mDisplayDeviceConfig; } /** @@ -292,4 +301,8 @@ abstract class DisplayDevice { pw.println("mCurrentDisplayRect=" + mCurrentDisplayRect); pw.println("mCurrentSurface=" + mCurrentSurface); } + + private DisplayDeviceConfig loadDisplayDeviceConfig() { + return DisplayDeviceConfig.create(mContext, false); + } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 1b25427adf71..49328f1019c3 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -120,7 +120,20 @@ public class DisplayDeviceConfig { // If no config can be loaded from any ddc xml at all, // prepare a whole config using the global config.xml. // Guaranteed not null - if (isDefaultDisplay) { + return create(context, isDefaultDisplay); + } + + /** + * Creates an instance using global values since no display device config xml exists. + * Uses values from config or PowerManager. + * + * @param context + * @param useConfigXml + * @return A configuration instance. + */ + public static DisplayDeviceConfig create(Context context, boolean useConfigXml) { + DisplayDeviceConfig config; + if (useConfigXml) { config = getConfigFromGlobalXml(context); } else { config = getConfigFromPmValues(context); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index ed010441ceb7..a4e2c7001b27 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -40,7 +40,6 @@ import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; -import com.android.internal.os.BackgroundThread; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.lights.LightsManager; @@ -213,7 +212,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { private SurfaceControl.DisplayMode mActiveSfDisplayMode; private Spline mSystemBrightnessToNits; private Spline mNitsToHalBrightness; - private DisplayDeviceConfig mDisplayDeviceConfig; private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides = new DisplayEventReceiver.FrameRateOverride[0]; @@ -222,7 +220,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { SurfaceControl.StaticDisplayInfo staticDisplayInfo, SurfaceControl.DynamicDisplayInfo dynamicInfo, SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isDefaultDisplay) { - super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId); + super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId, + getContext()); mPhysicalDisplayId = physicalDisplayId; mIsDefaultDisplay = isDefaultDisplay; updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs); @@ -232,9 +231,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken); mGameContentTypeSupported = SurfaceControl.getGameContentTypeSupport(displayToken); mDisplayDeviceConfig = null; - // Defer configuration file loading - BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage( - LocalDisplayDevice::loadDisplayConfiguration, this)); } @Override @@ -413,6 +409,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { @Override public DisplayDeviceConfig getDisplayDeviceConfig() { + if (mDisplayDeviceConfig == null) { + loadDisplayConfiguration(); + } return mDisplayDeviceConfig; } diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index 69943e3904ed..330379cf58eb 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -281,7 +281,8 @@ final class OverlayDisplayAdapter extends DisplayAdapter { List<OverlayMode> modes, int activeMode, int defaultMode, float refreshRate, long presentationDeadlineNanos, OverlayFlags flags, int state, SurfaceTexture surfaceTexture, int number) { - super(OverlayDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + number); + super(OverlayDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + number, + getContext()); mName = name; mRefreshRate = refreshRate; mDisplayPresentationDeadlineNanos = presentationDeadlineNanos; diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index ff4717b7131b..52a810bd8caa 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -236,7 +236,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { int ownerUid, String ownerPackageName, Surface surface, int flags, Callback callback, String uniqueId, int uniqueIndex, VirtualDisplayConfig virtualDisplayConfig) { - super(VirtualDisplayAdapter.this, displayToken, uniqueId); + super(VirtualDisplayAdapter.this, displayToken, uniqueId, getContext()); mAppToken = appToken; mOwnerUid = ownerUid; mOwnerPackageName = ownerPackageName; diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index 57323170b327..d2baaf2228a1 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -598,7 +598,8 @@ final class WifiDisplayAdapter extends DisplayAdapter { public WifiDisplayDevice(IBinder displayToken, String name, int width, int height, float refreshRate, int flags, String address, Surface surface) { - super(WifiDisplayAdapter.this, displayToken, DISPLAY_NAME_PREFIX + address); + super(WifiDisplayAdapter.this, displayToken, DISPLAY_NAME_PREFIX + address, + getContext()); mName = name; mWidth = width; mHeight = height; diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java index 86a8e36d748d..983b6b5bceb4 100644 --- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java @@ -100,9 +100,9 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { @Override public boolean start() { - if (mIsCec20) { - sendSetStreamPath(); - } + // Wake-up on <Set Stream Path> was not mandatory before CEC 2.0. + // The message is re-sent at the end of the action for devices that don't support 2.0. + sendSetStreamPath(); int targetPowerStatus = localDevice().mService.getHdmiCecNetwork() .getCecDeviceInfo(getTargetAddress()).getDevicePowerStatus(); if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 0754df0e6b9f..1e6658930840 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2620,8 +2620,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); // Dispatch display id for InputMethodService to update context display. - executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( - MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken)); + executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOOO( + MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken, + mMethodMap.get(mCurMethodId).getConfigChanges())); scheduleNotifyImeUidToAudioService(mCurMethodUid); if (mCurClient != null) { clearClientSessionLocked(mCurClient); @@ -4466,7 +4467,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } final IBinder token = (IBinder) args.arg2; ((IInputMethod) args.arg1).initializeInternal(token, msg.arg1, - new InputMethodPrivilegedOperationsImpl(this, token)); + new InputMethodPrivilegedOperationsImpl(this, token), + (int) args.arg3); } catch (RemoteException e) { } args.recycle(); diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index f078242a659f..4500bbcd250f 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -39,6 +39,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.util.XmlUtils; +import com.android.server.pm.PackageManagerService; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -465,6 +466,7 @@ public class SnoozeHelper { return PendingIntent.getBroadcast(mContext, REQUEST_CODE_REPOST, new Intent(REPOST_ACTION) + .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME) .setData(new Uri.Builder().scheme(REPOST_SCHEME).appendPath(key).build()) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) .putExtra(EXTRA_KEY, key) diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 9e2ca9d32315..7bf3c5c1f4c9 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1207,8 +1207,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private void computeProgressLocked(boolean forcePublish) { - mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f) - + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f); + if (!mCommitted) { + mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f) + + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f); + } else { + // For incremental installs, continue publishing the install progress during committing. + mProgress = mIncrementalProgress; + } // Only publish when meaningful change if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) { @@ -1944,9 +1949,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); } - // Client staging is fully done at this point - mClientProgress = 1f; - computeProgressLocked(true); + if (!isIncrementalInstallation()) { + // For non-incremental installs, client staging is fully done at this point + mClientProgress = 1f; + computeProgressLocked(true); + } // This ongoing commit should keep session active, even though client // will probably close their end. @@ -3804,6 +3811,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public void onPackageLoadingProgressChanged(float progress) { synchronized (mLock) { mIncrementalProgress = progress; + computeProgressLocked(true); } } }); diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 1c55072cab2c..bc86d1d39b1c 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -993,7 +993,7 @@ public class DisplayManagerServiceTest { private DisplayDeviceInfo mDisplayDeviceInfo; FakeDisplayDevice() { - super(null, null, ""); + super(null, null, "", mContext); } public void setDisplayDeviceInfo(DisplayDeviceInfo displayDeviceInfo) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java index f49cbca12fd8..9bf95c0edcdb 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java @@ -221,7 +221,8 @@ public class DeviceSelectActionTest { "testDeviceSelect"); action.start(); mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_ON); mTestLooper.dispatchAll(); @@ -238,12 +239,15 @@ public class DeviceSelectActionTest { DeviceSelectAction action = createDeviceSelectAction(actionTimer, callback, /*isCec20=*/false); action.start(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_STANDBY); mTestLooper.dispatchAll(); HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed( ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER); assertThat(mNativeWrapper.getResultMessages()).contains(userControlPressed); + mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); action.processCommand(REPORT_POWER_STATUS_ON); @@ -261,6 +265,9 @@ public class DeviceSelectActionTest { DeviceSelectAction action = createDeviceSelectAction(actionTimer, callback, /*isCec20=*/false); action.start(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_STANDBY); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); @@ -285,6 +292,9 @@ public class DeviceSelectActionTest { DeviceSelectAction action = createDeviceSelectAction(actionTimer, callback, /*isCec20=*/false); action.start(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_STANDBY); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); @@ -330,8 +340,11 @@ public class DeviceSelectActionTest { action.start(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_ON); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -354,9 +367,12 @@ public class DeviceSelectActionTest { HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed( ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(userControlPressed); + mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); action.processCommand(REPORT_POWER_STATUS_ON); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java index 35b224a24061..2ae2ef7162a5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -51,6 +51,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.FastXmlSerializer; import com.android.server.UiServiceTestCase; +import com.android.server.pm.PackageManagerService; import org.junit.Before; import org.junit.Test; @@ -259,6 +260,17 @@ public class SnoozeHelperTest extends UiServiceTestCase { } @Test + public void testSnoozeSentToAndroid() throws Exception { + NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); + mSnoozeHelper.snooze(r, 1000); + ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class); + verify(mAm, times(1)).setExactAndAllowWhileIdle( + anyInt(), anyLong(), captor.capture()); + assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, + captor.getValue().getIntent().getPackage()); + } + + @Test public void testSnooze() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); mSnoozeHelper.snooze(r, (String) null); |