diff options
141 files changed, 2936 insertions, 1511 deletions
diff --git a/Android.bp b/Android.bp index 27b59bc62eef..970d66b6144d 100644 --- a/Android.bp +++ b/Android.bp @@ -792,9 +792,10 @@ java_library_host { name: "platformprotos", srcs: [ "cmds/am/proto/instrumentation_data.proto", + "cmds/statsd/src/**/*.proto", "core/proto/**/*.proto", "libs/incident/proto/**/*.proto", - "cmds/statsd/src/**/*.proto", + "proto/src/stats_enums.proto", ], proto: { include_dirs: ["external/protobuf/src"], @@ -832,6 +833,7 @@ java_library { srcs: [ "core/proto/**/*.proto", "libs/incident/proto/android/os/**/*.proto", + "proto/src/stats_enums.proto", ], // Protos have lots of MissingOverride and similar. errorprone: { @@ -857,6 +859,7 @@ cc_library { srcs: [ "core/proto/**/*.proto", "libs/incident/**/*.proto", + "proto/src/stats_enums.proto", ], target: { diff --git a/api/current.txt b/api/current.txt index ad7d31d3856a..f750e33a9610 100644..100755 --- a/api/current.txt +++ b/api/current.txt @@ -41867,6 +41867,9 @@ package android.telephony { field public static final java.lang.String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool"; field public static final java.lang.String KEY_APN_EXPAND_BOOL = "apn_expand_bool"; field public static final java.lang.String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool"; + field public static final java.lang.String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool"; + field public static final java.lang.String KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL = "call_barring_supports_password_change_bool"; + field public static final java.lang.String KEY_CALL_BARRING_VISIBILITY_BOOL = "call_barring_visibility_bool"; field public static final java.lang.String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array"; field public static final java.lang.String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool"; field public static final java.lang.String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings"; @@ -44486,6 +44489,11 @@ package android.text.style { method public abstract void chooseHeight(java.lang.CharSequence, int, int, int, int, android.graphics.Paint.FontMetricsInt); } + public static class LineHeightSpan.Standard implements android.text.style.LineHeightSpan { + ctor public LineHeightSpan.Standard(int); + method public void chooseHeight(java.lang.CharSequence, int, int, int, int, android.graphics.Paint.FontMetricsInt); + } + public static abstract interface LineHeightSpan.WithDensity implements android.text.style.LineHeightSpan { method public abstract void chooseHeight(java.lang.CharSequence, int, int, int, int, android.graphics.Paint.FontMetricsInt, android.text.TextPaint); } @@ -45429,6 +45437,7 @@ package android.util { field public static final int DENSITY_420 = 420; // 0x1a4 field public static final int DENSITY_440 = 440; // 0x1b8 field public static final int DENSITY_560 = 560; // 0x230 + field public static final int DENSITY_600 = 600; // 0x258 field public static final int DENSITY_DEFAULT = 160; // 0xa0 field public static final int DENSITY_DEVICE_STABLE; field public static final int DENSITY_HIGH = 240; // 0xf0 diff --git a/api/system-current.txt b/api/system-current.txt index 1d5f586ab5e9..afce431d49ec 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -474,9 +474,6 @@ package android.app { method public android.app.Vr2dDisplayProperties.Builder setEnabled(boolean); } - public static abstract class Vr2dDisplayProperties.Vr2dDisplayFlag implements java.lang.annotation.Annotation { - } - public class VrManager { method public int getVr2dDisplayId(); method public boolean isPersistentVrModeEnabled(); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 97891b0929b4..3c9f7eee10b6 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -29,6 +29,7 @@ import "frameworks/base/core/proto/android/server/enums.proto"; import "frameworks/base/core/proto/android/telecomm/enums.proto"; import "frameworks/base/core/proto/android/telephony/enums.proto"; import "frameworks/base/core/proto/android/view/enums.proto"; +import "frameworks/base/proto/src/stats_enums.proto"; /** * The master atom class. This message defines all of the available @@ -1770,7 +1771,7 @@ message GenericAtom { optional int32 uid = 1 [(is_uid) = true]; // An event_id indicates the type of event. - optional int32 event_id = 2; + optional android.os.statsd.EventType event_id = 2; } ////////////////////////////////////////////////////////////////////// diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index ec4c4dbaadc7..9b134208cb80 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -1151,7 +1151,9 @@ public class AlarmManager { public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(AlarmClockInfoProto.TRIGGER_TIME_MS, mTriggerTime); - mShowIntent.writeToProto(proto, AlarmClockInfoProto.SHOW_INTENT); + if (mShowIntent != null) { + mShowIntent.writeToProto(proto, AlarmClockInfoProto.SHOW_INTENT); + } proto.end(token); } } diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl index 4517446c3068..d3d7c68d0992 100644 --- a/core/java/android/app/IBackupAgent.aidl +++ b/core/java/android/app/IBackupAgent.aidl @@ -48,8 +48,7 @@ oneway interface IBackupAgent { * be echoed back to the backup service binder once the new * data has been written to the data and newState files. * - * @param callbackBinder Binder on which to indicate operation completion, - * passed here as a convenience to the agent. + * @param callbackBinder Binder on which to indicate operation completion. * * @param transportFlags Flags with additional information about the transport. */ @@ -133,8 +132,9 @@ oneway interface IBackupAgent { * Could be less than total backup size if backup process was interrupted * before finish of processing all backup data. * @param quotaBytes Current amount of backup data that is allowed for the app. + * @param callbackBinder Binder on which to indicate operation completion. */ - void doQuotaExceeded(long backupDataBytes, long quotaBytes); + void doQuotaExceeded(long backupDataBytes, long quotaBytes, IBackupCallback callbackBinder); /** * Restore a single "file" to the application. The file was typically obtained from diff --git a/core/java/android/app/Vr2dDisplayProperties.java b/core/java/android/app/Vr2dDisplayProperties.java index 2fd82b2d29e2..17521319aaf8 100644 --- a/core/java/android/app/Vr2dDisplayProperties.java +++ b/core/java/android/app/Vr2dDisplayProperties.java @@ -35,6 +35,7 @@ public final class Vr2dDisplayProperties implements Parcelable { public static final int FLAG_VIRTUAL_DISPLAY_ENABLED = 1; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({ FLAG_VIRTUAL_DISPLAY_ENABLED diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 62332d605145..df27d583d361 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -980,7 +980,7 @@ public abstract class BackupAgent extends ContextWrapper { try { callbackBinder.operationComplete(result); } catch (RemoteException e) { - // we'll time out anyway, so we're safe + // We will time out anyway. } // Don't close the fd out from under the system service if this was local @@ -1162,10 +1162,16 @@ public abstract class BackupAgent extends ContextWrapper { } @Override - public void doQuotaExceeded(long backupDataBytes, long quotaBytes) { + public void doQuotaExceeded( + long backupDataBytes, + long quotaBytes, + IBackupCallback callbackBinder) { long ident = Binder.clearCallingIdentity(); + + long result = RESULT_ERROR; try { BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes); + result = RESULT_SUCCESS; } catch (Exception e) { Log.d(TAG, "onQuotaExceeded(" + BackupAgent.this.getClass().getName() + ") threw", e); @@ -1173,6 +1179,12 @@ public abstract class BackupAgent extends ContextWrapper { } finally { waitForSharedPrefs(); Binder.restoreCallingIdentity(ident); + + try { + callbackBinder.operationComplete(result); + } catch (RemoteException e) { + // We will time out anyway. + } } } } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index f7aea978f5d1..57ec17842ff1 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1096,7 +1096,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration protoOutputStream.write(FONT_SCALE, fontScale); protoOutputStream.write(MCC, mcc); protoOutputStream.write(MNC, mnc); - mLocaleList.writeToProto(protoOutputStream, LOCALES); + if (mLocaleList != null) { + mLocaleList.writeToProto(protoOutputStream, LOCALES); + } protoOutputStream.write(SCREEN_LAYOUT, screenLayout); protoOutputStream.write(COLOR_MODE, colorMode); protoOutputStream.write(TOUCHSCREEN, touchscreen); @@ -1111,7 +1113,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration protoOutputStream.write(SCREEN_HEIGHT_DP, screenHeightDp); protoOutputStream.write(SMALLEST_SCREEN_WIDTH_DP, smallestScreenWidthDp); protoOutputStream.write(DENSITY_DPI, densityDpi); - windowConfiguration.writeToProto(protoOutputStream, WINDOW_CONFIGURATION); + if (windowConfiguration != null) { + windowConfiguration.writeToProto(protoOutputStream, WINDOW_CONFIGURATION); + } protoOutputStream.end(token); } diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java index 59195dc903f7..dbb25276dd97 100644 --- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -30,6 +30,21 @@ import java.util.concurrent.Executor; public interface BiometricAuthenticator { /** + * @hide + */ + int TYPE_FINGERPRINT = 1; + + /** + * @hide + */ + int TYPE_IRIS = 2; + + /** + * @hide + */ + int TYPE_FACE = 3; + + /** * Container for biometric data * @hide */ @@ -196,6 +211,22 @@ public interface BiometricAuthenticator { } /** + * @param acquireInfo + * @param vendorCode + * @return the help string associated with this code + */ + default String getAcquiredString(int acquireInfo, int vendorCode) { + throw new UnsupportedOperationException("Stub!"); + } + + /** + * @return one of {@link #TYPE_FINGERPRINT} {@link #TYPE_IRIS} or {@link #TYPE_FACE} + */ + default int getType() { + throw new UnsupportedOperationException("Stub!"); + } + + /** * This call warms up the hardware and starts scanning for valid biometrics. It terminates * when {@link AuthenticationCallback#onAuthenticationError(int, * CharSequence)} is called or when {@link AuthenticationCallback#onAuthenticationSucceeded( diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index b3b962f0aca8..0f83c8bb5e67 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -552,6 +552,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan /** * @hide */ + @Override public String getAcquiredString(int acquireInfo, int vendorCode) { switch (acquireInfo) { case FACE_ACQUIRED_GOOD: @@ -591,6 +592,14 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** + * @hide + */ + @Override + public int getType() { + return TYPE_FACE; + } + + /** * Used so BiometricPrompt can map the face ones onto existing public constants. * @hide */ diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 44b8faf0509c..b380a2e08c8a 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -979,6 +979,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * @hide */ + @Override public String getAcquiredString(int acquireInfo, int vendorCode) { switch (acquireInfo) { case FINGERPRINT_ACQUIRED_GOOD: @@ -1010,6 +1011,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing return null; } + /** + * @hide + */ + @Override + public int getType() { + return TYPE_FINGERPRINT; + } + private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() { @Override // binder call diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 46671b237203..9295bb7c021c 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -62,7 +62,6 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; -import android.view.WindowManager.BadTokenException; import android.view.animation.AnimationUtils; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CursorAnchorInfo; @@ -354,7 +353,6 @@ public class InputMethodService extends AbstractInputMethodService { SoftInputWindow mWindow; boolean mInitialized; boolean mWindowCreated; - boolean mWindowAdded; boolean mWindowVisible; boolean mWindowWasVisible; boolean mInShowWindow; @@ -559,16 +557,7 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "showSoftInput()"); boolean wasVis = isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { - try { - showWindow(true); - } catch (BadTokenException e) { - // We have ignored BadTokenException here since Jelly Bean MR-2 (API Level 18). - // We could ignore BadTokenException in InputMethodService#showWindow() instead, - // but it may break assumptions for those who override #showWindow() that we can - // detect errors in #showWindow() by checking BadTokenException. - // TODO: Investigate its feasibility. Update JavaDoc of #showWindow() of - // whether it's OK to override #showWindow() or not. - } + showWindow(true); } clearInsetOfPreviousIme(); // If user uses hard keyboard, IME button should always be shown. @@ -986,13 +975,7 @@ public class InputMethodService extends AbstractInputMethodService { mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( mInsetsComputer); doFinishInput(); - if (mWindowAdded) { - // Disable exit animation for the current IME window - // to avoid the race condition between the exit and enter animations - // when the current IME is being switched to another one. - mWindow.getWindow().setWindowAnimations(0); - mWindow.dismiss(); - } + mWindow.dismissForDestroyIfNecessary(); if (mSettingsObserver != null) { mSettingsObserver.unregister(); mSettingsObserver = null; @@ -1778,7 +1761,6 @@ public class InputMethodService extends AbstractInputMethodService { public void showWindow(boolean showInput) { if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput + " mShowInputRequested=" + mShowInputRequested - + " mWindowAdded=" + mWindowAdded + " mWindowCreated=" + mWindowCreated + " mWindowVisible=" + mWindowVisible + " mInputStarted=" + mInputStarted @@ -1788,27 +1770,12 @@ public class InputMethodService extends AbstractInputMethodService { Log.w(TAG, "Re-entrance in to showWindow"); return; } - - try { - mWindowWasVisible = mWindowVisible; - mInShowWindow = true; - showWindowInner(showInput); - } catch (BadTokenException e) { - // BadTokenException is a normal consequence in certain situations, e.g., swapping IMEs - // while there is a DO_SHOW_SOFT_INPUT message in the IIMethodWrapper queue. - if (DEBUG) Log.v(TAG, "BadTokenException: IME is done."); - mWindowVisible = false; - mWindowAdded = false; - // Rethrow the exception to preserve the existing behavior. Some IMEs may have directly - // called this method and relied on this exception for some clean-up tasks. - // TODO: Give developers a clear guideline of whether it's OK to call this method or - // InputMethodService#requestShowSelf(int) should always be used instead. - throw e; - } finally { - // TODO: Is it OK to set true when we get BadTokenException? - mWindowWasVisible = true; - mInShowWindow = false; - } + + mWindowWasVisible = mWindowVisible; + mInShowWindow = true; + showWindowInner(showInput); + mWindowWasVisible = true; + mInShowWindow = false; } void showWindowInner(boolean showInput) { @@ -1825,9 +1792,8 @@ public class InputMethodService extends AbstractInputMethodService { initialize(); updateFullscreenMode(); updateInputViewShown(); - - if (!mWindowAdded || !mWindowCreated) { - mWindowAdded = true; + + if (!mWindowCreated) { mWindowCreated = true; initialize(); if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView"); @@ -2852,8 +2818,7 @@ public class InputMethodService extends AbstractInputMethodService { @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { final Printer p = new PrintWriterPrinter(fout); p.println("Input method service state for " + this + ":"); - p.println(" mWindowCreated=" + mWindowCreated - + " mWindowAdded=" + mWindowAdded); + p.println(" mWindowCreated=" + mWindowCreated); p.println(" mWindowVisible=" + mWindowVisible + " mWindowWasVisible=" + mWindowWasVisible + " mInShowWindow=" + mInShowWindow); diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index 795117e493b6..b4b88871d251 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -16,15 +16,22 @@ package android.inputmethodservice; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.app.Dialog; import android.content.Context; import android.graphics.Rect; +import android.os.Debug; import android.os.IBinder; +import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.WindowManager; +import java.lang.annotation.Retention; + /** * A SoftInputWindow is a Dialog that is intended to be used for a top-level input * method window. It will be displayed along the edge of the screen, moving @@ -33,6 +40,9 @@ import android.view.WindowManager; * @hide */ public class SoftInputWindow extends Dialog { + private static final boolean DEBUG = false; + private static final String TAG = "SoftInputWindow"; + final String mName; final Callback mCallback; final KeyEvent.Callback mKeyEventCallback; @@ -42,16 +52,65 @@ public class SoftInputWindow extends Dialog { final boolean mTakesFocus; private final Rect mBounds = new Rect(); + @Retention(SOURCE) + @IntDef(value = {SoftInputWindowState.TOKEN_PENDING, SoftInputWindowState.TOKEN_SET, + SoftInputWindowState.SHOWN_AT_LEAST_ONCE, SoftInputWindowState.REJECTED_AT_LEAST_ONCE}) + private @interface SoftInputWindowState { + /** + * The window token is not set yet. + */ + int TOKEN_PENDING = 0; + /** + * The window token was set, but the window is not shown yet. + */ + int TOKEN_SET = 1; + /** + * The window was shown at least once. + */ + int SHOWN_AT_LEAST_ONCE = 2; + /** + * {@link android.view.WindowManager.BadTokenException} was sent when calling + * {@link Dialog#show()} at least once. + */ + int REJECTED_AT_LEAST_ONCE = 3; + /** + * The window is considered destroyed. Any incoming request should be ignored. + */ + int DESTROYED = 4; + } + + @SoftInputWindowState + private int mWindowState = SoftInputWindowState.TOKEN_PENDING; + public interface Callback { public void onBackPressed(); } public void setToken(IBinder token) { - WindowManager.LayoutParams lp = getWindow().getAttributes(); - lp.token = token; - getWindow().setAttributes(lp); + switch (mWindowState) { + case SoftInputWindowState.TOKEN_PENDING: + // Normal scenario. Nothing to worry about. + WindowManager.LayoutParams lp = getWindow().getAttributes(); + lp.token = token; + getWindow().setAttributes(lp); + updateWindowState(SoftInputWindowState.TOKEN_SET); + return; + case SoftInputWindowState.TOKEN_SET: + case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: + case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: + throw new IllegalStateException("setToken can be called only once"); + case SoftInputWindowState.DESTROYED: + // Just ignore. Since there are multiple event queues from the token is issued + // in the system server to the timing when it arrives here, it can be delivered + // after the is already destroyed. No one should be blamed because of such an + // unfortunate but possible scenario. + Log.i(TAG, "Ignoring setToken() because window is already destroyed."); + return; + default: + throw new IllegalStateException("Unexpected state=" + mWindowState); + } } - + /** * Create a SoftInputWindow that uses a custom style. * @@ -190,4 +249,109 @@ public class SoftInputWindow extends Dialog { getWindow().setFlags(windowSetFlags, windowModFlags); } + + @Override + public final void show() { + switch (mWindowState) { + case SoftInputWindowState.TOKEN_PENDING: + throw new IllegalStateException("Window token is not set yet."); + case SoftInputWindowState.TOKEN_SET: + case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: + // Normal scenario. Nothing to worry about. + try { + super.show(); + updateWindowState(SoftInputWindowState.SHOWN_AT_LEAST_ONCE); + } catch (WindowManager.BadTokenException e) { + // Just ignore this exception. Since show() can be requested from other + // components such as the system and there could be multiple event queues before + // the request finally arrives here, the system may have already invalidated the + // window token attached to our window. In such a scenario, receiving + // BadTokenException here is an expected behavior. We just ignore it and update + // the state so that we do not touch this window later. + Log.i(TAG, "Probably the IME window token is already invalidated." + + " show() does nothing."); + updateWindowState(SoftInputWindowState.REJECTED_AT_LEAST_ONCE); + } + return; + case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: + // Just ignore. In general we cannot completely avoid this kind of race condition. + Log.i(TAG, "Not trying to call show() because it was already rejected once."); + return; + case SoftInputWindowState.DESTROYED: + // Just ignore. In general we cannot completely avoid this kind of race condition. + Log.i(TAG, "Ignoring show() because the window is already destroyed."); + return; + default: + throw new IllegalStateException("Unexpected state=" + mWindowState); + } + } + + final void dismissForDestroyIfNecessary() { + switch (mWindowState) { + case SoftInputWindowState.TOKEN_PENDING: + case SoftInputWindowState.TOKEN_SET: + // nothing to do because the window has never been shown. + updateWindowState(SoftInputWindowState.DESTROYED); + return; + case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: + // Disable exit animation for the current IME window + // to avoid the race condition between the exit and enter animations + // when the current IME is being switched to another one. + try { + getWindow().setWindowAnimations(0); + dismiss(); + } catch (WindowManager.BadTokenException e) { + // Just ignore this exception. Since show() can be requested from other + // components such as the system and there could be multiple event queues before + // the request finally arrives here, the system may have already invalidated the + // window token attached to our window. In such a scenario, receiving + // BadTokenException here is an expected behavior. We just ignore it and update + // the state so that we do not touch this window later. + Log.i(TAG, "Probably the IME window token is already invalidated. " + + "No need to dismiss it."); + } + // Either way, consider that the window is destroyed. + updateWindowState(SoftInputWindowState.DESTROYED); + return; + case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: + // Just ignore. In general we cannot completely avoid this kind of race condition. + Log.i(TAG, + "Not trying to dismiss the window because it is most likely unnecessary."); + // Anyway, consider that the window is destroyed. + updateWindowState(SoftInputWindowState.DESTROYED); + return; + case SoftInputWindowState.DESTROYED: + throw new IllegalStateException( + "dismissForDestroyIfNecessary can be called only once"); + default: + throw new IllegalStateException("Unexpected state=" + mWindowState); + } + } + + private void updateWindowState(@SoftInputWindowState int newState) { + if (DEBUG) { + if (mWindowState != newState) { + Log.d(TAG, "WindowState: " + stateToString(mWindowState) + " -> " + + stateToString(newState) + " @ " + Debug.getCaller()); + } + } + mWindowState = newState; + } + + private static String stateToString(@SoftInputWindowState int state) { + switch (state) { + case SoftInputWindowState.TOKEN_PENDING: + return "TOKEN_PENDING"; + case SoftInputWindowState.TOKEN_SET: + return "TOKEN_SET"; + case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: + return "SHOWN_AT_LEAST_ONCE"; + case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: + return "REJECTED_AT_LEAST_ONCE"; + case SoftInputWindowState.DESTROYED: + return "DESTROYED"; + default: + throw new IllegalStateException("Unknown state=" + state); + } + } } diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index f17e0f026fda..684a8ee43c87 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -386,7 +386,9 @@ public final class Looper { final long looperToken = proto.start(fieldId); proto.write(LooperProto.THREAD_NAME, mThread.getName()); proto.write(LooperProto.THREAD_ID, mThread.getId()); - mQueue.writeToProto(proto, LooperProto.QUEUE); + if (mQueue != null) { + mQueue.writeToProto(proto, LooperProto.QUEUE); + } proto.end(looperToken); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 28467303df14..febdb83f0af3 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -45,6 +45,7 @@ import android.util.Log; import android.util.MergedConfiguration; import android.view.Display; import android.view.DisplayCutout; +import android.view.DisplayInfo; import android.view.Gravity; import android.view.IWindowSession; import android.view.InputChannel; @@ -163,7 +164,8 @@ public abstract class WallpaperService extends Service { int mType; int mCurWidth; int mCurHeight; - int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_SCALED; int mWindowPrivateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS; int mCurWindowFlags = mWindowFlags; @@ -763,9 +765,19 @@ public abstract class WallpaperService extends Service { mLayout.x = 0; mLayout.y = 0; - mLayout.width = myWidth; - mLayout.height = myHeight; - + + if (!fixedSize) { + mLayout.width = myWidth; + mLayout.height = myHeight; + } else { + // Force the wallpaper to cover the screen in both dimensions + // only internal implementations like ImageWallpaper + DisplayInfo displayInfo = new DisplayInfo(); + mDisplay.getDisplayInfo(displayInfo); + mLayout.width = Math.max(displayInfo.logicalWidth, myWidth); + mLayout.height = Math.max(displayInfo.logicalHeight, myHeight); + } + mLayout.format = mFormat; mCurWindowFlags = mWindowFlags; @@ -773,7 +785,8 @@ public abstract class WallpaperService extends Service { | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_SCALED; mCurWindowPrivateFlags = mWindowPrivateFlags; mLayout.privateFlags = mWindowPrivateFlags; @@ -847,6 +860,9 @@ public abstract class WallpaperService extends Service { mStableInsets.bottom += padding.bottom; mDisplayCutout.set(mDisplayCutout.get().inset(-padding.left, -padding.top, -padding.right, -padding.bottom)); + } else { + w = myWidth; + h = myHeight; } if (mCurWidth != w) { diff --git a/core/java/android/text/style/LineHeightSpan.java b/core/java/android/text/style/LineHeightSpan.java index 50ee5f387595..2742ae0416a2 100644 --- a/core/java/android/text/style/LineHeightSpan.java +++ b/core/java/android/text/style/LineHeightSpan.java @@ -16,11 +16,16 @@ package android.text.style; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Px; import android.graphics.Paint; import android.text.TextPaint; +import com.android.internal.util.Preconditions; + /** - * The classes that affect the height of the line should implement this interface. + * The classes that affect the line height of paragraph should implement this interface. */ public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan { /** @@ -38,8 +43,8 @@ public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan { Paint.FontMetricsInt fm); /** - * The classes that affect the height of the line with respect to density, should implement this - * interface. + * The classes that affect the line height of paragraph with respect to density, + * should implement this interface. */ public interface WithDensity extends LineHeightSpan { @@ -57,4 +62,38 @@ public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan { int spanstartv, int lineHeight, Paint.FontMetricsInt fm, TextPaint paint); } + + /** + * Default implementation of the {@link LineHeightSpan}, which changes the line height of the + * attached paragraph. + * <p> + * LineHeightSpan will change the line height of the entire paragraph, even though it + * covers only part of the paragraph. + * </p> + */ + class Standard implements LineHeightSpan { + + private final @Px int mHeight; + /** + * Set the line height of the paragraph to <code>height</code> physical pixels. + */ + public Standard(@Px @IntRange(from = 1) int height) { + Preconditions.checkArgument(height > 0, "Height:" + height + "must be positive"); + mHeight = height; + } + + @Override + public void chooseHeight(@NonNull CharSequence text, int start, int end, + int spanstartv, int lineHeight, + @NonNull Paint.FontMetricsInt fm) { + final int originHeight = fm.descent - fm.ascent; + // If original height is not positive, do nothing. + if (originHeight <= 0) { + return; + } + final float ratio = mHeight * 1.0f / originHeight; + fm.descent = Math.round(fm.descent * ratio); + fm.ascent = fm.descent - mHeight; + } + } } diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index b092fcf4fc50..f2747cf426fa 100644..100755 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -142,6 +142,14 @@ public class DisplayMetrics { public static final int DENSITY_560 = 560; /** + * Intermediate density for screens that sit somewhere between + * {@link #DENSITY_XXHIGH} (480 dpi) and {@link #DENSITY_XXXHIGH} (640 dpi). + * This is not a density that applications should target, instead relying + * on the system to scale their {@link #DENSITY_XXXHIGH} assets for them. + */ + public static final int DENSITY_600 = 600; + + /** * Standard quantized DPI for extra-extra-extra-high-density screens. Applications * should not generally worry about this density; relying on XHIGH graphics * being scaled up to it should be sufficient for almost all cases. A typical diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 3280d477b23d..8e2786d51a43 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -215,11 +215,11 @@ public final class Magnifier { final Point windowCoords = getCurrentClampedWindowCoordinates(); final InternalPopupWindow currentWindowInstance = mWindow; sPixelCopyHandlerThread.getThreadHandler().post(() -> { - if (mWindow != currentWindowInstance) { - // The magnifier was dismissed (and maybe shown again) in the meantime. - return; - } synchronized (mLock) { + if (mWindow != currentWindowInstance) { + // The magnifier was dismissed (and maybe shown again) in the meantime. + return; + } mWindow.setContentPositionForNextDraw(windowCoords.x, windowCoords.y); } }); diff --git a/core/java/com/android/internal/app/ColorDisplayController.java b/core/java/com/android/internal/app/ColorDisplayController.java index ba6cf269b737..75151806cfcf 100644 --- a/core/java/com/android/internal/app/ColorDisplayController.java +++ b/core/java/com/android/internal/app/ColorDisplayController.java @@ -112,8 +112,8 @@ public final class ColorDisplayController { private final Context mContext; private final int mUserId; - private final ContentObserver mContentObserver; + private ContentObserver mContentObserver; private Callback mCallback; private MetricsLogger mMetricsLogger; @@ -124,18 +124,6 @@ public final class ColorDisplayController { public ColorDisplayController(@NonNull Context context, int userId) { mContext = context.getApplicationContext(); mUserId = userId; - - mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange, Uri uri) { - super.onChange(selfChange, uri); - - final String setting = uri == null ? null : uri.getLastPathSegment(); - if (setting != null) { - onSettingChanged(setting); - } - } - }; } /** @@ -522,6 +510,20 @@ public final class ColorDisplayController { if (oldCallback != callback) { mCallback = callback; + if (mContentObserver == null) { + mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + + final String setting = uri == null ? null : uri.getLastPathSegment(); + if (setting != null) { + onSettingChanged(setting); + } + } + }; + } + if (callback == null) { // Stop listening for changes now that there IS NOT a listener. mContext.getContentResolver().unregisterContentObserver(mContentObserver); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 616520f4e2b1..c4214cf19fa9 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -141,7 +141,7 @@ oneway interface IStatusBar void showShutdownUi(boolean isReboot, String reason); // Used to show the dialog when BiometricService starts authentication - void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver); + void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type); // Used to hide the dialog when a biometric is authenticated void onBiometricAuthenticated(); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index b3af147063d0..e48e73305191 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -91,7 +91,7 @@ interface IStatusBarService void showPinningEscapeToast(); // Used to show the dialog when BiometricService starts authentication - void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver); + void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type); // Used to hide the dialog when a biometric is authenticated void onBiometricAuthenticated(); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5258518db281..f845bd0e3964 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3345,7 +3345,7 @@ @hide @removed --> <permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" - android:protectionLevel="signature" /> + android:protectionLevel="signature|privileged" /> <!-- Allows an application to capture secure video output. <p>Not for use by third-party applications.</p> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index c8ad31f2ec2c..cf1320c76978 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3352,9 +3352,6 @@ to occur. The expand button will have increased touch boundaries to accomodate this. --> <bool name="config_notificationHeaderClickableForExpand">false</bool> - <!-- Configuration for automotive --> - <bool name="enable_pbap_pce_profile">false</bool> - <!-- Default data warning level in mb --> <integer name="default_data_warning_level_mb">2048</integer> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e3b9052654d0..365e4a47ce23 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1414,8 +1414,8 @@ <string-array name="fingerprint_acquired_vendor"> </string-array> - <!-- Message shown by the fingerprint dialog when fingerprint is not recognized --> - <string name="fingerprint_not_recognized">Not recognized</string> + <!-- Message shown by the biometric dialog when biometric is not recognized --> + <string name="biometric_not_recognized">Not recognized</string> <!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] --> <string name="fingerprint_authenticated">Fingerprint authenticated</string> @@ -2881,55 +2881,55 @@ <!-- Title for EditText context menu [CHAR LIMIT=20] --> <string name="editTextMenuTitle">Text actions</string> - <!-- Label for item in the text selection menu to trigger an Email app [CHAR LIMIT=20] --> + <!-- Label for item in the text selection menu to trigger an Email app. Should be a verb. [CHAR LIMIT=20] --> <string name="email">Email</string> <!-- Accessibility description for an item in the text selection menu to trigger an Email app [CHAR LIMIT=NONE] --> <string name="email_desc">Email selected address</string> - <!-- Label for item in the text selection menu to trigger a Dialer app [CHAR LIMIT=20] --> + <!-- Label for item in the text selection menu to trigger a Dialer app. Should be a verb. [CHAR LIMIT=20] --> <string name="dial">Call</string> <!-- Accessibility description for an item in the text selection menu to call a phone number [CHAR LIMIT=NONE] --> <string name="dial_desc">Call selected phone number</string> - <!-- Label for item in the text selection menu to trigger a Map app [CHAR LIMIT=20] --> + <!-- Label for item in the text selection menu to trigger a Map app. Should be a verb. [CHAR LIMIT=20] --> <string name="map">Map</string> <!-- Accessibility description for an item in the text selection menu to open maps for an address [CHAR LIMIT=NONE] --> <string name="map_desc">Locate selected address</string> - <!-- Label for item in the text selection menu to trigger a Browser app [CHAR LIMIT=20] --> + <!-- Label for item in the text selection menu to trigger a Browser app. Should be a verb. [CHAR LIMIT=20] --> <string name="browse">Open</string> <!-- Accessibility description for an item in the text selection menu to open a URL in a browser [CHAR LIMIT=NONE] --> <string name="browse_desc">Open selected URL</string> - <!-- Label for item in the text selection menu to trigger an SMS app [CHAR LIMIT=20] --> + <!-- Label for item in the text selection menu to trigger an SMS app. Should be a verb. [CHAR LIMIT=20] --> <string name="sms">Message</string> <!-- Accessibility description for an item in the text selection menu to send an SMS to a phone number [CHAR LIMIT=NONE] --> <string name="sms_desc">Message selected phone number</string> - <!-- Label for item in the text selection menu to trigger adding a contact [CHAR LIMIT=20] --> + <!-- Label for item in the text selection menu to trigger adding a contact. Should be a verb. [CHAR LIMIT=20] --> <string name="add_contact">Add</string> <!-- Accessibility description for an item in the text selection menu to add the selected detail to contacts [CHAR LIMIT=NONE] --> <string name="add_contact_desc">Add to contacts</string> - <!-- Label for item in the text selection menu to view the calendar for the selected time/date [CHAR LIMIT=20] --> + <!-- Label for item in the text selection menu to view the calendar for the selected time/date. Should be a verb. [CHAR LIMIT=20] --> <string name="view_calendar">View</string> <!-- Accessibility description for an item in the text selection menu to view the calendar for a date [CHAR LIMIT=NONE]--> <string name="view_calendar_desc">View selected time in calendar</string> - <!-- Label for item in the text selection menu to create a calendar event at the selected time/date [CHAR LIMIT=20] --> + <!-- Label for item in the text selection menu to create a calendar event at the selected time/date. Should be a verb. [CHAR LIMIT=20] --> <string name="add_calendar_event">Schedule</string> <!-- Accessibility description for an item in the text selection menu to schedule an event for a date [CHAR LIMIT=NONE] --> <string name="add_calendar_event_desc">Schedule event for selected time</string> - <!-- Label for item in the text selection menu to track a selected flight number [CHAR LIMIT=20] --> + <!-- Label for item in the text selection menu to track a selected flight number. Should be a verb. [CHAR LIMIT=20] --> <string name="view_flight">Track</string> <!-- Accessibility description for an item in the text selection menu to track a flight [CHAR LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b82f9e5dd0de..48c263e8caef 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2394,6 +2394,7 @@ <!-- Biometric messages --> <java-symbol type="string" name="biometric_error_hw_unavailable" /> + <java-symbol type="string" name="biometric_not_recognized" /> <!-- Fingerprint messages --> <java-symbol type="string" name="fingerprint_error_unable_to_process" /> @@ -2412,7 +2413,6 @@ <java-symbol type="string" name="fingerprint_error_lockout" /> <java-symbol type="string" name="fingerprint_error_lockout_permanent" /> <java-symbol type="string" name="fingerprint_name_template" /> - <java-symbol type="string" name="fingerprint_not_recognized" /> <java-symbol type="string" name="fingerprint_authenticated" /> <java-symbol type="string" name="fingerprint_error_no_fingerprints" /> <java-symbol type="string" name="fingerprint_error_hw_not_present" /> @@ -3259,8 +3259,6 @@ <java-symbol type="style" name="Theme.DeviceDefault.QuickSettings" /> - <java-symbol type="bool" name="enable_pbap_pce_profile" /> - <java-symbol type="integer" name="default_data_warning_level_mb" /> <java-symbol type="bool" name="config_useVideoPauseWorkaround" /> <java-symbol type="bool" name="config_sendPackageName" /> diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index d5829838cb3f..edce3050255b 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -72,6 +72,7 @@ cc_defaults { "libft2", "libminikin", "libandroidfw", + "libcrypto", ], static_libs: [ "libEGL_blobCache", diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 670074871c71..073b4814305e 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -18,6 +18,8 @@ #include <algorithm> #include <log/log.h> #include <thread> +#include <array> +#include <openssl/sha.h> #include "FileBlobCache.h" #include "Properties.h" #include "utils/TraceUtils.h" @@ -41,7 +43,40 @@ ShaderCache& ShaderCache::get() { return sCache; } -void ShaderCache::initShaderDiskCache() { +bool ShaderCache::validateCache(const void* identity, ssize_t size) { + if (nullptr == identity && size == 0) + return true; + + if (nullptr == identity || size < 0) { + if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) { + ALOGW("ShaderCache::validateCache invalid cache identity"); + } + mBlobCache->clear(); + return false; + } + + SHA256_CTX ctx; + SHA256_Init(&ctx); + + SHA256_Update(&ctx, identity, size); + mIDHash.resize(SHA256_DIGEST_LENGTH); + SHA256_Final(mIDHash.data(), &ctx); + + std::array<uint8_t, SHA256_DIGEST_LENGTH> hash; + auto key = sIDKey; + auto loaded = mBlobCache->get(&key, sizeof(key), hash.data(), hash.size()); + + if (loaded && std::equal(hash.begin(), hash.end(), mIDHash.begin())) + return true; + + if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) { + ALOGW("ShaderCache::validateCache cache validation fails"); + } + mBlobCache->clear(); + return false; +} + +void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) { ATRACE_NAME("initShaderDiskCache"); std::lock_guard<std::mutex> lock(mMutex); @@ -50,6 +85,7 @@ void ShaderCache::initShaderDiskCache() { // desktop / laptop GPUs. Thus, disable the shader disk cache for emulator builds. if (!Properties::runningInEmulator && mFilename.length() > 0) { mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename)); + validateCache(identity, size); mInitialized = true; } } @@ -104,6 +140,18 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { return SkData::MakeFromMalloc(valueBuffer, valueSize); } +void ShaderCache::saveToDiskLocked() { + ATRACE_NAME("ShaderCache::saveToDiskLocked"); + if (mInitialized && mBlobCache && mSavePending) { + if (mIDHash.size()) { + auto key = sIDKey; + mBlobCache->set(&key, sizeof(key), mIDHash.data(), mIDHash.size()); + } + mBlobCache->writeToFile(); + } + mSavePending = false; +} + void ShaderCache::store(const SkData& key, const SkData& data) { ATRACE_NAME("ShaderCache::store"); std::lock_guard<std::mutex> lock(mMutex); @@ -129,11 +177,7 @@ void ShaderCache::store(const SkData& key, const SkData& data) { std::thread deferredSaveThread([this]() { sleep(mDeferredSaveDelay); std::lock_guard<std::mutex> lock(mMutex); - ATRACE_NAME("ShaderCache::saveToDisk"); - if (mInitialized && mBlobCache) { - mBlobCache->writeToFile(); - } - mSavePending = false; + saveToDiskLocked(); }); deferredSaveThread.detach(); } diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index 27473d67bd1a..82804cf93785 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -40,12 +40,21 @@ public: ANDROID_API static ShaderCache& get(); /** - * "initShaderDiskCache" loads the serialized cache contents from disk and puts the ShaderCache - * into an initialized state, such that it is able to insert and retrieve entries from the - * cache. This should be called when HWUI pipeline is initialized. When not in the initialized - * state the load and store methods will return without performing any cache operations. + * initShaderDiskCache" loads the serialized cache contents from disk, + * optionally checks that the on-disk cache matches a provided identity, + * and puts the ShaderCache into an initialized state, such that it is + * able to insert and retrieve entries from the cache. If identity is + * non-null and validation fails, the cache is initialized but contains + * no data. If size is less than zero, the cache is initilaized but + * contains no data. + * + * This should be called when HWUI pipeline is initialized. When not in + * the initialized state the load and store methods will return without + * performing any cache operations. */ - virtual void initShaderDiskCache(); + virtual void initShaderDiskCache(const void *identity, ssize_t size); + + virtual void initShaderDiskCache() { initShaderDiskCache(nullptr, 0); } /** * "setFilename" sets the name of the file that should be used to store @@ -83,6 +92,19 @@ private: BlobCache* getBlobCacheLocked(); /** + * "validateCache" updates the cache to match the given identity. If the + * cache currently has the wrong identity, all entries in the cache are cleared. + */ + bool validateCache(const void* identity, ssize_t size); + + /** + * "saveToDiskLocked" attemps to save the current contents of the cache to + * disk. If the identity hash exists, we will insert the identity hash into + * the cache for next validation. + */ + void saveToDiskLocked(); + + /** * "mInitialized" indicates whether the ShaderCache is in the initialized * state. It is initialized to false at construction time, and gets set to * true when initialize is called. @@ -111,6 +133,15 @@ private: std::string mFilename; /** + * "mIDHash" is the current identity hash for the cache validation. It is + * initialized to an empty vector at construction time, and its content is + * generated in the call of the validateCache method. An empty vector + * indicates that cache validation is not performed, and the hash should + * not be stored on disk. + */ + std::vector<uint8_t> mIDHash; + + /** * "mSavePending" indicates whether or not a deferred save operation is * pending. Each time a key/value pair is inserted into the cache via * load, a deferred save is initiated if one is not already pending. @@ -140,6 +171,11 @@ private: */ static ShaderCache sCache; + /** + * "sIDKey" is the cache key of the identity hash + */ + static constexpr uint8_t sIDKey = 0; + friend class ShaderCacheTestUtils; //used for unit testing }; diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index bec80b1e6011..82bfc4943526 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -50,7 +50,6 @@ CacheManager::CacheManager(const DisplayInfo& display) : mMaxSurfaceArea(display mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas( mMaxSurfaceArea / 2, skiapipeline::VectorDrawableAtlas::StorageMode::disallowSharedSurface); - skiapipeline::ShaderCache::get().initShaderDiskCache(); } void CacheManager::reset(sk_sp<GrContext> context) { @@ -103,7 +102,7 @@ public: } }; -void CacheManager::configureContext(GrContextOptions* contextOptions) { +void CacheManager::configureContext(GrContextOptions* contextOptions, const void* identity, ssize_t size) { contextOptions->fAllowPathMaskCaching = true; float screenMP = mMaxSurfaceArea / 1024.0f / 1024.0f; @@ -133,7 +132,9 @@ void CacheManager::configureContext(GrContextOptions* contextOptions) { contextOptions->fExecutor = mTaskProcessor.get(); } - contextOptions->fPersistentCache = &skiapipeline::ShaderCache::get(); + auto& cache = skiapipeline::ShaderCache::get(); + cache.initShaderDiskCache(identity, size); + contextOptions->fPersistentCache = &cache; contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting; } diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index 7d733525194f..35fc91a42510 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -44,7 +44,7 @@ class CacheManager { public: enum class TrimMemoryMode { Complete, UiHidden }; - void configureContext(GrContextOptions* context); + void configureContext(GrContextOptions* context, const void* identity, ssize_t size); void trimMemory(TrimMemoryMode mode); void trimStaleResources(); void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr); diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index c1284ec02655..36ffaee2458b 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -191,7 +191,9 @@ void RenderThread::requireGlContext() { GrContextOptions options; options.fPreferExternalImagesOverES3 = true; options.fDisableDistanceFieldPaths = true; - cacheManager().configureContext(&options); + auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION)); + auto size = glesVersion ? strlen(glesVersion) : -1; + cacheManager().configureContext(&options, glesVersion, size); sk_sp<GrContext> grContext(GrContext::MakeGL(std::move(glInterface), options)); LOG_ALWAYS_FATAL_IF(!grContext.get()); setGrContext(grContext); diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 1517f579a084..cc4b87adfd7b 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -391,7 +391,8 @@ void VulkanManager::initialize() { GrContextOptions options; options.fDisableDistanceFieldPaths = true; - mRenderThread.cacheManager().configureContext(&options); + // TODO: get a string describing the SPIR-V compiler version and use it here + mRenderThread.cacheManager().configureContext(&options, nullptr, 0); sk_sp<GrContext> grContext(GrContext::MakeVulkan(backendContext, options)); LOG_ALWAYS_FATAL_IF(!grContext.get()); mRenderThread.setGrContext(grContext); diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 43080a9460b3..1433aa0349f4 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -48,11 +48,18 @@ public: */ static void terminate(ShaderCache& cache, bool saveContent) { std::lock_guard<std::mutex> lock(cache.mMutex); - if (cache.mInitialized && cache.mBlobCache && saveContent) { - cache.mBlobCache->writeToFile(); - } + cache.mSavePending = saveContent; + cache.saveToDiskLocked(); cache.mBlobCache = NULL; } + + /** + * + */ + template <typename T> + static bool validateCache(ShaderCache& cache, std::vector<T> hash) { + return cache.validateCache(hash.data(), hash.size() * sizeof(T)); + } }; } /* namespace skiapipeline */ @@ -75,26 +82,39 @@ bool folderExist(const std::string& folderName) { return false; } -bool checkShader(const sk_sp<SkData>& shader, const char* program) { +inline bool +checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) { + return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size() + && 0 == memcmp(shader1->data(), shader2->data(), shader1->size()); +} + +inline bool +checkShader(const sk_sp<SkData>& shader, const char* program) { sk_sp<SkData> shader2 = SkData::MakeWithCString(program); - return shader->size() == shader2->size() - && 0 == memcmp(shader->data(), shader2->data(), shader->size()); + return checkShader(shader, shader2); } -bool checkShader(const sk_sp<SkData>& shader, std::vector<char>& program) { - sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size()); - return shader->size() == shader2->size() - && 0 == memcmp(shader->data(), shader2->data(), shader->size()); +template <typename T> +bool checkShader(const sk_sp<SkData>& shader, std::vector<T>& program) { + sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size() * sizeof(T)); + return checkShader(shader, shader2); } void setShader(sk_sp<SkData>& shader, const char* program) { shader = SkData::MakeWithCString(program); } -void setShader(sk_sp<SkData>& shader, std::vector<char>& program) { - shader = SkData::MakeWithCopy(program.data(), program.size()); +template <typename T> +void setShader(sk_sp<SkData>& shader, std::vector<T>& buffer) { + shader = SkData::MakeWithCopy(buffer.data(), buffer.size() * sizeof(T)); } +template <typename T> +void genRandomData(std::vector<T>& buffer) { + for (auto& data : buffer) { + data = T(std::rand()); + } +} #define GrProgramDescTest(a) (*SkData::MakeWithCString(#a).get()) @@ -110,6 +130,7 @@ TEST(ShaderCacheTest, testWriteAndRead) { //remove any test files from previous test run int deleteFile = remove(cacheFile1.c_str()); ASSERT_TRUE(0 == deleteFile || ENOENT == errno); + std::srand(0); //read the cache from a file that does not exist ShaderCache::get().setFilename(cacheFile1.c_str()); @@ -158,10 +179,8 @@ TEST(ShaderCacheTest, testWriteAndRead) { //write and read big data chunk (50K) size_t dataSize = 50*1024; - std::vector<char> dataBuffer(dataSize); - for (size_t i = 0; i < dataSize; i++) { - dataBuffer[0] = dataSize % 256; - } + std::vector<uint8_t> dataBuffer(dataSize); + genRandomData(dataBuffer); setShader(inVS, dataBuffer); ShaderCache::get().store(GrProgramDescTest(432), *inVS.get()); ShaderCacheTestUtils::terminate(ShaderCache::get(), true); @@ -173,4 +192,96 @@ TEST(ShaderCacheTest, testWriteAndRead) { remove(cacheFile1.c_str()); } +TEST(ShaderCacheTest, testCacheValidation) { + if (!folderExist(getExternalStorageFolder())) { + //don't run the test if external storage folder is not available + return; + } + std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1"; + std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2"; + + //remove any test files from previous test run + int deleteFile = remove(cacheFile1.c_str()); + ASSERT_TRUE(0 == deleteFile || ENOENT == errno); + std::srand(0); + + //generate identity and read the cache from a file that does not exist + ShaderCache::get().setFilename(cacheFile1.c_str()); + ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); //disable deferred save + std::vector<uint8_t> identity(1024); + genRandomData(identity); + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + + // generate random content in cache and store to disk + constexpr size_t numBlob(10); + constexpr size_t keySize(1024); + constexpr size_t dataSize(50 * 1024); + + std::vector< std::pair<sk_sp<SkData>, sk_sp<SkData>> > blobVec(numBlob); + for (auto& blob : blobVec) { + std::vector<uint8_t> keyBuffer(keySize); + std::vector<uint8_t> dataBuffer(dataSize); + genRandomData(keyBuffer); + genRandomData(dataBuffer); + + sk_sp<SkData> key, data; + setShader(key, keyBuffer); + setShader(data, dataBuffer); + + blob = std::make_pair(key, data); + ShaderCache::get().store(*key.get(), *data.get()); + } + ShaderCacheTestUtils::terminate(ShaderCache::get(), true); + + // change to a file that does not exist and verify validation fails + ShaderCache::get().setFilename(cacheFile2.c_str()); + ShaderCache::get().initShaderDiskCache(); + ASSERT_FALSE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + ShaderCacheTestUtils::terminate(ShaderCache::get(), false); + + // restore the original file and verify validation succeeds + ShaderCache::get().setFilename(cacheFile1.c_str()); + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + ASSERT_TRUE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + for (const auto& blob : blobVec) { + auto outVS = ShaderCache::get().load(*blob.first.get()); + ASSERT_TRUE( checkShader(outVS, blob.second) ); + } + + // generate error identity and verify load fails + ShaderCache::get().initShaderDiskCache(identity.data(), -1); + for (const auto& blob : blobVec) { + ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + } + ShaderCache::get().initShaderDiskCache(nullptr, identity.size() * + sizeof(decltype(identity)::value_type)); + for (const auto& blob : blobVec) { + ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + } + + // verify the cache validation again after load fails + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + ASSERT_TRUE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + for (const auto& blob : blobVec) { + auto outVS = ShaderCache::get().load(*blob.first.get()); + ASSERT_TRUE( checkShader(outVS, blob.second) ); + } + + // generate another identity and verify load fails + for (auto& data : identity) { + data += std::rand(); + } + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + for (const auto& blob : blobVec) { + ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + } + + ShaderCacheTestUtils::terminate(ShaderCache::get(), false); + remove(cacheFile1.c_str()); +} + } // namespace diff --git a/packages/CaptivePortalLogin/Android.mk b/packages/CaptivePortalLogin/Android.mk index 3ea334011041..8c63f450fda0 100644 --- a/packages/CaptivePortalLogin/Android.mk +++ b/packages/CaptivePortalLogin/Android.mk @@ -4,7 +4,6 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_USE_AAPT2 := true LOCAL_STATIC_ANDROID_LIBRARIES := androidx.legacy_legacy-support-v4 -LOCAL_STATIC_JAVA_LIBRARIES := services.net LOCAL_SRC_FILES := $(call all-java-files-under, src) diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 2be9311d91ea..7c8139928476 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -106,6 +106,14 @@ </intent-filter> </receiver> + <receiver android:name=".PackageInstalledReceiver" + android:exported="true"> + <intent-filter android:priority="1"> + <action android:name="android.intent.action.PACKAGE_ADDED" /> + <data android:scheme="package" /> + </intent-filter> + </receiver> + <activity android:name=".UninstallUninstalling" android:excludeFromRecents="true" android:exported="false" /> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java new file mode 100644 index 000000000000..67ac99fb12a6 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java @@ -0,0 +1,34 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * Receive new app installed broadcast and notify user new app installed. + */ +public class PackageInstalledReceiver extends BroadcastReceiver { + + private static final String TAG = "PackageInstalledReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + // TODO: Add logic to handle new app installed. + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index 36d209e96269..7000f9d7a7d2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -97,8 +97,6 @@ public class LocalBluetoothProfileManager { private PanProfile mPanProfile; private PbapClientProfile mPbapClientProfile; private PbapServerProfile mPbapProfile; - private final boolean mUsePbapPce; - private final boolean mUseMapClient; private HearingAidProfile mHearingAidProfile; /** @@ -115,9 +113,6 @@ public class LocalBluetoothProfileManager { mDeviceManager = deviceManager; mEventManager = eventManager; - mUsePbapPce = mContext.getResources().getBoolean(R.bool.enable_pbap_pce_profile); - // MAP Client is typically used in the same situations as PBAP Client - mUseMapClient = mContext.getResources().getBoolean(R.bool.enable_pbap_pce_profile); // pass this reference to adapter and event manager (circular dependency) adapter.setProfileManager(this); @@ -130,17 +125,17 @@ public class LocalBluetoothProfileManager { void updateLocalProfiles() { List<Integer> supportedList = BluetoothAdapter.getDefaultAdapter().getSupportedProfiles(); if (CollectionUtils.isEmpty(supportedList)) { - if(DEBUG) Log.d(TAG, "supportedList is null"); + if (DEBUG) Log.d(TAG, "supportedList is null"); return; } if (mA2dpProfile == null && supportedList.contains(BluetoothProfile.A2DP)) { - if(DEBUG) Log.d(TAG, "Adding local A2DP profile"); + if (DEBUG) Log.d(TAG, "Adding local A2DP profile"); mA2dpProfile = new A2dpProfile(mContext, mDeviceManager, this); addProfile(mA2dpProfile, A2dpProfile.NAME, BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); } if (mA2dpSinkProfile == null && supportedList.contains(BluetoothProfile.A2DP_SINK)) { - if(DEBUG) Log.d(TAG, "Adding local A2DP SINK profile"); + if (DEBUG) Log.d(TAG, "Adding local A2DP SINK profile"); mA2dpSinkProfile = new A2dpSinkProfile(mContext, mDeviceManager, this); addProfile(mA2dpSinkProfile, A2dpSinkProfile.NAME, BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED); @@ -154,66 +149,63 @@ public class LocalBluetoothProfileManager { BluetoothHeadset.STATE_AUDIO_DISCONNECTED); } if (mHfpClientProfile == null && supportedList.contains(BluetoothProfile.HEADSET_CLIENT)) { - if(DEBUG) Log.d(TAG, "Adding local HfpClient profile"); + if (DEBUG) Log.d(TAG, "Adding local HfpClient profile"); mHfpClientProfile = new HfpClientProfile(mContext, mDeviceManager, this); addHeadsetProfile(mHfpClientProfile, HfpClientProfile.NAME, BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED, BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED); } - if (mUseMapClient) { - if (mMapClientProfile == null && supportedList.contains(BluetoothProfile.MAP_CLIENT)) { - if(DEBUG) Log.d(TAG, "Adding local MAP CLIENT profile"); - mMapClientProfile = - new MapClientProfile(mContext, mDeviceManager,this); - addProfile(mMapClientProfile, MapClientProfile.NAME, - BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED); - } - } else if (mMapProfile == null && supportedList.contains(BluetoothProfile.MAP)) { - if(DEBUG) Log.d(TAG, "Adding local MAP profile"); + if (mMapClientProfile == null && supportedList.contains(BluetoothProfile.MAP_CLIENT)) { + if (DEBUG) Log.d(TAG, "Adding local MAP CLIENT profile"); + mMapClientProfile = new MapClientProfile(mContext, mDeviceManager,this); + addProfile(mMapClientProfile, MapClientProfile.NAME, + BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED); + } + if (mMapProfile == null && supportedList.contains(BluetoothProfile.MAP)) { + if (DEBUG) Log.d(TAG, "Adding local MAP profile"); mMapProfile = new MapProfile(mContext, mDeviceManager, this); addProfile(mMapProfile, MapProfile.NAME, BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); } if (mOppProfile == null && supportedList.contains(BluetoothProfile.OPP)) { - if(DEBUG) Log.d(TAG, "Adding local OPP profile"); + if (DEBUG) Log.d(TAG, "Adding local OPP profile"); mOppProfile = new OppProfile(); // Note: no event handler for OPP, only name map. mProfileNameMap.put(OppProfile.NAME, mOppProfile); } if (mHearingAidProfile == null && supportedList.contains(BluetoothProfile.HEARING_AID)) { - if(DEBUG) Log.d(TAG, "Adding local Hearing Aid profile"); + if (DEBUG) Log.d(TAG, "Adding local Hearing Aid profile"); mHearingAidProfile = new HearingAidProfile(mContext, mDeviceManager, this); addProfile(mHearingAidProfile, HearingAidProfile.NAME, BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); } if (mHidProfile == null && supportedList.contains(BluetoothProfile.HID_HOST)) { - if(DEBUG) Log.d(TAG, "Adding local HID_HOST profile"); + if (DEBUG) Log.d(TAG, "Adding local HID_HOST profile"); mHidProfile = new HidProfile(mContext, mDeviceManager, this); addProfile(mHidProfile, HidProfile.NAME, BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED); } if (mHidDeviceProfile == null && supportedList.contains(BluetoothProfile.HID_DEVICE)) { - if(DEBUG) Log.d(TAG, "Adding local HID_DEVICE profile"); + if (DEBUG) Log.d(TAG, "Adding local HID_DEVICE profile"); mHidDeviceProfile = new HidDeviceProfile(mContext, mDeviceManager, this); addProfile(mHidDeviceProfile, HidDeviceProfile.NAME, BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED); } if (mPanProfile == null && supportedList.contains(BluetoothProfile.PAN)) { - if(DEBUG) Log.d(TAG, "Adding local PAN profile"); + if (DEBUG) Log.d(TAG, "Adding local PAN profile"); mPanProfile = new PanProfile(mContext); addPanProfile(mPanProfile, PanProfile.NAME, BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); } if (mPbapProfile == null && supportedList.contains(BluetoothProfile.PBAP)) { - if(DEBUG) Log.d(TAG, "Adding local PBAP profile"); + if (DEBUG) Log.d(TAG, "Adding local PBAP profile"); mPbapProfile = new PbapServerProfile(mContext); addProfile(mPbapProfile, PbapServerProfile.NAME, BluetoothPbap.ACTION_CONNECTION_STATE_CHANGED); } - if (mUsePbapPce && mPbapClientProfile == null && supportedList.contains( - BluetoothProfile.PBAP_CLIENT)) { - if(DEBUG) Log.d(TAG, "Adding local PBAP Client profile"); + if (mPbapClientProfile == null && supportedList.contains(BluetoothProfile.PBAP_CLIENT)) { + if (DEBUG) Log.d(TAG, "Adding local PBAP Client profile"); mPbapClientProfile = new PbapClientProfile(mContext, mDeviceManager,this); addProfile(mPbapClientProfile, PbapClientProfile.NAME, BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED); @@ -544,7 +536,9 @@ public class LocalBluetoothProfileManager { removedProfiles.remove(mMapClientProfile); } - if (mUsePbapPce) { + if ((mPbapClientProfile != null) && + BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.PBAP_PCE) && + BluetoothUuid.containsAnyUuid(uuids, PbapClientProfile.SRC_UUIDS)) { profiles.add(mPbapClientProfile); removedProfiles.remove(mPbapClientProfile); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java index 2213db88f2fd..274696bfec0e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java @@ -172,11 +172,13 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback { public UserIconDrawable setBadgeIfManagedUser(Context context, int userId) { Drawable badge = null; - boolean isManaged = context.getSystemService(DevicePolicyManager.class) - .getProfileOwnerAsUser(userId) != null; - if (isManaged) { - badge = getDrawableForDisplayDensity( - context, com.android.internal.R.drawable.ic_corp_badge_case); + if (userId != UserHandle.USER_NULL) { + boolean isManaged = context.getSystemService(DevicePolicyManager.class) + .getProfileOwnerAsUser(userId) != null; + if (isManaged) { + badge = getDrawableForDisplayDensity( + context, com.android.internal.R.drawable.ic_corp_badge_case); + } } return setBadge(badge); } diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java index 88be2b0a0bd2..3e3c03918a0b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java +++ b/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java @@ -27,7 +27,6 @@ import android.icu.util.ULocale; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.TtsSpan; -import android.util.Log; import com.android.settingslib.R; @@ -37,8 +36,6 @@ import java.util.Locale; /** Utility class for generally useful string methods **/ public class StringUtil { - private static final String TAG = "StringUtil"; - public static final int SECONDS_PER_MINUTE = 60; public static final int SECONDS_PER_HOUR = 60 * 60; public static final int SECONDS_PER_DAY = 24 * 60 * 60; @@ -97,7 +94,6 @@ public class StringUtil { final Locale locale = context.getResources().getConfiguration().locale; final MeasureFormat measureFormat = MeasureFormat.getInstance( locale, FormatWidth.SHORT); - Log.i(TAG, "Locale is: " + locale); sb.append(measureFormat.formatMeasures(measureArray)); if (measureArray.length == 1 && MeasureUnit.MINUTE.equals(measureArray[0].getUnit())) { @@ -150,7 +146,6 @@ public class StringUtil { null /* default NumberFormat */, RelativeDateTimeFormatter.Style.LONG, android.icu.text.DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE); - Log.i(TAG, "Locale is: " + locale); return formatter.format(value, RelativeDateTimeFormatter.Direction.LAST, unit); } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java index c268d325bd02..0cdb509a5209 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java @@ -29,7 +29,7 @@ public abstract class QSIconView extends ViewGroup { super(context); } - public abstract void setIcon(State state); + public abstract void setIcon(State state, boolean allowAnimations); public abstract void disableAnimation(); public abstract View getIconView(); } diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index eb71698136fc..710b5f724add 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -24,20 +24,3 @@ android_library { // no compatibility issues with launcher java_version: "1.7", } - -android_app { - - name: "SysUISharedLib", - platform_apis: true, - srcs: [ - "src/**/*.java", - "src/**/I*.aidl", - ], - - static_libs: ["SystemUISharedLib"], - - optimize: { - enabled: false, - }, - -} diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index acc7b49d9c35..77f4bf529f21 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -35,6 +35,8 @@ import android.view.Surface; import android.view.SurfaceHolder; import android.view.WindowManager; +import com.android.internal.annotations.VisibleForTesting; + import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; @@ -77,6 +79,13 @@ public class ImageWallpaper extends WallpaperService { unloadWallpaper(false /* forgetSize */); }; + // Surface is rejected if size below a threshold on some devices (ie. 8px on elfin) + // set min to 64 px (CTS covers this) + @VisibleForTesting + static final int MIN_BACKGROUND_WIDTH = 64; + @VisibleForTesting + static final int MIN_BACKGROUND_HEIGHT = 64; + Bitmap mBackground; int mBackgroundWidth = -1, mBackgroundHeight = -1; int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; @@ -156,9 +165,9 @@ public class ImageWallpaper extends WallpaperService { hasWallpaper = false; } - // Force the wallpaper to cover the screen in both dimensions - int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth); - int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight); + // Set surface size equal to bitmap size, prevent memory waste + int surfaceWidth = Math.max(MIN_BACKGROUND_WIDTH, mBackgroundWidth); + int surfaceHeight = Math.max(MIN_BACKGROUND_HEIGHT, mBackgroundHeight); // Used a fixed size surface, because we are special. We can do // this because we know the current design of window animations doesn't @@ -257,7 +266,8 @@ public class ImageWallpaper extends WallpaperService { drawFrame(); } - private DisplayInfo getDefaultDisplayInfo() { + @VisibleForTesting + DisplayInfo getDefaultDisplayInfo() { mDefaultDisplay.getDisplayInfo(mTmpDisplayInfo); return mTmpDisplayInfo; } @@ -420,7 +430,8 @@ public class ImageWallpaper extends WallpaperService { }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - private void updateBitmap(Bitmap bitmap) { + @VisibleForTesting + void updateBitmap(Bitmap bitmap) { mBackground = null; mBackgroundWidth = -1; mBackgroundHeight = -1; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java index 6e62b0d575a2..8fe577a4cec0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java @@ -36,7 +36,7 @@ import com.android.systemui.statusbar.CommandQueue; * FingerprintDialogView). */ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks { - private static final String TAG = "FingerprintDialogImpl"; + private static final String TAG = "BiometricDialogImpl"; private static final boolean DEBUG = true; private static final int MSG_SHOW_DIALOG = 1; @@ -120,8 +120,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver) { - if (DEBUG) Log.d(TAG, "showBiometricDialog"); + public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type) { + if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type); // Remove these messages as they are part of the previous client mHandler.removeMessages(MSG_BIOMETRIC_ERROR); mHandler.removeMessages(MSG_BIOMETRIC_HELP); diff --git a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java index e7eefe8d5e56..376e6ae16cc8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java @@ -43,9 +43,9 @@ public class CellTileView extends SignalTileView { R.dimen.qs_tile_icon_size)); } - protected void updateIcon(ImageView iv, State state) { + protected void updateIcon(ImageView iv, State state, boolean allowAnimations) { if (!(state.icon instanceof SignalIcon)) { - super.updateIcon(iv, state); + super.updateIcon(iv, state, allowAnimations); return; } else if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))) { mSignalDrawable.setLevel(((SignalIcon) state.icon).getState()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index f1b7eeca5c07..ca1b48901c9d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -261,6 +261,12 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { return mPages.get(0).mColumns; } + public int getNumVisibleTiles() { + if (mPages.size() == 0) return 0; + TilePage currentPage = mPages.get(getCurrentItem()); + return currentPage.mRecords.size(); + } + public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) { if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0 || !beginFakeDrag()) { // Do not start the reveal animation unless there are tiles to animate, multiple diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index 2a4bb607673e..3744d7dc7bde 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -155,6 +155,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha TouchAnimator.Builder translationYBuilder = new Builder(); if (mQsPanel.getHost() == null) return; + if (mQuickQsPanel.getTileLayout().getNumVisibleTiles() < 1) return; Collection<QSTile> tiles = mQsPanel.getHost().getTiles(); int count = 0; int[] loc1 = new int[2]; @@ -169,6 +170,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha QSTileLayout tileLayout = mQsPanel.getTileLayout(); mAllViews.add((View) tileLayout); int height = mQs.getView() != null ? mQs.getView().getMeasuredHeight() : 0; + int width = mQs.getView() != null ? mQs.getView().getMeasuredWidth() : 0; int heightDiff = height - mQs.getHeader().getBottom() + mQs.getHeader().getPaddingBottom(); firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0); @@ -181,7 +183,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } final View tileIcon = tileView.getIcon().getIconView(); View view = mQs.getView(); - if (count < mNumQuickTiles && mAllowFancy) { + + // This case: less tiles to animate in small displays. + if (count < mQuickQsPanel.getTileLayout().getNumVisibleTiles() && mAllowFancy) { // Quick tiles. QSTileView quickTileView = mQuickQsPanel.getTileView(tile); if (quickTileView == null) continue; @@ -192,18 +196,26 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha final int xDiff = loc2[0] - loc1[0]; final int yDiff = loc2[1] - loc1[1]; lastXDiff = loc1[0] - lastX; - // Move the quick tile right from its location to the new one. - translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff); - translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff); - // Counteract the parent translation on the tile. So we have a static base to - // animate the label position off from. - //firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0); + if (count < tileLayout.getNumVisibleTiles()) { + // Move the quick tile right from its location to the new one. + translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff); + translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff); - // Move the real tile from the quick tile position to its final - // location. - translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); - translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); + // Counteract the parent translation on the tile. So we have a static base to + // animate the label position off from. + //firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0); + + // Move the real tile from the quick tile position to its final + // location. + translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); + translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); + + } else { // These tiles disappear when expanding + firstPageBuilder.addFloat(quickTileView, "alpha", 1, 0); + translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff); + translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff + width); + } mQuickQsViews.add(tileView.getIconWithBackground()); mAllViews.add(tileView.getIcon()); @@ -218,10 +230,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha final int xDiff = loc2[0] - loc1[0]; final int yDiff = loc2[1] - loc1[1]; - firstPageBuilder.addFloat(tileView, "translationY", heightDiff, 0); - translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); + firstPageBuilder.addFloat(tileView, "translationY", -heightDiff, 0); translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); - translationYBuilder.addFloat(tileIcon, "translationY", -yDiff, 0); + translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); mAllViews.add(tileIcon); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 7cb22a3607d9..03febda3f4c5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -101,12 +101,7 @@ public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks { if (savedInstanceState != null) { setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED)); setListening(savedInstanceState.getBoolean(EXTRA_LISTENING)); - int[] loc = new int[2]; - View edit = view.findViewById(android.R.id.edit); - edit.getLocationInWindow(loc); - int x = loc[0] + edit.getWidth() / 2; - int y = loc[1] + edit.getHeight() / 2; - mQSCustomizer.setEditLocation(x, y); + setEditLocation(view); mQSCustomizer.restoreInstanceState(savedInstanceState); } SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this); @@ -161,15 +156,24 @@ public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks { @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + setEditLocation(getView()); if (newConfig.getLayoutDirection() != mLayoutDirection) { mLayoutDirection = newConfig.getLayoutDirection(); - if (mQSAnimator != null) { mQSAnimator.onRtlChanged(); } } } + private void setEditLocation(View view) { + Log.w(TAG, "I'm changing the location of the button!!!"); + View edit = view.findViewById(android.R.id.edit); + int[] loc = edit.getLocationOnScreen(); + int x = loc[0] + edit.getWidth() / 2; + int y = loc[1] + edit.getHeight() / 2; + mQSCustomizer.setEditLocation(x, y); + } + @Override public void setContainer(ViewGroup container) { if (container instanceof NotificationsQuickSettingsContainer) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 3fc258b1e8e9..762fd75f48dc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -29,6 +29,7 @@ import android.os.Handler; import android.os.Message; import android.service.quicksettings.Tile; import android.util.AttributeSet; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; @@ -60,6 +61,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness"; public static final String QS_SHOW_HEADER = "qs_show_header"; + private static final String TAG = "QSPanel"; + protected final Context mContext; protected final ArrayList<TileRecord> mRecords = new ArrayList<>(); protected final View mBrightnessView; @@ -313,7 +316,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void onCollapse() { if (mCustomizePanel != null && mCustomizePanel.isShown()) { - mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2); + mCustomizePanel.hide(); } } @@ -480,8 +483,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void run() { if (mCustomizePanel != null) { if (!mCustomizePanel.isCustomizing()) { - int[] loc = new int[2]; - v.getLocationInWindow(loc); + int[] loc = v.getLocationOnScreen(); int x = loc[0] + v.getWidth() / 2; int y = loc[1] + v.getHeight() / 2; mCustomizePanel.show(x, y); @@ -495,7 +497,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void closeDetail() { if (mCustomizePanel != null && mCustomizePanel.isShown()) { // Treat this as a detail panel for now, to make things easy. - mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2); + mCustomizePanel.hide(); return; } showDetail(false, mDetailRecord); @@ -663,5 +665,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne void setListening(boolean listening); default void setExpansion(float expansion) {} + + int getNumVisibleTiles(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 1c50f797e1a7..556786a16caf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -18,18 +18,17 @@ package com.android.systemui.qs; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Rect; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.widget.LinearLayout; -import android.widget.Space; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.SignalState; import com.android.systemui.plugins.qs.QSTile.State; -import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -43,6 +42,7 @@ import java.util.Collection; public class QuickQSPanel extends QSPanel { public static final String NUM_QUICK_TILES = "sysui_qqs_count"; + private static final String TAG = "QuickQSPanel"; private boolean mDisabledByPolicy; private static int mDefaultMaxTiles; @@ -178,121 +178,95 @@ public class QuickQSPanel extends QSPanel { super.setVisibility(visibility); } - private static class HeaderTileLayout extends LinearLayout implements QSTileLayout { + private static class HeaderTileLayout extends TileLayout { - protected final ArrayList<TileRecord> mRecords = new ArrayList<>(); private boolean mListening; - /** Size of the QS tile (width & height). */ - private int mTileDimensionSize; public HeaderTileLayout(Context context) { super(context); setClipChildren(false); setClipToPadding(false); - - mTileDimensionSize = mContext.getResources().getDimensionPixelSize( - R.dimen.qs_quick_tile_size); - updateLayoutParams(); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - updateLayoutParams(); + updateResources(); + } + + @Override + public void onFinishInflate(){ + updateResources(); } private void updateLayoutParams() { - setGravity(Gravity.CENTER); int width = getResources().getDimensionPixelSize(R.dimen.qs_quick_layout_width); - LayoutParams lp = new LayoutParams(width, LayoutParams.MATCH_PARENT); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(width, LayoutParams.MATCH_PARENT); lp.gravity = Gravity.CENTER_HORIZONTAL; setLayoutParams(lp); } - /** - * Returns {@link LayoutParams} based on the given {@code spaceWidth}. If the width is 0, - * then we're going to have the space expand to take up as much space as possible. If the - * width is non-zero, we want the inter-tile spacers to be fixed. - */ - private LayoutParams generateSpaceLayoutParams() { - LayoutParams lp = new LayoutParams(0, mTileDimensionSize); - lp.weight = 1; - lp.gravity = Gravity.CENTER; + private LayoutParams generateTileLayoutParams() { + LayoutParams lp = new LayoutParams(mCellWidth, mCellHeight); return lp; } @Override - public void setListening(boolean listening) { - if (mListening == listening) return; - mListening = listening; - for (TileRecord record : mRecords) { - record.tile.setListening(this, mListening); - } - } - - @Override - public void addTile(TileRecord tile) { - if (getChildCount() != 0) { - addView(new Space(mContext), getChildCount(), generateSpaceLayoutParams()); - } - + protected void addTileView(TileRecord tile) { addView(tile.tileView, getChildCount(), generateTileLayoutParams()); - mRecords.add(tile); - tile.tile.setListening(this, mListening); - } - - private LayoutParams generateTileLayoutParams() { - LayoutParams lp = new LayoutParams(mTileDimensionSize, mTileDimensionSize); - lp.gravity = Gravity.CENTER; - return lp; } @Override - public void removeTile(TileRecord tile) { - int childIndex = getChildIndex(tile.tileView); - // Remove the tile. - removeViewAt(childIndex); - if (getChildCount() != 0) { - // Remove its spacer as well. - removeViewAt(childIndex); - } - mRecords.remove(tile); - tile.tile.setListening(this, false); - } + protected void onLayout(boolean changed, int l, int t, int r, int b) { + // We only care about clipping on the right side + Rect bounds = new Rect(0, 0, r - l, 10000); + setClipBounds(bounds); - private int getChildIndex(QSTileView tileView) { - final int childViewCount = getChildCount(); - for (int i = 0; i < childViewCount; i++) { - if (getChildAt(i) == tileView) { - return i; - } + calculateColumns(); + + for (int i = 0; i < mRecords.size(); i++) { + mRecords.get(i).tileView.setVisibility( i < mColumns ? View.VISIBLE : View.GONE); } - return -1; - } - @Override - public int getOffsetTop(TileRecord tile) { - return 0; + setAccessibilityOrder(); + layoutTileRecords(mColumns); } @Override public boolean updateResources() { - // No resources here. - return false; - } + mCellWidth = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); + mCellHeight = mCellWidth; + + updateLayoutParams(); - @Override - public boolean hasOverlappingRendering() { return false; } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (hideOverflowingChildren(widthMeasureSpec)) { - return; // Rely on visibility change to trigger remeasure. + private boolean calculateColumns() { + int prevNumColumns = mColumns; + int maxTiles = mRecords.size(); + + if (maxTiles == 0){ // Early return during setup + mColumns = 0; + return true; + } + + final int availableWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd(); + final int leftoverWithespace = availableWidth - maxTiles * mCellWidth; + final int smallestHorizontalMarginNeeded = leftoverWithespace / (maxTiles - 1); + + if (smallestHorizontalMarginNeeded > 0){ + mCellMarginHorizontal = smallestHorizontalMarginNeeded; + mColumns = maxTiles; + } else{ + mColumns = mCellWidth == 0 ? 1 : + Math.min(maxTiles, availableWidth / mCellWidth ); + mCellMarginHorizontal = (availableWidth - mColumns * mCellWidth) / (mColumns - 1); } + return mColumns != prevNumColumns; + } + private void setAccessibilityOrder() { if (mRecords != null && mRecords.size() > 0) { View previousView = this; for (TileRecord record : mRecords) { @@ -306,31 +280,28 @@ public class QuickQSPanel extends QSPanel { } } - /** - * Hide child views that would otherwise be clipped. - * @return {@code true} if any child visibilities have changed. - */ - private boolean hideOverflowingChildren(int widthMeasureSpec) { - if (getChildCount() == 0) { - return false; - } - boolean childVisibilityChanged = false; - int widthRemaining = MeasureSpec.getSize(widthMeasureSpec) - - getChildAt(0).getMeasuredWidth() - getPaddingStart() - getPaddingEnd(); - for (int i = 2; i < getChildCount(); i += 2) { - View tileChild = getChildAt(i); - LayoutParams lp = (LayoutParams) tileChild.getLayoutParams(); - // All Space views have 0 width; only tiles contribute to the total width. - widthRemaining = widthRemaining - - tileChild.getMeasuredWidth() - lp.getMarginEnd() - lp.getMarginStart(); - int newVisibility = widthRemaining < 0 ? View.GONE : View.VISIBLE; - if (tileChild.getVisibility() != newVisibility) { - tileChild.setVisibility(newVisibility); - getChildAt(i - 1).setVisibility(newVisibility); // Hide spacer as well. - childVisibilityChanged = true; - } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Measure each QS tile. + for (TileRecord record : mRecords) { + if (record.tileView.getVisibility() == GONE) continue; + record.tileView.measure(exactly(mCellWidth), exactly(mCellHeight)); } - return childVisibilityChanged; + + int height = mCellHeight; + if (height < 0) height = 0; + + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height); + } + + @Override + public int getNumVisibleTiles() { + return mColumns; + } + + @Override + protected int getColumnStart(int column) { + return getPaddingStart() + column * (mCellWidth + mCellMarginHorizontal); } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java index d9583af65df6..ce90fc107b82 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java @@ -18,14 +18,13 @@ package com.android.systemui.qs; import android.animation.ValueAnimator; import android.content.Context; -import android.graphics.drawable.Drawable; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import com.android.systemui.R; -import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.SignalState; +import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.qs.tileimpl.QSIconViewImpl; import com.android.systemui.qs.tileimpl.SlashImageView; @@ -119,9 +118,9 @@ public class SignalTileView extends QSIconViewImpl { } @Override - public void setIcon(QSTile.State state) { + public void setIcon(State state, boolean allowAnimations) { final SignalState s = (SignalState) state; - setIcon(mSignal, s); + setIcon(mSignal, s, allowAnimations); if (s.overlayIconId > 0) { mOverlay.setVisibility(VISIBLE); @@ -134,9 +133,9 @@ public class SignalTileView extends QSIconViewImpl { } else { mSignal.setPaddingRelative(0, 0, 0, 0); } - final boolean shown = isShown(); - setVisibility(mIn, shown, s.activityIn); - setVisibility(mOut, shown, s.activityOut); + final boolean shouldAnimate = allowAnimations && isShown(); + setVisibility(mIn, shouldAnimate, s.activityIn); + setVisibility(mOut, shouldAnimate, s.activityOut); } private void setVisibility(View view, boolean shown, boolean visible) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 45d63e0c1359..c67165ea95a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -56,6 +56,10 @@ public class TileLayout extends ViewGroup implements QSTileLayout { public void addTile(TileRecord tile) { mRecords.add(tile); tile.tile.setListening(this, mListening); + addTileView(tile); + } + + protected void addTileView(TileRecord tile) { addView(tile.tileView); } @@ -120,19 +124,18 @@ public class TileLayout extends ViewGroup implements QSTileLayout { return false; } - private static int exactly(int size) { + protected static int exactly(int size) { return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); } - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - final int w = getWidth(); + + protected void layoutTileRecords(int numRecords) { final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; int row = 0; int column = 0; // Layout each QS tile. - for (int i = 0; i < mRecords.size(); i++, column++) { + for (int i = 0; i < numRecords; i++, column++) { // If we reached the last column available to layout a tile, wrap back to the next row. if (column == mColumns) { column = 0; @@ -147,12 +150,22 @@ public class TileLayout extends ViewGroup implements QSTileLayout { } } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + layoutTileRecords(mRecords.size()); + } + private int getRowTop(int row) { return row * (mCellHeight + mCellMarginVertical) + mCellMarginTop; } - private int getColumnStart(int column) { + protected int getColumnStart(int column) { return getPaddingStart() + mSidePadding + mCellMarginHorizontal / 2 + column * (mCellWidth + mCellMarginHorizontal); } + + @Override + public int getNumVisibleTiles() { + return mRecords.size(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index 2ea15bd2ad3e..3f7eeb81e58e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -26,6 +26,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import android.util.AttributeSet; +import android.util.Log; import android.util.TypedValue; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; @@ -63,6 +64,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene private static final int MENU_RESET = Menu.FIRST; private static final String EXTRA_QS_CUSTOMIZING = "qs_customizing"; + private static final String TAG = "QSCustomizer"; private final QSDetailClipper mClipper; private final LightBarController mLightBarController; @@ -94,7 +96,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene mToolbar.setNavigationOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - hide((int) v.getX() + v.getWidth() / 2, (int) v.getY() + v.getHeight() / 2); + hide(); } }); mToolbar.setOnMenuItemClickListener(this); @@ -154,16 +156,20 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene mQs = qs; } + /** Animate and show QSCustomizer panel. + * @param x,y Location on screen of {@code edit} button to determine center of animation. + */ public void show(int x, int y) { if (!isShown) { - mX = x; - mY = y; + int containerLocation[] = findViewById(R.id.customize_container).getLocationOnScreen(); + mX = x - containerLocation[0]; + mY = y - containerLocation[1]; MetricsLogger.visible(getContext(), MetricsProto.MetricsEvent.QS_EDIT); isShown = true; mOpening = true; setTileSpecs(); setVisibility(View.VISIBLE); - mClipper.animateCircularClip(x, y, true, mExpandAnimationListener); + mClipper.animateCircularClip(mX, mY, true, mExpandAnimationListener); queryTiles(); mNotifQsContainer.setCustomizerAnimating(true); mNotifQsContainer.setCustomizerShowing(true); @@ -192,7 +198,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene mTileQueryHelper.queryTiles(mHost); } - public void hide(int x, int y) { + public void hide() { if (isShown) { MetricsLogger.hidden(getContext(), MetricsProto.MetricsEvent.QS_EDIT); isShown = false; @@ -278,16 +284,18 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene }); } } - + /** @param x,y Location on screen of animation center. + */ public void setEditLocation(int x, int y) { - mX = x; - mY = y; + int containerLocation[] = findViewById(R.id.customize_container).getLocationOnScreen(); + mX = x - containerLocation[0]; + mY = y - containerLocation[1]; } private final Callback mKeyguardCallback = () -> { if (!isAttachedToWindow()) return; if (Dependency.get(KeyguardMonitor.class).isShowing() && !mOpening) { - hide(0, 0); + hide(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index e7e756f87c84..9dd5d8fbc776 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -84,16 +84,15 @@ public class QSIconViewImpl extends QSIconView { layout(mIcon, iconLeft, top); } - public void setIcon(QSTile.State state) { - setIcon((ImageView) mIcon, state); + public void setIcon(State state, boolean allowAnimations) { + setIcon((ImageView) mIcon, state, allowAnimations); } - protected void updateIcon(ImageView iv, State state) { + protected void updateIcon(ImageView iv, State state, boolean allowAnimations) { final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon; if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag)) || !Objects.equals(state.slash, iv.getTag(R.id.qs_slash_tag))) { - boolean shouldAnimate = iv.isShown() && mAnimationEnabled - && iv.getDrawable() != null; + boolean shouldAnimate = allowAnimations && shouldAnimate(iv); Drawable d = icon != null ? shouldAnimate ? icon.getDrawable(mContext) : icon.getInvisibleDrawable(mContext) : null; @@ -128,7 +127,11 @@ public class QSIconViewImpl extends QSIconView { } } - protected void setIcon(ImageView iv, QSTile.State state) { + private boolean shouldAnimate(ImageView iv) { + return mAnimationEnabled && iv.isShown() && iv.getDrawable() != null; + } + + protected void setIcon(ImageView iv, QSTile.State state, boolean allowAnimations) { if (state.disabledByPolicy) { iv.setColorFilter(getContext().getColor(R.color.qs_tile_disabled_color)); } else { @@ -137,8 +140,8 @@ public class QSIconViewImpl extends QSIconView { if (state.state != mState) { int color = getColor(state.state); mState = state.state; - if (iv.isShown() && mTint != 0) { - animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state)); + if (mTint != 0 && allowAnimations && shouldAnimate(iv)) { + animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations)); mTint = color; } else { if (iv instanceof AlphaControlledSlashImageView) { @@ -148,10 +151,10 @@ public class QSIconViewImpl extends QSIconView { setTint(iv, color); } mTint = color; - updateIcon(iv, state); + updateIcon(iv, state, allowAnimations); } } else { - updateIcon(iv, state); + updateIcon(iv, state, allowAnimations); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 91afef02a246..d42127e74944 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -47,6 +47,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private static final String TAG = "QSTileBaseView"; private final H mHandler = new H(); + private final int[] mLocInScreen = new int[2]; private final FrameLayout mIconFrame; protected QSIconView mIcon; protected RippleDrawable mRipple; @@ -178,8 +179,9 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { protected void handleStateChanged(QSTile.State state) { int circleColor = getCircleColor(state.state); + boolean allowAnimations = animationsEnabled(); if (circleColor != mCircleColor) { - if (mBg.isShown() && animationsEnabled()) { + if (allowAnimations) { ValueAnimator animator = ValueAnimator.ofArgb(mCircleColor, circleColor) .setDuration(QS_ANIM_LENGTH); animator.addUpdateListener(animation -> mBg.setImageTintList(ColorStateList.valueOf( @@ -192,7 +194,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } setClickable(state.state != Tile.STATE_UNAVAILABLE); - mIcon.setIcon(state); + mIcon.setIcon(state, allowAnimations); setContentDescription(state.contentDescription); mAccessibilityClass = state.expandedAccessibilityClassName; @@ -205,8 +207,17 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } } + /* The view should not be animated if it's not on screen and no part of it is visible. + */ protected boolean animationsEnabled() { - return true; + if (!isShown()) { + return false; + } + if (getAlpha() != 1f) { + return false; + } + getLocationOnScreen(mLocInScreen); + return mLocInScreen[1] >= -getHeight(); } private int getCircleColor(int state) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java index c0171048e974..35e9d55c83b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java @@ -36,10 +36,20 @@ import java.util.stream.Stream; * remove notifications that appear on screen for a period of time and dismiss themselves at the * appropriate time. These include heads up notifications and ambient pulses. */ -public abstract class AlertingNotificationManager { +public abstract class AlertingNotificationManager implements NotificationLifetimeExtender { private static final String TAG = "AlertNotifManager"; protected final Clock mClock = new Clock(); protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>(); + + /** + * This is the list of entries that have already been removed from the + * NotificationManagerService side, but we keep it to prevent the UI from looking weird and + * will remove when possible. See {@link NotificationLifetimeExtender} + */ + protected final ArraySet<NotificationData.Entry> mExtendedLifetimeAlertEntries = + new ArraySet<>(); + + protected NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback; protected int mMinimumDisplayTime; protected int mAutoDismissNotificationDecay; @VisibleForTesting @@ -74,7 +84,7 @@ public abstract class AlertingNotificationManager { if (alertEntry == null) { return true; } - if (releaseImmediately || alertEntry.wasShownLongEnough()) { + if (releaseImmediately || canRemoveImmediately(key)) { removeAlertEntry(key); } else { alertEntry.removeAsSoonAsPossible(); @@ -191,6 +201,12 @@ public abstract class AlertingNotificationManager { onAlertEntryRemoved(alertEntry); entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); alertEntry.reset(); + if (mExtendedLifetimeAlertEntries.contains(entry)) { + if (mNotificationLifetimeFinishedCallback != null) { + mNotificationLifetimeFinishedCallback.onSafeToRemove(key); + } + mExtendedLifetimeAlertEntries.remove(entry); + } } /** @@ -207,6 +223,40 @@ public abstract class AlertingNotificationManager { return new AlertEntry(); } + /** + * Whether or not the alert can be removed currently. If it hasn't been on screen long enough + * it should not be removed unless forced + * @param key the key to check if removable + * @return true if the alert entry can be removed + */ + protected boolean canRemoveImmediately(String key) { + AlertEntry alertEntry = mAlertEntries.get(key); + return alertEntry == null || alertEntry.wasShownLongEnough(); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // NotificationLifetimeExtender Methods + + @Override + public void setCallback(NotificationSafeToRemoveCallback callback) { + mNotificationLifetimeFinishedCallback = callback; + } + + @Override + public boolean shouldExtendLifetime(NotificationData.Entry entry) { + return !canRemoveImmediately(entry.key); + } + + @Override + public void setShouldExtendLifetime(NotificationData.Entry entry, boolean shouldExtend) { + if (shouldExtend) { + mExtendedLifetimeAlertEntries.add(entry); + } else { + mExtendedLifetimeAlertEntries.remove(entry); + } + } + /////////////////////////////////////////////////////////////////////////////////////////////// + protected class AlertEntry implements Comparable<AlertEntry> { @Nullable public NotificationData.Entry mEntry; public long mPostTime; @@ -214,11 +264,11 @@ public abstract class AlertingNotificationManager { @Nullable protected Runnable mRemoveAlertRunnable; - public void setEntry(@Nullable final NotificationData.Entry entry) { + public void setEntry(@NonNull final NotificationData.Entry entry) { setEntry(entry, () -> removeAlertEntry(entry.key)); } - public void setEntry(@Nullable final NotificationData.Entry entry, + public void setEntry(@NonNull final NotificationData.Entry entry, @Nullable Runnable removeAlertRunnable) { mEntry = entry; mRemoveAlertRunnable = removeAlertRunnable; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 909cd794f3fe..e19c8441c44d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -160,7 +160,8 @@ public class CommandQueue extends IStatusBar.Stub { default void onRotationProposal(int rotation, boolean isValid) { } - default void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver) { } + default void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, + int type) { } default void onBiometricAuthenticated() { } default void onBiometricHelp(String message) { } default void onBiometricError(String error) { } @@ -513,11 +514,12 @@ public class CommandQueue extends IStatusBar.Stub { } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver) { + public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; args.arg2 = receiver; + args.argi1 = type; mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args) .sendToTarget(); } @@ -756,11 +758,14 @@ public class CommandQueue extends IStatusBar.Stub { mHandler.removeMessages(MSG_BIOMETRIC_ERROR); mHandler.removeMessages(MSG_BIOMETRIC_HELP); mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED); + SomeArgs someArgs = (SomeArgs) msg.obj; for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).showBiometricDialog( - (Bundle)((SomeArgs)msg.obj).arg1, - (IBiometricPromptReceiver)((SomeArgs)msg.obj).arg2); + (Bundle) someArgs.arg1, + (IBiometricPromptReceiver) someArgs.arg2, + someArgs.argi1); } + someArgs.recycle(); break; case MSG_BIOMETRIC_AUTHENTICATED: for (int i = 0; i < mCallbacks.size(); i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java new file mode 100644 index 000000000000..42e380f9b076 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java @@ -0,0 +1,53 @@ +package com.android.systemui.statusbar; + +import com.android.systemui.statusbar.notification.NotificationData; + +import androidx.annotation.NonNull; + +/** + * Interface for anything that may need to keep notifications managed even after + * {@link NotificationListener} removes it. The lifetime extender is in charge of performing the + * callback when the notification is then safe to remove. + */ +public interface NotificationLifetimeExtender { + + /** + * Set the handler to callback to when the notification is safe to remove. + * + * @param callback the handler to callback + */ + void setCallback(@NonNull NotificationSafeToRemoveCallback callback); + + /** + * Determines whether or not the extender needs the notification kept after removal. + * + * @param entry the entry containing the notification to check + * @return true if the notification lifetime should be extended + */ + boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry); + + /** + * Sets whether or not the lifetime should be extended. In practice, if shouldExtend is + * true, this is where the extender starts managing the entry internally and is now + * responsible for calling {@link NotificationSafeToRemoveCallback#onSafeToRemove(String)} when + * the entry is safe to remove. If shouldExtend is false, the extender no longer needs to + * worry about it (either because we will be removing it anyway or the entry is no longer + * removed due to an update). + * + * @param entry the entry to mark as having an extended lifetime + * @param shouldExtend true if the extender should manage the entry now, false otherwise + */ + void setShouldExtendLifetime(@NonNull NotificationData.Entry entry, boolean shouldExtend); + + /** + * The callback for when the notification is now safe to remove (i.e. its lifetime has ended). + */ + interface NotificationSafeToRemoveCallback { + /** + * Called when the lifetime extender determines it's safe to remove. + * + * @param key key of the entry that is now safe to remove + */ + void onSafeToRemove(String key); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 9b375df46034..cfa09bc93adb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -76,7 +76,6 @@ public class NotificationListener extends NotificationListenerWithPlugins { mPresenter.getHandler().post(() -> { processForRemoteInput(sbn.getNotification(), mContext); String key = sbn.getKey(); - mEntryManager.removeKeyKeptForRemoteInput(key); boolean isUpdate = mEntryManager.getNotificationData().get(key) != null; // In case we don't allow child notifications, we ignore children of diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 929713c2c50b..1a3e812a1368 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -17,8 +17,10 @@ package com.android.systemui.statusbar; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityOptions; +import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; import android.content.Context; @@ -29,6 +31,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserManager; import android.service.notification.StatusBarNotification; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.view.MotionEvent; @@ -50,6 +53,7 @@ import com.android.systemui.statusbar.policy.RemoteInputView; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Set; /** @@ -61,10 +65,10 @@ import java.util.Set; public class NotificationRemoteInputManager implements Dumpable { public static final boolean ENABLE_REMOTE_INPUT = SystemProperties.getBoolean("debug.enable_remote_input", true); - public static final boolean FORCE_REMOTE_INPUT_HISTORY = + public static boolean FORCE_REMOTE_INPUT_HISTORY = SystemProperties.getBoolean("debug.force_remoteinput_history", true); private static final boolean DEBUG = false; - private static final String TAG = "NotificationRemoteInputManager"; + private static final String TAG = "NotifRemoteInputManager"; /** * How long to wait before auto-dismissing a notification that was kept for remote input, and @@ -74,12 +78,25 @@ public class NotificationRemoteInputManager implements Dumpable { */ private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200; - protected final ArraySet<NotificationData.Entry> mRemoteInputEntriesToRemoveOnCollapse = + /** + * Notifications that are already removed but are kept around because we want to show the + * remote input history. See {@link RemoteInputHistoryExtender} and + * {@link SmartReplyHistoryExtender}. + */ + protected final ArraySet<String> mKeysKeptForRemoteInputHistory = new ArraySet<>(); + + /** + * Notifications that are already removed but are kept around because the remote input is + * actively being used (i.e. user is typing in it). See {@link RemoteInputActiveExtender}. + */ + protected final ArraySet<NotificationData.Entry> mEntriesKeptForRemoteInputActive = new ArraySet<>(); // Dependencies: protected final NotificationLockscreenUserManager mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class); + protected final SmartReplyController mSmartReplyController = + Dependency.get(SmartReplyController.class); protected final Context mContext; private final UserManager mUserManager; @@ -87,8 +104,11 @@ public class NotificationRemoteInputManager implements Dumpable { protected RemoteInputController mRemoteInputController; protected NotificationPresenter mPresenter; protected NotificationEntryManager mEntryManager; + protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback + mNotificationLifetimeFinishedCallback; protected IStatusBarService mBarService; protected Callback mCallback; + protected final ArrayList<NotificationLifetimeExtender> mLifetimeExtenders = new ArrayList<>(); private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { @@ -276,6 +296,7 @@ public class NotificationRemoteInputManager implements Dumpable { mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + addLifetimeExtenders(); } public void setUpWithPresenter(NotificationPresenter presenter, @@ -290,16 +311,16 @@ public class NotificationRemoteInputManager implements Dumpable { @Override public void onRemoteInputSent(NotificationData.Entry entry) { if (FORCE_REMOTE_INPUT_HISTORY - && mEntryManager.isNotificationKeptForRemoteInput(entry.key)) { - mEntryManager.removeNotification(entry.key, null); - } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) { + && isNotificationKeptForRemoteInputHistory(entry.key)) { + mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.key); + } else if (mEntriesKeptForRemoteInputActive.contains(entry)) { // We're currently holding onto this notification, but from the apps point of // view it is already canceled, so we'll need to cancel it on the apps behalf // after sending - unless the app posts an update in the mean time, so wait a // bit. mPresenter.getHandler().postDelayed(() -> { - if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) { - mEntryManager.removeNotification(entry.key, null); + if (mEntriesKeptForRemoteInputActive.remove(entry)) { + mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.key); } }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); } @@ -310,45 +331,74 @@ public class NotificationRemoteInputManager implements Dumpable { } } }); - + mSmartReplyController.setCallback((entry, reply) -> { + StatusBarNotification newSbn = + rebuildNotificationWithRemoteInput(entry, reply, true /* showSpinner */); + mEntryManager.updateNotification(newSbn, null /* ranking */); + }); } - public RemoteInputController getController() { - return mRemoteInputController; + /** + * Adds all the notification lifetime extenders. Each extender represents a reason for the + * NotificationRemoteInputManager to keep a notification lifetime extended. + */ + protected void addLifetimeExtenders() { + mLifetimeExtenders.add(new RemoteInputHistoryExtender()); + mLifetimeExtenders.add(new SmartReplyHistoryExtender()); + mLifetimeExtenders.add(new RemoteInputActiveExtender()); } - public void onUpdateNotification(NotificationData.Entry entry) { - mRemoteInputEntriesToRemoveOnCollapse.remove(entry); + public ArrayList<NotificationLifetimeExtender> getLifetimeExtenders() { + return mLifetimeExtenders; } - /** - * Returns true if NotificationRemoteInputManager wants to keep this notification around. - * - * @param entry notification being removed - */ - public boolean onRemoveNotification(NotificationData.Entry entry) { - if (entry != null && mRemoteInputController.isRemoteInputActive(entry) - && (entry.row != null && !entry.row.isDismissed())) { - mRemoteInputEntriesToRemoveOnCollapse.add(entry); - return true; - } - return false; + public RemoteInputController getController() { + return mRemoteInputController; } public void onPerformRemoveNotification(StatusBarNotification n, NotificationData.Entry entry) { + if (mKeysKeptForRemoteInputHistory.contains(n.getKey())) { + mKeysKeptForRemoteInputHistory.remove(n.getKey()); + } if (mRemoteInputController.isRemoteInputActive(entry)) { mRemoteInputController.removeRemoteInput(entry, null); } } - public void removeRemoteInputEntriesKeptUntilCollapsed() { - for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) { - NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i); + public void onPanelCollapsed() { + for (int i = 0; i < mEntriesKeptForRemoteInputActive.size(); i++) { + NotificationData.Entry entry = mEntriesKeptForRemoteInputActive.valueAt(i); mRemoteInputController.removeRemoteInput(entry, null); - mEntryManager.removeNotification(entry.key, mEntryManager.getLatestRankingMap()); + if (mNotificationLifetimeFinishedCallback != null) { + mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.key); + } + } + mEntriesKeptForRemoteInputActive.clear(); + } + + public boolean isNotificationKeptForRemoteInputHistory(String key) { + return mKeysKeptForRemoteInputHistory.contains(key); + } + + public boolean shouldKeepForRemoteInputHistory(NotificationData.Entry entry) { + if (entry.row == null || entry.row.isDismissed()) { + return false; } - mRemoteInputEntriesToRemoveOnCollapse.clear(); + if (!FORCE_REMOTE_INPUT_HISTORY) { + return false; + } + return (mRemoteInputController.isSpinning(entry.key) || entry.hasJustSentRemoteInput()); + } + + public boolean shouldKeepForSmartReplyHistory(NotificationData.Entry entry) { + if (entry.row == null || entry.row.isDismissed()) { + return false; + } + if (!FORCE_REMOTE_INPUT_HISTORY) { + return false; + } + return mSmartReplyController.isSendingSmartReply(entry.key); } public void checkRemoteInputOutside(MotionEvent event) { @@ -359,11 +409,63 @@ public class NotificationRemoteInputManager implements Dumpable { } } + @VisibleForTesting + StatusBarNotification rebuildNotificationForCanceledSmartReplies( + NotificationData.Entry entry) { + return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */, + false /* showSpinner */); + } + + @VisibleForTesting + StatusBarNotification rebuildNotificationWithRemoteInput(NotificationData.Entry entry, + CharSequence remoteInputText, boolean showSpinner) { + StatusBarNotification sbn = entry.notification; + + Notification.Builder b = Notification.Builder + .recoverBuilder(mContext, sbn.getNotification().clone()); + if (remoteInputText != null) { + CharSequence[] oldHistory = sbn.getNotification().extras + .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + CharSequence[] newHistory; + if (oldHistory == null) { + newHistory = new CharSequence[1]; + } else { + newHistory = new CharSequence[oldHistory.length + 1]; + System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length); + } + newHistory[0] = String.valueOf(remoteInputText); + b.setRemoteInputHistory(newHistory); + } + b.setShowRemoteInputSpinner(showSpinner); + b.setHideSmartReplies(true); + + Notification newNotification = b.build(); + + // Undo any compatibility view inflation + newNotification.contentView = sbn.getNotification().contentView; + newNotification.bigContentView = sbn.getNotification().bigContentView; + newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; + + return new StatusBarNotification( + sbn.getPackageName(), + sbn.getOpPkg(), + sbn.getId(), + sbn.getTag(), + sbn.getUid(), + sbn.getInitialPid(), + newNotification, + sbn.getUser(), + sbn.getOverrideGroupKey(), + sbn.getPostTime()); + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NotificationRemoteInputManager state:"); - pw.print(" mRemoteInputEntriesToRemoveOnCollapse: "); - pw.println(mRemoteInputEntriesToRemoveOnCollapse); + pw.print(" mKeysKeptForRemoteInputHistory: "); + pw.println(mKeysKeptForRemoteInputHistory); + pw.print(" mEntriesKeptForRemoteInputActive: "); + pw.println(mEntriesKeptForRemoteInputActive); } public void bindRow(ExpandableNotificationRow row) { @@ -372,8 +474,133 @@ public class NotificationRemoteInputManager implements Dumpable { } @VisibleForTesting - public Set<NotificationData.Entry> getRemoteInputEntriesToRemoveOnCollapse() { - return mRemoteInputEntriesToRemoveOnCollapse; + public Set<NotificationData.Entry> getEntriesKeptForRemoteInputActive() { + return mEntriesKeptForRemoteInputActive; + } + + /** + * NotificationRemoteInputManager has multiple reasons to keep notification lifetime extended + * so we implement multiple NotificationLifetimeExtenders + */ + protected abstract class RemoteInputExtender implements NotificationLifetimeExtender { + @Override + public void setCallback(NotificationSafeToRemoveCallback callback) { + if (mNotificationLifetimeFinishedCallback == null) { + mNotificationLifetimeFinishedCallback = callback; + } + } + } + + /** + * Notification is kept alive as it was cancelled in response to a remote input interaction. + * This allows us to show what you replied and allows you to continue typing into it. + */ + protected class RemoteInputHistoryExtender extends RemoteInputExtender { + @Override + public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) { + return shouldKeepForRemoteInputHistory(entry); + } + + @Override + public void setShouldExtendLifetime(NotificationData.Entry entry, + boolean shouldExtend) { + if (shouldExtend) { + CharSequence remoteInputText = entry.remoteInputText; + if (TextUtils.isEmpty(remoteInputText)) { + remoteInputText = entry.remoteInputTextWhenReset; + } + StatusBarNotification newSbn = rebuildNotificationWithRemoteInput(entry, + remoteInputText, false /* showSpinner */); + entry.onRemoteInputInserted(); + + if (newSbn == null) { + return; + } + + mEntryManager.updateNotification(newSbn, null); + + // Ensure the entry hasn't already been removed. This can happen if there is an + // inflation exception while updating the remote history + if (entry.row == null || entry.row.isRemoved()) { + return; + } + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Keeping notification around after sending remote input " + + entry.key); + } + + mKeysKeptForRemoteInputHistory.add(entry.key); + } else { + mKeysKeptForRemoteInputHistory.remove(entry.key); + } + } + } + + /** + * Notification is kept alive for smart reply history. Similar to REMOTE_INPUT_HISTORY but with + * {@link SmartReplyController} specific logic + */ + protected class SmartReplyHistoryExtender extends RemoteInputExtender { + @Override + public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) { + return shouldKeepForSmartReplyHistory(entry); + } + + @Override + public void setShouldExtendLifetime(NotificationData.Entry entry, + boolean shouldExtend) { + if (shouldExtend) { + StatusBarNotification newSbn = rebuildNotificationForCanceledSmartReplies(entry); + + if (newSbn == null) { + return; + } + + mEntryManager.updateNotification(newSbn, null); + + if (entry.row == null || entry.row.isRemoved()) { + return; + } + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Keeping notification around after sending smart reply " + + entry.key); + } + + mKeysKeptForRemoteInputHistory.add(entry.key); + } else { + mKeysKeptForRemoteInputHistory.remove(entry.key); + mSmartReplyController.stopSending(entry); + } + } + } + + /** + * Notification is kept alive because the user is still using the remote input + */ + protected class RemoteInputActiveExtender extends RemoteInputExtender { + @Override + public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) { + if (entry.row == null || entry.row.isDismissed()) { + return false; + } + return mRemoteInputController.isRemoteInputActive(entry); + } + + @Override + public void setShouldExtendLifetime(NotificationData.Entry entry, + boolean shouldExtend) { + if (shouldExtend) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Keeping notification around while remote input active " + + entry.key); + } + mEntriesKeptForRemoteInputActive.add(entry); + } else { + mEntriesKeptForRemoteInputActive.remove(entry); + } + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java index e43c9e5a2cf2..fb888ddc5f13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java @@ -33,20 +33,19 @@ import java.util.Set; public class SmartReplyController { private IStatusBarService mBarService; private Set<String> mSendingKeys = new ArraySet<>(); + private Callback mCallback; public SmartReplyController() { mBarService = Dependency.get(IStatusBarService.class); } + public void setCallback(Callback callback) { + mCallback = callback; + } + public void smartReplySent(NotificationData.Entry entry, int replyIndex, CharSequence reply) { - NotificationEntryManager notificationEntryManager - = Dependency.get(NotificationEntryManager.class); - StatusBarNotification newSbn = - notificationEntryManager.rebuildNotificationWithRemoteInput(entry, reply, - true /* showSpinner */); - notificationEntryManager.updateNotification(newSbn, null /* ranking */); + mCallback.onSmartReplySent(entry, reply); mSendingKeys.add(entry.key); - try { mBarService.onNotificationSmartReplySent(entry.notification.getKey(), replyIndex); @@ -77,4 +76,17 @@ public class SmartReplyController { mSendingKeys.remove(entry.notification.getKey()); } } + + /** + * Callback for any class that needs to do something in response to a smart reply being sent. + */ + public interface Callback { + /** + * A smart reply has just been sent for a notification + * + * @param entry the entry for the notification + * @param reply the reply that was sent + */ + void onSmartReplySent(NotificationData.Entry entry, CharSequence reply); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index b655a6bab5b8..906bbb9cdd88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -37,7 +37,6 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; @@ -58,6 +57,7 @@ import com.android.systemui.ForegroundServiceController; import com.android.systemui.R; import com.android.systemui.UiOffloadThread; import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; @@ -65,7 +65,6 @@ import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.NotificationUpdateHandler; -import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.row.NotificationInflater; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -100,8 +99,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. protected final Context mContext; protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>(); protected final NotificationClicker mNotificationClicker = new NotificationClicker(); - protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch = - new ArraySet<>(); // Dependencies: protected final NotificationLockscreenUserManager mLockscreenUserManager = @@ -124,8 +121,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. Dependency.get(ForegroundServiceController.class); protected final NotificationListener mNotificationListener = Dependency.get(NotificationListener.class); - private final SmartReplyController mSmartReplyController = - Dependency.get(SmartReplyController.class); protected IStatusBarService mBarService; protected NotificationPresenter mPresenter; @@ -139,13 +134,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. protected boolean mUseHeadsUp = false; protected boolean mDisableNotificationAlerts; protected NotificationListContainer mListContainer; + protected final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders + = new ArrayList<>(); private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; - /** - * Notifications with keys in this set are not actually around anymore. We kept them around - * when they were canceled in response to a remote input interaction. This allows us to show - * what you replied and allows you to continue typing into it. - */ - private final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>(); private final class NotificationClicker implements View.OnClickListener { @@ -198,14 +189,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } }; - public NotificationListenerService.RankingMap getLatestRankingMap() { - return mLatestRankingMap; - } - - public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) { - mLatestRankingMap = latestRankingMap; - } - public void setDisableNotificationAlerts(boolean disableNotificationAlerts) { mDisableNotificationAlerts = disableNotificationAlerts; mHeadsUpObserver.onChange(true); @@ -215,18 +198,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); } - public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { - if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { - removeNotification(entry.key, getLatestRankingMap()); - mHeadsUpEntriesToRemoveOnSwitch.remove(entry); - if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { - setLatestRankingMap(null); - } - } else { - updateNotificationRanking(null); - } - } - @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NotificationEntryManager state:"); @@ -240,8 +211,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } pw.print(" mUseHeadsUp="); pw.println(mUseHeadsUp); - pw.print(" mKeysKeptForRemoteInput: "); - pw.println(mKeysKeptForRemoteInput); } public NotificationEntryManager(Context context) { @@ -294,6 +263,14 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mHeadsUpObserver); } + mNotificationLifetimeExtenders.add(mHeadsUpManager); + mNotificationLifetimeExtenders.add(mGutsManager); + mNotificationLifetimeExtenders.addAll(mRemoteInputManager.getLifetimeExtenders()); + + for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) { + extender.setCallback(key -> removeNotification(key, mLatestRankingMap)); + } + mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); mHeadsUpObserver.onChange(true); // set up @@ -397,11 +374,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. true); NotificationData.Entry entry = mNotificationData.get(n.getKey()); - if (FORCE_REMOTE_INPUT_HISTORY - && mKeysKeptForRemoteInput.contains(n.getKey())) { - mKeysKeptForRemoteInput.remove(n.getKey()); - } - mRemoteInputManager.onPerformRemoveNotification(n, entry); final String pkg = n.getPackageName(); final String tag = n.getTag(); @@ -433,7 +405,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. * WARNING: this will call back into us. Don't hold any locks. */ void handleNotificationError(StatusBarNotification n, String message) { - removeNotification(n.getKey(), null); + removeNotificationInternal(n.getKey(), null, true /* forceRemove */); try { mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), n.getInitialPid(), message, n.getUserId()); @@ -487,7 +459,11 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. @Override public void removeNotification(String key, NotificationListenerService.RankingMap ranking) { - boolean deferRemoval = false; + removeNotificationInternal(key, ranking, false /* forceRemove */); + } + + private void removeNotificationInternal(String key, + @Nullable NotificationListenerService.RankingMap ranking, boolean forceRemove) { abortExistingInflation(key); if (mHeadsUpManager.contains(key)) { // A cancel() in response to a remote input shouldn't be delayed, as it makes the @@ -497,154 +473,53 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key) && !FORCE_REMOTE_INPUT_HISTORY || !mVisualStabilityManager.isReorderingAllowed(); - deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); - } - mMediaManager.onNotificationRemoved(key); - - NotificationData.Entry entry = mNotificationData.get(key); - if (FORCE_REMOTE_INPUT_HISTORY - && shouldKeepForRemoteInput(entry) - && entry.row != null && !entry.row.isDismissed()) { - CharSequence remoteInputText = entry.remoteInputText; - if (TextUtils.isEmpty(remoteInputText)) { - remoteInputText = entry.remoteInputTextWhenReset; - } - StatusBarNotification newSbn = rebuildNotificationWithRemoteInput(entry, - remoteInputText, false /* showSpinner */); - boolean updated = false; - entry.onRemoteInputInserted(); - try { - updateNotificationInternal(newSbn, null); - updated = true; - } catch (InflationException e) { - deferRemoval = false; - } - if (updated) { - Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key); - addKeyKeptForRemoteInput(entry.key); - return; - } - } - if (FORCE_REMOTE_INPUT_HISTORY - && shouldKeepForSmartReply(entry) - && entry.row != null && !entry.row.isDismissed()) { - // Turn off the spinner and hide buttons when an app cancels the notification. - StatusBarNotification newSbn = rebuildNotificationForCanceledSmartReplies(entry); - boolean updated = false; - try { - updateNotificationInternal(newSbn, null); - updated = true; - } catch (InflationException e) { - // Ignore just don't keep the notification around. - } - // Treat the reply as longer sending. - mSmartReplyController.stopSending(entry); - if (updated) { - Log.w(TAG, "Keeping notification around after sending smart reply " + entry.key); - addKeyKeptForRemoteInput(entry.key); - return; - } + // Attempt to remove notification. + mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); } - // Actually removing notification so smart reply controller can forget about it. - mSmartReplyController.stopSending(entry); + NotificationData.Entry entry = mNotificationData.get(key); - if (deferRemoval) { - mLatestRankingMap = ranking; - mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key)); + if (entry == null) { + mCallback.onNotificationRemoved(key, null /* old */); return; } - if (mRemoteInputManager.onRemoveNotification(entry)) { - mLatestRankingMap = ranking; - return; + // If a manager needs to keep the notification around for whatever reason, we return early + // and keep the notification + if (!forceRemove) { + for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) { + if (extender.shouldExtendLifetime(entry)) { + mLatestRankingMap = ranking; + extender.setShouldExtendLifetime(entry, true /* shouldExtend */); + return; + } + } } - if (entry != null && mGutsManager.getExposedGuts() != null - && mGutsManager.getExposedGuts() == entry.row.getGuts() - && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) { - Log.w(TAG, "Keeping notification because it's showing guts. " + key); - mLatestRankingMap = ranking; - mGutsManager.setKeyToRemoveOnGutsClosed(key); - return; - } + // At this point, we are guaranteed the notification will be removed - if (entry != null) { - mForegroundServiceController.removeNotification(entry.notification); + // Ensure any managers keeping the lifetime extended stop managing the entry + for (NotificationLifetimeExtender extender: mNotificationLifetimeExtenders) { + extender.setShouldExtendLifetime(entry, false /* shouldExtend */); } - if (entry != null && entry.row != null) { + mMediaManager.onNotificationRemoved(key); + mForegroundServiceController.removeNotification(entry.notification); + + if (entry.row != null) { entry.row.setRemoved(); mListContainer.cleanUpViewState(entry.row); } + // Let's remove the children if this was a summary handleGroupSummaryRemoved(key); + StatusBarNotification old = removeNotificationViews(key, ranking); mCallback.onNotificationRemoved(key, old); } - public StatusBarNotification rebuildNotificationWithRemoteInput(NotificationData.Entry entry, - CharSequence remoteInputText, boolean showSpinner) { - StatusBarNotification sbn = entry.notification; - - Notification.Builder b = Notification.Builder - .recoverBuilder(mContext, sbn.getNotification().clone()); - if (remoteInputText != null) { - CharSequence[] oldHistory = sbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); - CharSequence[] newHistory; - if (oldHistory == null) { - newHistory = new CharSequence[1]; - } else { - newHistory = new CharSequence[oldHistory.length + 1]; - System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length); - } - newHistory[0] = String.valueOf(remoteInputText); - b.setRemoteInputHistory(newHistory); - } - b.setShowRemoteInputSpinner(showSpinner); - b.setHideSmartReplies(true); - - Notification newNotification = b.build(); - - // Undo any compatibility view inflation - newNotification.contentView = sbn.getNotification().contentView; - newNotification.bigContentView = sbn.getNotification().bigContentView; - newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; - - StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(), - sbn.getOpPkg(), - sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), - newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); - return newSbn; - } - - @VisibleForTesting - StatusBarNotification rebuildNotificationForCanceledSmartReplies( - NotificationData.Entry entry) { - return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */, - false /* showSpinner */); - } - - private boolean shouldKeepForSmartReply(NotificationData.Entry entry) { - return entry != null && mSmartReplyController.isSendingSmartReply(entry.key); - } - - private boolean shouldKeepForRemoteInput(NotificationData.Entry entry) { - if (entry == null) { - return false; - } - if (mRemoteInputManager.getController().isSpinning(entry.key)) { - return true; - } - if (entry.hasJustSentRemoteInput()) { - return true; - } - return false; - } - private StatusBarNotification removeNotificationViews(String key, NotificationListenerService.RankingMap ranking) { NotificationData.Entry entry = mNotificationData.remove(key, ranking); @@ -683,9 +558,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. NotificationData.Entry childEntry = row.getEntry(); boolean isForeground = (row.getStatusBarNotification().getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; - boolean keepForReply = FORCE_REMOTE_INPUT_HISTORY - && (shouldKeepForRemoteInput(childEntry) - || shouldKeepForSmartReply(childEntry)); + boolean keepForReply = + mRemoteInputManager.shouldKeepForRemoteInputHistory(childEntry) + || mRemoteInputManager.shouldKeepForSmartReplyHistory(childEntry); if (isForeground || keepForReply) { // the child is a foreground service notification which we can't remove or it's // a child we're keeping around for reply! @@ -868,13 +743,11 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. if (entry == null) { return; } - mHeadsUpEntriesToRemoveOnSwitch.remove(entry); - mRemoteInputManager.onUpdateNotification(entry); - mSmartReplyController.stopSending(entry); - if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) { - mGutsManager.setKeyToRemoveOnGutsClosed(null); - Log.w(TAG, "Notification that was kept for guts was updated. " + key); + // Notification is updated so it is essentially re-added and thus alive again. Don't need + // to keep it's lifetime extended. + for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) { + extender.setShouldExtendLifetime(entry, false /* shouldExtend */); } Notification n = notification.getNotification(); @@ -1080,20 +953,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. return mHeadsUpManager.contains(key); } - public boolean isNotificationKeptForRemoteInput(String key) { - return mKeysKeptForRemoteInput.contains(key); - } - - public void removeKeyKeptForRemoteInput(String key) { - mKeysKeptForRemoteInput.remove(key); - } - - public void addKeyKeptForRemoteInput(String key) { - if (FORCE_REMOTE_INPUT_HISTORY) { - mKeysKeptForRemoteInput.add(key); - } - } - /** * Callback for NotificationEntryManager. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index e635976310a6..a096baa2df9d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -38,27 +38,27 @@ import android.view.HapticFeedbackConstants; import android.view.View; import android.view.accessibility.AccessibilityManager; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.statusbar.NotificationLifetimeExtender; +import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; -import androidx.annotation.VisibleForTesting; - /** * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and * closing guts, and keeping track of the currently exposed notification guts. */ -public class NotificationGutsManager implements Dumpable { +public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender { private static final String TAG = "NotificationGutsManager"; // Must match constant in Settings. Used to highlight preferences when linking to Settings. @@ -75,12 +75,13 @@ public class NotificationGutsManager implements Dumpable { // which notification is currently being longpress-examined by the user private NotificationGuts mNotificationGutsExposed; private NotificationMenuRowPlugin.MenuItem mGutsMenuItem; - protected NotificationPresenter mPresenter; - protected NotificationEntryManager mEntryManager; + private NotificationPresenter mPresenter; + private NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback; private NotificationListContainer mListContainer; private NotificationInfo.CheckSaveListener mCheckSaveListener; private OnSettingsClickListener mOnSettingsClickListener; - private String mKeyToRemoveOnGutsClosed; + @VisibleForTesting + protected String mKeyToRemoveOnGutsClosed; public NotificationGutsManager(Context context) { mContext = context; @@ -91,24 +92,15 @@ public class NotificationGutsManager implements Dumpable { } public void setUpWithPresenter(NotificationPresenter presenter, - NotificationEntryManager entryManager, NotificationListContainer listContainer, + NotificationListContainer listContainer, NotificationInfo.CheckSaveListener checkSaveListener, OnSettingsClickListener onSettingsClickListener) { mPresenter = presenter; - mEntryManager = entryManager; mListContainer = listContainer; mCheckSaveListener = checkSaveListener; mOnSettingsClickListener = onSettingsClickListener; } - public String getKeyToRemoveOnGutsClosed() { - return mKeyToRemoveOnGutsClosed; - } - - public void setKeyToRemoveOnGutsClosed(String keyToRemoveOnGutsClosed) { - mKeyToRemoveOnGutsClosed = keyToRemoveOnGutsClosed; - } - public void onDensityOrFontScaleChanged(ExpandableNotificationRow row) { setExposedGuts(row.getGuts()); bindGuts(row); @@ -171,7 +163,9 @@ public class NotificationGutsManager implements Dumpable { String key = sbn.getKey(); if (key.equals(mKeyToRemoveOnGutsClosed)) { mKeyToRemoveOnGutsClosed = null; - mEntryManager.removeNotification(key, mEntryManager.getLatestRankingMap()); + if (mNotificationLifetimeFinishedCallback != null) { + mNotificationLifetimeFinishedCallback.onSafeToRemove(key); + } } }); @@ -410,6 +404,37 @@ public class NotificationGutsManager implements Dumpable { } @Override + public void setCallback(NotificationSafeToRemoveCallback callback) { + mNotificationLifetimeFinishedCallback = callback; + } + + @Override + public boolean shouldExtendLifetime(NotificationData.Entry entry) { + return entry != null + &&(mNotificationGutsExposed != null + && entry.row.getGuts() != null + && mNotificationGutsExposed == entry.row.getGuts() + && !mNotificationGutsExposed.isLeavebehind()); + } + + @Override + public void setShouldExtendLifetime(NotificationData.Entry entry, boolean shouldExtend) { + if (shouldExtend) { + mKeyToRemoveOnGutsClosed = entry.key; + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Keeping notification because it's showing guts. " + entry.key); + } + } else { + if (mKeyToRemoveOnGutsClosed != null && mKeyToRemoveOnGutsClosed.equals(entry.key)) { + mKeyToRemoveOnGutsClosed = null; + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Notification that was kept for guts was updated. " + entry.key); + } + } + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NotificationGutsManager state:"); pw.print(" mKeyToRemoveOnGutsClosed: "); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 6150c2f29391..4a059895ffe4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -258,18 +258,6 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, return mTrackingHeadsUp; } - /** - * React to the removal of the notification in the heads up. - * - * @return true if the notification was removed and false if it still needs to be kept around - * for a bit since it wasn't shown long enough - */ - @Override - public boolean removeNotification(@NonNull String key, boolean releaseImmediately) { - return super.removeNotification(key, canRemoveImmediately(key) - || releaseImmediately); - } - @Override public void snooze() { super.snooze(); @@ -405,7 +393,8 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, return (HeadsUpEntryPhone) getTopHeadsUpEntry(); } - private boolean canRemoveImmediately(@NonNull String key) { + @Override + protected boolean canRemoveImmediately(@NonNull String key) { if (mSwipedOutKeys.contains(key)) { // We always instantly dismiss views being manually swiped out. mSwipedOutKeys.remove(key); @@ -414,7 +403,8 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key); HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone(); - return headsUpEntry != topEntry || headsUpEntry.wasShownLongEnough(); + + return headsUpEntry == null || headsUpEntry != topEntry || super.canRemoveImmediately(key); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 57e01e7cd4f6..a900c14b2007 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -596,9 +596,10 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mContext.getString(R.string.instant_apps)); mCurrentNotifs.add(new Pair<>(pkg, userId)); String message = mContext.getString(R.string.instant_apps_message); - PendingIntent appInfoAction = PendingIntent.getActivity(mContext, 0, + UserHandle user = UserHandle.of(userId); + PendingIntent appInfoAction = PendingIntent.getActivityAsUser(mContext, 0, new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - .setData(Uri.fromParts("package", pkg, null)), 0); + .setData(Uri.fromParts("package", pkg, null)), 0, null, user); Action action = new Notification.Action.Builder(null, mContext.getString(R.string.app_info), appInfoAction).build(); @@ -611,8 +612,8 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, .addFlags(Intent.FLAG_IGNORE_EPHEMERAL) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PendingIntent pendingIntent = PendingIntent.getActivity(mContext, - 0 /* requestCode */, browserIntent, 0 /* flags */); + PendingIntent pendingIntent = PendingIntent.getActivityAsUser(mContext, + 0 /* requestCode */, browserIntent, 0 /* flags */, null, user); ComponentName aiaComponent = null; try { aiaComponent = AppGlobals.getPackageManager().getInstantAppInstallerComponent(); @@ -629,7 +630,8 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, .putExtra(Intent.EXTRA_LONG_VERSION_CODE, appInfo.versionCode) .putExtra(Intent.EXTRA_INSTANT_APP_FAILURE, pendingIntent); - PendingIntent webPendingIntent = PendingIntent.getActivity(mContext, 0, goToWebIntent, 0); + PendingIntent webPendingIntent = PendingIntent.getActivityAsUser(mContext, 0, + goToWebIntent, 0, null, user); Action webAction = new Notification.Action.Builder(null, mContext.getString(R.string.go_to_web), webPendingIntent).build(); builder.addAction(webAction); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 22a727c7626e..9beaa10d6bd0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -800,7 +800,7 @@ public class StatusBar extends SystemUI implements DemoMode, this, mNotificationPanel, notifListContainer); - mGutsManager.setUpWithPresenter(this, mEntryManager, notifListContainer, mCheckSaveListener, + mGutsManager.setUpWithPresenter(this, notifListContainer, mCheckSaveListener, key -> { try { mBarService.onNotificationSettingsViewed(key); @@ -1811,7 +1811,7 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarWindowController.setHeadsUpShowing(false); mHeadsUpManager.setHeadsUpGoingAway(false); } - mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed(); + mRemoteInputManager.onPanelCollapsed(); }); } } @@ -1828,7 +1828,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) { - mEntryManager.onHeadsUpStateChanged(entry, isHeadsUp); + mEntryManager.updateNotificationRanking(null /* rankingMap */); if (isHeadsUp) { mDozeServiceHost.fireNotificationHeadsUp(); @@ -1858,7 +1858,7 @@ public class StatusBar extends SystemUI implements DemoMode, } if (!isExpanded) { - mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed(); + mRemoteInputManager.onPanelCollapsed(); } } @@ -3751,7 +3751,7 @@ public class StatusBar extends SystemUI implements DemoMode, clearNotificationEffects(); } if (newState == StatusBarState.KEYGUARD) { - mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed(); + mRemoteInputManager.onPanelCollapsed(); maybeEscalateHeadsUp(); } } @@ -4862,7 +4862,8 @@ public class StatusBar extends SystemUI implements DemoMode, removeNotification(parentToCancelFinal); } if (shouldAutoCancel(sbn) - || mEntryManager.isNotificationKeptForRemoteInput(notificationKey)) { + || mRemoteInputManager.isNotificationKeptForRemoteInputHistory( + notificationKey)) { // Automatically remove all notifications that we may have kept around longer removeNotification(sbn); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index dbfbdd79dc3b..15b2f2bcb755 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -51,6 +51,7 @@ import com.android.internal.util.UserIcons; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.Utils; import com.android.systemui.Dependency; +import com.android.systemui.Dumpable; import com.android.systemui.GuestResumeSessionReceiver; import com.android.systemui.Prefs; import com.android.systemui.Prefs.Key; @@ -70,7 +71,7 @@ import java.util.List; /** * Keeps a list of all users on the device for user switching. */ -public class UserSwitcherController { +public class UserSwitcherController implements Dumpable { private static final String TAG = "UserSwitcherController"; private static final boolean DEBUG = false; @@ -549,6 +550,7 @@ public class UserSwitcherController { }; }; + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("UserSwitcherController state:"); pw.println(" mLastNonGuestUser=" + mLastNonGuestUser); diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java new file mode 100644 index 000000000000..521d5d10a621 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui; + + +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import android.graphics.Bitmap; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.DisplayInfo; +import android.view.SurfaceHolder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.CountDownLatch; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ImageWallpaperTest extends SysuiTestCase { + + private static final int BMP_WIDTH = 128; + private static final int BMP_HEIGHT = 128; + + private static final int INVALID_BMP_WIDTH = 1; + private static final int INVALID_BMP_HEIGHT = 1; + + private ImageWallpaper mImageWallpaper; + + @Mock private SurfaceHolder mSurfaceHolder; + @Mock private DisplayInfo mDisplayInfo; + + CountDownLatch mEventCountdown; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mEventCountdown = new CountDownLatch(1); + + mImageWallpaper = new ImageWallpaper() { + @Override + public Engine onCreateEngine() { + return new DrawableEngine() { + @Override + DisplayInfo getDefaultDisplayInfo() { + return mDisplayInfo; + } + + @Override + public SurfaceHolder getSurfaceHolder() { + return mSurfaceHolder; + } + + @Override + public void setFixedSizeAllowed(boolean allowed) { + super.setFixedSizeAllowed(allowed); + assertTrue("mFixedSizeAllowed should be true", allowed); + mEventCountdown.countDown(); + } + }; + } + }; + } + + @Test + public void testSetValidBitmapWallpaper() { + ImageWallpaper.DrawableEngine wallpaperEngine = + (ImageWallpaper.DrawableEngine) mImageWallpaper.onCreateEngine(); + + assertEquals("setFixedSizeAllowed should have been called.", + 0, mEventCountdown.getCount()); + + Bitmap mockedBitmap = mock(Bitmap.class); + when(mockedBitmap.getWidth()).thenReturn(BMP_WIDTH); + when(mockedBitmap.getHeight()).thenReturn(BMP_HEIGHT); + + wallpaperEngine.updateBitmap(mockedBitmap); + + assertEquals(BMP_WIDTH, wallpaperEngine.mBackgroundWidth); + assertEquals(BMP_HEIGHT, wallpaperEngine.mBackgroundHeight); + + verify(mSurfaceHolder, times(1)).setFixedSize(BMP_WIDTH, BMP_HEIGHT); + + } + + @Test + public void testSetTooSmallBitmapWallpaper() { + ImageWallpaper.DrawableEngine wallpaperEngine = + (ImageWallpaper.DrawableEngine) mImageWallpaper.onCreateEngine(); + + assertEquals("setFixedSizeAllowed should have been called.", + 0, mEventCountdown.getCount()); + + Bitmap mockedBitmap = mock(Bitmap.class); + when(mockedBitmap.getWidth()).thenReturn(INVALID_BMP_WIDTH); + when(mockedBitmap.getHeight()).thenReturn(INVALID_BMP_HEIGHT); + + wallpaperEngine.updateBitmap(mockedBitmap); + + assertEquals(INVALID_BMP_WIDTH, wallpaperEngine.mBackgroundWidth); + assertEquals(INVALID_BMP_HEIGHT, wallpaperEngine.mBackgroundHeight); + + verify(mSurfaceHolder, times(1)).setFixedSize(ImageWallpaper.DrawableEngine.MIN_BACKGROUND_WIDTH, ImageWallpaper.DrawableEngine.MIN_BACKGROUND_HEIGHT); + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java index f9f4f497a2ec..c5e404385f8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java @@ -59,14 +59,14 @@ public class QSIconViewImplTest extends SysuiTestCase { // No current icon, only the static drawable should be used. s.icon = mock(Icon.class); when(iv.getDrawable()).thenReturn(null); - mIconView.updateIcon(iv, s); + mIconView.updateIcon(iv, s, true); verify(s.icon, never()).getDrawable(any()); verify(s.icon).getInvisibleDrawable(any()); // Has icon, should use the standard (animated) form. s.icon = mock(Icon.class); when(iv.getDrawable()).thenReturn(mock(Drawable.class)); - mIconView.updateIcon(iv, s); + mIconView.updateIcon(iv, s, true); verify(s.icon).getDrawable(any()); verify(s.icon, never()).getInvisibleDrawable(any()); } @@ -79,7 +79,7 @@ public class QSIconViewImplTest extends SysuiTestCase { int desiredColor = mIconView.getColor(s.state); when(iv.isShown()).thenReturn(true); - mIconView.setIcon(iv, s); + mIconView.setIcon(iv, s, true); verify(iv).setImageTintList(argThat(stateList -> stateList.getColors()[0] == desiredColor)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java index f04a1151384e..32c972cbd188 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java @@ -91,7 +91,7 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { return new TestableAlertingNotificationManager(); } - private StatusBarNotification createNewNotification(int id) { + protected StatusBarNotification createNewNotification(int id) { Notification.Builder n = new Notification.Builder(mContext, "") .setSmallIcon(R.drawable.ic_person) .setContentTitle("Title") @@ -154,7 +154,7 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { public void testRemoveNotification_forceRemove() { mAlertingNotificationManager.showNotification(mEntry); - //Remove forcibly with releaseImmediately = true. + // Remove forcibly with releaseImmediately = true. mAlertingNotificationManager.removeNotification(mEntry.key, true /* releaseImmediately */); assertFalse(mAlertingNotificationManager.contains(mEntry.key)); @@ -173,4 +173,30 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { assertEquals(0, mAlertingNotificationManager.getAllEntries().count()); } + + @Test + public void testShouldExtendLifetime_notShownLongEnough() { + mAlertingNotificationManager.showNotification(mEntry); + + // The entry has just been added so the lifetime should be extended + assertTrue(mAlertingNotificationManager.shouldExtendLifetime(mEntry)); + } + + @Test + public void testSetShouldExtendLifetime_setShouldExtend() { + mAlertingNotificationManager.showNotification(mEntry); + + mAlertingNotificationManager.setShouldExtendLifetime(mEntry, true /* shouldExtend */); + + assertTrue(mAlertingNotificationManager.mExtendedLifetimeAlertEntries.contains(mEntry)); + } + + @Test + public void testSetShouldExtendLifetime_setShouldNotExtend() { + mAlertingNotificationManager.mExtendedLifetimeAlertEntries.add(mEntry); + + mAlertingNotificationManager.setShouldExtendLifetime(mEntry, false /* shouldExtend */); + + assertFalse(mAlertingNotificationManager.mExtendedLifetimeAlertEntries.contains(mEntry)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java index 8129b018a7be..09c19319429b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java @@ -86,8 +86,8 @@ public class NonPhoneDependencyTest extends SysuiTestCase { entryManager.setUpWithPresenter(mPresenter, mListContainer, mEntryManagerCallback, mHeadsUpManager); - gutsManager.setUpWithPresenter(mPresenter, entryManager, mListContainer, - mCheckSaveListener, mOnClickListener); + gutsManager.setUpWithPresenter(mPresenter, mListContainer, mCheckSaveListener, + mOnClickListener); notificationLogger.setUpWithEntryManager(entryManager, mListContainer); mediaManager.setUpWithPresenter(mPresenter, entryManager); remoteInputManager.setUpWithPresenter(mPresenter, entryManager, mRemoteInputManagerCallback, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java index 3cafaf4521c5..7b0c0a077332 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java @@ -85,13 +85,6 @@ public class NotificationListenerTest extends SysuiTestCase { } @Test - public void testPostNotificationRemovesKeyKeptForRemoteInput() { - mListener.onNotificationPosted(mSbn, mRanking); - TestableLooper.get(this).processAllMessages(); - verify(mEntryManager).removeKeyKeptForRemoteInput(mSbn.getKey()); - } - - @Test public void testNotificationUpdateCallsUpdateNotification() { when(mNotificationData.get(mSbn.getKey())).thenReturn(new NotificationData.Entry(mSbn)); mListener.onNotificationPosted(mSbn, mRanking); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index afe2cf6fc1af..b2493b37327b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -1,8 +1,10 @@ package com.android.systemui.statusbar; -import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertFalse; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -10,6 +12,7 @@ import android.app.Notification; import android.content.Context; import android.os.Handler; import android.os.Looper; +import android.os.SystemClock; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -22,8 +25,14 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputActiveExtender; +import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputHistoryExtender; +import com.android.systemui.statusbar.NotificationRemoteInputManager.SmartReplyHistoryExtender; + import com.google.android.collect.Sets; +import junit.framework.Assert; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,6 +50,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { @Mock private RemoteInputController.Delegate mDelegate; @Mock private NotificationRemoteInputManager.Callback mCallback; @Mock private RemoteInputController mController; + @Mock private SmartReplyController mSmartReplyController; @Mock private NotificationListenerService.RankingMap mRanking; @Mock private ExpandableNotificationRow mRow; @@ -51,6 +61,9 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { private TestableNotificationRemoteInputManager mRemoteInputManager; private StatusBarNotification mSbn; private NotificationData.Entry mEntry; + private RemoteInputHistoryExtender mRemoteInputHistoryExtender; + private SmartReplyHistoryExtender mSmartReplyHistoryExtender; + private RemoteInputActiveExtender mRemoteInputActiveExtender; @Before public void setUp() { @@ -58,9 +71,9 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); mDependency.injectTestDependency(NotificationLockscreenUserManager.class, mLockscreenUserManager); + mDependency.injectTestDependency(SmartReplyController.class, mSmartReplyController); when(mPresenter.getHandler()).thenReturn(Handler.createAsync(Looper.myLooper())); - when(mEntryManager.getLatestRankingMap()).thenReturn(mRanking); mRemoteInputManager = new TestableNotificationRemoteInputManager(mContext); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, @@ -70,40 +83,119 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { mRemoteInputManager.setUpWithPresenterForTest(mPresenter, mEntryManager, mCallback, mDelegate, mController); + for (NotificationLifetimeExtender extender : mRemoteInputManager.getLifetimeExtenders()) { + extender.setCallback( + mock(NotificationLifetimeExtender.NotificationSafeToRemoveCallback.class)); + } } @Test - public void testOnRemoveNotificationNotKept() { - assertFalse(mRemoteInputManager.onRemoveNotification(mEntry)); - assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty()); + public void testPerformOnRemoveNotification() { + when(mController.isRemoteInputActive(mEntry)).thenReturn(true); + mRemoteInputManager.onPerformRemoveNotification(mSbn, mEntry); + + verify(mController).removeRemoteInput(mEntry, null); } @Test - public void testOnRemoveNotificationKept() { + public void testShouldExtendLifetime_remoteInputActive() { when(mController.isRemoteInputActive(mEntry)).thenReturn(true); - assertTrue(mRemoteInputManager.onRemoveNotification(mEntry)); - assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().equals( - Sets.newArraySet(mEntry))); + + assertTrue(mRemoteInputActiveExtender.shouldExtendLifetime(mEntry)); } @Test - public void testPerformOnRemoveNotification() { - when(mController.isRemoteInputActive(mEntry)).thenReturn(true); - mRemoteInputManager.onPerformRemoveNotification(mSbn, mEntry); + public void testShouldExtendLifetime_isSpinning() { + NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true; + when(mController.isSpinning(mEntry.key)).thenReturn(true); - verify(mController).removeRemoteInput(mEntry, null); + assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry)); } @Test - public void testRemoveRemoteInputEntriesKeptUntilCollapsed() { - mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().add(mEntry); - mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed(); + public void testShouldExtendLifetime_recentRemoteInput() { + NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true; + mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime(); - assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty()); - verify(mController).removeRemoteInput(mEntry, null); - verify(mEntryManager).removeNotification(mEntry.key, mRanking); + assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry)); } + @Test + public void testShouldExtendLifetime_smartReplySending() { + NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true; + when(mSmartReplyController.isSendingSmartReply(mEntry.key)).thenReturn(true); + + assertTrue(mSmartReplyHistoryExtender.shouldExtendLifetime(mEntry)); + } + + @Test + public void testNotificationWithRemoteInputActiveIsRemovedOnCollapse() { + mRemoteInputActiveExtender.setShouldExtendLifetime(mEntry, true); + + assertEquals(mRemoteInputManager.getEntriesKeptForRemoteInputActive(), + Sets.newArraySet(mEntry)); + + mRemoteInputManager.onPanelCollapsed(); + + assertTrue(mRemoteInputManager.getEntriesKeptForRemoteInputActive().isEmpty()); + } + + @Test + public void testRebuildWithRemoteInput_noExistingInputNoSpinner() { + StatusBarNotification newSbn = + mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false); + CharSequence[] messages = newSbn.getNotification().extras + .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + assertEquals(1, messages.length); + assertEquals("A Reply", messages[0]); + assertFalse(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); + assertTrue(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); + } + + @Test + public void testRebuildWithRemoteInput_noExistingInputWithSpinner() { + StatusBarNotification newSbn = + mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", true); + CharSequence[] messages = newSbn.getNotification().extras + .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + assertEquals(1, messages.length); + assertEquals("A Reply", messages[0]); + assertTrue(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); + assertTrue(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); + } + + @Test + public void testRebuildWithRemoteInput_withExistingInput() { + // Setup a notification entry with 1 remote input. + StatusBarNotification newSbn = + mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false); + NotificationData.Entry entry = new NotificationData.Entry(newSbn); + + // Try rebuilding to add another reply. + newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput(entry, "Reply 2", true); + CharSequence[] messages = newSbn.getNotification().extras + .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + assertEquals(2, messages.length); + assertEquals("Reply 2", messages[0]); + assertEquals("A Reply", messages[1]); + } + + @Test + public void testRebuildNotificationForCanceledSmartReplies() { + // Try rebuilding to remove spinner and hide buttons. + StatusBarNotification newSbn = + mRemoteInputManager.rebuildNotificationForCanceledSmartReplies(mEntry); + assertFalse(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); + assertTrue(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); + } + + private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager { public TestableNotificationRemoteInputManager(Context context) { @@ -118,5 +210,15 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { super.setUpWithPresenter(presenter, entryManager, callback, delegate); mRemoteInputController = controller; } + + @Override + protected void addLifetimeExtenders() { + mRemoteInputActiveExtender = new RemoteInputActiveExtender(); + mRemoteInputHistoryExtender = new RemoteInputHistoryExtender(); + mSmartReplyHistoryExtender = new SmartReplyHistoryExtender(); + mLifetimeExtenders.add(mRemoteInputHistoryExtender); + mLifetimeExtenders.add(mSmartReplyHistoryExtender); + mLifetimeExtenders.add(mRemoteInputActiveExtender); + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java index ada57851a4c0..17daaacded87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java @@ -23,8 +23,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; import android.app.Notification; import android.os.RemoteException; +import android.os.UserHandle; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; @@ -35,6 +38,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import org.junit.Before; import org.junit.Test; @@ -46,97 +50,88 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper @SmallTest public class SmartReplyControllerTest extends SysuiTestCase { - private static final String TEST_NOTIFICATION_KEY = "akey"; + private static final String TEST_PACKAGE_NAME = "test"; + private static final int TEST_UID = 0; private static final String TEST_CHOICE_TEXT = "A Reply"; private static final int TEST_CHOICE_INDEX = 2; private static final int TEST_CHOICE_COUNT = 4; private Notification mNotification; private NotificationData.Entry mEntry; + private SmartReplyController mSmartReplyController; + private NotificationRemoteInputManager mRemoteInputManager; - @Mock - private NotificationEntryManager mNotificationEntryManager; - @Mock - private IStatusBarService mIStatusBarService; + @Mock private NotificationPresenter mPresenter; + @Mock private RemoteInputController.Delegate mDelegate; + @Mock private NotificationRemoteInputManager.Callback mCallback; + @Mock private StatusBarNotification mSbn; + @Mock private NotificationEntryManager mNotificationEntryManager; + @Mock private IStatusBarService mIStatusBarService; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager); mDependency.injectTestDependency(IStatusBarService.class, mIStatusBarService); + mSmartReplyController = new SmartReplyController(); + mDependency.injectTestDependency(SmartReplyController.class, + mSmartReplyController); + + mRemoteInputManager = new NotificationRemoteInputManager(mContext); + mRemoteInputManager.setUpWithPresenter(mPresenter, mNotificationEntryManager, mCallback, + mDelegate); mNotification = new Notification.Builder(mContext, "") .setSmallIcon(R.drawable.ic_person) .setContentTitle("Title") .setContentText("Text").build(); - StatusBarNotification sbn = mock(StatusBarNotification.class); - when(sbn.getNotification()).thenReturn(mNotification); - when(sbn.getKey()).thenReturn(TEST_NOTIFICATION_KEY); - mEntry = new NotificationData.Entry(sbn); + + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, + 0, mNotification, new UserHandle(ActivityManager.getCurrentUser()), null, 0); + mEntry = new NotificationData.Entry(mSbn); } @Test public void testSendSmartReply_updatesRemoteInput() { - StatusBarNotification sbn = mock(StatusBarNotification.class); - when(sbn.getKey()).thenReturn(TEST_NOTIFICATION_KEY); - when(mNotificationEntryManager.rebuildNotificationWithRemoteInput( - argThat(entry -> entry.notification.getKey().equals(TEST_NOTIFICATION_KEY)), - eq(TEST_CHOICE_TEXT), eq(true))).thenReturn(sbn); - - SmartReplyController controller = new SmartReplyController(); - controller.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT); + mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT); // Sending smart reply should make calls to NotificationEntryManager // to update the notification with reply and spinner. - verify(mNotificationEntryManager).rebuildNotificationWithRemoteInput( - argThat(entry -> entry.notification.getKey().equals(TEST_NOTIFICATION_KEY)), - eq(TEST_CHOICE_TEXT), eq(true)); verify(mNotificationEntryManager).updateNotification( - argThat(sbn2 -> sbn2.getKey().equals(TEST_NOTIFICATION_KEY)), isNull()); + argThat(sbn -> sbn.getKey().equals(mSbn.getKey())), isNull()); } @Test public void testSendSmartReply_logsToStatusBar() throws RemoteException { - StatusBarNotification sbn = mock(StatusBarNotification.class); - when(sbn.getKey()).thenReturn(TEST_NOTIFICATION_KEY); - when(mNotificationEntryManager.rebuildNotificationWithRemoteInput( - argThat(entry -> entry.notification.getKey().equals(TEST_NOTIFICATION_KEY)), - eq(TEST_CHOICE_TEXT), eq(true))).thenReturn(sbn); - - SmartReplyController controller = new SmartReplyController(); - controller.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT); + mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT); // Check we log the result to the status bar service. - verify(mIStatusBarService).onNotificationSmartReplySent(TEST_NOTIFICATION_KEY, + verify(mIStatusBarService).onNotificationSmartReplySent(mSbn.getKey(), TEST_CHOICE_INDEX); } @Test public void testShowSmartReply_logsToStatusBar() throws RemoteException { - SmartReplyController controller = new SmartReplyController(); - controller.smartRepliesAdded(mEntry, TEST_CHOICE_COUNT); + mSmartReplyController.smartRepliesAdded(mEntry, TEST_CHOICE_COUNT); // Check we log the result to the status bar service. - verify(mIStatusBarService).onNotificationSmartRepliesAdded(TEST_NOTIFICATION_KEY, + verify(mIStatusBarService).onNotificationSmartRepliesAdded(mSbn.getKey(), TEST_CHOICE_COUNT); } @Test public void testSendSmartReply_reportsSending() { - SmartReplyController controller = new SmartReplyController(); - controller.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT); + mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT); - assertTrue(controller.isSendingSmartReply(TEST_NOTIFICATION_KEY)); + assertTrue(mSmartReplyController.isSendingSmartReply(mSbn.getKey())); } @Test public void testSendingSmartReply_afterRemove_shouldReturnFalse() { - SmartReplyController controller = new SmartReplyController(); - controller.isSendingSmartReply(TEST_NOTIFICATION_KEY); - controller.stopSending(mEntry); + mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT); + mSmartReplyController.stopSending(mEntry); - assertFalse(controller.isSendingSmartReply(TEST_NOTIFICATION_KEY)); + assertFalse(mSmartReplyController.isSendingSmartReply(mSbn.getKey())); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 6543bdb32913..dacf59c584ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -57,6 +57,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.ForegroundServiceController; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; @@ -84,7 +85,6 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -143,6 +143,10 @@ public class NotificationEntryManagerTest extends SysuiTestCase { public CountDownLatch getCountDownLatch() { return mCountDownLatch; } + + public ArrayList<NotificationLifetimeExtender> getLifetimeExtenders() { + return mNotificationLifetimeExtenders; + } } private void setUserSentiment(String key, int sentiment) { @@ -279,7 +283,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(), any(), anyInt()); - verify(mRemoteInputManager).onUpdateNotification(mEntry); verify(mPresenter).updateNotificationViews(); verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt()); verify(mCallback).onNotificationUpdated(mSbn); @@ -301,8 +304,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { any(), anyInt()); verify(mMediaManager).onNotificationRemoved(mSbn.getKey()); - verify(mRemoteInputManager).onRemoveNotification(mEntry); - verify(mSmartReplyController).stopSending(mEntry); verify(mForegroundServiceController).removeNotification(mSbn); verify(mListContainer).cleanUpViewState(mRow); verify(mPresenter).updateNotificationViews(); @@ -313,17 +314,23 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test - public void testRemoveNotification_blockedBySendingSmartReply() throws Exception { + public void testRemoveNotification_blockedByLifetimeExtender() { com.android.systemui.util.Assert.isNotMainThread(); + NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class); + when(extender.shouldExtendLifetime(mEntry)).thenReturn(true); + + ArrayList<NotificationLifetimeExtender> extenders = mEntryManager.getLifetimeExtenders(); + extenders.clear(); + extenders.add(extender); + mEntry.row = mRow; mEntryManager.getNotificationData().add(mEntry); - when(mSmartReplyController.isSendingSmartReply(mEntry.key)).thenReturn(true); mEntryManager.removeNotification(mSbn.getKey(), mRankingMap); assertNotNull(mEntryManager.getNotificationData().get(mSbn.getKey())); - assertTrue(mEntryManager.isNotificationKeptForRemoteInput(mEntry.key)); + verify(extender).setShouldExtendLifetime(mEntry, true); } @Test @@ -411,61 +418,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test - public void testRebuildWithRemoteInput_noExistingInputNoSpinner() { - StatusBarNotification newSbn = - mEntryManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false); - CharSequence[] messages = newSbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); - Assert.assertEquals(1, messages.length); - Assert.assertEquals("A Reply", messages[0]); - Assert.assertFalse(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); - Assert.assertTrue(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); - } - - @Test - public void testRebuildWithRemoteInput_noExistingInputWithSpinner() { - StatusBarNotification newSbn = - mEntryManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", true); - CharSequence[] messages = newSbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); - Assert.assertEquals(1, messages.length); - Assert.assertEquals("A Reply", messages[0]); - Assert.assertTrue(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); - Assert.assertTrue(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); - } - - @Test - public void testRebuildWithRemoteInput_withExistingInput() { - // Setup a notification entry with 1 remote input. - StatusBarNotification newSbn = - mEntryManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false); - NotificationData.Entry entry = new NotificationData.Entry(newSbn); - - // Try rebuilding to add another reply. - newSbn = mEntryManager.rebuildNotificationWithRemoteInput(entry, "Reply 2", true); - CharSequence[] messages = newSbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); - Assert.assertEquals(2, messages.length); - Assert.assertEquals("Reply 2", messages[0]); - Assert.assertEquals("A Reply", messages[1]); - } - - @Test - public void testRebuildNotificationForCanceledSmartReplies() { - // Try rebuilding to remove spinner and hide buttons. - StatusBarNotification newSbn = - mEntryManager.rebuildNotificationForCanceledSmartReplies(mEntry); - Assert.assertFalse(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); - Assert.assertTrue(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); - } - - @Test public void testUpdateNotificationRanking() { when(mPresenter.isDeviceProvisioned()).thenReturn(true); when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 676cb617cc9f..6656fdd42e92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -22,6 +22,8 @@ import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -30,6 +32,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.Mockito.spy; @@ -54,6 +57,7 @@ import android.view.View; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationTestHelper; @@ -99,7 +103,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { mHelper = new NotificationTestHelper(mContext); mGutsManager = new NotificationGutsManager(mContext); - mGutsManager.setUpWithPresenter(mPresenter, mEntryManager, mStackScroller, + mGutsManager.setUpWithPresenter(mPresenter, mStackScroller, mCheckSaveListener, mOnSettingsClickListener); } @@ -346,6 +350,35 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(true) /* isUserSentimentNegative */); } + @Test + public void testShouldExtendLifetime() { + NotificationGuts guts = new NotificationGuts(mContext); + ExpandableNotificationRow row = spy(createTestNotificationRow()); + doReturn(guts).when(row).getGuts(); + NotificationData.Entry entry = row.getEntry(); + entry.row = row; + mGutsManager.setExposedGuts(guts); + + assertTrue(mGutsManager.shouldExtendLifetime(entry)); + } + + @Test + public void testSetShouldExtendLifetime_setShouldExtend() { + NotificationData.Entry entry = createTestNotificationRow().getEntry(); + mGutsManager.setShouldExtendLifetime(entry, true /* shouldExtend */); + + assertTrue(entry.key.equals(mGutsManager.mKeyToRemoveOnGutsClosed)); + } + + @Test + public void testSetShouldExtendLifetime_setShouldNotExtend() { + NotificationData.Entry entry = createTestNotificationRow().getEntry(); + mGutsManager.mKeyToRemoveOnGutsClosed = entry.key; + mGutsManager.setShouldExtendLifetime(entry, false /* shouldExtend */); + + assertNull(mGutsManager.mKeyToRemoveOnGutsClosed); + } + //////////////////////////////////////////////////////////////////////////////////////////////// // Utility methods: diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index c19188c9b7c0..ce0bd58064fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -112,7 +112,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mEntryManager = new TestableNotificationEntryManager(mSystemServicesProxy, mPowerManager, mContext); - mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, null, null, mNotificationData); + mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, null, mHeadsUpManager, + mNotificationData); mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); NotificationShelf notificationShelf = spy(new NotificationShelf(getContext(), null)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java index bdf7cd31c791..a81d17f12de1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -23,6 +23,7 @@ import android.testing.TestableLooper; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AlertingNotificationManagerTest; +import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.VisualStabilityManager; import org.junit.Before; @@ -83,4 +84,26 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { assertFalse(mHeadsUpManager.contains(mEntry.key)); } + + @Test + public void testShouldExtendLifetime_swipedOut() { + mHeadsUpManager.showNotification(mEntry); + mHeadsUpManager.addSwipedOutNotification(mEntry.key); + + // Notification is swiped so its lifetime should not be extended even if it hasn't been + // shown long enough + assertFalse(mHeadsUpManager.shouldExtendLifetime(mEntry)); + } + + @Test + public void testShouldExtendLifetime_notTopEntry() { + NotificationData.Entry laterEntry = new NotificationData.Entry(createNewNotification(1)); + laterEntry.row = mRow; + mHeadsUpManager.showNotification(mEntry); + mHeadsUpManager.showNotification(laterEntry); + + // Notification is "behind" a higher priority notification so we have no reason to keep + // its lifetime extended + assertFalse(mHeadsUpManager.shouldExtendLifetime(mEntry)); + } } diff --git a/proto/src/stats_enums.proto b/proto/src/stats_enums.proto new file mode 100644 index 000000000000..6c892cfeae6c --- /dev/null +++ b/proto/src/stats_enums.proto @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2018 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. + */ + +syntax = "proto2"; + +package android.os.statsd; +option java_package = "com.android.os"; +option java_outer_classname = "StatsEnums"; + +enum EventType { + // Unknown. + TYPE_UNKNOWN = 0; +} diff --git a/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java b/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java index 49fa1ccce5fe..df46d260b961 100644 --- a/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java +++ b/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java @@ -57,6 +57,10 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver { public static final String SETTING_RESTORE_AGENT_FINISHED_TIMEOUT_MILLIS = "restore_agent_finished_timeout_millis"; + @VisibleForTesting + public static final String SETTING_QUOTA_EXCEEDED_TIMEOUT_MILLIS = + "quota_exceeded_timeout_millis"; + // Default values @VisibleForTesting public static final long DEFAULT_KV_BACKUP_AGENT_TIMEOUT_MILLIS = 30 * 1000; @@ -71,6 +75,9 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver { @VisibleForTesting public static final long DEFAULT_RESTORE_AGENT_FINISHED_TIMEOUT_MILLIS = 30 * 1000; + @VisibleForTesting + public static final long DEFAULT_QUOTA_EXCEEDED_TIMEOUT_MILLIS = 3 * 1000; + @GuardedBy("mLock") private long mKvBackupAgentTimeoutMillis; @@ -86,6 +93,9 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver { @GuardedBy("mLock") private long mRestoreAgentFinishedTimeoutMillis; + @GuardedBy("mLock") + private long mQuotaExceededTimeoutMillis; + private final Object mLock = new Object(); public BackupAgentTimeoutParameters(Handler handler, ContentResolver resolver) { @@ -118,6 +128,10 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver { parser.getLong( SETTING_RESTORE_AGENT_FINISHED_TIMEOUT_MILLIS, DEFAULT_RESTORE_AGENT_FINISHED_TIMEOUT_MILLIS); + mQuotaExceededTimeoutMillis = + parser.getLong( + SETTING_QUOTA_EXCEEDED_TIMEOUT_MILLIS, + DEFAULT_QUOTA_EXCEEDED_TIMEOUT_MILLIS); } } @@ -170,4 +184,16 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver { return mRestoreAgentFinishedTimeoutMillis; } } + + public long getQuotaExceededTimeoutMillis() { + synchronized (mLock) { + if (BackupManagerService.DEBUG_SCHEDULING) { + Slog.v( + TAG, + "getQuotaExceededTimeoutMillis(): " + + mQuotaExceededTimeoutMillis); + } + return mQuotaExceededTimeoutMillis; + } + } } diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java index 56946596eea3..16906f74c8a8 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java +++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java @@ -46,6 +46,7 @@ import com.android.server.AppWidgetBackupBridge; import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupManagerService; import com.android.server.backup.BackupRestoreTask; +import com.android.server.backup.remote.RemoteCall; import com.android.server.backup.utils.FullBackupUtils; import java.io.BufferedOutputStream; @@ -270,10 +271,12 @@ public class FullBackupEngine { return result; } - public void sendQuotaExceeded(final long backupDataBytes, final long quotaBytes) { + public void sendQuotaExceeded(long backupDataBytes, long quotaBytes) { if (initializeAgent()) { try { - mAgent.doQuotaExceeded(backupDataBytes, quotaBytes); + RemoteCall.execute( + callback -> mAgent.doQuotaExceeded(backupDataBytes, quotaBytes, callback), + mAgentTimeoutParameters.getQuotaExceededTimeoutMillis()); } catch (RemoteException e) { Slog.e(TAG, "Remote exception while telling agent about quota exceeded"); } diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index f7c1c109266a..e1080260697f 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -51,6 +51,7 @@ import com.android.server.backup.BackupManagerService; import com.android.server.backup.TransportManager; import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.internal.Operation; +import com.android.server.backup.remote.RemoteCall; import com.android.server.backup.transport.TransportClient; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.utils.AppBackupUtils; @@ -739,7 +740,9 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba Slog.d(TAG, "Package hit quota limit on preflight " + pkg.packageName + ": " + totalSize + " of " + mQuota); } - agent.doQuotaExceeded(totalSize, mQuota); + RemoteCall.execute( + callback -> agent.doQuotaExceeded(totalSize, mQuota, callback), + mAgentTimeoutParameters.getQuotaExceededTimeoutMillis()); } } catch (Exception e) { Slog.w(TAG, "Exception preflighting " + pkg.packageName + ": " + e.getMessage()); diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java index 2f32775a84a3..54e6b1d82f74 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java @@ -377,9 +377,9 @@ public class KeyValueBackupReporter { Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e); } - void onRemoteCallReturned(RemoteResult result) { + void onRemoteCallReturned(RemoteResult result, String logIdentifier) { if (MORE_DEBUG) { - Slog.v(TAG, "Agent call returned " + result); + Slog.v(TAG, "Agent call " + logIdentifier + " returned " + result); } } diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index fbe0d6f6d21c..a4cd629ff6fa 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -694,8 +694,6 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.extractAgentData()"); long quota = transport.getBackupQuota(packageName, /* isFullBackup */ false); int transportFlags = transport.getTransportFlags(); - long kvBackupAgentTimeoutMillis = - mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis(); callingAgent = true; agentResult = @@ -708,7 +706,8 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { quota, callback, transportFlags), - kvBackupAgentTimeoutMillis); + mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis(), + "doBackup()"); } catch (Exception e) { mReporter.onCallAgentDoBackupError(packageName, callingAgent, e); errorCleanup(); @@ -908,14 +907,16 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { return false; } - private void agentDoQuotaExceeded( - @Nullable IBackupAgent agent, String packageName, long backupDataSize) { + private void agentDoQuotaExceeded(@Nullable IBackupAgent agent, String packageName, long size) { if (agent != null) { try { IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.agentDoQuotaExceeded()"); long quota = transport.getBackupQuota(packageName, false); - agent.doQuotaExceeded(backupDataSize, quota); + remoteCall( + callback -> agent.doQuotaExceeded(size, quota, callback), + mAgentTimeoutParameters.getQuotaExceededTimeoutMillis(), + "doQuotaExceeded()"); } catch (Exception e) { mReporter.onAgentDoQuotaExceededError(e); } @@ -1056,11 +1057,12 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { } } - private RemoteResult remoteCall(RemoteCallable<IBackupCallback> remoteCallable, long timeoutMs) + private RemoteResult remoteCall( + RemoteCallable<IBackupCallback> remoteCallable, long timeoutMs, String logIdentifier) throws RemoteException { mPendingCall = new RemoteCall(mCancelled, remoteCallable, timeoutMs); RemoteResult result = mPendingCall.call(); - mReporter.onRemoteCallReturned(result); + mReporter.onRemoteCallReturned(result, logIdentifier); mPendingCall = null; return result; } diff --git a/services/backup/java/com/android/server/backup/remote/RemoteCall.java b/services/backup/java/com/android/server/backup/remote/RemoteCall.java index b3e802ed3be4..3af9e1d60dc9 100644 --- a/services/backup/java/com/android/server/backup/remote/RemoteCall.java +++ b/services/backup/java/com/android/server/backup/remote/RemoteCall.java @@ -44,6 +44,21 @@ import java.util.concurrent.ExecutionException; */ // TODO: Kick-off callable in dedicated thread (because of local calls, which are synchronous) public class RemoteCall { + /** + * Creates a {@link RemoteCall} object with {@code callable} and {@code timeoutMs} and calls + * {@link #call()} on it immediately after. + * + * <p>Note that you won't be able to cancel the call, to do that construct an object regularly + * first, then use {@link #call()}. + * + * @see #RemoteCall(RemoteCallable, long) + * @see #call() + */ + public static RemoteResult execute(RemoteCallable<IBackupCallback> callable, long timeoutMs) + throws RemoteException { + return new RemoteCall(callable, timeoutMs).call(); + } + private final RemoteCallable<IBackupCallback> mCallable; private final CompletableFuture<RemoteResult> mFuture; private final long mTimeoutMs; diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index ba5f32308a30..760209024c57 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -183,6 +183,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -202,6 +203,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final String DIAG_ARG = "--diag"; public static final String SHORT_ARG = "--short"; private static final String TETHERING_ARG = "tethering"; + private static final String NETWORK_ARG = "networks"; + private static final String REQUEST_ARG = "requests"; private static final boolean DBG = true; private static final boolean VDBG = false; @@ -1978,7 +1981,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void dumpNetworkDiagnostics(IndentingPrintWriter pw) { final List<NetworkDiagnostics> netDiags = new ArrayList<NetworkDiagnostics>(); final long DIAG_TIME_MS = 5000; - for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + for (NetworkAgentInfo nai : networksSortedById()) { // Start gathering diagnostic information. netDiags.add(new NetworkDiagnostics( nai.network, @@ -2009,6 +2012,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } else if (ArrayUtils.contains(args, TETHERING_ARG)) { mTethering.dump(fd, pw, args); return; + } else if (ArrayUtils.contains(args, NETWORK_ARG)) { + dumpNetworks(pw); + return; + } else if (ArrayUtils.contains(args, REQUEST_ARG)) { + dumpNetworkRequests(pw); + return; } pw.print("NetworkFactories for:"); @@ -2029,36 +2038,15 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println("Current Networks:"); pw.increaseIndent(); - for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { - pw.println(nai.toString()); - pw.increaseIndent(); - pw.println(String.format( - "Requests: REQUEST:%d LISTEN:%d BACKGROUND_REQUEST:%d total:%d", - nai.numForegroundNetworkRequests(), - nai.numNetworkRequests() - nai.numRequestNetworkRequests(), - nai.numBackgroundNetworkRequests(), - nai.numNetworkRequests())); - pw.increaseIndent(); - for (int i = 0; i < nai.numNetworkRequests(); i++) { - pw.println(nai.requestAt(i).toString()); - } - pw.decreaseIndent(); - pw.println("Lingered:"); - pw.increaseIndent(); - nai.dumpLingerTimers(pw); - pw.decreaseIndent(); - pw.decreaseIndent(); - } + dumpNetworks(pw); pw.decreaseIndent(); pw.println(); pw.println("Network Requests:"); pw.increaseIndent(); - for (NetworkRequestInfo nri : mNetworkRequests.values()) { - pw.println(nri.toString()); - } - pw.println(); + dumpNetworkRequests(pw); pw.decreaseIndent(); + pw.println(); mLegacyTypeTracker.dump(pw); @@ -2126,6 +2114,55 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private void dumpNetworks(IndentingPrintWriter pw) { + for (NetworkAgentInfo nai : networksSortedById()) { + pw.println(nai.toString()); + pw.increaseIndent(); + pw.println(String.format( + "Requests: REQUEST:%d LISTEN:%d BACKGROUND_REQUEST:%d total:%d", + nai.numForegroundNetworkRequests(), + nai.numNetworkRequests() - nai.numRequestNetworkRequests(), + nai.numBackgroundNetworkRequests(), + nai.numNetworkRequests())); + pw.increaseIndent(); + for (int i = 0; i < nai.numNetworkRequests(); i++) { + pw.println(nai.requestAt(i).toString()); + } + pw.decreaseIndent(); + pw.println("Lingered:"); + pw.increaseIndent(); + nai.dumpLingerTimers(pw); + pw.decreaseIndent(); + pw.decreaseIndent(); + } + } + + private void dumpNetworkRequests(IndentingPrintWriter pw) { + for (NetworkRequestInfo nri : requestsSortedById()) { + pw.println(nri.toString()); + } + } + + /** + * Return an array of all current NetworkAgentInfos sorted by network id. + */ + private NetworkAgentInfo[] networksSortedById() { + NetworkAgentInfo[] networks = new NetworkAgentInfo[0]; + networks = mNetworkAgentInfos.values().toArray(networks); + Arrays.sort(networks, Comparator.comparingInt(nai -> nai.network.netId)); + return networks; + } + + /** + * Return an array of all current NetworkRequest sorted by request id. + */ + private NetworkRequestInfo[] requestsSortedById() { + NetworkRequestInfo[] requests = new NetworkRequestInfo[0]; + requests = mNetworkRequests.values().toArray(requests); + Arrays.sort(requests, Comparator.comparingInt(nri -> nri.request.requestId)); + return requests; + } + private boolean isLiveNetworkAgent(NetworkAgentInfo nai, int what) { if (nai.network == null) return false; final NetworkAgentInfo officialNai = getNetworkAgentInfoForNetwork(nai.network); @@ -2916,7 +2953,7 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println("User setting: " + description); pw.println("Network overrides:"); pw.increaseIndent(); - for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + for (NetworkAgentInfo nai : networksSortedById()) { if (nai.avoidUnvalidated) { pw.println(nai.name()); } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 665c6b7b4d10..4d3468e21e75 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -204,8 +204,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_START_INPUT = 2000; static final int MSG_START_VR_INPUT = 2010; - static final int MSG_ADD_CLIENT = 2980; - static final int MSG_REMOVE_CLIENT = 2990; static final int MSG_UNBIND_CLIENT = 3000; static final int MSG_BIND_CLIENT = 3010; static final int MSG_SET_ACTIVE = 3020; @@ -1302,7 +1300,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onStart() { LocalServices.addService(InputMethodManagerInternal.class, - new LocalServiceImpl(mService.mHandler)); + new LocalServiceImpl(mService)); publishBinderService(Context.INPUT_METHOD_SERVICE, mService); } @@ -1561,7 +1559,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final String defaultImiId = mSettings.getSelectedInputMethod(); final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */); - resetDefaultImeLocked(mContext); updateFromSettingsLocked(true); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, mSettings.getEnabledInputMethodListLocked(), currentUserId, @@ -3396,15 +3393,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } // --------------------------------------------------------- - case MSG_ADD_CLIENT: - addClient((ClientState) msg.obj); - return true; - - case MSG_REMOVE_CLIENT: - removeClient((IInputMethodClient) msg.obj); - return true; - - // --------------------------------------------------------- case MSG_UNBIND_CLIENT: try { @@ -4397,22 +4385,27 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final class LocalServiceImpl extends InputMethodManagerInternal { @NonNull + private final InputMethodManagerService mService; + @NonNull private final Handler mHandler; - LocalServiceImpl(@NonNull final Handler handler) { - mHandler = handler; + LocalServiceImpl(@NonNull InputMethodManagerService service) { + mService = service; + mHandler = service.mHandler; } @Override public void addClient(IInputMethodClient client, IInputContext inputContext, int uid, int pid) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_ADD_CLIENT, - new ClientState(client, inputContext, uid, pid))); + // Work around Bug 113877122: We need to handle this synchronously. Otherwise, some + // IMM binder calls from the client process before we register this client. + mService.addClient(new ClientState(client, inputContext, uid, pid)); } @Override public void removeClient(IInputMethodClient client) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_CLIENT, client)); + // Handle this synchronously to be consistent with addClient(). + mService.removeClient(client); } @Override diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 566ce4f48e25..c44a81e2dcd0 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -50,6 +50,7 @@ import android.telephony.TelephonyManager; import android.telephony.VoLteServiceState; import android.util.LocalLog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.telephony.IOnSubscriptionsChangedListener; import com.android.internal.telephony.IPhoneStateListener; @@ -82,7 +83,8 @@ import java.util.NoSuchElementException; * Eventually we may want to remove the notion of dummy value but for now this * looks like the best approach. */ -class TelephonyRegistry extends ITelephonyRegistry.Stub { +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +public class TelephonyRegistry extends ITelephonyRegistry.Stub { private static final String TAG = "TelephonyRegistry"; private static final boolean DBG = false; // STOPSHIP if true private static final boolean DBG_LOC = false; // STOPSHIP if true @@ -315,7 +317,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { // calls go through a oneway interface and local calls going through a // handler before they get to app code. - TelephonyRegistry(Context context) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public TelephonyRegistry(Context context) { CellLocation location = CellLocation.getEmpty(); mContext = context; diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index 3568a473b2ec..aa5a2e091130 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -138,6 +138,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> return new DisplayWindowController(mDisplay, this); } + DisplayWindowController getWindowContainerController() { + return mWindowContainerController; + } + void updateBounds() { mDisplay.getSize(mTmpDisplaySize); setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); @@ -837,7 +841,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> if (mStacks.isEmpty() && mRemoved) { mWindowContainerController.removeContainer(); mWindowContainerController = null; - mSupervisor.releaseActivityDisplayLocked(mDisplayId); + mSupervisor.removeChild(this); } } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 78fef65687fd..355d890f1bd1 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -775,6 +775,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai true /* includingParents */); } + void positionChildWindowContainerAtBottom(TaskRecord child) { + mWindowContainerController.positionChildAtBottom(child.getWindowContainerController(), + true /* includingParents */); + } + /** * Returns whether to defer the scheduling of the multi-window mode. */ @@ -2859,8 +2864,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final int position = getAdjustedPositionForTask(task, mTaskHistory.size(), starting); mTaskHistory.add(position, task); updateTaskMovement(task, true); - mWindowContainerController.positionChildAtTop(task.getWindowContainerController(), - true /* includingParents */); + positionChildWindowContainerAtTop(task); } private void insertTaskAtBottom(TaskRecord task) { @@ -2869,8 +2873,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final int position = getAdjustedPositionForTask(task, 0, null); mTaskHistory.add(position, task); updateTaskMovement(task, true); - mWindowContainerController.positionChildAtBottom(task.getWindowContainerController(), - true /* includingParents */); + positionChildWindowContainerAtBottom(task); } void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity, @@ -3141,8 +3144,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai p.reparent(targetTask, 0 /* position - bottom */, "resetTargetTaskIfNeeded"); } - mWindowContainerController.positionChildAtBottom( - targetTask.getWindowContainerController(), false /* includingParents */); + positionChildWindowContainerAtBottom(targetTask); replyChainEnd = -1; } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) { // If the activity should just be removed -- either @@ -3277,8 +3279,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Pulling activity " + p + " from " + srcPos + " in to resetting task " + task); } - mWindowContainerController.positionChildAtTop( - task.getWindowContainerController(), true /* includingParents */); + positionChildWindowContainerAtTop(task); // Now we've moved it in to place... but what if this is // a singleTop activity and we have put it on top of another @@ -5239,8 +5240,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai addTask(task, toTop ? MAX_VALUE : 0, true /* schedulePictureInPictureModeChange */, reason); if (toTop) { // TODO: figure-out a way to remove this call. - mWindowContainerController.positionChildAtTop(task.getWindowContainerController(), - true /* includingParents */); + positionChildWindowContainerAtTop(task); } } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 4cfcbee08a57..1ffdc6738c1d 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -176,7 +176,10 @@ import com.android.server.LocalServices; import com.android.server.am.ActivityStack.ActivityState; import com.android.server.wm.ActivityTaskManagerInternal.SleepToken; import com.android.server.wm.ConfigurationContainer; +import com.android.server.wm.DisplayWindowController; import com.android.server.wm.PinnedStackWindowController; +import com.android.server.wm.RootWindowContainerController; +import com.android.server.wm.RootWindowContainerListener; import com.android.server.wm.WindowManagerService; import java.io.FileDescriptor; @@ -190,7 +193,7 @@ import java.util.List; import java.util.Set; public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener, - RecentTasks.Callbacks { + RecentTasks.Callbacks, RootWindowContainerListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM; private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; private static final String TAG_IDLE = TAG + POSTFIX_IDLE; @@ -416,9 +419,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D /** Stack id of the front stack when user switched, indexed by userId. */ SparseIntArray mUserStackInFront = new SparseIntArray(2); - // TODO: There should be an ActivityDisplayController coordinating am/wm interaction. - /** Mapping from displayId to display current state */ - private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>(); + /** Reference to default display so we can quickly look it up. */ + private ActivityDisplay mDefaultDisplay; + + /** + * List of displays which contain activities, sorted by z-order. + * The last entry in the list is the topmost. + */ + private final ArrayList<ActivityDisplay> mActivityDisplays = new ArrayList<>(); private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>(); @@ -453,7 +461,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D @Override protected ActivityDisplay getChildAt(int index) { - return mActivityDisplays.valueAt(index); + return mActivityDisplays.get(index); } @Override @@ -531,13 +539,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private final FindTaskResult mTmpFindTaskResult = new FindTaskResult(); /** - * Temp storage for display ids sorted in focus order. - * Maps position to id. Using {@link SparseIntArray} instead of {@link ArrayList} because - * it's more efficient, as the number of displays is usually small. - */ - private SparseIntArray mTmpOrderedDisplayIds = new SparseIntArray(); - - /** * Used to keep track whether app visibilities got changed since the last pause. Useful to * determine whether to invoke the task stack change listener after pausing. */ @@ -569,6 +570,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private boolean mInitialized; + private RootWindowContainerController mWindowContainerController; + /** * Description of a request to start a new activity, which has been held * due to app switches being disabled. @@ -612,6 +615,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mService = service; } + @VisibleForTesting + void setWindowContainerController(RootWindowContainerController controller) { + mWindowContainerController = controller; + } + public void initialize() { if (mInitialized) { return; @@ -664,38 +672,57 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void setWindowManager(WindowManagerService wm) { mWindowManager = wm; getKeyguardController().setWindowManager(wm); + setWindowContainerController(new RootWindowContainerController(this)); - mDisplayManager = - (DisplayManager) mService.mContext.getSystemService(Context.DISPLAY_SERVICE); + mDisplayManager = mService.mContext.getSystemService(DisplayManager.class); mDisplayManager.registerDisplayListener(this, null); mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); - Display[] displays = mDisplayManager.getDisplays(); + final Display[] displays = mDisplayManager.getDisplays(); for (int displayNdx = displays.length - 1; displayNdx >= 0; --displayNdx) { final Display display = displays[displayNdx]; - ActivityDisplay activityDisplay = new ActivityDisplay(this, display); - mActivityDisplays.put(display.getDisplayId(), activityDisplay); + final ActivityDisplay activityDisplay = new ActivityDisplay(this, display); + if (activityDisplay.mDisplayId == DEFAULT_DISPLAY) { + mDefaultDisplay = activityDisplay; + } + addChild(activityDisplay, ActivityDisplay.POSITION_TOP); calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay); } final ActivityDisplay defaultDisplay = getDefaultDisplay(); mHomeStack = mLastFocusedStack = defaultDisplay.getOrCreateStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + positionChildAt(defaultDisplay, ActivityDisplay.POSITION_TOP); } - ActivityStack getTopDisplayFocusedStack() { - mWindowManager.getDisplaysInFocusOrder(mTmpOrderedDisplayIds); + /** Change the z-order of the given display. */ + private void positionChildAt(ActivityDisplay display, int position) { + if (position >= mActivityDisplays.size()) { + position = mActivityDisplays.size() - 1; + } else if (position < 0) { + position = 0; + } - for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) { - final int displayId = mTmpOrderedDisplayIds.get(i); - final ActivityDisplay display = mActivityDisplays.get(displayId); + if (mActivityDisplays.isEmpty()) { + mActivityDisplays.add(display); + } else if (mActivityDisplays.get(position) != display) { + mActivityDisplays.remove(display); + mActivityDisplays.add(position, display); + } + } - // If WindowManagerService has encountered the display before we have, ignore as there - // will be no stacks present and therefore no activities. - if (display == null) { - continue; - } - final ActivityStack focusedStack = display.getFocusedStack(); + @Override + public void onChildPositionChanged(DisplayWindowController childController, int position) { + // Assume AM lock is held from positionChildAt of controller in each hierarchy. + final ActivityDisplay display = getActivityDisplay(childController.getDisplayId()); + if (display != null) { + positionChildAt(display, position); + } + } + + ActivityStack getTopDisplayFocusedStack() { + for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { + final ActivityStack focusedStack = mActivityDisplays.get(i).getFocusedStack(); if (focusedStack != null) { return focusedStack; } @@ -718,16 +745,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } // The top focused stack might not have a resumed activity yet - look on all displays in // focus order. - mWindowManager.getDisplaysInFocusOrder(mTmpOrderedDisplayIds); - for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) { - final int displayId = mTmpOrderedDisplayIds.get(i); - final ActivityDisplay display = mActivityDisplays.get(displayId); - - // If WindowManagerService has encountered the display before we have, ignore as there - // will be no stacks present and therefore no activities. - if (display == null) { - continue; - } + for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { + final ActivityDisplay display = mActivityDisplays.get(i); final ActivityRecord resumedActivityOnDisplay = display.getResumedActivity(); if (resumedActivityOnDisplay != null) { return resumedActivityOnDisplay; @@ -848,7 +867,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); final TaskRecord task = stack.taskForIdLocked(id); @@ -905,7 +924,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ActivityRecord isInAnyStackLocked(IBinder token) { int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); final ActivityRecord r = stack.isInStackLocked(token); @@ -947,7 +966,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mWindowManager.deferSurfaceLayout(); try { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); final List<TaskRecord> tasks = stack.getAllTasks(); @@ -1011,7 +1030,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final String processName = app.processName; boolean didSomething = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); if (!isTopDisplayFocusedStack(stack)) { @@ -1046,7 +1065,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean allResumedActivitiesIdle() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); if (!isTopDisplayFocusedStack(stack) || stack.numActivities() == 0) { @@ -1067,7 +1086,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean allResumedActivitiesComplete() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); if (isTopDisplayFocusedStack(stack)) { @@ -1090,7 +1109,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private boolean allResumedActivitiesVisible() { boolean foundResumed = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); final ActivityRecord r = stack.getResumedActivity(); @@ -1116,7 +1135,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) { boolean someActivityPaused = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - someActivityPaused |= mActivityDisplays.valueAt(displayNdx) + someActivityPaused |= mActivityDisplays.get(displayNdx) .pauseBackStacks(userLeaving, resuming, dontWait); } return someActivityPaused; @@ -1125,7 +1144,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean allPausedActivitiesComplete() { boolean pausing = true; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); final ActivityRecord r = stack.mPausingActivity; @@ -1145,7 +1164,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void cancelInitializingActivities() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); stack.cancelInitializingActivities(); @@ -1266,17 +1285,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } // Look in other non-focused and non-home stacks. - mWindowManager.getDisplaysInFocusOrder(mTmpOrderedDisplayIds); - - for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) { - final int displayId = mTmpOrderedDisplayIds.get(i); - final ActivityDisplay display = mActivityDisplays.get(displayId); - - // If WindowManagerService has encountered the display before we have, ignore as there - // will be no stacks present and therefore no activities. - if (display == null) { - continue; - } + for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { + final ActivityDisplay display = mActivityDisplays.get(i); // TODO: We probably want to consider the top fullscreen stack as we could have a pinned // stack on top. @@ -1757,7 +1767,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean noResumedActivities = true; boolean allFocusedProcessesDiffer = true; for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) { - final ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx); final ActivityRecord resumedActivity = activityDisplay.getResumedActivity(); final WindowProcessController resumedActivityProcess = resumedActivity == null ? null : resumedActivity.app; @@ -1927,7 +1937,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void updateUIDsPresentOnDisplay() { mDisplayAccessUIDs.clear(); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx); // Only bother calculating the whitelist for private displays if (activityDisplay.isPrivate()) { mDisplayAccessUIDs.append( @@ -2173,7 +2183,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean handleAppDiedLocked(WindowProcessController app) { boolean hasVisibleActivities = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); hasVisibleActivities |= stack.handleAppDiedLocked(app); @@ -2184,7 +2194,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void closeSystemDialogsLocked() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); stack.closeSystemDialogsLocked(); @@ -2213,7 +2223,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean doit, boolean evenPersistent, int userId) { boolean didSomething = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); if (stack.finishDisabledPackageActivitiesLocked( @@ -2235,7 +2245,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // hosted by the process that is actually still the foreground. WindowProcessController fgApp = null; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); if (isTopDisplayFocusedStack(stack)) { @@ -2277,7 +2287,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Resume all top activities in focused stacks on all displays. for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); final ActivityStack focusedStack = display.getFocusedStack(); if (focusedStack == null) { continue; @@ -2296,7 +2306,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void updateActivityApplicationInfoLocked(ApplicationInfo aInfo) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); stack.updateActivityApplicationInfoLocked(aInfo); @@ -2314,7 +2324,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D TaskRecord finishedTask = null; ActivityStack focusedStack = getTopDisplayFocusedStack(); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); // It is possible that request to finish activity might also remove its task and stack, // so we need to be careful with indexes in the loop and check child count every time. for (int stackNdx = 0; stackNdx < display.getChildCount(); ++stackNdx) { @@ -2330,7 +2340,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void finishVoiceTask(IVoiceInteractionSession session) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); final int numStacks = display.getChildCount(); for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); @@ -2417,7 +2427,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D protected <T extends ActivityStack> T getStack(int stackId) { for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { - final T stack = mActivityDisplays.valueAt(i).getStack(stackId); + final T stack = mActivityDisplays.get(i).getStack(stackId); if (stack != null) { return stack; } @@ -2428,7 +2438,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D /** @see ActivityDisplay#getStack(int, int) */ private <T extends ActivityStack> T getStack(int windowingMode, int activityType) { for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { - final T stack = mActivityDisplays.valueAt(i).getStack(windowingMode, activityType); + final T stack = mActivityDisplays.get(i).getStack(windowingMode, activityType); if (stack != null) { return stack; } @@ -2642,19 +2652,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } // Now look through all displays - mWindowManager.getDisplaysInFocusOrder(mTmpOrderedDisplayIds); - for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) { - final int displayId = mTmpOrderedDisplayIds.get(i); - if (displayId == preferredDisplay.mDisplayId) { + for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { + final ActivityDisplay display = mActivityDisplays.get(i); + if (display == preferredDisplay) { // We've already checked this one continue; } - // If a display is registered in WM, it must also be available in AM. - final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId); - if (display == null) { - // Looks like the display no longer exists in the system... - continue; - } final ActivityStack nextFocusableStack = display.getNextFocusableStack(currentFocus, ignoreCurrent); if (nextFocusableStack != null) { @@ -2676,13 +2679,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * @return Next valid {@link ActivityStack}, null if not found. */ ActivityStack getNextValidLaunchStackLocked(@NonNull ActivityRecord r, int currentFocus) { - mWindowManager.getDisplaysInFocusOrder(mTmpOrderedDisplayIds); - for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) { - final int displayId = mTmpOrderedDisplayIds.get(i); - if (displayId == currentFocus) { + for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { + final ActivityDisplay display = mActivityDisplays.get(i); + if (display.mDisplayId == currentFocus) { continue; } - final ActivityStack stack = getValidLaunchStackOnDisplay(displayId, r, + final ActivityStack stack = getValidLaunchStackOnDisplay(display.mDisplayId, r, null /* options */); if (stack != null) { return stack; @@ -3081,13 +3083,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D */ void removeStacksInWindowingModes(int... windowingModes) { for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { - mActivityDisplays.valueAt(i).removeStacksInWindowingModes(windowingModes); + mActivityDisplays.get(i).removeStacksInWindowingModes(windowingModes); } } void removeStacksWithActivityTypes(int... activityTypes) { for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { - mActivityDisplays.valueAt(i).removeStacksWithActivityTypes(activityTypes); + mActivityDisplays.get(i).removeStacksWithActivityTypes(activityTypes); } } @@ -3463,7 +3465,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ActivityRecord affinityMatch = null; if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); if (!r.hasCompatibleActivityType(stack)) { @@ -3500,7 +3502,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ActivityRecord findActivityLocked(Intent intent, ActivityInfo info, boolean compareIntentFilters) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); final ActivityRecord ar = stack.findActivityLocked( @@ -3515,7 +3517,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean hasAwakeDisplay() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); if (!display.shouldSleep()) { return true; } @@ -3543,7 +3545,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void prepareForShutdownLocked() { for (int i = 0; i < mActivityDisplays.size(); i++) { - createSleepTokenLocked("shutdown", mActivityDisplays.keyAt(i)); + createSleepTokenLocked("shutdown", mActivityDisplays.get(i).mDisplayId); } } @@ -3586,7 +3588,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void applySleepTokensLocked(boolean applyToStacks) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { // Set the sleeping state of the display. - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); final boolean displayShouldSleep = display.shouldSleep(); if (displayShouldSleep == display.isSleeping()) { continue; @@ -3666,7 +3668,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private boolean putStacksToSleepLocked(boolean allowDelay, boolean shuttingDown) { boolean allSleep = true; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); if (allowDelay) { @@ -3697,7 +3699,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void handleAppCrashLocked(WindowProcessController app) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); stack.handleAppCrashLocked(app); @@ -3746,7 +3748,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D try { // First the front stacks. In case any are not fullscreen and are in front of home. for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); stack.ensureActivitiesVisibleLocked(starting, configChanges, preserveWindows, @@ -3760,7 +3762,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void addStartingWindowsForVisibleActivities(boolean taskSwitch) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); stack.addStartingWindowsForVisibleActivities(taskSwitch); @@ -3778,7 +3780,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } mTaskLayersChanged = false; for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); displayNdx++) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); int baseLayer = 0; for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); @@ -3789,7 +3791,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void clearOtherAppTimeTrackers(AppTimeTracker except) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); stack.clearOtherAppTimeTrackers(except); @@ -3799,7 +3801,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void scheduleDestroyAllActivities(WindowProcessController app, String reason) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); stack.scheduleDestroyActivities(app, reason); @@ -3818,7 +3820,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // let's iterate through the tasks and release the oldest one. final int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); final int stackCount = display.getChildCount(); // Step through all stacks starting from behind, to hit the oldest things first. for (int stackNdx = 0; stackNdx < stackCount; stackNdx++) { @@ -3849,7 +3851,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mStartingUsers.add(uss); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); stack.switchUserLocked(userId); @@ -3953,7 +3955,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void validateTopActivitiesLocked() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); final ActivityRecord r = stack.topRunningActivityLocked(); @@ -3984,7 +3986,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D public void dumpDisplays(PrintWriter pw) { for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { - final ActivityDisplay display = mActivityDisplays.valueAt(i); + final ActivityDisplay display = mActivityDisplays.get(i); pw.print("[id:" + display.mDisplayId + " stacks:"); display.dumpStacks(pw); pw.print("]"); @@ -3998,7 +4000,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D pw.println("mCurTaskIdForUser=" + mCurTaskIdForUser); pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront); for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { - final ActivityDisplay display = mActivityDisplays.valueAt(i); + final ActivityDisplay display = mActivityDisplays.get(i); display.dump(pw, prefix); } if (!mWaitingForActivityVisible.isEmpty()) { @@ -4018,7 +4020,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final long token = proto.start(fieldId); super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */); for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) { - final ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx); activityDisplay.writeToProto(proto, DISPLAYS); } getKeyguardController().writeToProto(proto, KEYGUARD_CONTROLLER); @@ -4047,7 +4049,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D pw.print(prefix); pw.println("Display override configurations:"); final int displayCount = mActivityDisplays.size(); for (int i = 0; i < displayCount; i++) { - final ActivityDisplay activityDisplay = mActivityDisplays.valueAt(i); + final ActivityDisplay activityDisplay = mActivityDisplays.get(i); pw.print(prefix); pw.print(" "); pw.print(activityDisplay.mDisplayId); pw.print(": "); pw.println(activityDisplay.getOverrideConfiguration()); } @@ -4065,7 +4067,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ArrayList<ActivityRecord> activities = new ArrayList<>(); int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) { @@ -4096,11 +4098,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean dumpClient, String dumpPackage) { boolean printed = false; boolean needSep = false; - for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) { - ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx); + for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { + ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx); pw.print("Display #"); pw.print(activityDisplay.mDisplayId); pw.println(" (activities from top to bottom):"); - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); pw.println(); @@ -4299,12 +4301,18 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // TODO: Look into consolidating with getActivityDisplayOrCreateLocked() ActivityDisplay getActivityDisplay(int displayId) { - return mActivityDisplays.get(displayId); + for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { + final ActivityDisplay activityDisplay = mActivityDisplays.get(i); + if (activityDisplay.mDisplayId == displayId) { + return activityDisplay; + } + } + return null; } // TODO(multi-display): Look at all callpoints to make sure they make sense in multi-display. ActivityDisplay getDefaultDisplay() { - return mActivityDisplays.get(DEFAULT_DISPLAY); + return mDefaultDisplay; } /** @@ -4313,7 +4321,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D */ // TODO: Look into consolidating with getActivityDisplay() ActivityDisplay getActivityDisplayOrCreateLocked(int displayId) { - ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); + ActivityDisplay activityDisplay = getActivityDisplay(displayId); if (activityDisplay != null) { return activityDisplay; } @@ -4328,15 +4336,23 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } // The display hasn't been added to ActivityManager yet, create a new record now. activityDisplay = new ActivityDisplay(this, display); - attachDisplay(activityDisplay); + addChild(activityDisplay, ActivityDisplay.POSITION_BOTTOM); calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay); mWindowManager.onDisplayAdded(displayId); return activityDisplay; } @VisibleForTesting - void attachDisplay(ActivityDisplay display) { - mActivityDisplays.put(display.mDisplayId, display); + void addChild(ActivityDisplay activityDisplay, int position) { + positionChildAt(activityDisplay, position); + mWindowContainerController.positionChildAt( + activityDisplay.getWindowContainerController(), position); + } + + void removeChild(ActivityDisplay activityDisplay) { + // The caller must tell the controller of {@link ActivityDisplay} to release its container + // {@link DisplayContent}. That is done in {@link ActivityDisplay#releaseSelfIfNeeded}). + mActivityDisplays.remove(activityDisplay); } private void calculateDefaultMinimalSizeOfResizeableTasks(ActivityDisplay display) { @@ -4351,7 +4367,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } synchronized (mService.mGlobalLock) { - final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); + final ActivityDisplay activityDisplay = getActivityDisplay(displayId); if (activityDisplay == null) { return; } @@ -4362,14 +4378,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - void releaseActivityDisplayLocked(int displayId) { - mActivityDisplays.remove(displayId); - } - - private void handleDisplayChanged(int displayId) { synchronized (mService.mGlobalLock) { - ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); + ActivityDisplay activityDisplay = getActivityDisplay(displayId); // TODO: The following code block should be moved into {@link ActivityDisplay}. if (activityDisplay != null) { // The window policy is responsible for stopping activities on the default display @@ -4392,7 +4403,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } SleepToken createSleepTokenLocked(String tag, int displayId) { - ActivityDisplay display = mActivityDisplays.get(displayId); + final ActivityDisplay display = getActivityDisplay(displayId); if (display == null) { throw new IllegalArgumentException("Invalid display: " + displayId); } @@ -4406,7 +4417,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private void removeSleepTokenLocked(SleepTokenImpl token) { mSleepTokens.remove(token); - ActivityDisplay display = mActivityDisplays.get(token.mDisplayId); + final ActivityDisplay display = getActivityDisplay(token.mDisplayId); if (display != null) { display.mAllSleepTokens.remove(token); if (display.mAllSleepTokens.isEmpty()) { @@ -4429,7 +4440,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private StackInfo getStackInfo(ActivityStack stack) { final int displayId = stack.mDisplayId; - final ActivityDisplay display = mActivityDisplays.get(displayId); + final ActivityDisplay display = getActivityDisplay(displayId); StackInfo info = new StackInfo(); stack.getWindowContainerBounds(info.bounds); info.displayId = displayId; @@ -4483,7 +4494,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ArrayList<StackInfo> getAllStackInfosLocked() { ArrayList<StackInfo> list = new ArrayList<>(); for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); list.add(getStackInfo(stack)); @@ -4765,14 +4776,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } ActivityStack findStackBehind(ActivityStack stack) { - // TODO(multi-display): We are only looking for stacks on the default display. - final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY); - if (display == null) { - return null; - } - for (int i = display.getChildCount() - 1; i >= 0; i--) { - if (display.getChildAt(i) == stack && i > 0) { - return display.getChildAt(i - 1); + final ActivityDisplay display = getActivityDisplay(stack.mDisplayId); + if (display != null) { + for (int i = display.getChildCount() - 1; i >= 0; i--) { + if (display.getChildAt(i) == stack && i > 0) { + return display.getChildAt(i - 1); + } } } throw new IllegalStateException("Failed to find a stack behind stack=" + stack @@ -4904,7 +4913,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ActivityStack topFocusedStack = getTopDisplayFocusedStack(); // Traverse all displays. for (int i = mActivityDisplays.size() - 1; i >= 0; i--) { - final ActivityDisplay display = mActivityDisplays.valueAt(i); + final ActivityDisplay display = mActivityDisplays.get(i); // Traverse all stacks on a display. for (int j = display.getChildCount() - 1; j >= 0; --j) { final ActivityStack stack = display.getChildAt(j); diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS index f60c5c3208d9..79c98e550774 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -4,7 +4,6 @@ yamasani@google.com jsharkey@google.com hackbod@google.com omakoto@google.com -fkupolov@google.com ctate@google.com huiyu@google.com mwachens@google.com @@ -28,7 +27,4 @@ toddke@google.com michaelwr@google.com narayan@google.com -per-file GlobalSettingsToPropertiesMapper.java=fkupolov@google.com -per-file GlobalSettingsToPropertiesMapper.java=omakoto@google.com -per-file GlobalSettingsToPropertiesMapper.java=svetoslavganov@google.com -per-file GlobalSettingsToPropertiesMapper.java=yamasani@google.com +per-file GlobalSettingsToPropertiesMapper.java = omakoto@google.com, svetoslavganov@google.com, yamasani@google.com diff --git a/services/core/java/com/android/server/am/RunningTasks.java b/services/core/java/com/android/server/am/RunningTasks.java index 7008cee395d7..d878f5124f43 100644 --- a/services/core/java/com/android/server/am/RunningTasks.java +++ b/services/core/java/com/android/server/am/RunningTasks.java @@ -16,13 +16,9 @@ package com.android.server.am; -import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; -import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; - import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration.ActivityType; import android.app.WindowConfiguration.WindowingMode; -import android.util.SparseArray; import java.util.ArrayList; import java.util.Comparator; @@ -45,7 +41,7 @@ class RunningTasks { private final ArrayList<TaskRecord> mTmpStackTasks = new ArrayList<>(); void getTasks(int maxNum, List<RunningTaskInfo> list, @ActivityType int ignoreActivityType, - @WindowingMode int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays, + @WindowingMode int ignoreWindowingMode, ArrayList<ActivityDisplay> activityDisplays, int callingUid, boolean allowed) { // Return early if there are no tasks to fetch if (maxNum <= 0) { @@ -56,7 +52,7 @@ class RunningTasks { mTmpSortedSet.clear(); final int numDisplays = activityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final ActivityDisplay display = activityDisplays.valueAt(displayNdx); + final ActivityDisplay display = activityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); mTmpStackTasks.clear(); diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index 06462a2dcd5a..36e7cba9c818 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -21,7 +21,6 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricPromptReceiver; -import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -45,17 +44,16 @@ public abstract class AuthenticationClient extends ClientMonitor { public static final int LOCKOUT_TIMED = 1; public static final int LOCKOUT_PERMANENT = 2; + private final BiometricAuthenticator mAuthenticator; // Callback mechanism received from the client - // (BiometricPrompt -> FingerprintManager -> FingerprintService -> AuthenticationClient) + // (BiometricPrompt -> BiometricPromptService -> <Biometric>Service -> AuthenticationClient) private IBiometricPromptReceiver mDialogReceiverFromClient; private Bundle mBundle; private IStatusBarService mStatusBarService; private boolean mInLockout; - // TODO: BiometricManager, after other biometric modalities are introduced. - private final FingerprintManager mFingerprintManager; protected boolean mDialogDismissed; - // Receives events from SystemUI and handles them before forwarding them to FingerprintDialog + // Receives events from SystemUI and handles them before forwarding them to BiometricDialog protected IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() { @Override // binder call public void onDialogDismissed(int reason) { @@ -81,7 +79,7 @@ public abstract class AuthenticationClient extends ClientMonitor { public abstract void onStart(); /** - * This method is called when a fingerprint is authenticated or authentication is stopped + * This method is called when a biometric is authenticated or authentication is stopped * (cancelled by the user, or an error such as lockout has occurred). */ public abstract void onStop(); @@ -90,15 +88,15 @@ public abstract class AuthenticationClient extends ClientMonitor { BiometricService.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricService.ServiceListener listener, int targetUserId, int groupId, long opId, boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService) { + IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService, + BiometricAuthenticator authenticator) { super(context, metrics, daemon, halDeviceId, token, listener, targetUserId, groupId, restricted, owner); mOpId = opId; mBundle = bundle; mDialogReceiverFromClient = dialogReceiver; mStatusBarService = statusBarService; - mFingerprintManager = (FingerprintManager) getContext() - .getSystemService(Context.FINGERPRINT_SERVICE); + mAuthenticator = authenticator; mHandler = new Handler(Looper.getMainLooper()); } @@ -118,7 +116,7 @@ public abstract class AuthenticationClient extends ClientMonitor { try { if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { mStatusBarService.onBiometricHelp( - mFingerprintManager.getAcquiredString(acquiredInfo, vendorCode)); + mAuthenticator.getAcquiredString(acquiredInfo, vendorCode)); } return false; // acquisition continues } catch (RemoteException e) { @@ -139,15 +137,15 @@ public abstract class AuthenticationClient extends ClientMonitor { public boolean onError(long deviceId, int error, int vendorCode) { if (mDialogDismissed) { // If user cancels authentication, the application has already received the - // FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED message from onDialogDismissed() - // and stopped the fingerprint hardware, so there is no need to send a - // FingerprintManager.FINGERPRINT_ERROR_CANCELED message. + // ERROR_USER_CANCELED message from onDialogDismissed() + // and stopped the biometric hardware, so there is no need to send a + // ERROR_CANCELED message. return true; } if (mBundle != null) { try { mStatusBarService.onBiometricError( - mFingerprintManager.getErrorString(error, vendorCode)); + mAuthenticator.getErrorString(error, vendorCode)); } catch (RemoteException e) { Slog.e(getLogTag(), "Remote exception when sending error", e); } @@ -160,15 +158,14 @@ public abstract class AuthenticationClient extends ClientMonitor { boolean authenticated) { boolean result = false; - // If the fingerprint dialog is showing, notify authentication succeeded - // TODO: this goes to BiometricPrompt, split between biometric modalities + // If the biometric dialog is showing, notify authentication succeeded if (mBundle != null) { try { if (authenticated) { mStatusBarService.onBiometricAuthenticated(); } else { mStatusBarService.onBiometricHelp(getContext().getResources().getString( - com.android.internal.R.string.fingerprint_not_recognized)); + com.android.internal.R.string.biometric_not_recognized)); } } catch (RemoteException e) { Slog.e(getLogTag(), "Failed to notify Authenticated:", e); @@ -223,7 +220,7 @@ public abstract class AuthenticationClient extends ClientMonitor { // Send the lockout message to the system dialog if (mBundle != null) { mStatusBarService.onBiometricError( - mFingerprintManager.getErrorString(errorCode, 0 /* vendorCode */)); + mAuthenticator.getErrorString(errorCode, 0 /* vendorCode */)); mHandler.postDelayed(() -> { try { listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */); @@ -243,7 +240,7 @@ public abstract class AuthenticationClient extends ClientMonitor { if (listener != null) { vibrateSuccess(); } - result |= true; // we have a valid fingerprint, done + result |= true; // we have a valid biometric, done resetFailedAttempts(); onStop(); } @@ -270,9 +267,10 @@ public abstract class AuthenticationClient extends ClientMonitor { // If authenticating with system dialog, show the dialog if (mBundle != null) { try { - mStatusBarService.showBiometricDialog(mBundle, mDialogReceiver); + mStatusBarService.showBiometricDialog(mBundle, mDialogReceiver, + mAuthenticator.getType()); } catch (RemoteException e) { - Slog.e(getLogTag(), "Unable to show fingerprint dialog", e); + Slog.e(getLogTag(), "Unable to show biometric dialog", e); } } } catch (RemoteException e) { @@ -297,7 +295,8 @@ public abstract class AuthenticationClient extends ClientMonitor { Slog.w(getLogTag(), "stopAuthentication failed, result=" + result); return result; } - if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is no longer authenticating"); + if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + + " is no longer authenticating"); } catch (RemoteException e) { Slog.e(getLogTag(), "stopAuthentication failed", e); return ERROR_ESRCH; @@ -310,7 +309,7 @@ public abstract class AuthenticationClient extends ClientMonitor { try { mStatusBarService.hideBiometricDialog(); } catch (RemoteException e) { - Slog.e(getLogTag(), "Unable to hide fingerprint dialog", e); + Slog.e(getLogTag(), "Unable to hide biometric dialog", e); } } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index cc2e81ff4306..a181b6105471 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -47,6 +47,7 @@ import android.os.IHwBinder; import android.os.IRemoteCallback; import android.os.PowerManager; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -96,6 +97,7 @@ public abstract class BiometricService extends SystemService implements IHwBinde private final LockoutReceiver mLockoutReceiver = new LockoutReceiver(); private final ArrayList<LockoutResetMonitor> mLockoutMonitors = new ArrayList<>(); + protected final IStatusBarService mStatusBarService; protected final Map<Integer, Long> mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>()); protected final ResetFailedAttemptsForUserRunnable mResetFailedAttemptsForCurrentUserRunnable = @@ -221,10 +223,10 @@ public abstract class BiometricService extends SystemService implements IHwBinde IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, boolean restricted, String owner, Bundle bundle, IBiometricPromptReceiver dialogReceiver, - IStatusBarService statusBarService) { + IStatusBarService statusBarService, BiometricAuthenticator authenticator) { super(context, getMetrics(), daemon, halDeviceId, token, listener, targetUserId, groupId, opId, restricted, owner, bundle, dialogReceiver, - statusBarService); + statusBarService, authenticator); } @Override @@ -524,6 +526,8 @@ public abstract class BiometricService extends SystemService implements IHwBinde public BiometricService(Context context) { super(context); mContext = context; + mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString( com.android.internal.R.string.config_keyguardComponent)).getPackageName(); mAppOps = context.getSystemService(AppOpsManager.class); diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 2e76406489fe..f211e1716d13 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -42,7 +42,6 @@ import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; import android.os.SELinux; -import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; @@ -50,7 +49,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; -import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; import com.android.server.biometrics.BiometricService; @@ -133,7 +131,7 @@ public class FaceService extends BiometricService { final AuthenticationClientImpl client = new AuthenticationClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, - null /* bundle */, null /* dialogReceiver */, mStatusBarService); + null /* bundle */, null /* dialogReceiver */, mStatusBarService, mFaceManager); authenticateInternal(client, opId, opPackageName); } @@ -149,7 +147,7 @@ public class FaceService extends BiometricService { mDaemonWrapper, mHalDeviceId, token, new BiometricPromptServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, - bundle, dialogReceiver, mStatusBarService); + bundle, dialogReceiver, mStatusBarService, mFaceManager); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } @@ -326,13 +324,10 @@ public class FaceService extends BiometricService { */ private class BiometricPromptServiceListenerImpl implements ServiceListener { - // Use FaceManager to get strings, so BiometricPrompt interface is cleaner - private FaceManager mFaceManager; private IBiometricPromptServiceReceiver mBiometricPromptServiceReceiver; public BiometricPromptServiceListenerImpl(IBiometricPromptServiceReceiver receiver) { mBiometricPromptServiceReceiver = receiver; - mFaceManager = (FaceManager) getContext().getSystemService(Context.FACE_SERVICE); } @Override @@ -451,9 +446,9 @@ public class FaceService extends BiometricService { @GuardedBy("this") private IBiometricsFace mDaemon; - private long mHalDeviceId; - private IStatusBarService mStatusBarService; + // Use FaceManager to get strings, so BiometricPrompt interface is cleaner + private FaceManager mFaceManager; /** * Receives callbacks from the HAL. @@ -586,15 +581,14 @@ public class FaceService extends BiometricService { public FaceService(Context context) { super(context); - // TODO: can this be retrieved from AuthenticationClient, or BiometricService? - mStatusBarService = IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)); } @Override public void onStart() { + super.onStart(); publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper()); SystemServerInitThreadPool.get().submit(this::getFaceDaemon, TAG + ".onStart"); + mFaceManager = (FaceManager) getContext().getSystemService(Context.FACE_SERVICE); } @Override diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index a25b4b4bdf05..95fb9e33dded 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -47,7 +47,6 @@ import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; import android.os.SELinux; -import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; @@ -55,7 +54,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; -import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; import com.android.server.biometrics.BiometricService; @@ -154,7 +152,7 @@ public class FingerprintService extends BiometricService { final AuthenticationClientImpl client = new AuthenticationClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId, opId, restricted, opPackageName, null /* bundle */, - null /* dialogReceiver */, mStatusBarService); + null /* dialogReceiver */, mStatusBarService, mFingerprintManager); authenticateInternal(client, opId, opPackageName); } @@ -170,7 +168,7 @@ public class FingerprintService extends BiometricService { mDaemonWrapper, mHalDeviceId, token, new BiometricPromptServiceListenerImpl(receiver), mCurrentUserId, groupId, opId, restricted, opPackageName, bundle, - dialogReceiver, mStatusBarService); + dialogReceiver, mStatusBarService, mFingerprintManager); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } @@ -362,14 +360,10 @@ public class FingerprintService extends BiometricService { */ private class BiometricPromptServiceListenerImpl implements ServiceListener { - // Use FingerprintManager to get strings, so BiometricPrompt interface is cleaner - private FingerprintManager mFingerprintManager; private IBiometricPromptServiceReceiver mBiometricPromptServiceReceiver; public BiometricPromptServiceListenerImpl(IBiometricPromptServiceReceiver receiver) { mBiometricPromptServiceReceiver = receiver; - mFingerprintManager = (FingerprintManager) - getContext().getSystemService(Context.FINGERPRINT_SERVICE); } @Override @@ -571,9 +565,10 @@ public class FingerprintService extends BiometricService { private IBiometricsFingerprint mDaemon; private long mHalDeviceId; - private IStatusBarService mStatusBarService; private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw fingerprints + // Use FingerprintManager to get strings, so BiometricPrompt interface is cleaner. + private FingerprintManager mFingerprintManager; /** * Receives callbacks from the HAL. @@ -715,9 +710,6 @@ public class FingerprintService extends BiometricService { public FingerprintService(Context context) { super(context); - // TODO: can this be retrieved from AuthenticationClient, or BiometricService? - mStatusBarService = IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)); } @Override @@ -725,6 +717,8 @@ public class FingerprintService extends BiometricService { super.onStart(); publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper()); SystemServerInitThreadPool.get().submit(this::getFingerprintDaemon, TAG + ".onStart"); + mFingerprintManager = (FingerprintManager) + getContext().getSystemService(Context.FINGERPRINT_SERVICE); } @Override diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index e471c7d84b56..7b8571c131b6 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -24,6 +24,7 @@ import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; import static android.content.pm.PackageManager.GET_PERMISSIONS; +import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -34,6 +35,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.net.Uri; +import android.os.Build; import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.UserHandle; @@ -155,9 +157,8 @@ public class PermissionMonitor { } @VisibleForTesting - boolean isPreinstalledSystemApp(PackageInfo app) { - int flags = app.applicationInfo != null ? app.applicationInfo.flags : 0; - return (flags & (FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP)) != 0; + static boolean isVendorApp(@NonNull ApplicationInfo appInfo) { + return appInfo.isVendor() || appInfo.isOem() || appInfo.isProduct(); } @VisibleForTesting @@ -177,7 +178,13 @@ public class PermissionMonitor { } private boolean hasRestrictedNetworkPermission(PackageInfo app) { - if (isPreinstalledSystemApp(app)) return true; + // TODO : remove this check in the future(b/31479477). All apps should just + // request the appropriate permission for their use case since android Q. + if (app.applicationInfo != null + && app.applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q + && isVendorApp(app.applicationInfo)) { + return true; + } return hasPermission(app, CONNECTIVITY_INTERNAL) || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS); } @@ -186,13 +193,8 @@ public class PermissionMonitor { // This function defines what it means to hold the permission to use // background networks. return hasPermission(app, CHANGE_NETWORK_STATE) - || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS) - || hasPermission(app, CONNECTIVITY_INTERNAL) || hasPermission(app, NETWORK_STACK) - // TODO : remove this check (b/31479477). Not all preinstalled apps should - // have access to background networks, they should just request the appropriate - // permission for their use case from the list above. - || isPreinstalledSystemApp(app); + || hasRestrictedNetworkPermission(app); } public boolean hasUseBackgroundNetworksPermission(int uid) { diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java index 66994447b0bb..2b1d9196fe18 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -162,6 +162,9 @@ public class TetheringConfiguration { dumpStringArray(pw, "provisioningApp", provisioningApp); pw.print("provisioningAppNoUi: "); pw.println(provisioningAppNoUi); + + pw.print("enableLegacyDhcpServer: "); + pw.println(enableLegacyDhcpServer); } public String toString() { @@ -176,6 +179,7 @@ public class TetheringConfiguration { makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes)))); sj.add(String.format("provisioningApp:%s", makeString(provisioningApp))); sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi)); + sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer)); return String.format("TetheringConfiguration{%s}", sj.toString()); } diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index c473ef23db3e..019d726d8c48 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -51,6 +51,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.ColorDisplayController; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.RingBuffer; @@ -72,7 +73,6 @@ import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.Deque; import java.util.HashMap; @@ -363,12 +363,9 @@ public class BrightnessTracker { return; } - builder.setNightMode(mInjector.getSecureIntForUser(mContentResolver, - Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 0, UserHandle.USER_CURRENT) - == 1); - builder.setColorTemperature(mInjector.getSecureIntForUser(mContentResolver, - Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, - 0, UserHandle.USER_CURRENT)); + builder.setNightMode(mInjector.isNightModeActive(mContext, UserHandle.USER_CURRENT)); + builder.setColorTemperature(mInjector.getColorTemperature(mContext, + UserHandle.USER_CURRENT)); BrightnessChangeEvent event = builder.build(); if (DEBUG) { @@ -952,5 +949,13 @@ public class BrightnessTracker { public boolean isInteractive(Context context) { return context.getSystemService(PowerManager.class).isInteractive(); } + + public int getColorTemperature(Context context, int userId) { + return new ColorDisplayController(context, userId).getColorTemperature(); + } + + public boolean isNightModeActive(Context context, int userId) { + return new ColorDisplayController(context, userId).isActivated(); + } } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index b57356f12223..b5a9f7416237 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -28,6 +28,10 @@ public abstract class InputMethodManagerInternal { /** * Called by the window manager service when a client process is being attached to the window * manager service. + * + * <p>The caller must not have WindowManagerService lock. This method internally acquires + * InputMethodManagerService lock.</p> + * * @param client {@link android.os.Binder} proxy that is associated with the singleton instance * of {@link android.view.inputmethod.InputMethodManager} that runs on the client * process @@ -42,6 +46,10 @@ public abstract class InputMethodManagerInternal { /** * Called by the window manager service when a client process is being attached to the window * manager service. + * + * <p>The caller must not have WindowManagerService lock. This method internally acquires + * InputMethodManagerService lock.</p> + * * @param client {@link android.os.Binder} proxy that is associated with the singleton instance * of {@link android.view.inputmethod.InputMethodManager} that runs on the client * process diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 182901ac0ba6..07f3e176fc93 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1988,6 +1988,14 @@ public class PackageManagerService extends IPackageManager.Stub mRequiredVerifierPackage, null /*finishedReceiver*/, updateUserIds, instantUserIds); } + // If package installer is defined, notify package installer about new + // app installed + if (mRequiredInstallerPackage != null) { + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, + extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/, + mRequiredInstallerPackage, null /*finishedReceiver*/, + firstUserIds, instantUserIds); + } // Send replaced for users that don't see the package for the first time if (update) { @@ -8960,15 +8968,15 @@ public class PackageManagerService extends IPackageManager.Stub } /** - * Enforces that only the system UID or shell's UID can call a method exposed - * via Binder. + * Enforces that only the system UID or root's UID or shell's UID can call + * a method exposed via Binder. * * @param message used as message if SecurityException is thrown * @throws SecurityException if the caller is not system or shell */ - private static void enforceSystemOrShell(String message) { + private static void enforceSystemOrRootOrShell(String message) { final int uid = Binder.getCallingUid(); - if (uid != Process.SYSTEM_UID && uid != Process.SHELL_UID) { + if (uid != Process.SYSTEM_UID && uid != Process.ROOT_UID && uid != Process.SHELL_UID) { throw new SecurityException(message); } } @@ -9454,7 +9462,7 @@ public class PackageManagerService extends IPackageManager.Stub if (getInstantAppPackageName(Binder.getCallingUid()) != null) { return false; } - enforceSystemOrShell("runBackgroundDexoptJob"); + enforceSystemOrRootOrShell("runBackgroundDexoptJob"); final long identity = Binder.clearCallingIdentity(); try { return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext, packageNames); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index f2c0395f9f0e..361416adc4ef 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -1302,6 +1302,7 @@ class PackageManagerShellCommand extends ShellCommand { } boolean result = mInterface.runBackgroundDexoptJob(packageNames.isEmpty() ? null : packageNames); + getOutPrintWriter().println(result ? "Success" : "Failure"); return result ? 0 : -1; } diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 556038f3f646..41c0be63a5bb 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -19,7 +19,6 @@ import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.AlarmManager.OnAlarmListener; -import android.app.PendingIntent; import android.app.ProcessMemoryState; import android.app.StatsManager; import android.bluetooth.BluetoothActivityEnergyInfo; @@ -65,10 +64,10 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.net.NetworkStatsFactory; import com.android.internal.os.BinderCallsStats.ExportedCallStat; import com.android.internal.os.KernelCpuSpeedReader; -import com.android.internal.os.KernelUidCpuTimeReader; -import com.android.internal.os.KernelUidCpuClusterTimeReader; import com.android.internal.os.KernelUidCpuActiveTimeReader; +import com.android.internal.os.KernelUidCpuClusterTimeReader; import com.android.internal.os.KernelUidCpuFreqTimeReader; +import com.android.internal.os.KernelUidCpuTimeReader; import com.android.internal.os.KernelWakelockReader; import com.android.internal.os.KernelWakelockStats; import com.android.internal.os.PowerProfile; @@ -79,7 +78,6 @@ import com.android.server.SystemService; import java.io.File; import java.io.FileDescriptor; -import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -328,7 +326,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { PackageManager pm = context.getPackageManager(); String app = intent.getData().getSchemeSpecificPart(); sStatsd.informOnePackageRemoved(app, uid); - StatsLog.write(StatsLog.GENERIC_ATOM, uid, 1000); } } else { PackageManager pm = context.getPackageManager(); @@ -337,7 +334,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { String app = intent.getData().getSchemeSpecificPart(); PackageInfo pi = pm.getPackageInfo(app, PackageManager.MATCH_ANY_USER); sStatsd.informOnePackage(app, uid, pi.getLongVersionCode()); - StatsLog.write(StatsLog.GENERIC_ATOM, uid, 1001); } } catch (Exception e) { Slog.w(TAG, "Failed to inform statsd of an app update", e); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index b8c9be777fef..14294ec54ef1 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -565,10 +565,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver) { + public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type) { if (mBar != null) { try { - mBar.showBiometricDialog(bundle, receiver); + mBar.showBiometricDialog(bundle, receiver, type); } catch (RemoteException ex) { } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ba467373398d..32fa9bf97930 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -921,6 +921,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } @Override + DisplayWindowController getController() { + return (DisplayWindowController) super.getController(); + } + + @Override public Display getDisplay() { return mDisplay; } diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java index 74a8a353a5c9..76b6dbea36b9 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowController.java +++ b/services/core/java/com/android/server/wm/DisplayWindowController.java @@ -73,6 +73,10 @@ public class DisplayWindowController // override configuration propagation to just here. } + public int getDisplayId() { + return mDisplayId; + } + /** * Positions the task stack at the given position in the task stack container. */ diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index d8cbb2677f91..86b14337139e 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -174,24 +174,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { return null; } - /** - * Get an array with display ids ordered by focus priority - last items should be given - * focus first. Sparse array just maps position to displayId. - */ - void getDisplaysInFocusOrder(SparseIntArray displaysInFocusOrder) { - displaysInFocusOrder.clear(); - - final int size = mChildren.size(); - for (int i = 0; i < size; ++i) { - final DisplayContent displayContent = mChildren.get(i); - if (displayContent.isRemovalDeferred()) { - // Don't report displays that are going to be removed soon. - continue; - } - displaysInFocusOrder.put(i, displayContent.getDisplayId()); - } - } - DisplayContent getDisplayContent(int displayId) { for (int i = mChildren.size() - 1; i >= 0; --i) { final DisplayContent current = mChildren.get(i); @@ -1098,6 +1080,25 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { } @Override + void positionChildAt(int position, DisplayContent child, boolean includingParents) { + super.positionChildAt(position, child, includingParents); + final RootWindowContainerController controller = getController(); + if (controller != null) { + controller.onChildPositionChanged(child, position); + } + } + + void positionChildAt(int position, DisplayContent child) { + // Only called from controller so no need to notify the change to controller. + super.positionChildAt(position, child, false /* includingParents */); + } + + @Override + RootWindowContainerController getController() { + return (RootWindowContainerController) super.getController(); + } + + @Override void scheduleAnimation() { mService.scheduleAnimationLocked(); } diff --git a/services/core/java/com/android/server/wm/RootWindowContainerController.java b/services/core/java/com/android/server/wm/RootWindowContainerController.java new file mode 100644 index 000000000000..93be6e9b68e3 --- /dev/null +++ b/services/core/java/com/android/server/wm/RootWindowContainerController.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +/** + * Controller for the root container. This is created by activity manager to link activity + * stack supervisor to the root window container they use in window manager. + */ +public class RootWindowContainerController + extends WindowContainerController<RootWindowContainer, RootWindowContainerListener> { + + public RootWindowContainerController(RootWindowContainerListener listener) { + super(listener, WindowManagerService.getInstance()); + synchronized (mWindowMap) { + mRoot.setController(this); + } + } + + void onChildPositionChanged(DisplayContent child, int position) { + // This callback invokes to AM directly so here assumes AM lock is held. If there is another + // path called only with WM lock, it should change to use handler to post or move outside of + // WM lock with adding AM lock. + mListener.onChildPositionChanged(child.getController(), position); + } + + /** Move the display to the given position. */ + public void positionChildAt(DisplayWindowController child, int position) { + synchronized (mWindowMap) { + mContainer.positionChildAt(position, child.mContainer); + } + } +} diff --git a/services/core/java/com/android/server/wm/RootWindowContainerListener.java b/services/core/java/com/android/server/wm/RootWindowContainerListener.java new file mode 100644 index 000000000000..f413e3f7c2ea --- /dev/null +++ b/services/core/java/com/android/server/wm/RootWindowContainerListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +/** + * Interface used by the creator of {@link RootWindowContainerController} to notify the changes to + * the display container in activity manager. + */ +public interface RootWindowContainerListener extends WindowContainerListener { + /** Called when the z-order of display is changed. */ + void onChildPositionChanged(DisplayWindowController childController, int position); +} diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 71f34c98ee1f..eb419c9684f5 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -22,10 +22,8 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRA import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.res.Configuration.EMPTY; + import static com.android.server.EventLogTags.WM_TASK_REMOVED; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.TaskProto.APP_WINDOW_TOKENS; import static com.android.server.wm.TaskProto.BOUNDS; import static com.android.server.wm.TaskProto.DEFER_REMOVAL; @@ -33,6 +31,9 @@ import static com.android.server.wm.TaskProto.FILLS_PARENT; import static com.android.server.wm.TaskProto.ID; import static com.android.server.wm.TaskProto.TEMP_INSET_BOUNDS; import static com.android.server.wm.TaskProto.WINDOW_CONTAINER; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.CallSuper; import android.app.ActivityManager.TaskDescription; @@ -43,8 +44,8 @@ import android.util.EventLog; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.Surface; - import android.view.SurfaceControl; + import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; @@ -472,7 +473,8 @@ class Task extends WindowContainer<AppWindowToken> { void setDragResizing(boolean dragResizing, int dragResizeMode) { if (mDragResizing != dragResizing) { - if (!DragResizeMode.isModeAllowedForStack(mStack, dragResizeMode)) { + // No need to check if the mode is allowed if it's leaving dragResize + if (dragResizing && !DragResizeMode.isModeAllowedForStack(mStack, dragResizeMode)) { throw new IllegalArgumentException("Drag resize mode not allow for stack stackId=" + mStack.mStackId + " dragResizeMode=" + dragResizeMode); } diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java index 5f41df72394a..228bfade25d5 100644 --- a/services/core/java/com/android/server/wm/WindowFrames.java +++ b/services/core/java/com/android/server/wm/WindowFrames.java @@ -30,18 +30,19 @@ import static com.android.server.wm.WindowFramesProto.VISIBLE_FRAME; import android.annotation.NonNull; import android.graphics.Rect; import android.util.proto.ProtoOutputStream; - -import java.io.PrintWriter; import android.view.DisplayCutout; import com.android.server.wm.utils.WmDisplayCutout; +import java.io.PrintWriter; + /** * Container class for all the window frames that affect how windows are laid out. * * TODO(b/111611553): Investigate which frames are still needed and which are duplicates */ public class WindowFrames { + private static final StringBuilder sTmpSB = new StringBuilder(); /** * In most cases, this is the area of the entire screen. @@ -197,29 +198,18 @@ public class WindowFrames { } public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("Frames: containing="); - mContainingFrame.printShortString(pw); - pw.print(" parent="); mParentFrame.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print(" display="); - mDisplayFrame.printShortString(pw); - pw.print(" overscan="); mOverscanFrame.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print(" content="); - mContentFrame.printShortString(pw); - pw.print(" visible="); mVisibleFrame.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print(" decor="); - mDecorFrame.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print(" outset="); - mOutsetFrame.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw); - pw.print(" last="); mLastFrame.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print(" cutout=" + mDisplayCutout.getDisplayCutout()); - pw.print(" last=" + mLastDisplayCutout.getDisplayCutout()); - pw.println(); + pw.println(prefix + "Frames: containing=" + + mContainingFrame.toShortString(sTmpSB) + + " parent=" + mParentFrame.toShortString(sTmpSB)); + pw.println(prefix + " display=" + mDisplayFrame.toShortString(sTmpSB) + + " overscan=" + mOverscanFrame.toShortString(sTmpSB)); + pw.println(prefix + " content=" + mContentFrame.toShortString(sTmpSB) + + " visible=" + mVisibleFrame.toShortString(sTmpSB)); + pw.println(prefix + " decor=" + mDecorFrame.toShortString(sTmpSB)); + pw.println(prefix + " outset=" + mOutsetFrame.toShortString(sTmpSB)); + pw.println(prefix + "mFrame=" + mFrame.toShortString(sTmpSB) + + " last=" + mLastFrame.toShortString(sTmpSB)); + pw.println(prefix + " cutout=" + mDisplayCutout.getDisplayCutout() + + " last=" + mLastDisplayCutout.getDisplayCutout()); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f8d0c7291646..e80a47eef2d5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5345,17 +5345,6 @@ public class WindowManagerService extends IWindowManager.Stub mWindowPlacerLocked.performSurfacePlacement(); } - /** - * Get an array with display ids ordered by focus priority - last items should be given - * focus first. Sparse array just maps position to displayId. - */ - // TODO: Maintain display list in focus order in ActivityManager and remove this call. - public void getDisplaysInFocusOrder(SparseIntArray displaysInFocusOrder) { - synchronized(mWindowMap) { - mRoot.getDisplaysInFocusOrder(displaysInFocusOrder); - } - } - @Override public void setOverscan(int displayId, int left, int top, int right, int bottom) { if (mContext.checkCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 97313f2891c3..466e298974d0 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -602,6 +602,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ private long mFrameNumber = -1; + private static final StringBuilder sTmpSB = new StringBuilder(); + /** * Compares two window sub-layers and returns -1 if the first is lesser than the second in terms * of z-order and 1 otherwise. @@ -1113,9 +1115,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWindowFrames.mFrame.bottom - mWindowFrames.mStableFrame.bottom, 0)); } - mWindowFrames.setDisplayCutout( - windowFrames.mDisplayCutout.calculateRelativeTo(windowFrames.mFrame)); + windowFrames.mDisplayCutout.calculateRelativeTo(mWindowFrames.mFrame)); // Offset the actual frame by the amount layout frame is off. mWindowFrames.mFrame.offset(-layoutXDiff, -layoutYDiff); @@ -3336,183 +3337,160 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { final TaskStack stack = getStack(); - pw.print(prefix); pw.print("mDisplayId="); pw.print(getDisplayId()); - if (stack != null) { - pw.print(" stackId="); pw.print(stack.mStackId); - } - pw.print(" mSession="); pw.print(mSession); - pw.print(" mClient="); pw.println(mClient.asBinder()); - pw.print(prefix); pw.print("mOwnerUid="); pw.print(mOwnerUid); - pw.print(" mShowToOwnerOnly="); pw.print(mShowToOwnerOnly); - pw.print(" package="); pw.print(mAttrs.packageName); - pw.print(" appop="); pw.println(AppOpsManager.opToName(mAppOp)); - pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs.toString(prefix)); - pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth); - pw.print(" h="); pw.print(mRequestedHeight); - pw.print(" mLayoutSeq="); pw.println(mLayoutSeq); + pw.print(prefix + "mDisplayId=" + getDisplayId()); + if (stack != null) { + pw.print(" stackId=" + stack.mStackId); + } + pw.println(" mSession=" + mSession + + " mClient=" + mClient.asBinder()); + pw.println(prefix + "mOwnerUid=" + mOwnerUid + + " mShowToOwnerOnly=" + mShowToOwnerOnly + + " package=" + mAttrs.packageName + + " appop=" + AppOpsManager.opToName(mAppOp)); + pw.println(prefix + "mAttrs=" + mAttrs.toString(prefix)); + pw.println(prefix + "Requested w=" + mRequestedWidth + + " h=" + mRequestedHeight + + " mLayoutSeq=" + mLayoutSeq); if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) { - pw.print(prefix); pw.print("LastRequested w="); pw.print(mLastRequestedWidth); - pw.print(" h="); pw.println(mLastRequestedHeight); + pw.println(prefix + "LastRequested w=" + mLastRequestedWidth + + " h=" + mLastRequestedHeight); } if (mIsChildWindow || mLayoutAttached) { - pw.print(prefix); pw.print("mParentWindow="); pw.print(getParentWindow()); - pw.print(" mLayoutAttached="); pw.println(mLayoutAttached); + pw.println(prefix + "mParentWindow=" + getParentWindow() + + " mLayoutAttached=" + mLayoutAttached); } if (mIsImWindow || mIsWallpaper || mIsFloatingLayer) { - pw.print(prefix); pw.print("mIsImWindow="); pw.print(mIsImWindow); - pw.print(" mIsWallpaper="); pw.print(mIsWallpaper); - pw.print(" mIsFloatingLayer="); pw.print(mIsFloatingLayer); - pw.print(" mWallpaperVisible="); pw.println(mWallpaperVisible); + pw.println(prefix + "mIsImWindow=" + mIsImWindow + + " mIsWallpaper=" + mIsWallpaper + + " mIsFloatingLayer=" + mIsFloatingLayer + + " mWallpaperVisible=" + mWallpaperVisible); } if (dumpAll) { - pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer); - pw.print(" mSubLayer="); pw.print(mSubLayer); - pw.print(" mAnimLayer="); pw.print(mLayer); pw.print("+"); - pw.print("="); pw.print(mWinAnimator.mAnimLayer); - pw.print(" mLastLayer="); pw.println(mWinAnimator.mLastLayer); + pw.println(prefix + "mBaseLayer=" + mBaseLayer + + " mSubLayer=" + mSubLayer + + " mAnimLayer=" + mLayer + "=" + mWinAnimator.mAnimLayer + + " mLastLayer=" + mWinAnimator.mLastLayer); } if (dumpAll) { - pw.print(prefix); pw.print("mToken="); pw.println(mToken); + pw.println(prefix + "mToken=" + mToken); if (mAppToken != null) { - pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken); - pw.print(prefix); pw.print(" isAnimatingWithSavedSurface()="); - pw.print(" mAppDied=");pw.print(mAppDied); - pw.print(prefix); pw.print("drawnStateEvaluated="); - pw.print(getDrawnStateEvaluated()); - pw.print(prefix); pw.print("mightAffectAllDrawn="); - pw.println(mightAffectAllDrawn()); + pw.println(prefix + "mAppToken=" + mAppToken); + pw.print(prefix + "mAppDied=" + mAppDied); + pw.print(prefix + "drawnStateEvaluated=" + getDrawnStateEvaluated()); + pw.println(prefix + "mightAffectAllDrawn=" + mightAffectAllDrawn()); } - pw.print(prefix); pw.print("mViewVisibility=0x"); - pw.print(Integer.toHexString(mViewVisibility)); - pw.print(" mHaveFrame="); pw.print(mHaveFrame); - pw.print(" mObscured="); pw.println(mObscured); - pw.print(prefix); pw.print("mSeq="); pw.print(mSeq); - pw.print(" mSystemUiVisibility=0x"); - pw.println(Integer.toHexString(mSystemUiVisibility)); + pw.println(prefix + "mViewVisibility=0x" + Integer.toHexString(mViewVisibility) + + " mHaveFrame=" + mHaveFrame + + " mObscured=" + mObscured); + pw.println(prefix + "mSeq=" + mSeq + + " mSystemUiVisibility=0x" + Integer.toHexString(mSystemUiVisibility)); } if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim || !mAppOpVisibility - || isParentWindowHidden()|| mPermanentlyHidden || mForceHideNonSystemOverlayWindow + || isParentWindowHidden() || mPermanentlyHidden || mForceHideNonSystemOverlayWindow || mHiddenWhileSuspended) { - pw.print(prefix); pw.print("mPolicyVisibility="); - pw.print(mPolicyVisibility); - pw.print(" mPolicyVisibilityAfterAnim="); - pw.print(mPolicyVisibilityAfterAnim); - pw.print(" mAppOpVisibility="); - pw.print(mAppOpVisibility); - pw.print(" parentHidden="); pw.print(isParentWindowHidden()); - pw.print(" mPermanentlyHidden="); pw.print(mPermanentlyHidden); - pw.print(" mHiddenWhileSuspended="); pw.print(mHiddenWhileSuspended); - pw.print(" mForceHideNonSystemOverlayWindow="); pw.println( - mForceHideNonSystemOverlayWindow); + pw.println(prefix + "mPolicyVisibility=" + mPolicyVisibility + + " mPolicyVisibilityAfterAnim=" + mPolicyVisibilityAfterAnim + + " mAppOpVisibility=" + mAppOpVisibility + + " parentHidden=" + isParentWindowHidden() + + " mPermanentlyHidden=" + mPermanentlyHidden + + " mHiddenWhileSuspended=" + mHiddenWhileSuspended + + " mForceHideNonSystemOverlayWindow=" + mForceHideNonSystemOverlayWindow); } if (!mRelayoutCalled || mLayoutNeeded) { - pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled); - pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded); + pw.println(prefix + "mRelayoutCalled=" + mRelayoutCalled + + " mLayoutNeeded=" + mLayoutNeeded); } if (dumpAll) { - pw.print(prefix); pw.print("mGivenContentInsets="); - mGivenContentInsets.printShortString(pw); - pw.print(" mGivenVisibleInsets="); - mGivenVisibleInsets.printShortString(pw); - pw.println(); + pw.println(prefix + "mGivenContentInsets=" + mGivenContentInsets.toShortString(sTmpSB) + + " mGivenVisibleInsets=" + mGivenVisibleInsets.toShortString(sTmpSB)); if (mTouchableInsets != 0 || mGivenInsetsPending) { - pw.print(prefix); pw.print("mTouchableInsets="); pw.print(mTouchableInsets); - pw.print(" mGivenInsetsPending="); pw.println(mGivenInsetsPending); + pw.println(prefix + "mTouchableInsets=" + mTouchableInsets + + " mGivenInsetsPending=" + mGivenInsetsPending); Region region = new Region(); getTouchableRegion(region); - pw.print(prefix); pw.print("touchable region="); pw.println(region); + pw.println(prefix + "touchable region=" + region); } - pw.print(prefix); pw.print("mFullConfiguration="); pw.println(getConfiguration()); - pw.print(prefix); pw.print("mLastReportedConfiguration="); - pw.println(getLastReportedConfiguration()); + pw.println(prefix + "mFullConfiguration=" + getConfiguration()); + pw.println(prefix + "mLastReportedConfiguration=" + getLastReportedConfiguration()); } - pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface); - pw.print(" isReadyForDisplay()="); pw.print(isReadyForDisplay()); - pw.print(" mWindowRemovalAllowed="); pw.println(mWindowRemovalAllowed); + pw.println(prefix + "mHasSurface=" + mHasSurface + + " isReadyForDisplay()=" + isReadyForDisplay() + + " mWindowRemovalAllowed=" + mWindowRemovalAllowed); if (mEnforceSizeCompat) { - pw.print(prefix); pw.print("mCompatFrame="); mCompatFrame.printShortString(pw); - pw.println(); + pw.println(prefix + "mCompatFrame=" + mCompatFrame.toShortString(sTmpSB)); } if (dumpAll) { mWindowFrames.dump(pw, prefix); - pw.print(prefix); pw.print("Cur insets: overscan="); - mOverscanInsets.printShortString(pw); - pw.print(" content="); mContentInsets.printShortString(pw); - pw.print(" visible="); mVisibleInsets.printShortString(pw); - pw.print(" stable="); mStableInsets.printShortString(pw); - pw.print(" surface="); mAttrs.surfaceInsets.printShortString(pw); - pw.print(" outsets="); mOutsets.printShortString(pw); - pw.print(prefix); pw.print("Lst insets: overscan="); - mLastOverscanInsets.printShortString(pw); - pw.print(" content="); mLastContentInsets.printShortString(pw); - pw.print(" visible="); mLastVisibleInsets.printShortString(pw); - pw.print(" stable="); mLastStableInsets.printShortString(pw); - pw.print(" physical="); mLastOutsets.printShortString(pw); - pw.print(" outset="); mLastOutsets.printShortString(pw); - pw.println(); + pw.print(prefix + "Cur insets: overscan=" + mOverscanInsets.toShortString(sTmpSB) + + " content=" + mContentInsets.toShortString(sTmpSB) + + " visible=" + mVisibleInsets.toShortString(sTmpSB) + + " stable=" + mStableInsets.toShortString(sTmpSB) + + " surface=" + mAttrs.surfaceInsets.toShortString(sTmpSB) + + " outsets=" + mOutsets.toShortString(sTmpSB)); + pw.println(prefix + "Lst insets: overscan=" + mLastOverscanInsets.toShortString(sTmpSB) + + " content=" + mLastContentInsets.toShortString(sTmpSB) + + " visible=" + mLastVisibleInsets.toShortString(sTmpSB) + + " stable=" + mLastStableInsets.toShortString(sTmpSB) + + " outset=" + mLastOutsets.toShortString(sTmpSB)); } super.dump(pw, prefix, dumpAll); - pw.print(prefix); pw.print(mWinAnimator); pw.println(":"); + pw.println(prefix + mWinAnimator + ":"); mWinAnimator.dump(pw, prefix + " ", dumpAll); if (mAnimatingExit || mRemoveOnExit || mDestroying || mRemoved) { - pw.print(prefix); pw.print("mAnimatingExit="); pw.print(mAnimatingExit); - pw.print(" mRemoveOnExit="); pw.print(mRemoveOnExit); - pw.print(" mDestroying="); pw.print(mDestroying); - pw.print(" mRemoved="); pw.println(mRemoved); + pw.println(prefix + "mAnimatingExit=" + mAnimatingExit + + " mRemoveOnExit=" + mRemoveOnExit + + " mDestroying=" + mDestroying + + " mRemoved=" + mRemoved); } if (getOrientationChanging() || mAppFreezing || mReportOrientationChanged) { - pw.print(prefix); pw.print("mOrientationChanging="); - pw.print(mOrientationChanging); - pw.print(" configOrientationChanging="); - pw.print(getLastReportedConfiguration().orientation - != getConfiguration().orientation); - pw.print(" mAppFreezing="); pw.print(mAppFreezing); - pw.print(" mReportOrientationChanged="); pw.println(mReportOrientationChanged); + pw.println(prefix + "mOrientationChanging=" + mOrientationChanging + + " configOrientationChanging=" + + (getLastReportedConfiguration().orientation != getConfiguration().orientation) + + " mAppFreezing=" + mAppFreezing + + " mReportOrientationChanged=" + mReportOrientationChanged); } if (mLastFreezeDuration != 0) { - pw.print(prefix); pw.print("mLastFreezeDuration="); - TimeUtils.formatDuration(mLastFreezeDuration, pw); pw.println(); + pw.print(prefix + "mLastFreezeDuration="); + TimeUtils.formatDuration(mLastFreezeDuration, pw); + pw.println(); } - pw.print(prefix); pw.print("mForceSeamlesslyRotate="); pw.print(mForceSeamlesslyRotate); - pw.print(" seamlesslyRotate: pending="); + pw.print(prefix + "mForceSeamlesslyRotate=" + mForceSeamlesslyRotate + + " seamlesslyRotate: pending="); if (mPendingSeamlessRotate != null) { mPendingSeamlessRotate.dump(pw); } else { pw.print("null"); } - pw.print(" finishedFrameNumber="); pw.print(mFinishSeamlessRotateFrameNumber); - pw.println(); + pw.println(" finishedFrameNumber=" + mFinishSeamlessRotateFrameNumber); if (mHScale != 1 || mVScale != 1) { - pw.print(prefix); pw.print("mHScale="); pw.print(mHScale); - pw.print(" mVScale="); pw.println(mVScale); + pw.println(prefix + "mHScale=" + mHScale + + " mVScale=" + mVScale); } if (mWallpaperX != -1 || mWallpaperY != -1) { - pw.print(prefix); pw.print("mWallpaperX="); pw.print(mWallpaperX); - pw.print(" mWallpaperY="); pw.println(mWallpaperY); + pw.println(prefix + "mWallpaperX=" + mWallpaperX + + " mWallpaperY=" + mWallpaperY); } if (mWallpaperXStep != -1 || mWallpaperYStep != -1) { - pw.print(prefix); pw.print("mWallpaperXStep="); pw.print(mWallpaperXStep); - pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep); + pw.println(prefix + "mWallpaperXStep=" + mWallpaperXStep + + " mWallpaperYStep=" + mWallpaperYStep); } if (mWallpaperDisplayOffsetX != Integer.MIN_VALUE || mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - pw.print(prefix); pw.print("mWallpaperDisplayOffsetX="); - pw.print(mWallpaperDisplayOffsetX); - pw.print(" mWallpaperDisplayOffsetY="); - pw.println(mWallpaperDisplayOffsetY); + pw.println(prefix + "mWallpaperDisplayOffsetX=" + mWallpaperDisplayOffsetX + + " mWallpaperDisplayOffsetY=" + mWallpaperDisplayOffsetY); } if (mDrawLock != null) { - pw.print(prefix); pw.println("mDrawLock=" + mDrawLock); + pw.println(prefix + "mDrawLock=" + mDrawLock); } if (isDragResizing()) { - pw.print(prefix); pw.println("isDragResizing=" + isDragResizing()); + pw.println(prefix + "isDragResizing=" + isDragResizing()); } if (computeDragResizing()) { - pw.print(prefix); pw.println("computeDragResizing=" + computeDragResizing()); + pw.println(prefix + "computeDragResizing=" + computeDragResizing()); } - pw.print(prefix); pw.println("isOnScreen=" + isOnScreen()); - pw.print(prefix); pw.println("isVisible=" + isVisible()); + pw.println(prefix + "isOnScreen=" + isOnScreen()); + pw.println(prefix + "isVisible=" + isVisible()); } @Override @@ -4934,9 +4912,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("from="); pw.print(mFrom); - pw.print(" to="); pw.print(mTo); - pw.print(" duration="); pw.println(mDuration); + pw.println(prefix + "from=" + mFrom + + " to=" + mTo + + " duration=" + mDuration); } @Override diff --git a/services/net/java/android/net/dhcp/DhcpLease.java b/services/net/java/android/net/dhcp/DhcpLease.java index d2a15b37dcc0..6cdd2aa8579d 100644 --- a/services/net/java/android/net/dhcp/DhcpLease.java +++ b/services/net/java/android/net/dhcp/DhcpLease.java @@ -130,9 +130,14 @@ public class DhcpLease { return HexDump.toHexString(bytes); } + static String inet4AddrToString(@Nullable Inet4Address addr) { + return (addr == null) ? "null" : addr.getHostAddress(); + } + @Override public String toString() { return String.format("clientId: %s, hwAddr: %s, netAddr: %s, expTime: %d, hostname: %s", - clientIdToString(mClientId), mHwAddr.toString(), mNetAddr, mExpTime, mHostname); + clientIdToString(mClientId), mHwAddr.toString(), inet4AddrToString(mNetAddr), + mExpTime, mHostname); } } diff --git a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java b/services/net/java/android/net/dhcp/DhcpLeaseRepository.java index 9f77ed0ae5dd..2dda42124c75 100644 --- a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java +++ b/services/net/java/android/net/dhcp/DhcpLeaseRepository.java @@ -20,6 +20,7 @@ import static android.net.NetworkUtils.inet4AddressToIntHTH; import static android.net.NetworkUtils.intToInet4AddressHTH; import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH; import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER; +import static android.net.dhcp.DhcpLease.inet4AddrToString; import static android.net.util.NetworkConstants.IPV4_ADDR_BITS; import static java.lang.Math.min; @@ -98,6 +99,12 @@ class DhcpLeaseRepository { } } + static class InvalidSubnetException extends DhcpLeaseException { + InvalidSubnetException(String message) { + super(message); + } + } + /** * Leases by IP address */ @@ -152,25 +159,17 @@ class DhcpLeaseRepository { * @param reqAddr Requested address by the client (option 50), or {@link #INETADDR_UNSPEC} * @param hostname Client-provided hostname, or {@link DhcpLease#HOSTNAME_NONE} * @throws OutOfAddressesException The server does not have any available address - * @throws InvalidAddressException The lease was requested from an unsupported subnet + * @throws InvalidSubnetException The lease was requested from an unsupported subnet */ @NonNull public DhcpLease getOffer(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, - @NonNull Inet4Address relayAddr, - @Nullable Inet4Address reqAddr, @Nullable String hostname) - throws OutOfAddressesException, InvalidAddressException { + @NonNull Inet4Address relayAddr, @Nullable Inet4Address reqAddr, + @Nullable String hostname) throws OutOfAddressesException, InvalidSubnetException { final long currentTime = mClock.elapsedRealtime(); final long expTime = currentTime + mLeaseTimeMs; removeExpiredLeases(currentTime); - - // As per #4.3.1, addresses are assigned based on the relay address if present. This - // implementation only assigns addresses if the relayAddr is inside our configured subnet. - // This also applies when the client requested a specific address for consistency between - // requests, and with older behavior. - if (isIpAddrOutsidePrefix(mPrefix, relayAddr)) { - throw new InvalidAddressException("Lease requested by relay from outside of subnet"); - } + checkValidRelayAddr(relayAddr); final DhcpLease currentLease = findByClient(clientId, hwAddr); final DhcpLease newLease; @@ -188,7 +187,19 @@ class DhcpLeaseRepository { return newLease; } - private static boolean isIpAddrOutsidePrefix(IpPrefix prefix, Inet4Address addr) { + private void checkValidRelayAddr(@Nullable Inet4Address relayAddr) + throws InvalidSubnetException { + // As per #4.3.1, addresses are assigned based on the relay address if present. This + // implementation only assigns addresses if the relayAddr is inside our configured subnet. + // This also applies when the client requested a specific address for consistency between + // requests, and with older behavior. + if (isIpAddrOutsidePrefix(mPrefix, relayAddr)) { + throw new InvalidSubnetException("Lease requested by relay from outside of subnet"); + } + } + + private static boolean isIpAddrOutsidePrefix(@NonNull IpPrefix prefix, + @Nullable Inet4Address addr) { return addr != null && !addr.equals(Inet4Address.ANY) && !prefix.contains(addr); } @@ -222,10 +233,12 @@ class DhcpLeaseRepository { */ @NonNull public DhcpLease requestLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, - @NonNull Inet4Address clientAddr, @Nullable Inet4Address reqAddr, boolean sidSet, - @Nullable String hostname) throws InvalidAddressException { + @NonNull Inet4Address clientAddr, @NonNull Inet4Address relayAddr, + @Nullable Inet4Address reqAddr, boolean sidSet, @Nullable String hostname) + throws InvalidAddressException, InvalidSubnetException { final long currentTime = mClock.elapsedRealtime(); removeExpiredLeases(currentTime); + checkValidRelayAddr(relayAddr); final DhcpLease assignedLease = findByClient(clientId, hwAddr); final Inet4Address leaseAddr = reqAddr != null ? reqAddr : clientAddr; @@ -252,7 +265,7 @@ class DhcpLeaseRepository { final DhcpLease lease = checkClientAndMakeLease(clientId, hwAddr, leaseAddr, hostname, currentTime); mLog.logf("DHCPREQUEST assignedLease %s, reqAddr=%s, sidSet=%s: created/renewed lease %s", - assignedLease, reqAddr, sidSet, lease); + assignedLease, inet4AddrToString(reqAddr), sidSet, lease); return lease; } @@ -304,7 +317,7 @@ class DhcpLeaseRepository { @NonNull Inet4Address addr) { final DhcpLease currentLease = mCommittedLeases.getOrDefault(addr, null); if (currentLease == null) { - mLog.w("Could not release unknown lease for " + addr); + mLog.w("Could not release unknown lease for " + inet4AddrToString(addr)); return false; } if (currentLease.matchesClient(clientId, hwAddr)) { @@ -319,12 +332,13 @@ class DhcpLeaseRepository { public void markLeaseDeclined(@NonNull Inet4Address addr) { if (mDeclinedAddrs.containsKey(addr) || !isValidAddress(addr)) { - mLog.logf("Not marking %s as declined: already declined or not assignable", addr); + mLog.logf("Not marking %s as declined: already declined or not assignable", + inet4AddrToString(addr)); return; } final long expTime = mClock.elapsedRealtime() + mLeaseTimeMs; mDeclinedAddrs.put(addr, expTime); - mLog.logf("Marked %s as declined expiring %d", addr, expTime); + mLog.logf("Marked %s as declined expiring %d", inet4AddrToString(addr), expTime); maybeUpdateEarliestExpiration(expTime); } @@ -515,7 +529,8 @@ class DhcpLeaseRepository { while (it.hasNext()) { final Inet4Address addr = it.next(); it.remove(); - mLog.logf("Out of addresses in address pool: dropped declined addr %s", addr); + mLog.logf("Out of addresses in address pool: dropped declined addr %s", + inet4AddrToString(addr)); // isValidAddress() is always verified for entries in mDeclinedAddrs. // However declined addresses may have been requested (typically by the machine that was // already using the address) after being declined. diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java index 595a12922a7e..77a3e2102452 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/services/net/java/android/net/dhcp/DhcpPacket.java @@ -1281,12 +1281,12 @@ public abstract class DhcpPacket { */ public static ByteBuffer buildAckPacket(int encap, int transactionId, boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp, Inet4Address yourIp, - byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr, - List<Inet4Address> gateways, List<Inet4Address> dnsServers, + Inet4Address requestClientIp, byte[] mac, Integer timeout, Inet4Address netMask, + Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers, Inet4Address dhcpServerIdentifier, String domainName, boolean metered) { DhcpPacket pkt = new DhcpAckPacket( - transactionId, (short) 0, broadcast, serverIpAddr, relayIp, - INADDR_ANY /* clientIp */, yourIp, mac); + transactionId, (short) 0, broadcast, serverIpAddr, relayIp, requestClientIp, yourIp, + mac); pkt.mGateways = gateways; pkt.mDnsServers = dnsServers; pkt.mLeaseTime = timeout; diff --git a/services/net/java/android/net/dhcp/DhcpServer.java b/services/net/java/android/net/dhcp/DhcpServer.java index da8c8bbcf057..2b3d577b0eae 100644 --- a/services/net/java/android/net/dhcp/DhcpServer.java +++ b/services/net/java/android/net/dhcp/DhcpServer.java @@ -269,6 +269,11 @@ public class DhcpServer { } } + private void logIgnoredPacketInvalidSubnet(DhcpLeaseRepository.InvalidSubnetException e) { + // Not an internal error: only logging exception message, not stacktrace + mLog.e("Ignored packet from invalid subnet: " + e.getMessage()); + } + private void processDiscover(@NonNull DhcpDiscoverPacket packet) throws MalformedPacketException { final DhcpLease lease; @@ -279,8 +284,8 @@ public class DhcpServer { } catch (DhcpLeaseRepository.OutOfAddressesException e) { transmitNak(packet, "Out of addresses to offer"); return; - } catch (DhcpLeaseRepository.InvalidAddressException e) { - transmitNak(packet, "Lease requested from an invalid subnet"); + } catch (DhcpLeaseRepository.InvalidSubnetException e) { + logIgnoredPacketInvalidSubnet(e); return; } @@ -294,16 +299,20 @@ public class DhcpServer { final MacAddress clientMac = getMacAddr(packet); try { lease = mLeaseRepo.requestLease(packet.getExplicitClientIdOrNull(), clientMac, - packet.mClientIp, packet.mRequestedIp, sidSet, packet.mHostName); + packet.mClientIp, packet.mRelayIp, packet.mRequestedIp, sidSet, + packet.mHostName); } catch (DhcpLeaseRepository.InvalidAddressException e) { transmitNak(packet, "Invalid requested address"); return; + } catch (DhcpLeaseRepository.InvalidSubnetException e) { + logIgnoredPacketInvalidSubnet(e); + return; } transmitAck(packet, lease, clientMac); } - private void processRelease(@Nullable DhcpReleasePacket packet) + private void processRelease(@NonNull DhcpReleasePacket packet) throws MalformedPacketException { final byte[] clientId = packet.getExplicitClientIdOrNull(); final MacAddress macAddr = getMacAddr(packet); @@ -367,7 +376,7 @@ public class DhcpServer { final int timeout = getLeaseTimeout(lease); final ByteBuffer ackPacket = DhcpPacket.buildAckPacket(ENCAP_BOOTP, request.mTransId, broadcastFlag, mServingParams.getServerInet4Addr(), request.mRelayIp, - lease.getNetAddr(), request.mClientMac, timeout, + lease.getNetAddr(), request.mClientIp, request.mClientMac, timeout, mServingParams.getPrefixMaskAsAddress(), mServingParams.getBroadcastAddress(), new ArrayList<>(mServingParams.defaultRouters), new ArrayList<>(mServingParams.dnsServers), @@ -464,7 +473,7 @@ public class DhcpServer { } } - private static boolean isEmpty(@NonNull Inet4Address address) { + private static boolean isEmpty(@Nullable Inet4Address address) { return address == null || Inet4Address.ANY.equals(address); } diff --git a/services/robotests/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java b/services/robotests/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java index 801451ee549b..0d2c2218eedf 100644 --- a/services/robotests/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java +++ b/services/robotests/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java @@ -16,6 +16,8 @@ package com.android.server.backup; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import android.content.ContentResolver; @@ -51,7 +53,6 @@ public class BackupAgentTimeoutParametersTest { mContentResolver = context.getContentResolver(); mParameters = new BackupAgentTimeoutParameters(new Handler(), mContentResolver); - mParameters.start(); } /** Stop observing changes to the setting. */ @@ -61,8 +62,11 @@ public class BackupAgentTimeoutParametersTest { } /** Tests that timeout parameters are initialized with default values on creation. */ + // TODO: Break down tests @Test public void testGetParameters_afterConstructorWithStart_returnsDefaultValues() { + mParameters.start(); + long kvBackupAgentTimeoutMillis = mParameters.getKvBackupAgentTimeoutMillis(); long fullBackupAgentTimeoutMillis = mParameters.getFullBackupAgentTimeoutMillis(); long sharedBackupAgentTimeoutMillis = mParameters.getSharedBackupAgentTimeoutMillis(); @@ -86,13 +90,33 @@ public class BackupAgentTimeoutParametersTest { restoreAgentFinishedTimeoutMillis); } + @Test + public void testGetQuotaExceededTimeoutMillis_returnsDefaultValue() { + mParameters.start(); + + long timeout = mParameters.getQuotaExceededTimeoutMillis(); + + assertThat(timeout) + .isEqualTo(BackupAgentTimeoutParameters.DEFAULT_QUOTA_EXCEEDED_TIMEOUT_MILLIS); + } + + @Test + public void testGetQuotaExceededTimeoutMillis_whenSettingSet_returnsSetValue() { + putStringAndNotify( + BackupAgentTimeoutParameters.SETTING_QUOTA_EXCEEDED_TIMEOUT_MILLIS + "=" + 1279); + mParameters.start(); + + long timeout = mParameters.getQuotaExceededTimeoutMillis(); + + assertThat(timeout).isEqualTo(1279); + } + /** * Tests that timeout parameters are updated when we call start, even when a setting change * occurs while we are not observing. */ @Test public void testGetParameters_withSettingChangeBeforeStart_updatesValues() { - mParameters.stop(); long testTimeout = BackupAgentTimeoutParameters.DEFAULT_KV_BACKUP_AGENT_TIMEOUT_MILLIS * 2; final String setting = BackupAgentTimeoutParameters.SETTING_KV_BACKUP_AGENT_TIMEOUT_MILLIS @@ -112,6 +136,7 @@ public class BackupAgentTimeoutParametersTest { */ @Test public void testGetParameters_withSettingChangeAfterStart_updatesValues() { + mParameters.start(); long testTimeout = BackupAgentTimeoutParameters.DEFAULT_KV_BACKUP_AGENT_TIMEOUT_MILLIS * 2; final String setting = BackupAgentTimeoutParameters.SETTING_KV_BACKUP_AGENT_TIMEOUT_MILLIS diff --git a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java index 72ba4396b82e..21b90f1c2281 100644 --- a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java +++ b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java @@ -27,6 +27,7 @@ import android.platform.test.annotations.Presubmit; import android.util.Log; import com.android.server.backup.BackupManagerService; +import com.android.server.backup.remote.RemoteResult; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderPackages; import com.android.server.testing.shadows.ShadowEventLog; @@ -37,6 +38,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLog; + +import java.lang.reflect.Field; @RunWith(FrameworkRobolectricTestRunner.class) @Config( @@ -77,4 +81,39 @@ public class KeyValueBackupReporterTest { assertThat(observer).isEqualTo(mObserver); } + + @Test + public void testOnRevertTask_logsCorrectly() throws Exception { + setMoreDebug(true); + + mReporter.onRevertTask(); + + assertLogcat(TAG, Log.INFO); + } + + @Test + public void testOnRemoteCallReturned_logsCorrectly() throws Exception { + setMoreDebug(true); + + mReporter.onRemoteCallReturned(RemoteResult.of(3), "onFoo()"); + + assertLogcat(TAG, Log.VERBOSE); + ShadowLog.LogItem log = ShadowLog.getLogsForTag(TAG).get(0); + assertThat(log.msg).contains("onFoo()"); + assertThat(log.msg).contains("3"); + } + + /** + * HACK: We actually want {@link KeyValueBackupReporter#MORE_DEBUG} to be a constant to be able + * to strip those lines at build time. So, we have to do this to test :( + */ + private static void setMoreDebug(boolean value) + throws NoSuchFieldException, IllegalAccessException { + if (KeyValueBackupReporter.MORE_DEBUG == value) { + return; + } + Field moreDebugField = KeyValueBackupReporter.class.getDeclaredField("MORE_DEBUG"); + moreDebugField.setAccessible(true); + moreDebugField.set(null, value); + } } diff --git a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 9fae43a46d51..82d7ab89ee20 100644 --- a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -63,6 +63,7 @@ import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import static org.robolectric.shadow.api.Shadow.extract; import static org.testng.Assert.fail; +import static org.testng.Assert.expectThrows; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.util.Collections.emptyList; @@ -1941,6 +1942,29 @@ public class KeyValueBackupTaskTest { task.markCancel(); } + @Test + public void testHandleCancel_callsMarkCancelAndWaitCancel() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgentWithData(PACKAGE_1); + KeyValueBackupTask task = spy(createKeyValueBackupTask(transportMock, PACKAGE_1)); + doNothing().when(task).waitCancel(); + + task.handleCancel(true); + + InOrder inOrder = inOrder(task); + inOrder.verify(task).markCancel(); + inOrder.verify(task).waitCancel(); + } + + @Test + public void testHandleCancel_whenCancelAllFalse_throws() throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + setUpAgentWithData(PACKAGE_1); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); + + expectThrows(IllegalArgumentException.class, () -> task.handleCancel(false)); + } + private void runTask(KeyValueBackupTask task) { // Pretend we are not on the main-thread to prevent RemoteCall from complaining mShadowMainLooper.setCurrentThread(false); diff --git a/services/robotests/src/com/android/server/backup/remote/RemoteCallTest.java b/services/robotests/src/com/android/server/backup/remote/RemoteCallTest.java index 6434c4f4e8e9..1d92bed2fde1 100644 --- a/services/robotests/src/com/android/server/backup/remote/RemoteCallTest.java +++ b/services/robotests/src/com/android/server/backup/remote/RemoteCallTest.java @@ -222,6 +222,37 @@ public class RemoteCallTest { assertThat(result.get()).isEqualTo(RemoteResult.FAILED_CANCELLED); } + @Test + public void testExecute_whenCallbackIsCalledBeforeTimeout_returnsResult() throws Exception { + RemoteResult result = + runInWorkerThread( + () -> RemoteCall.execute(callback -> callback.operationComplete(3), 1000)); + + assertThat(result.get()).isEqualTo(3); + } + + @Test + public void testExecute_whenTimesOutBeforeCallback_returnsTimeOut() throws Exception { + ConditionVariable scheduled = new ConditionVariable(false); + + Future<RemoteResult> result = + runInWorkerThreadAsync( + () -> + RemoteCall.execute( + callback -> { + postDelayed( + Handler.getMain(), + () -> callback.operationComplete(0), + 1000); + scheduled.open(); + }, + 500)); + + scheduled.block(); + runToEndOfTasks(Looper.getMainLooper()); + assertThat(result.get()).isEqualTo(RemoteResult.FAILED_TIMED_OUT); + } + private static <T> Future<T> runInWorkerThreadAsync(Callable<T> supplier) { CompletableFuture<T> future = new CompletableFuture<>(); new Thread(() -> future.complete(uncheck(supplier)), "test-worker-thread").start(); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java index 20df2aeed66f..1aa80c884eeb 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java @@ -33,9 +33,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; @@ -46,7 +44,6 @@ import android.app.ActivityOptions; import android.app.WaitResult; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; -import android.util.SparseIntArray; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; @@ -54,7 +51,6 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.invocation.InvocationOnMock; import java.util.ArrayList; @@ -246,22 +242,6 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { null /* target */, null /* targetOptions */); } - @Test - public void testTopRunningActivityLockedWithNonExistentDisplay() throws Exception { - // Create display that ActivityManagerService does not know about - final int unknownDisplayId = 100; - - doAnswer((InvocationOnMock invocationOnMock) -> { - final SparseIntArray displayIds = invocationOnMock.<SparseIntArray>getArgument(0); - displayIds.put(0, 0); - displayIds.put(1, unknownDisplayId); - return null; - }).when(mSupervisor.mWindowManager).getDisplaysInFocusOrder(any()); - - // Supervisor should skip over the non-existent display. - assertEquals(null, mSupervisor.topRunningActivityLocked()); - } - /** * Verifies that removal of activity with task and stack is done correctly. */ @@ -339,12 +319,6 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) .setStack(stack).build(); - doAnswer((InvocationOnMock invocationOnMock) -> { - final SparseIntArray displayIds = invocationOnMock.<SparseIntArray>getArgument(0); - displayIds.put(0, display.mDisplayId); - return null; - }).when(mSupervisor.mWindowManager).getDisplaysInFocusOrder(any()); - // Make sure the top running activity is not affected when keyguard is not locked assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked()); assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked( diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index 9c0b525a411f..aef5537badeb 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -35,7 +35,6 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; @@ -45,7 +44,6 @@ import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.DisplayWindowController; import org.junit.Rule; -import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import android.app.IApplicationThread; @@ -63,26 +61,22 @@ import android.os.Looper; import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; import android.testing.DexmakerShareClassLoaderRule; -import android.util.SparseIntArray; import androidx.test.InstrumentationRegistry; import com.android.internal.app.IVoiceInteractor; import com.android.server.AttributeCache; import com.android.server.wm.AppWindowContainerController; -import com.android.server.wm.DisplayWindowController; import com.android.server.wm.PinnedStackWindowController; +import com.android.server.wm.RootWindowContainerController; import com.android.server.wm.StackWindowController; import com.android.server.wm.TaskWindowContainerController; import com.android.server.wm.WindowManagerService; import com.android.server.wm.WindowTestUtils; -import com.android.server.uri.UriGrantsManagerInternal; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; import java.util.List; @@ -500,13 +494,14 @@ public class ActivityTestsBase { (DisplayManager) mService.mContext.getSystemService(Context.DISPLAY_SERVICE); mWindowManager = prepareMockWindowManager(); mKeyguardController = mock(KeyguardController.class); + setWindowContainerController(mock(RootWindowContainerController.class)); } @Override public void initialize() { super.initialize(); mDisplay = spy(new TestActivityDisplay(this, DEFAULT_DISPLAY)); - attachDisplay(mDisplay); + addChild(mDisplay, ActivityDisplay.POSITION_TOP); } @Override @@ -576,12 +571,6 @@ public class ActivityTestsBase { return null; }).when(service).inSurfaceTransaction(any()); - doAnswer((InvocationOnMock invocationOnMock) -> { - final SparseIntArray displayIds = invocationOnMock.<SparseIntArray>getArgument(0); - displayIds.put(0, 0); - return null; - }).when(service).getDisplaysInFocusOrder(any()); - return service; } diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java index ba82487ec591..519521429f1b 100644 --- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java @@ -872,8 +872,8 @@ public class RecentTasksTest extends ActivityTestsBase { super.initialize(); mDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY); mOtherDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY); - attachDisplay(mOtherDisplay); - attachDisplay(mDisplay); + addChild(mOtherDisplay, ActivityDisplay.POSITION_TOP); + addChild(mDisplay, ActivityDisplay.POSITION_TOP); } @Override @@ -1045,7 +1045,7 @@ public class RecentTasksTest extends ActivityTestsBase { @Override void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType, - int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays, + int ignoreWindowingMode, ArrayList<ActivityDisplay> activityDisplays, int callingUid, boolean allowed) { lastAllowed = allowed; super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays, diff --git a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java index 283c0278dff2..d56c6a6d7403 100644 --- a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java @@ -68,9 +68,9 @@ public class RunningTasksTest extends ActivityTestsBase { public void testCollectTasksByLastActiveTime() throws Exception { // Create a number of stacks with tasks (of incrementing active time) final ActivityStackSupervisor supervisor = mService.mStackSupervisor; - final SparseArray<ActivityDisplay> displays = new SparseArray<>(); + final ArrayList<ActivityDisplay> displays = new ArrayList<>(); final ActivityDisplay display = new TestActivityDisplay(supervisor, DEFAULT_DISPLAY); - displays.put(DEFAULT_DISPLAY, display); + displays.add(display); final int numStacks = 2; for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) { diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index ece9f42bd0eb..79a654b2c0f4 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -52,6 +52,8 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.R; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -82,6 +84,8 @@ public class BrightnessTrackerTest { private static HandlerThread sThread = new HandlerThread("brightness.test", android.os.Process.THREAD_PRIORITY_BACKGROUND); + private int mDefaultNightModeColorTemperature; + private static Handler ensureHandler() { synchronized (sHandlerLock) { if (sHandler == null) { @@ -98,6 +102,9 @@ public class BrightnessTrackerTest { mInjector = new TestInjector(ensureHandler()); mTracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector); + mDefaultNightModeColorTemperature = + InstrumentationRegistry.getContext().getResources().getInteger( + R.integer.config_nightDisplayColorTemperatureDefault); } @Test @@ -188,7 +195,7 @@ public class BrightnessTrackerTest { // System had no data so these should all be at defaults. assertEquals(Float.NaN, event.batteryLevel, 0.0); assertFalse(event.nightMode); - assertEquals(0, event.colorTemperature); + assertEquals(mDefaultNightModeColorTemperature, event.colorTemperature); } @Test @@ -863,5 +870,17 @@ public class BrightnessTrackerTest { public boolean isInteractive(Context context) { return mInteractive; } + + @Override + public int getColorTemperature(Context context, int userId) { + return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, + mDefaultNightModeColorTemperature); + } + + @Override + public boolean isNightModeActive(Context context, int userId) { + return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, + 0) == 1; + } } } diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index 0d40c5e25acb..b3303049be39 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java @@ -446,24 +446,6 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(anotherAlwaysOnTopStack, mDisplayContent.getStacks().get(topPosition - 1)); } - /** - * Test that WM does not report displays to AM that are pending to be removed. - */ - @Test - public void testDontReportDeferredRemoval() { - // Create a display and add an animating window to it. - final DisplayContent dc = createNewDisplay(); - final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); - window.mAnimatingExit = true; - // Request display removal, it should be deferred. - dc.removeIfPossible(); - // Request ordered display ids from WM. - final SparseIntArray orderedDisplayIds = new SparseIntArray(); - sWm.getDisplaysInFocusOrder(orderedDisplayIds); - // Make sure that display that is marked for removal is not reported. - assertEquals(-1, orderedDisplayIds.indexOfValue(dc.getDisplayId())); - } - @Test public void testDisplayCutout_rot0() throws Exception { synchronized (sWm.getWindowManagerLock()) { diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java index 5a42a848ef92..b43d9a671751 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java @@ -50,13 +50,19 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.graphics.Matrix; +import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.util.Size; +import android.view.DisplayCutout; import android.view.SurfaceControl; import android.view.WindowManager; +import com.android.server.wm.utils.WmDisplayCutout; + import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; import java.util.LinkedList; import androidx.test.filters.FlakyTest; @@ -382,6 +388,20 @@ public class WindowStateTests extends WindowTestsBase { } } + @Test + public void testDisplayCutoutIsCalculatedRelativeToFrame() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + WindowFrames wf = new WindowFrames(); + wf.mParentFrame.set(7, 10, 185, 380); + wf.mDisplayFrame.set(wf.mParentFrame); + final DisplayCutout cutout = new DisplayCutout(new Rect(0, 15, 0, 22), + Arrays.asList(new Rect(95, 0, 105, 15), new Rect(95, 378, 105, 400))); + wf.setDisplayCutout(new WmDisplayCutout(cutout, new Size(200, 400))); + + app.computeFrameLw(wf); + assertThat(app.getWmDisplayCutout().getDisplayCutout(), is(cutout.inset(7, 10, 5, 20))); + } + private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) { reset(mPowerManagerWrapper); final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 8703e6501fe4..ffbe7d34e5e7 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -107,13 +107,34 @@ public class CarrierConfigManager { /** * Boolean indicating if the "Call barring" item is visible in the Call Settings menu. - * true means visible. false means gone. - * @hide + * If true, the "Call Barring" menu will be visible. If false, the menu will be gone. + * + * Disabled by default. */ public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = "call_barring_visibility_bool"; /** + * Flag indicating whether or not changing the call barring password via the "Call Barring" + * settings menu is supported. If true, the option will be visible in the "Call + * Barring" settings menu. If false, the option will not be visible. + * + * Enabled by default. + */ + public static final String KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL = + "call_barring_supports_password_change_bool"; + + /** + * Flag indicating whether or not deactivating all call barring features via the "Call Barring" + * settings menu is supported. If true, the option will be visible in the "Call + * Barring" settings menu. If false, the option will not be visible. + * + * Enabled by default. + */ + public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = + "call_barring_supports_deactivate_all_bool"; + + /** * Flag indicating whether the Phone app should ignore EVENT_SIM_NETWORK_LOCKED * events from the Sim. * If true, this will prevent the IccNetworkDepersonalizationPanel from being shown, and @@ -2125,6 +2146,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONED_BOOL, false); sDefaults.putBoolean(KEY_CALL_BARRING_VISIBILITY_BOOL, false); + sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL, true); + sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL, true); sDefaults.putBoolean(KEY_CALL_FORWARDING_VISIBILITY_BOOL, true); sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL, true); sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL, true); diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index bd6a59d7492c..498be968265f 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -23,6 +23,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IPhoneStateListener; import java.lang.ref.WeakReference; @@ -778,8 +779,12 @@ public class PhoneStateListener { } } + /** + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @UnsupportedAppUsage - IPhoneStateListener callback = new IPhoneStateListenerStub(this); + public final IPhoneStateListener callback = new IPhoneStateListenerStub(this); private void log(String s) { Rlog.d(LOG_TAG, s); diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 2ee1a09e87b6..f2b73dccee2d 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -353,9 +353,11 @@ public class ServiceState implements Parcelable { mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration; mIsUsingCarrierAggregation = s.mIsUsingCarrierAggregation; mChannelNumber = s.mChannelNumber; - mCellBandwidths = Arrays.copyOf(s.mCellBandwidths, s.mCellBandwidths.length); + mCellBandwidths = s.mCellBandwidths == null ? null : + Arrays.copyOf(s.mCellBandwidths, s.mCellBandwidths.length); mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost; - mNetworkRegistrationStates = new ArrayList<>(s.mNetworkRegistrationStates); + mNetworkRegistrationStates = s.mNetworkRegistrationStates == null ? null : + new ArrayList<>(s.mNetworkRegistrationStates); } /** @@ -812,7 +814,9 @@ public class ServiceState implements Parcelable { && mIsEmergencyOnly == s.mIsEmergencyOnly && mIsDataRoamingFromRegistration == s.mIsDataRoamingFromRegistration && mIsUsingCarrierAggregation == s.mIsUsingCarrierAggregation) - && mNetworkRegistrationStates.containsAll(s.mNetworkRegistrationStates); + && (mNetworkRegistrationStates == null ? s.mNetworkRegistrationStates == null : + s.mNetworkRegistrationStates != null && + mNetworkRegistrationStates.containsAll(s.mNetworkRegistrationStates)); } /** diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java index e509d2d87bf7..fd20f4a1fa77 100644 --- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java +++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java @@ -154,6 +154,8 @@ public final class BackgroundDexOptServiceIntegrationTests { stdout.append(new String(buf, 0, bytesRead)); } fis.close(); + Log.i(TAG, "stdout"); + Log.i(TAG, stdout.toString()); return stdout.toString(); } @@ -202,7 +204,10 @@ public final class BackgroundDexOptServiceIntegrationTests { // TODO(aeubanks): figure out how to get scheduled bg-dexopt to run private static void runBackgroundDexOpt() throws IOException { - runShellCommand("cmd package bg-dexopt-job " + PACKAGE_NAME); + String result = runShellCommand("cmd package bg-dexopt-job " + PACKAGE_NAME); + if (!result.trim().equals("Success")) { + throw new IllegalStateException("Expected command success, received >" + result + "<"); + } } // Set the time ahead of the last use time of the test app in days. diff --git a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java b/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java index 590bd6733c9f..7f8e7b5456c2 100644 --- a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java +++ b/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java @@ -32,6 +32,7 @@ import static java.lang.String.format; import static java.net.InetAddress.parseNumericAddress; import android.annotation.NonNull; +import android.annotation.Nullable; import android.net.IpPrefix; import android.net.MacAddress; import android.net.util.SharedLog; @@ -107,7 +108,7 @@ public class DhcpLeaseRepositoryTest { MacAddress newMac = MacAddress.fromBytes(hwAddrBytes); final String hostname = "host_" + i; final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, newMac, - INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname); assertNotNull(lease); assertEquals(newMac, lease.getHwAddr()); @@ -115,7 +116,7 @@ public class DhcpLeaseRepositoryTest { assertTrue(format("Duplicate address allocated: %s in %s", lease.getNetAddr(), addrs), addrs.add(lease.getNetAddr())); - mRepo.requestLease(null, newMac, null, lease.getNetAddr(), true, hostname); + requestLeaseSelecting(newMac, lease.getNetAddr(), hostname); } } @@ -129,7 +130,7 @@ public class DhcpLeaseRepositoryTest { try { mRepo.getOffer(null, TEST_MAC_2, - null /* relayAddr */, null /* reqAddr */, HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); fail("Should be out of addresses"); } catch (DhcpLeaseRepository.OutOfAddressesException e) { // Expected @@ -145,8 +146,7 @@ public class DhcpLeaseRepositoryTest { // Inside /28, but not available there (first address of the range) final Inet4Address declinedFirstAddrIn28 = parseAddr4("192.168.42.240"); - final DhcpLease reqAddrIn28Lease = mRepo.requestLease( - CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, reqAddrIn28, false, HOSTNAME_NONE); + final DhcpLease reqAddrIn28Lease = requestLeaseSelecting(TEST_MAC_1, reqAddrIn28); mRepo.markLeaseDeclined(declinedAddrIn28); mRepo.markLeaseDeclined(declinedFirstAddrIn28); @@ -154,14 +154,12 @@ public class DhcpLeaseRepositoryTest { final Inet4Address reqAddrIn22 = parseAddr4("192.168.42.3"); final Inet4Address declinedAddrIn22 = parseAddr4("192.168.42.4"); - final DhcpLease reqAddrIn22Lease = mRepo.requestLease( - CLIENTID_UNSPEC, TEST_MAC_3, INET4_ANY, reqAddrIn22, false, HOSTNAME_NONE); + final DhcpLease reqAddrIn22Lease = requestLeaseSelecting(TEST_MAC_3, reqAddrIn22); mRepo.markLeaseDeclined(declinedAddrIn22); // Address that will be reserved in the updateParams call below final Inet4Address reservedAddr = parseAddr4("192.168.42.244"); - final DhcpLease reservedAddrLease = mRepo.requestLease( - CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, reservedAddr, false, HOSTNAME_NONE); + final DhcpLease reservedAddrLease = requestLeaseSelecting(TEST_MAC_2, reservedAddr); // Update from /22 to /28 and add another reserved address Set<Inet4Address> newReserved = new HashSet<>(TEST_EXCL_SET); @@ -183,11 +181,11 @@ public class DhcpLeaseRepositoryTest { public void testGetOffer_StableAddress() throws Exception { for (final MacAddress macAddr : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, - INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); // Same lease is offered twice final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, - INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(lease, newLease); } } @@ -198,17 +196,16 @@ public class DhcpLeaseRepositoryTest { mRepo.updateParams(newPrefix, TEST_EXCL_SET, TEST_LEASE_TIME_MS); DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertTrue(newPrefix.contains(lease.getNetAddr())); } @Test public void testGetOffer_ExistingLease() throws Exception { - mRepo.requestLease( - CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, TEST_HOSTNAME_1); + requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1, TEST_HOSTNAME_1); DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); } @@ -216,12 +213,12 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_ClientIdHasExistingLease() throws Exception { final byte[] clientId = new byte[] { 1, 2 }; - mRepo.requestLease(clientId, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, - TEST_HOSTNAME_1); + mRepo.requestLease(clientId, TEST_MAC_1, INET4_ANY /* clientAddr */, + INET4_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1); // Different MAC, but same clientId DhcpLease offer = mRepo.getOffer(clientId, TEST_MAC_2, - INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); } @@ -230,12 +227,12 @@ public class DhcpLeaseRepositoryTest { public void testGetOffer_DifferentClientId() throws Exception { final byte[] clientId1 = new byte[] { 1, 2 }; final byte[] clientId2 = new byte[] { 3, 4 }; - mRepo.requestLease(clientId1, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, - TEST_HOSTNAME_1); + mRepo.requestLease(clientId1, TEST_MAC_1, INET4_ANY /* clientAddr */, + INET4_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1); // Same MAC, different client ID DhcpLease offer = mRepo.getOffer(clientId2, TEST_MAC_1, - INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); // Obtains a different address assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(HOSTNAME_NONE, offer.getHostname()); @@ -244,58 +241,57 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_RequestedAddress() throws Exception { - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - TEST_INETADDR_1, TEST_HOSTNAME_1); + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + TEST_INETADDR_1 /* reqAddr */, TEST_HOSTNAME_1); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); } @Test public void testGetOffer_RequestedAddressInUse() throws Exception { - mRepo.requestLease( - CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, HOSTNAME_NONE); - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, - TEST_INETADDR_1, HOSTNAME_NONE); + requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY /* relayAddr */, + TEST_INETADDR_1 /* reqAddr */, HOSTNAME_NONE); assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); } @Test public void testGetOffer_RequestedAddressReserved() throws Exception { - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - TEST_RESERVED_ADDR, HOSTNAME_NONE); + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + TEST_RESERVED_ADDR /* reqAddr */, HOSTNAME_NONE); assertNotEquals(TEST_RESERVED_ADDR, offer.getNetAddr()); } @Test public void testGetOffer_RequestedAddressInvalid() throws Exception { final Inet4Address invalidAddr = parseAddr4("192.168.42.0"); - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - invalidAddr, HOSTNAME_NONE); + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + invalidAddr /* reqAddr */, HOSTNAME_NONE); assertNotEquals(invalidAddr, offer.getNetAddr()); } @Test public void testGetOffer_RequestedAddressOutsideSubnet() throws Exception { final Inet4Address invalidAddr = parseAddr4("192.168.254.2"); - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - invalidAddr, HOSTNAME_NONE); + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + invalidAddr /* reqAddr */, HOSTNAME_NONE); assertNotEquals(invalidAddr, offer.getNetAddr()); } - @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + @Test(expected = DhcpLeaseRepository.InvalidSubnetException.class) public void testGetOffer_RelayInInvalidSubnet() throws Exception { - mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - parseAddr4("192.168.254.2") /* relayAddr */, INETADDR_UNSPEC, HOSTNAME_NONE); + mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, parseAddr4("192.168.254.2") /* relayAddr */, + INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); } @Test public void testRequestLease_SelectingTwice() throws Exception { - DhcpLease lease1 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - TEST_INETADDR_1, true /* sidSet */, TEST_HOSTNAME_1); + final DhcpLease lease1 = requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1, + TEST_HOSTNAME_1); // Second request from same client for a different address - DhcpLease lease2 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - TEST_INETADDR_2, true /* sidSet */, TEST_HOSTNAME_2); + final DhcpLease lease2 = requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_2, + TEST_HOSTNAME_2); assertEquals(TEST_INETADDR_1, lease1.getNetAddr()); assertEquals(TEST_HOSTNAME_1, lease1.getHostname()); @@ -304,43 +300,43 @@ public class DhcpLeaseRepositoryTest { assertEquals(TEST_HOSTNAME_2, lease2.getHostname()); // First address freed when client requested a different one: another client can request it - DhcpLease lease3 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, - TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + final DhcpLease lease3 = requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1, HOSTNAME_NONE); assertEquals(TEST_INETADDR_1, lease3.getNetAddr()); } @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_SelectingInvalid() throws Exception { - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - parseAddr4("192.168.254.5"), true /* sidSet */, HOSTNAME_NONE); + requestLeaseSelecting(TEST_MAC_1, parseAddr4("192.168.254.5")); } @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_SelectingInUse() throws Exception { - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, - TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); + requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1); } @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_SelectingReserved() throws Exception { - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - TEST_RESERVED_ADDR, true /* sidSet */, HOSTNAME_NONE); + requestLeaseSelecting(TEST_MAC_1, TEST_RESERVED_ADDR); + } + + @Test(expected = DhcpLeaseRepository.InvalidSubnetException.class) + public void testRequestLease_SelectingRelayInInvalidSubnet() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* clientAddr */, + parseAddr4("192.168.128.1") /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, + true /* sidSet */, HOSTNAME_NONE); } @Test public void testRequestLease_InitReboot() throws Exception { // Request address once - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); final long newTime = TEST_TIME + 100; when(mClock.elapsedRealtime()).thenReturn(newTime); // init-reboot (sidSet == false): verify configuration - DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - TEST_INETADDR_1, false, HOSTNAME_NONE); + final DhcpLease lease = requestLeaseInitReboot(TEST_MAC_1, TEST_INETADDR_1); assertEquals(TEST_INETADDR_1, lease.getNetAddr()); assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); } @@ -348,18 +344,15 @@ public class DhcpLeaseRepositoryTest { @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_InitRebootWrongAddr() throws Exception { // Request address once - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); // init-reboot with different requested address - mRepo.requestLease( - CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_2, false, HOSTNAME_NONE); + requestLeaseInitReboot(TEST_MAC_1, TEST_INETADDR_2); } @Test public void testRequestLease_InitRebootUnknownAddr() throws Exception { // init-reboot with unknown requested address - DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - TEST_INETADDR_2, false, HOSTNAME_NONE); + final DhcpLease lease = requestLeaseInitReboot(TEST_MAC_1, TEST_INETADDR_2); // RFC2131 says we should not reply to accommodate other servers, but since we are // authoritative we allow creating the lease to avoid issues with lost lease DB (same as // dnsmasq behavior) @@ -368,22 +361,17 @@ public class DhcpLeaseRepositoryTest { @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_InitRebootWrongSubnet() throws Exception { - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - parseAddr4("192.168.254.2"), false /* sidSet */, HOSTNAME_NONE); + requestLeaseInitReboot(TEST_MAC_1, parseAddr4("192.168.254.2")); } @Test public void testRequestLease_Renewing() throws Exception { - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, - INET4_ANY /* clientAddr */, TEST_INETADDR_1 /* reqAddr */, true, HOSTNAME_NONE); + requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); final long newTime = TEST_TIME + 100; when(mClock.elapsedRealtime()).thenReturn(newTime); - // Renewing: clientAddr filled in, no reqAddr - DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, - TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false, - HOSTNAME_NONE); + final DhcpLease lease = requestLeaseRenewing(TEST_MAC_1, TEST_INETADDR_1); assertEquals(TEST_INETADDR_1, lease.getNetAddr()); assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); @@ -393,9 +381,7 @@ public class DhcpLeaseRepositoryTest { public void testRequestLease_RenewingUnknownAddr() throws Exception { final long newTime = TEST_TIME + 100; when(mClock.elapsedRealtime()).thenReturn(newTime); - DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, - TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false, - HOSTNAME_NONE); + final DhcpLease lease = requestLeaseRenewing(TEST_MAC_1, TEST_INETADDR_1); // Allows renewing an unknown address if available assertEquals(TEST_INETADDR_1, lease.getNetAddr()); assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); @@ -403,31 +389,24 @@ public class DhcpLeaseRepositoryTest { @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_RenewingAddrInUse() throws Exception { - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, - INET4_ANY /* clientAddr */, TEST_INETADDR_1 /* reqAddr */, true, HOSTNAME_NONE); - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, - TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false, - HOSTNAME_NONE); + requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1); + requestLeaseRenewing(TEST_MAC_1, TEST_INETADDR_1); } @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) public void testRequestLease_RenewingInvalidAddr() throws Exception { - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, parseAddr4("192.168.254.2") /* clientAddr */, - INETADDR_UNSPEC /* reqAddr */, false, HOSTNAME_NONE); + requestLeaseRenewing(TEST_MAC_1, parseAddr4("192.168.254.2")); } @Test public void testReleaseLease() throws Exception { - DhcpLease lease1 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, - TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + final DhcpLease lease1 = requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); assertHasLease(lease1); assertTrue(mRepo.releaseLease(CLIENTID_UNSPEC, TEST_MAC_1, TEST_INETADDR_1)); assertNoLease(lease1); - DhcpLease lease2 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, - TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); - + final DhcpLease lease2 = requestLeaseSelecting(TEST_MAC_2, TEST_INETADDR_1); assertEquals(TEST_INETADDR_1, lease2.getNetAddr()); } @@ -440,15 +419,14 @@ public class DhcpLeaseRepositoryTest { public void testReleaseLease_StableOffer() throws Exception { for (MacAddress mac : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, mac, - INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); - mRepo.requestLease( - CLIENTID_UNSPEC, mac, INET4_ANY, lease.getNetAddr(), true, - HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + + requestLeaseSelecting(mac, lease.getNetAddr()); mRepo.releaseLease(CLIENTID_UNSPEC, mac, lease.getNetAddr()); // Same lease is offered after it was released final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, mac, - INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(lease.getNetAddr(), newLease.getNetAddr()); } } @@ -456,13 +434,13 @@ public class DhcpLeaseRepositoryTest { @Test public void testMarkLeaseDeclined() throws Exception { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); mRepo.markLeaseDeclined(lease.getNetAddr()); // Same lease is not offered again final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertNotEquals(lease.getNetAddr(), newLease.getNetAddr()); } @@ -475,22 +453,20 @@ public class DhcpLeaseRepositoryTest { mRepo.markLeaseDeclined(TEST_INETADDR_2); // /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses - requestAddresses((byte)9); + requestAddresses((byte) 9); // Last 2 addresses: addresses marked declined should be used final DhcpLease firstLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1); - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, firstLease.getNetAddr(), true, - HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1); + requestLeaseSelecting(TEST_MAC_1, firstLease.getNetAddr()); final DhcpLease secondLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, - INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2); - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, secondLease.getNetAddr(), true, - HOSTNAME_NONE); + INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2); + requestLeaseSelecting(TEST_MAC_2, secondLease.getNetAddr()); // Now out of addresses try { - mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, INETADDR_UNSPEC /* relayAddr */, + mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); fail("Repository should be out of addresses and throw"); } catch (DhcpLeaseRepository.OutOfAddressesException e) { /* expected */ } @@ -501,6 +477,50 @@ public class DhcpLeaseRepositoryTest { assertEquals(TEST_HOSTNAME_2, secondLease.getHostname()); } + private DhcpLease requestLease(@NonNull MacAddress macAddr, @NonNull Inet4Address clientAddr, + @Nullable Inet4Address reqAddr, @Nullable String hostname, boolean sidSet) + throws DhcpLeaseRepository.DhcpLeaseException { + return mRepo.requestLease(CLIENTID_UNSPEC, macAddr, clientAddr, INET4_ANY /* relayAddr */, + reqAddr, sidSet, hostname); + } + + /** + * Request a lease simulating a client in the SELECTING state. + */ + private DhcpLease requestLeaseSelecting(@NonNull MacAddress macAddr, + @NonNull Inet4Address reqAddr, @Nullable String hostname) + throws DhcpLeaseRepository.DhcpLeaseException { + return requestLease(macAddr, INET4_ANY /* clientAddr */, reqAddr, hostname, + true /* sidSet */); + } + + /** + * Request a lease simulating a client in the SELECTING state. + */ + private DhcpLease requestLeaseSelecting(@NonNull MacAddress macAddr, + @NonNull Inet4Address reqAddr) throws DhcpLeaseRepository.DhcpLeaseException { + return requestLeaseSelecting(macAddr, reqAddr, HOSTNAME_NONE); + } + + /** + * Request a lease simulating a client in the INIT-REBOOT state. + */ + private DhcpLease requestLeaseInitReboot(@NonNull MacAddress macAddr, + @NonNull Inet4Address reqAddr) throws DhcpLeaseRepository.DhcpLeaseException { + return requestLease(macAddr, INET4_ANY /* clientAddr */, reqAddr, HOSTNAME_NONE, + false /* sidSet */); + } + + /** + * Request a lease simulating a client in the RENEWING state. + */ + private DhcpLease requestLeaseRenewing(@NonNull MacAddress macAddr, + @NonNull Inet4Address clientAddr) throws DhcpLeaseRepository.DhcpLeaseException { + // Renewing: clientAddr filled in, no reqAddr + return requestLease(macAddr, clientAddr, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE, + true /* sidSet */); + } + private void assertNoLease(DhcpLease lease) { assertFalse("Leases contain " + lease, mRepo.getCommittedLeases().contains(lease)); } diff --git a/tests/net/java/android/net/dhcp/DhcpServerTest.java b/tests/net/java/android/net/dhcp/DhcpServerTest.java index 66db5a8ddc50..45a50d9a8b5f 100644 --- a/tests/net/java/android/net/dhcp/DhcpServerTest.java +++ b/tests/net/java/android/net/dhcp/DhcpServerTest.java @@ -216,8 +216,8 @@ public class DhcpServerTest { @Test public void testRequest_Selecting_Ack() throws Exception { when(mRepository.requestLease(isNull() /* clientId */, eq(TEST_CLIENT_MAC), - eq(INADDR_ANY) /* clientAddr */, eq(TEST_CLIENT_ADDR) /* reqAddr */, - eq(true) /* sidSet */, isNull() /* hostname */)) + eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */, + eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, isNull() /* hostname */)) .thenReturn(TEST_LEASE); final DhcpRequestPacket request = makeRequestSelectingPacket(); @@ -231,8 +231,8 @@ public class DhcpServerTest { @Test public void testRequest_Selecting_Nak() throws Exception { when(mRepository.requestLease(isNull(), eq(TEST_CLIENT_MAC), - eq(INADDR_ANY) /* clientAddr */, eq(TEST_CLIENT_ADDR) /* reqAddr */, - eq(true) /* sidSet */, isNull() /* hostname */)) + eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */, + eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, isNull() /* hostname */)) .thenThrow(new InvalidAddressException("Test error")); final DhcpRequestPacket request = makeRequestSelectingPacket(); @@ -248,7 +248,8 @@ public class DhcpServerTest { final DhcpRequestPacket request = makeRequestSelectingPacket(); mServer.processPacket(request, 50000); - verify(mRepository, never()).requestLease(any(), any(), any(), any(), anyBoolean(), any()); + verify(mRepository, never()) + .requestLease(any(), any(), any(), any(), any(), anyBoolean(), any()); verify(mDeps, never()).sendPacket(any(), any(), any()); } diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java index f025f41f292b..4dc63f249a52 100644 --- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -21,7 +21,9 @@ import static android.Manifest.permission.CHANGE_WIFI_STATE; import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; import static android.Manifest.permission.NETWORK_STACK; -import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; +import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_OEM; +import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRODUCT; +import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static org.junit.Assert.assertFalse; @@ -34,6 +36,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.os.Build; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -48,6 +51,10 @@ import org.mockito.MockitoAnnotations; public class PermissionMonitorTest { private static final int MOCK_UID = 10001; private static final String[] MOCK_PACKAGE_NAMES = new String[] { "com.foo.bar" }; + private static final String PARTITION_SYSTEM = "system"; + private static final String PARTITION_OEM = "oem"; + private static final String PARTITION_PRODUCT = "product"; + private static final String PARTITION_VENDOR = "vendor"; @Mock private Context mContext; @Mock private PackageManager mPackageManager; @@ -62,39 +69,53 @@ public class PermissionMonitorTest { mPermissionMonitor = new PermissionMonitor(mContext, null); } - private void expectPermission(String[] permissions, boolean preinstalled) throws Exception { - final PackageInfo packageInfo = packageInfoWithPermissions(permissions, preinstalled); + private void expectPermission(String[] permissions, String partition, + int targetSdkVersion) throws Exception { + final PackageInfo packageInfo = packageInfoWithPermissions(permissions, partition); + packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion; when(mPackageManager.getPackageInfoAsUser( eq(MOCK_PACKAGE_NAMES[0]), eq(GET_PERMISSIONS), anyInt())).thenReturn(packageInfo); } - private PackageInfo packageInfoWithPermissions(String[] permissions, boolean preinstalled) { + private PackageInfo packageInfoWithPermissions(String[] permissions, String partition) { final PackageInfo packageInfo = new PackageInfo(); packageInfo.requestedPermissions = permissions; packageInfo.applicationInfo = new ApplicationInfo(); - packageInfo.applicationInfo.flags = preinstalled ? FLAG_SYSTEM : 0; + int privateFlags = 0; + switch (partition) { + case PARTITION_OEM: + privateFlags = PRIVATE_FLAG_OEM; + break; + case PARTITION_PRODUCT: + privateFlags = PRIVATE_FLAG_PRODUCT; + break; + case PARTITION_VENDOR: + privateFlags = PRIVATE_FLAG_VENDOR; + break; + } + packageInfo.applicationInfo.privateFlags = privateFlags; return packageInfo; } @Test public void testHasPermission() { - PackageInfo app = packageInfoWithPermissions(new String[] {}, false); + PackageInfo app = packageInfoWithPermissions(new String[] {}, PARTITION_SYSTEM); assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE)); assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK)); assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL)); app = packageInfoWithPermissions(new String[] { - CHANGE_NETWORK_STATE, NETWORK_STACK - }, false); + CHANGE_NETWORK_STATE, NETWORK_STACK + }, PARTITION_SYSTEM); assertTrue(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE)); assertTrue(mPermissionMonitor.hasPermission(app, NETWORK_STACK)); assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL)); app = packageInfoWithPermissions(new String[] { - CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL - }, false); + CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL + }, PARTITION_SYSTEM); assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE)); assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK)); assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); @@ -102,35 +123,64 @@ public class PermissionMonitorTest { } @Test - public void testIsPreinstalledSystemApp() { - PackageInfo app = packageInfoWithPermissions(new String[] {}, false); - assertFalse(mPermissionMonitor.isPreinstalledSystemApp(app)); - - app = packageInfoWithPermissions(new String[] {}, true); - assertTrue(mPermissionMonitor.isPreinstalledSystemApp(app)); + public void testIsVendorApp() { + PackageInfo app = packageInfoWithPermissions(new String[] {}, PARTITION_SYSTEM); + assertFalse(mPermissionMonitor.isVendorApp(app.applicationInfo)); + app = packageInfoWithPermissions(new String[] {}, PARTITION_OEM); + assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo)); + app = packageInfoWithPermissions(new String[] {}, PARTITION_PRODUCT); + assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo)); + app = packageInfoWithPermissions(new String[] {}, PARTITION_VENDOR); + assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo)); } @Test public void testHasUseBackgroundNetworksPermission() throws Exception { - expectPermission(new String[] { CHANGE_NETWORK_STATE }, false); + expectPermission(new String[] { CHANGE_NETWORK_STATE }, + PARTITION_SYSTEM, Build.VERSION_CODES.P); assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - - expectPermission(new String[] { NETWORK_STACK, CONNECTIVITY_INTERNAL }, false); + expectPermission(new String[] { NETWORK_STACK }, PARTITION_SYSTEM, Build.VERSION_CODES.P); assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - - // TODO : make this false when b/31479477 is fixed - expectPermission(new String[] {}, true); + expectPermission(new String[] { CONNECTIVITY_INTERNAL }, + PARTITION_SYSTEM, Build.VERSION_CODES.P); assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { CHANGE_WIFI_STATE }, true); + expectPermission(new String[] { CONNECTIVITY_USE_RESTRICTED_NETWORKS }, + PARTITION_SYSTEM, Build.VERSION_CODES.P); assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { NETWORK_STACK, CONNECTIVITY_INTERNAL }, true); + expectPermission(new String[] { CHANGE_NETWORK_STATE }, + PARTITION_VENDOR, Build.VERSION_CODES.P); + assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); + expectPermission(new String[] { NETWORK_STACK }, + PARTITION_VENDOR, Build.VERSION_CODES.P); + assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); + expectPermission(new String[] { CONNECTIVITY_INTERNAL }, + PARTITION_VENDOR, Build.VERSION_CODES.P); + assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); + expectPermission(new String[] { CONNECTIVITY_USE_RESTRICTED_NETWORKS }, + PARTITION_VENDOR, Build.VERSION_CODES.P); assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] {}, false); + expectPermission(new String[] {}, PARTITION_SYSTEM, Build.VERSION_CODES.P); assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); + expectPermission(new String[] { CHANGE_WIFI_STATE }, + PARTITION_SYSTEM, Build.VERSION_CODES.P); + assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); + expectPermission(new String[] {}, PARTITION_VENDOR, Build.VERSION_CODES.P); + assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); + expectPermission(new String[] { CHANGE_WIFI_STATE }, + PARTITION_VENDOR, Build.VERSION_CODES.P); + assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { CHANGE_WIFI_STATE }, false); + expectPermission(new String[] {}, PARTITION_SYSTEM, Build.VERSION_CODES.Q); + assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); + expectPermission(new String[] { CHANGE_WIFI_STATE }, + PARTITION_SYSTEM, Build.VERSION_CODES.Q); + assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); + expectPermission(new String[] {}, PARTITION_VENDOR, Build.VERSION_CODES.Q); + assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); + expectPermission(new String[] { CHANGE_WIFI_STATE }, + PARTITION_VENDOR, Build.VERSION_CODES.Q); assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); } } diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 35179847541c..3b9f93e503be 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -18,14 +18,14 @@ package android.net.wifi; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertFalse; -import android.os.Parcel; import android.net.MacAddress; import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; -import android.net.wifi.WifiInfo; +import android.os.Parcel; +import android.support.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; @@ -33,6 +33,7 @@ import org.junit.Test; /** * Unit tests for {@link android.net.wifi.WifiConfiguration}. */ +@SmallTest public class WifiConfigurationTest { @Before diff --git a/wifi/tests/src/android/net/wifi/WifiSsidTest.java b/wifi/tests/src/android/net/wifi/WifiSsidTest.java index e5794c5bf02a..b58f2c76dab9 100644 --- a/wifi/tests/src/android/net/wifi/WifiSsidTest.java +++ b/wifi/tests/src/android/net/wifi/WifiSsidTest.java @@ -19,14 +19,16 @@ package android.net.wifi; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import android.support.test.filters.SmallTest; + import org.junit.Test; import java.nio.charset.StandardCharsets; -import java.util.Arrays; /** * Unit tests for {@link android.net.wifi.WifiSsid}. */ +@SmallTest public class WifiSsidTest { private static final String TEST_SSID = "Test SSID"; diff --git a/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java b/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java index e49247520ac7..80f00a4ccfa3 100644 --- a/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java +++ b/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java @@ -19,11 +19,14 @@ package android.net.wifi.p2p; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import android.support.test.filters.SmallTest; + import org.junit.Test; /** * Unit test harness for {@link android.net.wifi.p2p.WifiP2pDevice} */ +@SmallTest public class WifiP2pDeviceTest { /** diff --git a/wifi/tests/src/android/net/wifi/p2p/WifiP2pManagerTest.java b/wifi/tests/src/android/net/wifi/p2p/WifiP2pManagerTest.java index e8e4dc2080c2..2132b418ffac 100644 --- a/wifi/tests/src/android/net/wifi/p2p/WifiP2pManagerTest.java +++ b/wifi/tests/src/android/net/wifi/p2p/WifiP2pManagerTest.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.os.test.TestLooper; +import android.support.test.filters.SmallTest; import libcore.junit.util.ResourceLeakageDetector; @@ -33,6 +34,7 @@ import org.mockito.MockitoAnnotations; /** * Unit test harness for WifiP2pManager. */ +@SmallTest public class WifiP2pManagerTest { private WifiP2pManager mDut; private TestLooper mTestLooper; diff --git a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java index ccb90319d8cd..8997ae9daf1d 100644 --- a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java +++ b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java @@ -31,6 +31,7 @@ import android.net.wifi.aware.PeerHandle; import android.os.IBinder; import android.os.Parcel; import android.os.test.TestLooper; +import android.support.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; @@ -45,6 +46,7 @@ import java.util.concurrent.Executor; /** * Unit test harness for WifiRttManager class. */ +@SmallTest public class WifiRttManagerTest { private WifiRttManager mDut; private TestLooper mMockLooper; |