diff options
427 files changed, 9922 insertions, 3762 deletions
diff --git a/Android.mk b/Android.mk index 00dc78441028..933ac6200a3e 100644 --- a/Android.mk +++ b/Android.mk @@ -357,6 +357,7 @@ LOCAL_SRC_FILES += \ core/java/android/view/IPinnedStackController.aidl \ core/java/android/view/IPinnedStackListener.aidl \ core/java/android/view/IRotationWatcher.aidl \ + core/java/android/view/IWallpaperVisibilityListener.aidl \ core/java/android/view/IWindow.aidl \ core/java/android/view/IWindowFocusObserver.aidl \ core/java/android/view/IWindowId.aidl \ @@ -772,14 +773,9 @@ aidl_files := \ frameworks/base/core/java/android/view/textservice/SuggestionsInfo.aidl \ frameworks/base/core/java/android/service/carrier/CarrierIdentifier.aidl \ frameworks/base/core/java/android/service/carrier/MessagePdu.aidl \ - frameworks/base/core/java/android/service/euicc/DeleteResult.aidl \ - frameworks/base/core/java/android/service/euicc/DownloadResult.aidl \ - frameworks/base/core/java/android/service/euicc/EraseResult.aidl \ frameworks/base/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.aidl \ frameworks/base/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.aidl \ frameworks/base/core/java/android/service/euicc/GetEuiccProfileInfoListResult.aidl \ - frameworks/base/core/java/android/service/euicc/SwitchResult.aidl \ - frameworks/base/core/java/android/service/euicc/UpdateNicknameResult.aidl \ frameworks/base/core/java/android/service/notification/Adjustment.aidl \ frameworks/base/core/java/android/service/notification/Condition.aidl \ frameworks/base/core/java/android/service/notification/SnoozeCriterion.aidl \ diff --git a/api/current.txt b/api/current.txt index 5dd3839b48a9..d7f8ecd0f606 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6089,13 +6089,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); + ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); method public int describeContents(); - method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); - method public boolean supportsDarkText(); + method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); + method public static android.app.WallpaperColors fromDrawable(android.graphics.drawable.Drawable); + method public int getColorHints(); + method public android.graphics.Color getPrimaryColor(); + method public android.graphics.Color getSecondaryColor(); + method public android.graphics.Color getTertiaryColor(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; + field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1 } public final class WallpaperInfo implements android.os.Parcelable { @@ -37581,12 +37585,10 @@ package android.service.wallpaper { method public int getDesiredMinimumHeight(); method public int getDesiredMinimumWidth(); method public android.view.SurfaceHolder getSurfaceHolder(); - method public void invalidateColors(); method public boolean isPreview(); method public boolean isVisible(); method public void onApplyWindowInsets(android.view.WindowInsets); method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean); - method public android.app.WallpaperColors onComputeWallpaperColors(); method public void onCreate(android.view.SurfaceHolder); method public void onDesiredSizeChanged(int, int); method public void onDestroy(); diff --git a/api/system-current.txt b/api/system-current.txt index dfad7a2d6883..85e917563602 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6299,13 +6299,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); + ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); method public int describeContents(); - method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); - method public boolean supportsDarkText(); + method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); + method public static android.app.WallpaperColors fromDrawable(android.graphics.drawable.Drawable); + method public int getColorHints(); + method public android.graphics.Color getPrimaryColor(); + method public android.graphics.Color getSecondaryColor(); + method public android.graphics.Color getTertiaryColor(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; + field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1 } public final class WallpaperInfo implements android.os.Parcelable { @@ -40793,12 +40797,10 @@ package android.service.wallpaper { method public int getDesiredMinimumHeight(); method public int getDesiredMinimumWidth(); method public android.view.SurfaceHolder getSurfaceHolder(); - method public void invalidateColors(); method public boolean isPreview(); method public boolean isVisible(); method public void onApplyWindowInsets(android.view.WindowInsets); method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean); - method public android.app.WallpaperColors onComputeWallpaperColors(); method public void onCreate(android.view.SurfaceHolder); method public void onDesiredSizeChanged(int, int); method public void onDestroy(); diff --git a/api/test-current.txt b/api/test-current.txt index 9bafd5f74b14..ed9071a2eec9 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6110,13 +6110,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); + ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); method public int describeContents(); - method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); - method public boolean supportsDarkText(); + method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); + method public static android.app.WallpaperColors fromDrawable(android.graphics.drawable.Drawable); + method public int getColorHints(); + method public android.graphics.Color getPrimaryColor(); + method public android.graphics.Color getSecondaryColor(); + method public android.graphics.Color getTertiaryColor(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; + field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1 } public final class WallpaperInfo implements android.os.Parcelable { @@ -37788,12 +37792,10 @@ package android.service.wallpaper { method public int getDesiredMinimumHeight(); method public int getDesiredMinimumWidth(); method public android.view.SurfaceHolder getSurfaceHolder(); - method public void invalidateColors(); method public boolean isPreview(); method public boolean isVisible(); method public void onApplyWindowInsets(android.view.WindowInsets); method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean); - method public android.app.WallpaperColors onComputeWallpaperColors(); method public void onCreate(android.view.SurfaceHolder); method public void onDesiredSizeChanged(int, int); method public void onDestroy(); diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java index 8eefd256a69d..4966b4380d9b 100644 --- a/cmds/am/src/com/android/commands/am/Instrument.java +++ b/cmds/am/src/com/android/commands/am/Instrument.java @@ -382,6 +382,7 @@ public class Instrument { oldAnims = mWm.getAnimationScales(); mWm.setAnimationScale(0, 0.0f); mWm.setAnimationScale(1, 0.0f); + mWm.setAnimationScale(2, 0.0f); } // Figure out which component we are tring to do. diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index 607e6e0186e7..23668786abee 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -251,5 +251,7 @@ int main(int argc, char** argv) if (mapbase != MAP_FAILED) { munmap((void *)mapbase, mapsize); } - return 0; + + // b/36066697: Avoid running static destructors. + _exit(0); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 3574f8d05055..bc6e9cd0ab7e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -16,17 +16,9 @@ package android.app; -import android.graphics.Rect; -import android.view.ViewRootImpl.ActivityConfigCallback; -import android.view.autofill.AutofillManager; -import android.view.autofill.AutofillPopupWindow; -import android.view.autofill.IAutofillWindowPresenter; -import com.android.internal.annotations.GuardedBy; -import com.android.internal.app.IVoiceInteractor; -import com.android.internal.app.ToolbarActionBar; -import com.android.internal.app.WindowDecorActionBar; -import com.android.internal.policy.DecorView; -import com.android.internal.policy.PhoneWindow; +import static android.os.Build.VERSION_CODES.O; + +import static java.lang.Character.MIN_VALUE; import android.annotation.CallSuper; import android.annotation.DrawableRes; @@ -62,6 +54,7 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.session.MediaController; @@ -114,15 +107,26 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewManager; import android.view.ViewRootImpl; +import android.view.ViewRootImpl.ActivityConfigCallback; import android.view.Window; import android.view.Window.WindowControllerCallback; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; +import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillPopupWindow; +import android.view.autofill.IAutofillWindowPresenter; import android.widget.AdapterView; import android.widget.Toast; import android.widget.Toolbar; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.IVoiceInteractor; +import com.android.internal.app.ToolbarActionBar; +import com.android.internal.app.WindowDecorActionBar; +import com.android.internal.policy.DecorView; +import com.android.internal.policy.PhoneWindow; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -131,9 +135,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import static android.os.Build.VERSION_CODES.O; -import static java.lang.Character.MIN_VALUE; - /** * An activity is a single, focused thing that the user can do. Almost all * activities interact with the user, so the Activity class takes care of @@ -719,7 +720,7 @@ public class Activity extends ContextThemeWrapper public static final int FINISH_TASK_WITH_ACTIVITY = 2; static final String FRAGMENTS_TAG = "android:fragments"; - private static final String LAST_ACCESSIBILITY_ID = "android:lastAccessibilityId"; + private static final String LAST_AUTOFILL_ID = "android:lastAutofillId"; private static final String AUTOFILL_RESET_NEEDED = "@android:autofillResetNeeded"; private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState"; @@ -853,8 +854,8 @@ public class Activity extends ContextThemeWrapper private boolean mAutoFillResetNeeded; - /** The last accessibility id that was returned from {@link #getNextAccessibilityId()} */ - private int mLastAccessibilityId = View.LAST_APP_ACCESSIBILITY_ID; + /** The last autofill id that was returned from {@link #getNextAutofillId()} */ + private int mLastAutofillId = View.LAST_APP_AUTOFILL_ID; private AutofillPopupWindow mAutofillPopupWindow; @@ -999,7 +1000,8 @@ public class Activity extends ContextThemeWrapper } if (savedInstanceState != null) { mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED, false); - mLastAccessibilityId = savedInstanceState.getInt(LAST_ACCESSIBILITY_ID, View.NO_ID); + mLastAutofillId = savedInstanceState.getInt(LAST_AUTOFILL_ID, + View.LAST_APP_AUTOFILL_ID); if (mAutoFillResetNeeded) { getAutofillManager().onCreate(savedInstanceState); @@ -1348,24 +1350,23 @@ public class Activity extends ContextThemeWrapper } /** - * Gets the next accessibility ID. + * Gets the next autofill ID. * - * <p>All IDs will be bigger than {@link View#LAST_APP_ACCESSIBILITY_ID}. All IDs returned + * <p>All IDs will be bigger than {@link View#LAST_APP_AUTOFILL_ID}. All IDs returned * will be unique. * * @return A ID that is unique in the activity * * {@hide} */ - @Override - public int getNextAccessibilityId() { - if (mLastAccessibilityId == Integer.MAX_VALUE - 1) { - mLastAccessibilityId = View.LAST_APP_ACCESSIBILITY_ID; + public int getNextAutofillId() { + if (mLastAutofillId == Integer.MAX_VALUE - 1) { + mLastAutofillId = View.LAST_APP_AUTOFILL_ID; } - mLastAccessibilityId++; + mLastAutofillId++; - return mLastAccessibilityId; + return mLastAutofillId; } /** @@ -1563,7 +1564,7 @@ public class Activity extends ContextThemeWrapper protected void onSaveInstanceState(Bundle outState) { outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); - outState.putInt(LAST_ACCESSIBILITY_ID, mLastAccessibilityId); + outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId); Parcelable p = mFragments.saveAllState(); if (p != null) { outState.putParcelable(FRAGMENTS_TAG, p); @@ -7455,7 +7456,7 @@ public class Activity extends ContextThemeWrapper /** @hide */ @Override - @NonNull public View[] findViewsByAccessibilityIdTraversal(@NonNull int[] viewIds) { + @NonNull public View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds) { final View[] views = new View[viewIds.length]; final ArrayList<ViewRootImpl> roots = WindowManagerGlobal.getInstance().getRootViews(getActivityToken()); @@ -7466,7 +7467,7 @@ public class Activity extends ContextThemeWrapper if (rootView != null) { for (int viewNum = 0; viewNum < viewIds.length; viewNum++) { if (views[viewNum] == null) { - views[viewNum] = rootView.findViewByAccessibilityIdTraversal( + views[viewNum] = rootView.findViewByAutofillIdTraversal( viewIds[viewNum]); } } @@ -7478,14 +7479,14 @@ public class Activity extends ContextThemeWrapper /** @hide */ @Override - @Nullable public View findViewByAccessibilityIdTraversal(int viewId) { + @Nullable public View findViewByAutofillIdTraversal(int viewId) { final ArrayList<ViewRootImpl> roots = WindowManagerGlobal.getInstance().getRootViews(getActivityToken()); for (int rootNum = 0; rootNum < roots.size(); rootNum++) { final View rootView = roots.get(rootNum).getView(); if (rootView != null) { - final View view = rootView.findViewByAccessibilityIdTraversal(viewId); + final View view = rootView.findViewByAutofillIdTraversal(viewId); if (view != null) { return view; } @@ -7499,7 +7500,7 @@ public class Activity extends ContextThemeWrapper @Override @NonNull public boolean[] getViewVisibility(@NonNull int[] viewIds) { final boolean[] isVisible = new boolean[viewIds.length]; - final View views[] = findViewsByAccessibilityIdTraversal(viewIds); + final View views[] = findViewsByAutofillIdTraversal(viewIds); for (int i = 0; i < viewIds.length; i++) { View view = views[i]; diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index d3b4b403e1ac..e5fe2402dae1 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; +import android.os.SystemClock; import android.service.voice.IVoiceInteractionSession; import android.util.SparseIntArray; @@ -134,8 +135,10 @@ public abstract class ActivityManagerInternal { * * @param reasons A map from stack id to a reason integer why the transition was started,, which * must be one of the APP_TRANSITION_* values. + * @param timestamp The time at which the app transition started in + * {@link SystemClock#uptimeMillis()} timebase. */ - public abstract void notifyAppTransitionStarting(SparseIntArray reasons); + public abstract void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp); /** * Callback for window manager to let activity manager know that the app transition was diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index cbb93a059613..6dead3e6c0dc 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -1244,7 +1244,7 @@ public class ActivityOptions { // Once we parcel the thumbnail for transfering over to the system, create a copy of // the bitmap to a hardware bitmap and pass through the GraphicBuffer if (mThumbnail != null) { - final Bitmap hwBitmap = mThumbnail.copy(Config.HARDWARE, true /* immutable */); + final Bitmap hwBitmap = mThumbnail.copy(Config.HARDWARE, false /* isMutable */); if (hwBitmap != null) { b.putParcelable(KEY_ANIM_THUMBNAIL, hwBitmap.createGraphicBufferHandle()); } else { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index df550807dd30..8a8f8dd681fd 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2708,8 +2708,14 @@ public final class ActivityThread { Activity activity = null; try { java.lang.ClassLoader cl = appContext.getClassLoader(); - activity = mInstrumentation.newActivity( - cl, component.getClassName(), r.intent); + if (appContext.getApplicationContext() instanceof Application) { + activity = ((Application) appContext.getApplicationContext()) + .instantiateActivity(cl, component.getClassName(), r.intent); + } + if (activity == null) { + activity = mInstrumentation.newActivity( + cl, component.getClassName(), r.intent); + } StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); r.intent.prepareToEnterProcess(); @@ -3234,7 +3240,8 @@ public final class ActivityThread { data.intent.setExtrasClassLoader(cl); data.intent.prepareToEnterProcess(); data.setExtrasClassLoader(cl); - receiver = (BroadcastReceiver)cl.loadClass(component).newInstance(); + receiver = instantiate(cl, component, data.intent, app, + Application::instantiateReceiver); } catch (Exception e) { if (DEBUG_BROADCAST) Slog.i(TAG, "Finishing failed broadcast to " + data.intent.getComponent()); @@ -3322,12 +3329,13 @@ public final class ActivityThread { } else { try { if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname); + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); java.lang.ClassLoader cl = packageInfo.getClassLoader(); - agent = (BackupAgent) cl.loadClass(classname).newInstance(); + agent = instantiate(cl, classname, context, + Application::instantiateBackupAgent); // set up the agent's context - ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(agent); agent.attach(context); @@ -3387,9 +3395,12 @@ public final class ActivityThread { LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; + Application app = null; try { + app = packageInfo.makeApplication(false, mInstrumentation); java.lang.ClassLoader cl = packageInfo.getClassLoader(); - service = (Service) cl.loadClass(data.info.name).newInstance(); + service = instantiate(cl, data.info.name, data.intent, app, + Application::instantiateService); } catch (Exception e) { if (!mInstrumentation.onException(service, e)) { throw new RuntimeException( @@ -3404,7 +3415,6 @@ public final class ActivityThread { ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); - Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); service.onCreate(); @@ -5721,8 +5731,8 @@ public final class ActivityThread { try { final ClassLoader cl = instrContext.getClassLoader(); - mInstrumentation = (Instrumentation) - cl.loadClass(data.instrumentationName.getClassName()).newInstance(); + mInstrumentation = instantiate(cl, data.instrumentationName.getClassName(), + instrContext, Application::instantiateInstrumentation); } catch (Exception e) { throw new RuntimeException( "Unable to instantiate instrumentation " @@ -6267,8 +6277,8 @@ public final class ActivityThread { try { final java.lang.ClassLoader cl = c.getClassLoader(); - localProvider = (ContentProvider)cl. - loadClass(info.name).newInstance(); + localProvider = instantiate(cl, info.name, context, + Application::instantiateProvider); provider = localProvider.getIContentProvider(); if (provider == null) { Slog.e(TAG, "Failed to instantiate class " + @@ -6467,6 +6477,38 @@ public final class ActivityThread { } } + private <T> T instantiate(ClassLoader cl, String className, Context c, + Instantiator<T> instantiator) + throws ClassNotFoundException, IllegalAccessException, InstantiationException { + if (c.getApplicationContext() instanceof Application) { + T a = instantiator.instantiate((Application) c.getApplicationContext(), + cl, className); + if (a != null) return a; + } + return (T) cl.loadClass(className).newInstance(); + } + + private <T> T instantiate(ClassLoader cl, String className, Intent intent, Context c, + IntentInstantiator<T> instantiator) + throws ClassNotFoundException, IllegalAccessException, InstantiationException { + if (c.getApplicationContext() instanceof Application) { + T a = instantiator.instantiate((Application) c.getApplicationContext(), + cl, className, intent); + if (a != null) return a; + } + return (T) cl.loadClass(className).newInstance(); + } + + private interface Instantiator<T> { + T instantiate(Application app, ClassLoader cl, String className) + throws ClassNotFoundException, IllegalAccessException, InstantiationException; + } + + private interface IntentInstantiator<T> { + T instantiate(Application app, ClassLoader cl, String className, Intent intent) + throws ClassNotFoundException, IllegalAccessException, InstantiationException; + } + private static class EventLoggingReporter implements EventLogger.Reporter { @Override public void report (int code, Object... list) { diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java index 156df36a600c..7fb5e2e7e442 100644 --- a/core/java/android/app/Application.java +++ b/core/java/android/app/Application.java @@ -16,17 +16,20 @@ package android.app; -import java.util.ArrayList; - import android.annotation.CallSuper; +import android.app.backup.BackupAgent; +import android.content.BroadcastReceiver; import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; +import android.content.ContentProvider; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; +import java.util.ArrayList; + /** * Base class for maintaining global application state. You can provide your own * implementation by creating a subclass and specifying the fully-qualified name @@ -289,4 +292,79 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } } } + + /** + * Allows application to override the creation of activities. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. Return null to use the default creation flow. + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @param intent Intent creating the class. + * @hide + */ + public Activity instantiateActivity(ClassLoader cl, String className, Intent intent) { + return null; + } + + /** + * Allows application to override the creation of receivers. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. Return null to use the default creation flow. + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @param intent Intent creating the class. + * @hide + */ + public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent) { + return null; + } + + /** + * Allows application to override the creation of services. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. Return null to use the default creation flow. + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @param intent Intent creating the class. + * @hide + */ + public Service instantiateService(ClassLoader cl, String className, Intent intent) { + return null; + } + + /** + * Allows application to override the creation of providers. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. Return null to use the default creation flow. + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @hide + */ + public ContentProvider instantiateProvider(ClassLoader cl, String className) { + return null; + } + + /** + * Allows application to override the creation of backup agents. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. Return null to use the default creation flow. + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @hide + */ + public BackupAgent instantiateBackupAgent(ClassLoader cl, String className) { + return null; + } + + /** + * Allows application to override the creation of instrumentation. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. Return null to use the default creation flow. + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @hide + */ + public Instrumentation instantiateInstrumentation(ClassLoader cl, String className) { + return null; + } }
\ No newline at end of file diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 8c6412998572..a040520ffb9f 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -256,28 +256,34 @@ class ContextImpl extends Context { @Override public void setTheme(int resId) { - if (mThemeResource != resId) { - mThemeResource = resId; - initializeTheme(); + synchronized (mSync) { + if (mThemeResource != resId) { + mThemeResource = resId; + initializeTheme(); + } } } @Override public int getThemeResId() { - return mThemeResource; + synchronized (mSync) { + return mThemeResource; + } } @Override public Resources.Theme getTheme() { - if (mTheme != null) { - return mTheme; - } + synchronized (mSync) { + if (mTheme != null) { + return mTheme; + } - mThemeResource = Resources.selectDefaultTheme(mThemeResource, - getOuterContext().getApplicationInfo().targetSdkVersion); - initializeTheme(); + mThemeResource = Resources.selectDefaultTheme(mThemeResource, + getOuterContext().getApplicationInfo().targetSdkVersion); + initializeTheme(); - return mTheme; + return mTheme; + } } private void initializeTheme() { diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 46f115f9491b..79e5407a17d3 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -455,6 +455,7 @@ public final class LoadedApk { if (!outZipPaths.contains(lib)) { outZipPaths.add(index, lib); index++; + appendApkLibPathIfNeeded(lib, aInfo, outLibPaths); } } } @@ -463,11 +464,33 @@ public final class LoadedApk { for (String lib : instrumentationLibs) { if (!outZipPaths.contains(lib)) { outZipPaths.add(0, lib); + appendApkLibPathIfNeeded(lib, aInfo, outLibPaths); } } } } + /** + * This method appends a path to the appropriate native library folder of a + * library if this library is hosted in an APK. This allows support for native + * shared libraries. The library API is determined based on the application + * ABI. + * + * @param path Path to the library. + * @param applicationInfo The application depending on the library. + * @param outLibPaths List to which to add the native lib path if needed. + */ + private static void appendApkLibPathIfNeeded(@NonNull String path, + @NonNull ApplicationInfo applicationInfo, @Nullable List<String> outLibPaths) { + // Looking at the suffix is a little hacky but a safe and simple solution. + // We will be revisiting code in the next release and clean this up. + if (outLibPaths != null && applicationInfo.primaryCpuAbi != null && path.endsWith(".apk")) { + if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) { + outLibPaths.add(path + "!/lib/" + applicationInfo.primaryCpuAbi); + } + } + } + /* * All indices received by the super class should be shifted by 1 when accessing mSplitNames, * etc. The super class assumes the base APK is index 0, while the PackageManager APIs don't diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 042eece68b53..b7d3f578df27 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -604,7 +604,7 @@ public final class PendingIntent implements Parcelable { /** * Retrieve a PendingIntent that will start a foreground service, like calling - * {@link Context#startService Context.startForegroundService()}. The start + * {@link Context#startForegroundService Context.startForegroundService()}. The start * arguments given to the service will come from the extras of the Intent. * * <p class="note">For security reasons, the {@link android.content.Intent} diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java index b3c70a49f660..8f172ba806f6 100644 --- a/core/java/android/app/WallpaperColors.java +++ b/core/java/android/app/WallpaperColors.java @@ -16,63 +16,206 @@ package android.app; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; +import android.util.Size; -import android.util.Pair; +import com.android.internal.graphics.palette.Palette; +import com.android.internal.graphics.palette.VariationalKMeansQuantizer; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * A class containing information about the colors of a wallpaper. + * Provides information about the colors of a wallpaper. + * <p> + * This class contains two main components: + * <ul> + * <li>Named colors: Most visually representative colors of a wallpaper. Can be either + * {@link WallpaperColors#getPrimaryColor()}, {@link WallpaperColors#getSecondaryColor()} + * or {@link WallpaperColors#getTertiaryColor()}. + * </li> + * <li>Hints: How colors may affect other system components. Currently the only supported hint is + * {@link WallpaperColors#HINT_SUPPORTS_DARK_TEXT}, which specifies if dark text is preferred + * over the wallpaper.</li> + * </ul> */ public final class WallpaperColors implements Parcelable { - private static final float BRIGHT_LUMINANCE = 0.9f; - private final List<Pair<Color, Integer>> mColors; - private final boolean mSupportsDarkText; + /** + * Specifies that dark text is preferred over the current wallpaper for best presentation. + * <p> + * eg. A launcher may set its text color to black if this flag is specified. + */ + public static final int HINT_SUPPORTS_DARK_TEXT = 0x1; + + // Maximum size that a bitmap can have to keep our calculations sane + private static final int MAX_BITMAP_SIZE = 112; + + // Even though we have a maximum size, we'll mainly match bitmap sizes + // using the area instead. This way our comparisons are aspect ratio independent. + private static final int MAX_WALLPAPER_EXTRACTION_AREA = MAX_BITMAP_SIZE * MAX_BITMAP_SIZE; + + // When extracting the main colors, only consider colors + // present in at least MIN_COLOR_OCCURRENCE of the image + private static final float MIN_COLOR_OCCURRENCE = 0.05f; + + // Minimum mean luminosity that an image needs to have to support dark text + private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.9f; + // We also check if the image has dark pixels in it, + // to avoid bright images with some dark spots. + private static final float DARK_PIXEL_LUMINANCE = 0.45f; + private static final float MAX_DARK_AREA = 0.05f; + + private final ArrayList<Color> mMainColors; + private int mColorHints; public WallpaperColors(Parcel parcel) { - mColors = new ArrayList<>(); - int count = parcel.readInt(); - for (int i=0; i < count; i++) { - Color color = Color.valueOf(parcel.readInt()); - int weight = parcel.readInt(); - mColors.add(new Pair<>(color, weight)); + mMainColors = new ArrayList<>(); + final int count = parcel.readInt(); + for (int i = 0; i < count; i++) { + final int colorInt = parcel.readInt(); + Color color = Color.valueOf(colorInt); + mMainColors.add(color); } - mSupportsDarkText = parcel.readBoolean(); + mColorHints = parcel.readInt(); } /** - * Wallpaper color details containing a list of colors and their weights, - * as if it were an histogram. - * This list can be extracted from a bitmap by the Palette API. + * Constructs {@link WallpaperColors} from a drawable. + * <p> + * Main colors will be extracted from the drawable and hints will be calculated. * - * Dark text support will be calculated internally based on the histogram. + * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @param drawable Source where to extract from. + */ + public static WallpaperColors fromDrawable(Drawable drawable) { + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + + // Some drawables do not have intrinsic dimensions + if (width <= 0 || height <= 0) { + width = MAX_BITMAP_SIZE; + height = MAX_BITMAP_SIZE; + } + + Size optimalSize = calculateOptimalSize(width, height); + Bitmap bitmap = Bitmap.createBitmap(optimalSize.getWidth(), optimalSize.getHeight(), + Bitmap.Config.ARGB_8888); + final Canvas bmpCanvas = new Canvas(bitmap); + drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); + drawable.draw(bmpCanvas); + + final WallpaperColors colors = WallpaperColors.fromBitmap(bitmap); + bitmap.recycle(); + + return colors; + } + + /** + * Constructs {@link WallpaperColors} from a bitmap. + * <p> + * Main colors will be extracted from the bitmap and hints will be calculated. * - * @param colors list of pairs where each pair contains a color - * and number of occurrences/influence. + * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @param bitmap Source where to extract from. */ - public WallpaperColors(List<Pair<Color, Integer>> colors) { - this(colors, calculateDarkTextSupport(colors)); + public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap) { + if (bitmap == null) { + throw new IllegalArgumentException("Bitmap can't be null"); + } + + final int bitmapArea = bitmap.getWidth() * bitmap.getHeight(); + if (bitmapArea > MAX_WALLPAPER_EXTRACTION_AREA) { + Size optimalSize = calculateOptimalSize(bitmap.getWidth(), bitmap.getHeight()); + Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, optimalSize.getWidth(), + optimalSize.getHeight(), true /* filter */); + bitmap.recycle(); + bitmap = scaledBitmap; + } + + final Palette palette = Palette + .from(bitmap) + .setQuantizer(new VariationalKMeansQuantizer()) + .maximumColorCount(5) + .clearFilters() + .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA) + .generate(); + + // Remove insignificant colors and sort swatches by population + final ArrayList<Palette.Swatch> swatches = new ArrayList<>(palette.getSwatches()); + final float minColorArea = bitmap.getWidth() * bitmap.getHeight() * MIN_COLOR_OCCURRENCE; + swatches.removeIf(s -> s.getPopulation() < minColorArea); + swatches.sort((a, b) -> b.getPopulation() - a.getPopulation()); + + final int swatchesSize = swatches.size(); + Color primary = null, secondary = null, tertiary = null; + + swatchLoop: + for (int i = 0; i < swatchesSize; i++) { + Color color = Color.valueOf(swatches.get(i).getRgb()); + switch (i) { + case 0: + primary = color; + break; + case 1: + secondary = color; + break; + case 2: + tertiary = color; + break; + default: + // out of bounds + break swatchLoop; + } + } + + int hints = 0; + if (calculateDarkTextSupport(bitmap)) { + hints |= HINT_SUPPORTS_DARK_TEXT; + } + return new WallpaperColors(primary, secondary, tertiary, hints); } /** - * Wallpaper color details containing a list of colors and their weights, - * as if it were an histogram. - * Explicit dark text support. + * Constructs a new object from three colors, where hints can be specified. * - * @param colors list of pairs where each pair contains a color - * and number of occurrences/influence. - * @param supportsDarkText can have dark text on top or not + * @param primaryColor Primary color. + * @param secondaryColor Secondary color. + * @param tertiaryColor Tertiary color. + * @param colorHints A combination of WallpaperColor hints. + * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @see WallpaperColors#fromBitmap(Bitmap) + * @see WallpaperColors#fromDrawable(Drawable) */ - public WallpaperColors(List<Pair<Color, Integer>> colors, boolean supportsDarkText) { - if (colors == null) - colors = new ArrayList<>(); - mColors = colors; - mSupportsDarkText = supportsDarkText; + public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor, + @Nullable Color tertiaryColor, int colorHints) { + + if (primaryColor == null) { + throw new IllegalArgumentException("Primary color should never be null."); + } + + mMainColors = new ArrayList<>(3); + mMainColors.add(primaryColor); + if (secondaryColor != null) { + mMainColors.add(secondaryColor); + } + if (tertiaryColor != null) { + if (secondaryColor == null) { + throw new IllegalArgumentException("tertiaryColor can't be specified when " + + "secondaryColor is null"); + } + mMainColors.add(tertiaryColor); + } + + mColorHints = colorHints; } public static final Creator<WallpaperColors> CREATOR = new Creator<WallpaperColors>() { @@ -94,21 +237,53 @@ public final class WallpaperColors implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - int count = mColors.size(); + List<Color> mainColors = getMainColors(); + int count = mainColors.size(); dest.writeInt(count); - for (Pair<Color, Integer> color : mColors) { - dest.writeInt(color.first.toArgb()); - dest.writeInt(color.second); + for (int i = 0; i < count; i++) { + Color color = mainColors.get(i); + dest.writeInt(color.toArgb()); } - dest.writeBoolean(mSupportsDarkText); + dest.writeInt(mColorHints); + } + + /** + * Gets the most visually representative color of the wallpaper. + * "Visually representative" means easily noticeable in the image, + * probably happening at high frequency. + * + * @return A color. + */ + public @NonNull Color getPrimaryColor() { + return mMainColors.get(0); + } + + /** + * Gets the second most preeminent color of the wallpaper. Can be null. + * + * @return A color, may be null. + */ + public @Nullable Color getSecondaryColor() { + return mMainColors.size() < 2 ? null : mMainColors.get(1); + } + + /** + * Gets the third most preeminent color of the wallpaper. Can be null. + * + * @return A color, may be null. + */ + public @Nullable Color getTertiaryColor() { + return mMainColors.size() < 3 ? null : mMainColors.get(2); } /** - * List of colors with their occurrences. The bigger the int, the more relevant the color. - * @return list of colors paired with their weights. + * List of most preeminent colors, sorted by importance. + * + * @return List of colors. + * @hide */ - public List<Pair<Color, Integer>> getColors() { - return mColors; + public @NonNull List<Color> getMainColors() { + return Collections.unmodifiableList(mMainColors); } @Override @@ -118,38 +293,91 @@ public final class WallpaperColors implements Parcelable { } WallpaperColors other = (WallpaperColors) o; - return mColors.equals(other.mColors) && mSupportsDarkText == other.mSupportsDarkText; + return mMainColors.equals(other.mMainColors) + && mColorHints == other.mColorHints; } @Override public int hashCode() { - return 31 * mColors.hashCode() + (mSupportsDarkText ? 1 : 0); + return 31 * mMainColors.hashCode() + mColorHints; } /** - * Whether or not dark text is legible on top of this wallpaper. + * Combination of WallpaperColor hints. * - * @return true if dark text is supported + * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @return True if dark text is supported. + */ + public int getColorHints() { + return mColorHints; + } + + /** + * @param colorHints Combination of WallpaperColors hints. + * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @hide */ - public boolean supportsDarkText() { - return mSupportsDarkText; + public void setColorHints(int colorHints) { + mColorHints = colorHints; } - private static boolean calculateDarkTextSupport(List<Pair<Color, Integer>> colors) { - if (colors == null) { + /** + * Checks if image is bright and clean enough to support light text. + * + * @param source What to read. + * @return Whether image supports dark text or not. + */ + private static boolean calculateDarkTextSupport(Bitmap source) { + if (source == null) { return false; } - Pair<Color, Integer> mainColor = null; + int[] pixels = new int[source.getWidth() * source.getHeight()]; + double totalLuminance = 0; + final int maxDarkPixels = (int) (pixels.length * MAX_DARK_AREA); + int darkPixels = 0; + source.getPixels(pixels, 0 /* offset */, source.getWidth(), 0 /* x */, 0 /* y */, + source.getWidth(), source.getHeight()); - for (Pair<Color, Integer> color : colors) { - if (mainColor == null) { - mainColor = color; - } else if (color.second > mainColor.second) { - mainColor = color; + // This bitmap was already resized to fit the maximum allowed area. + // Let's just loop through the pixels, no sweat! + for (int i = 0; i < pixels.length; i++) { + final float luminance = Color.luminance(pixels[i]); + final int alpha = Color.alpha(pixels[i]); + + // Make sure we don't have a dark pixel mass that will + // make text illegible. + if (luminance < DARK_PIXEL_LUMINANCE && alpha != 0) { + darkPixels++; + if (darkPixels > maxDarkPixels) { + return false; + } } + + totalLuminance += luminance; + } + return totalLuminance / pixels.length > BRIGHT_IMAGE_MEAN_LUMINANCE; + } + + private static Size calculateOptimalSize(int width, int height) { + // Calculate how big the bitmap needs to be. + // This avoids unnecessary processing and allocation inside Palette. + final int requestedArea = width * height; + double scale = 1; + if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) { + scale = Math.sqrt(MAX_WALLPAPER_EXTRACTION_AREA / (double) requestedArea); + } + int newWidth = (int) (width * scale); + int newHeight = (int) (height * scale); + // Dealing with edge cases of the drawable being too wide or too tall. + // Width or height would end up being 0, in this case we'll set it to 1. + if (newWidth == 0) { + newWidth = 1; } - return mainColor != null && - mainColor.first.luminance() > BRIGHT_LUMINANCE; + if (newHeight == 0) { + newHeight = 1; + } + + return new Size(newWidth, newHeight); } } diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 266fa7e24b2c..4e8277c388de 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -1051,6 +1051,9 @@ public class AssistStructure implements Parcelable { public void updateAutofillValue(AutofillValue value) { mAutofillValue = value; if (value.isText()) { + if (mText == null) { + mText = new ViewNodeText(); + } mText.mText = value.getTextValue(); } } diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java index 67d56d5060e9..dfd5996c6a00 100644 --- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java +++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java @@ -208,6 +208,8 @@ public final class BluetoothLeAdvertiser { if (wrapper == null) return; stopAdvertisingSet(wrapper); + + mLegacyAdvertisers.remove(callback); } } diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java index f3f0ae5cd959..1eac395bd06c 100644 --- a/core/java/android/bluetooth/le/BluetoothLeScanner.java +++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java @@ -136,6 +136,11 @@ public final class BluetoothLeScanner { * Start Bluetooth LE scan using a {@link PendingIntent}. The scan results will be delivered via * the PendingIntent. Use this method of scanning if your process is not always running and it * should be started when scan results are available. + * <p> + * When the PendingIntent is delivered, the Intent passed to the receiver or activity + * will contain one or more of the extras {@link #EXTRA_CALLBACK_TYPE}, + * {@link #EXTRA_ERROR_CODE} and {@link #EXTRA_LIST_SCAN_RESULT} to indicate the result of + * the scan. * * @param filters Optional list of ScanFilters for finding exact BLE devices. * @param settings Optional settings for the scan. diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java index 3665d1b85bcb..4ee38fe4990e 100644 --- a/core/java/android/companion/BluetoothDeviceFilterUtils.java +++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java @@ -58,7 +58,7 @@ public class BluetoothDeviceFilterUtils { static boolean matchesAddress(String deviceAddress, BluetoothDevice device) { final boolean result = deviceAddress == null - || (device == null || !deviceAddress.equals(device.getAddress())); + || (device != null && deviceAddress.equals(device.getAddress())); if (DEBUG) debugLogMatchResult(result, device, deviceAddress); return result; } diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index dabe608c038f..86a30cf0c846 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -214,10 +214,12 @@ public final class CompanionDeviceManager { return; } try { - mService.requestNotificationAccess(component).send(); + IntentSender intentSender = mService.requestNotificationAccess(component) + .getIntentSender(); + mContext.startIntentSender(intentSender, null, 0, 0, 0); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } catch (PendingIntent.CanceledException e) { + } catch (IntentSender.SendIntentException e) { throw new RuntimeException(e); } } @@ -288,6 +290,7 @@ public final class CompanionDeviceManager { @Override public void onActivityDestroyed(Activity activity) { + if (activity != getActivity()) return; try { mService.stopScan(mRequest, this, getCallingPackage()); } catch (RemoteException e) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2f795385b2e5..5929aca0a38f 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -487,27 +487,27 @@ public abstract class Context { */ public abstract Context getApplicationContext(); - /** Non-activity related accessibility ids are unique in the app */ - private static int sLastAccessibilityId = View.NO_ID; + /** Non-activity related autofill ids are unique in the app */ + private static int sLastAutofillId = View.NO_ID; /** - * Gets the next accessibility ID. + * Gets the next autofill ID. * - * <p>All IDs will be smaller or the same as {@link View#LAST_APP_ACCESSIBILITY_ID}. All IDs + * <p>All IDs will be smaller or the same as {@link View#LAST_APP_AUTOFILL_ID}. All IDs * returned will be unique. * * @return A ID that is unique in the process * * {@hide} */ - public int getNextAccessibilityId() { - if (sLastAccessibilityId == View.LAST_APP_ACCESSIBILITY_ID - 1) { - sLastAccessibilityId = View.NO_ID; + public int getNextAutofillId() { + if (sLastAutofillId == View.LAST_APP_AUTOFILL_ID - 1) { + sLastAutofillId = View.NO_ID; } - sLastAccessibilityId++; + sLastAutofillId++; - return sLastAccessibilityId; + return sLastAutofillId; } /** diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 20fafb2857f0..c719c6474cf1 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -961,8 +961,7 @@ public class ContextWrapper extends Context { /** * @hide */ - @Override - public int getNextAccessibilityId() { - return mBase.getNextAccessibilityId(); + public int getNextAutofillId() { + return mBase.getNextAutofillId(); } } diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java index bb24ccd5a5ed..663e6e476c5a 100644 --- a/core/java/android/content/SyncStatusInfo.java +++ b/core/java/android/content/SyncStatusInfo.java @@ -24,7 +24,11 @@ import java.util.ArrayList; /** @hide */ public class SyncStatusInfo implements Parcelable { - static final int VERSION = 2; + private static final String TAG = "Sync"; + + static final int VERSION = 3; + + private static final int MAX_EVENT_COUNT = 10; public final int authorityId; public long totalElapsedTime; @@ -47,7 +51,8 @@ public class SyncStatusInfo implements Parcelable { // no race conditions when accessing this list private ArrayList<Long> periodicSyncTimes; - private static final String TAG = "Sync"; + private final ArrayList<Long> mLastEventTimes = new ArrayList<>(); + private final ArrayList<String> mLastEvents = new ArrayList<>(); public SyncStatusInfo(int authorityId) { this.authorityId = authorityId; @@ -92,6 +97,11 @@ public class SyncStatusInfo implements Parcelable { } else { parcel.writeInt(-1); } + parcel.writeInt(mLastEventTimes.size()); + for (int i = 0; i < mLastEventTimes.size(); i++) { + parcel.writeLong(mLastEventTimes.get(i)); + parcel.writeString(mLastEvents.get(i)); + } } public SyncStatusInfo(Parcel parcel) { @@ -117,15 +127,24 @@ public class SyncStatusInfo implements Parcelable { if (version == 1) { periodicSyncTimes = null; } else { - int N = parcel.readInt(); - if (N < 0) { + final int count = parcel.readInt(); + if (count < 0) { periodicSyncTimes = null; } else { periodicSyncTimes = new ArrayList<Long>(); - for (int i=0; i<N; i++) { + for (int i = 0; i < count; i++) { periodicSyncTimes.add(parcel.readLong()); } } + if (version >= 3) { + mLastEventTimes.clear(); + mLastEvents.clear(); + final int nEvents = parcel.readInt(); + for (int i = 0; i < nEvents; i++) { + mLastEventTimes.add(parcel.readLong()); + mLastEvents.add(parcel.readString()); + } + } } } @@ -149,6 +168,8 @@ public class SyncStatusInfo implements Parcelable { if (other.periodicSyncTimes != null) { periodicSyncTimes = new ArrayList<Long>(other.periodicSyncTimes); } + mLastEventTimes.addAll(other.mLastEventTimes); + mLastEvents.addAll(other.mLastEvents); } public void setPeriodicSyncTime(int index, long when) { @@ -172,6 +193,31 @@ public class SyncStatusInfo implements Parcelable { } } + /** */ + public void addEvent(String message) { + if (mLastEventTimes.size() >= MAX_EVENT_COUNT) { + mLastEventTimes.remove(MAX_EVENT_COUNT - 1); + mLastEvents.remove(MAX_EVENT_COUNT - 1); + } + mLastEventTimes.add(0, System.currentTimeMillis()); + mLastEvents.add(0, message); + } + + /** */ + public int getEventCount() { + return mLastEventTimes.size(); + } + + /** */ + public long getEventTime(int i) { + return mLastEventTimes.get(i); + } + + /** */ + public String getEvent(int i) { + return mLastEvents.get(i); + } + public static final Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() { public SyncStatusInfo createFromParcel(Parcel in) { return new SyncStatusInfo(in); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 9b0bab427478..c67376c25885 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -5989,6 +5989,10 @@ public class PackageParser { } } + public boolean isLibrary() { + return staticSharedLibName != null || !ArrayUtils.isEmpty(libraryNames); + } + public List<String> getAllCodePaths() { ArrayList<String> paths = new ArrayList<>(); paths.add(baseCodePath); @@ -6852,7 +6856,7 @@ public class PackageParser { ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName); } ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state); - ai.resourceDirs = state.resourceDirs; + ai.resourceDirs = state.overlayPaths; } public static ApplicationInfo generateApplicationInfo(Package p, int flags, @@ -7000,6 +7004,7 @@ public class PackageParser { return null; } if (!copyNeeded(flags, a.owner, state, a.metaData, userId)) { + updateApplicationInfo(a.info.applicationInfo, flags, state); return a.info; } // Make shallow copies so we can store the metadata safely @@ -7088,6 +7093,7 @@ public class PackageParser { return null; } if (!copyNeeded(flags, s.owner, state, s.metaData, userId)) { + updateApplicationInfo(s.info.applicationInfo, flags, state); return s.info; } // Make shallow copies so we can store the metadata safely @@ -7183,6 +7189,7 @@ public class PackageParser { if (!copyNeeded(flags, p.owner, state, p.metaData, userId) && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0 || p.info.uriPermissionPatterns == null)) { + updateApplicationInfo(p.info.applicationInfo, flags, state); return p.info; } // Make shallow copies so we can store the metadata safely diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index 4e53914a3c68..470336cc70c7 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -55,7 +55,7 @@ public class PackageUserState { public ArraySet<String> disabledComponents; public ArraySet<String> enabledComponents; - public String[] resourceDirs; + public String[] overlayPaths; public PackageUserState() { installed = true; @@ -83,8 +83,8 @@ public class PackageUserState { installReason = o.installReason; disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents); enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents); - resourceDirs = - o.resourceDirs == null ? null : Arrays.copyOf(o.resourceDirs, o.resourceDirs.length); + overlayPaths = + o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length); } /** diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 88c1627f955b..6834ba816910 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -16,29 +16,26 @@ package android.content.res; -import android.graphics.Point; -import android.graphics.Rect; -import android.util.DisplayMetrics; -import android.view.Display; -import android.view.DisplayInfo; -import com.android.internal.util.XmlUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; +import android.graphics.Rect; import android.os.Build; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.view.DisplayInfo; import android.view.View; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -1818,9 +1815,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration } /** - * Return whether the screen has a wide color gamut. + * Return whether the screen has a wide color gamut and wide color gamut rendering + * is supported by this device. * - * @return true if the screen has a wide color gamut, false otherwise + * @return true if the screen has a wide color gamut and wide color gamut rendering + * is supported, false otherwise */ public boolean isScreenWideColorGamut() { return (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) == COLOR_MODE_WIDE_COLOR_GAMUT_YES; diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index b3938cbc3bc9..6825d363b918 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -158,7 +158,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession } @Override - public synchronized int capture(CaptureRequest request, CaptureCallback callback, + public int capture(CaptureRequest request, CaptureCallback callback, Handler handler) throws CameraAccessException { if (request == null) { throw new IllegalArgumentException("request must not be null"); @@ -169,21 +169,23 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession throw new IllegalArgumentException("capture request was created for another session"); } - checkNotClosed(); + synchronized (mDeviceImpl.mInterfaceLock) { + checkNotClosed(); - handler = checkHandler(handler, callback); + handler = checkHandler(handler, callback); - if (DEBUG) { - Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback + - " handler " + handler); - } + if (DEBUG) { + Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback + + " handler " + handler); + } - return addPendingSequence(mDeviceImpl.capture(request, - createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + return addPendingSequence(mDeviceImpl.capture(request, + createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + } } @Override - public synchronized int captureBurst(List<CaptureRequest> requests, CaptureCallback callback, + public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback, Handler handler) throws CameraAccessException { if (requests == null) { throw new IllegalArgumentException("Requests must not be null"); @@ -203,22 +205,24 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession } } - checkNotClosed(); + synchronized (mDeviceImpl.mInterfaceLock) { + checkNotClosed(); - handler = checkHandler(handler, callback); + handler = checkHandler(handler, callback); - if (DEBUG) { - CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]); - Log.v(TAG, mIdString + "captureBurst - requests " + Arrays.toString(requestArray) + - ", callback " + callback + " handler " + handler); - } + if (DEBUG) { + CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]); + Log.v(TAG, mIdString + "captureBurst - requests " + Arrays.toString(requestArray) + + ", callback " + callback + " handler " + handler); + } - return addPendingSequence(mDeviceImpl.captureBurst(requests, - createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + return addPendingSequence(mDeviceImpl.captureBurst(requests, + createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + } } @Override - public synchronized int setRepeatingRequest(CaptureRequest request, CaptureCallback callback, + public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback, Handler handler) throws CameraAccessException { if (request == null) { throw new IllegalArgumentException("request must not be null"); @@ -226,21 +230,23 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession throw new IllegalArgumentException("repeating reprocess requests are not supported"); } - checkNotClosed(); + synchronized (mDeviceImpl.mInterfaceLock) { + checkNotClosed(); - handler = checkHandler(handler, callback); + handler = checkHandler(handler, callback); - if (DEBUG) { - Log.v(TAG, mIdString + "setRepeatingRequest - request " + request + ", callback " + - callback + " handler" + " " + handler); - } + if (DEBUG) { + Log.v(TAG, mIdString + "setRepeatingRequest - request " + request + ", callback " + + callback + " handler" + " " + handler); + } - return addPendingSequence(mDeviceImpl.setRepeatingRequest(request, - createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + return addPendingSequence(mDeviceImpl.setRepeatingRequest(request, + createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + } } @Override - public synchronized int setRepeatingBurst(List<CaptureRequest> requests, + public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback callback, Handler handler) throws CameraAccessException { if (requests == null) { throw new IllegalArgumentException("requests must not be null"); @@ -255,34 +261,39 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession } } - checkNotClosed(); + synchronized (mDeviceImpl.mInterfaceLock) { + checkNotClosed(); - handler = checkHandler(handler, callback); + handler = checkHandler(handler, callback); - if (DEBUG) { - CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]); - Log.v(TAG, mIdString + "setRepeatingBurst - requests " + Arrays.toString(requestArray) + - ", callback " + callback + " handler" + "" + handler); - } + if (DEBUG) { + CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]); + Log.v(TAG, mIdString + "setRepeatingBurst - requests " + + Arrays.toString(requestArray) + ", callback " + callback + + " handler" + "" + handler); + } - return addPendingSequence(mDeviceImpl.setRepeatingBurst(requests, - createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + return addPendingSequence(mDeviceImpl.setRepeatingBurst(requests, + createCaptureCallbackProxy(handler, callback), mDeviceHandler)); + } } @Override - public synchronized void stopRepeating() throws CameraAccessException { - checkNotClosed(); + public void stopRepeating() throws CameraAccessException { + synchronized (mDeviceImpl.mInterfaceLock) { + checkNotClosed(); - if (DEBUG) { - Log.v(TAG, mIdString + "stopRepeating"); - } + if (DEBUG) { + Log.v(TAG, mIdString + "stopRepeating"); + } - mDeviceImpl.stopRepeating(); + mDeviceImpl.stopRepeating(); + } } @Override public void abortCaptures() throws CameraAccessException { - synchronized (this) { + synchronized (mDeviceImpl.mInterfaceLock) { checkNotClosed(); if (DEBUG) { @@ -296,13 +307,9 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession mAborting = true; mAbortDrainer.taskStarted(); - } - synchronized (mDeviceImpl.mInterfaceLock) { - synchronized (this) { - mDeviceImpl.flush(); - // The next BUSY -> IDLE set of transitions will mark the end of the abort. - } + mDeviceImpl.flush(); + // The next BUSY -> IDLE set of transitions will mark the end of the abort. } } @@ -332,7 +339,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession */ @Override public void replaceSessionClose() { - synchronized (this) { + synchronized (mDeviceImpl.mInterfaceLock) { /* * In order for creating new sessions to be fast, the new session should be created * before the old session is closed. @@ -357,13 +364,13 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession // configuration for the new session. If it was already called, then we don't care, // since it won't get called again. mSkipUnconfigure = true; + close(); } - close(); } @Override public void close() { - synchronized (this) { + synchronized (mDeviceImpl.mInterfaceLock) { if (mClosed) { if (DEBUG) Log.v(TAG, mIdString + "close - reentering"); return; @@ -372,42 +379,36 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession if (DEBUG) Log.v(TAG, mIdString + "close - first time"); mClosed = true; - } - synchronized (mDeviceImpl.mInterfaceLock) { - synchronized (this) { - /* - * Flush out any repeating request. Since camera is closed, no new requests - * can be queued, and eventually the entire request queue will be drained. - * - * If the camera device was already closed, short circuit and do nothing; since - * no more internal device callbacks will fire anyway. - * - * Otherwise, once stopRepeating is done, wait for camera to idle, then unconfigure - * the camera. Once that's done, fire #onClosed. - */ - try { - mDeviceImpl.stopRepeating(); - } catch (IllegalStateException e) { - // OK: Camera device may already be closed, nothing else to do + /* + * Flush out any repeating request. Since camera is closed, no new requests + * can be queued, and eventually the entire request queue will be drained. + * + * If the camera device was already closed, short circuit and do nothing; since + * no more internal device callbacks will fire anyway. + * + * Otherwise, once stopRepeating is done, wait for camera to idle, then unconfigure + * the camera. Once that's done, fire #onClosed. + */ + try { + mDeviceImpl.stopRepeating(); + } catch (IllegalStateException e) { + // OK: Camera device may already be closed, nothing else to do - // TODO: Fire onClosed anytime we get the device onClosed or the ISE? - // or just suppress the ISE only and rely onClosed. - // Also skip any of the draining work if this is already closed. + // TODO: Fire onClosed anytime we get the device onClosed or the ISE? + // or just suppress the ISE only and rely onClosed. + // Also skip any of the draining work if this is already closed. - // Short-circuit; queue callback immediately and return - mStateCallback.onClosed(this); - return; - } catch (CameraAccessException e) { - // OK: close does not throw checked exceptions. - Log.e(TAG, mIdString + "Exception while stopping repeating: ", e); + // Short-circuit; queue callback immediately and return + mStateCallback.onClosed(this); + return; + } catch (CameraAccessException e) { + // OK: close does not throw checked exceptions. + Log.e(TAG, mIdString + "Exception while stopping repeating: ", e); - // TODO: call onError instead of onClosed if this happens - } + // TODO: call onError instead of onClosed if this happens } - } - synchronized (this) { // If no sequences are pending, fire #onClosed immediately mSequenceDrainer.beginDrain(); } @@ -552,6 +553,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession @Override public CameraDeviceImpl.StateCallbackKK getDeviceStateCallback() { final CameraCaptureSession session = this; + final Object interfaceLock = mDeviceImpl.mInterfaceLock; + return new CameraDeviceImpl.StateCallbackKK() { private boolean mBusy = false; @@ -588,7 +591,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession boolean isAborting; if (DEBUG) Log.v(TAG, mIdString + "onIdle"); - synchronized (session) { + synchronized (interfaceLock) { isAborting = mAborting; } @@ -606,7 +609,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession if (mBusy && isAborting) { mAbortDrainer.taskFinished(); - synchronized (session) { + synchronized (interfaceLock) { mAborting = false; } } @@ -729,7 +732,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession @Override public void onDrained() { if (DEBUG) Log.v(TAG, mIdString + "onAbortDrained"); - synchronized (CameraCaptureSessionImpl.this) { + synchronized (mDeviceImpl.mInterfaceLock) { /* * Any queued aborts have now completed. * @@ -757,7 +760,6 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession // Take device lock before session lock so that we can call back into device // without causing a deadlock synchronized (mDeviceImpl.mInterfaceLock) { - synchronized (CameraCaptureSessionImpl.this) { /* * The device is now IDLE, and has settled. It will not transition to * ACTIVE or BUSY again by itself. @@ -766,33 +768,31 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession * * This operation is idempotent; a session will not be closed twice. */ - if (DEBUG) - Log.v(TAG, mIdString + "Session drain complete, skip unconfigure: " + - mSkipUnconfigure); - - // Fast path: A new capture session has replaced this one; don't wait for idle - // as we won't get state updates any more anyway. - if (mSkipUnconfigure) { - return; - } + if (DEBUG) + Log.v(TAG, mIdString + "Session drain complete, skip unconfigure: " + + mSkipUnconfigure); - // Final slow path: unconfigure the camera, no session has replaced us and - // everything is idle. - try { - // begin transition to unconfigured - mDeviceImpl.configureStreamsChecked(/*inputConfig*/null, /*outputs*/null, - /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE); - } catch (CameraAccessException e) { - // OK: do not throw checked exceptions. - Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e); - - // TODO: call onError instead of onClosed if this happens - } catch (IllegalStateException e) { - // Camera is already closed, so nothing left to do - if (DEBUG) Log.v(TAG, mIdString + - "Camera was already closed or busy, skipping unconfigure"); - } + // Fast path: A new capture session has replaced this one; don't wait for idle + // as we won't get state updates any more anyway. + if (mSkipUnconfigure) { + return; + } + + // Final slow path: unconfigure the camera, no session has replaced us and + // everything is idle. + try { + // begin transition to unconfigured + mDeviceImpl.configureStreamsChecked(/*inputConfig*/null, /*outputs*/null, + /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE); + } catch (CameraAccessException e) { + // OK: do not throw checked exceptions. + Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e); + // TODO: call onError instead of onClosed if this happens + } catch (IllegalStateException e) { + // Camera is already closed, so nothing left to do + if (DEBUG) Log.v(TAG, mIdString + + "Camera was already closed or busy, skipping unconfigure"); } } } diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java index 15dbf2620608..fec7fd97764c 100644 --- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java @@ -96,6 +96,9 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl CaptureRequest.Builder singleTargetRequestBuilder = new CaptureRequest.Builder( requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); + // Carry over userTag, as native metadata doesn't have this field. + singleTargetRequestBuilder.setTag(request.getTag()); + // Overwrite the capture intent to make sure a good value is set. Iterator<Surface> iterator = outputSurfaces.iterator(); Surface firstSurface = iterator.next(); @@ -118,6 +121,7 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl requestMetadata = new CameraMetadataNative(request.getNativeCopy()); doubleTargetRequestBuilder = new CaptureRequest.Builder( requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); + doubleTargetRequestBuilder.setTag(request.getTag()); doubleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD); doubleTargetRequestBuilder.addTarget(firstSurface); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index fcb99b167d1e..f47807142376 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -602,6 +602,15 @@ public class ConnectivityManager { public final static int REQUEST_ID_UNSET = 0; /** + * Static unique request used as a tombstone for NetworkCallbacks that have been unregistered. + * This allows to distinguish when unregistering NetworkCallbacks those that were never + * registered and those that were already unregistered. + * @hide + */ + private final static NetworkRequest ALREADY_UNREGISTERED = + new NetworkRequest.Builder().clearCapabilities().build(); + + /** * A NetID indicating no Network is selected. * Keep in sync with bionic/libc/dns/include/resolv_netid.h * @hide @@ -2686,10 +2695,6 @@ public class ConnectivityManager { public void onNetworkResumed(Network network) {} private NetworkRequest networkRequest; - - private boolean isRegistered() { - return (networkRequest != null) && (networkRequest.requestId != REQUEST_ID_UNSET); - } } /** @@ -2856,7 +2861,8 @@ public class ConnectivityManager { final NetworkRequest request; try { synchronized(sCallbacks) { - if (callback.isRegistered()) { + if (callback.networkRequest != null + && callback.networkRequest != ALREADY_UNREGISTERED) { // TODO: throw exception instead and enforce 1:1 mapping of callbacks // and requests (http://b/20701525). Log.e(TAG, "NetworkCallback was already registered"); @@ -3302,8 +3308,10 @@ public class ConnectivityManager { // Find all requests associated to this callback and stop callback triggers immediately. // Callback is reusable immediately. http://b/20701525, http://b/35921499. synchronized (sCallbacks) { - Preconditions.checkArgument( - networkCallback.isRegistered(), "NetworkCallback was not registered"); + Preconditions.checkArgument(networkCallback.networkRequest != null, + "NetworkCallback was not registered"); + Preconditions.checkArgument(networkCallback.networkRequest != ALREADY_UNREGISTERED, + "NetworkCallback was already unregistered"); for (Map.Entry<NetworkRequest, NetworkCallback> e : sCallbacks.entrySet()) { if (e.getValue() == networkCallback) { reqs.add(e.getKey()); @@ -3319,7 +3327,7 @@ public class ConnectivityManager { // Only remove mapping if rpc was successful. sCallbacks.remove(r); } - networkCallback.networkRequest = null; + networkCallback.networkRequest = ALREADY_UNREGISTERED; } } diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index 8678d95db17d..b69a23aa5854 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -696,6 +696,14 @@ public class Handler { } /** + * Return whether there are any messages or callbacks currently scheduled on this handler. + * @hide + */ + public final boolean hasMessagesOrCallbacks() { + return mQueue.hasMessages(this); + } + + /** * Check if there are any pending posts of messages with code 'what' and * whose obj is 'object' in the message queue. */ @@ -728,6 +736,18 @@ public class Handler { } } + /** + * @hide + */ + public final void dumpMine(Printer pw, String prefix) { + pw.println(prefix + this + " @ " + SystemClock.uptimeMillis()); + if (mLooper == null) { + pw.println(prefix + "looper uninitialized"); + } else { + mLooper.dump(pw, prefix + " ", this); + } + } + @Override public String toString() { return "Handler (" + getClass().getName() + ") {" diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index 44dbcfb09582..04cceb8e80ba 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -310,7 +310,20 @@ public final class Looper { */ public void dump(@NonNull Printer pw, @NonNull String prefix) { pw.println(prefix + toString()); - mQueue.dump(pw, prefix + " "); + mQueue.dump(pw, prefix + " ", null); + } + + /** + * Dumps the state of the looper for debugging purposes. + * + * @param pw A printer to receive the contents of the dump. + * @param prefix A prefix to prepend to each line which is printed. + * @param handler Only dump messages for this Handler. + * @hide + */ + public void dump(@NonNull Printer pw, @NonNull String prefix, Handler handler) { + pw.println(prefix + toString()); + mQueue.dump(pw, prefix + " ", handler); } /** @hide */ diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index 2a8c52e92c60..624e28a67ae6 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -620,6 +620,23 @@ public final class MessageQueue { } } + boolean hasMessages(Handler h) { + if (h == null) { + return false; + } + + synchronized (this) { + Message p = mMessages; + while (p != null) { + if (p.target == h) { + return true; + } + p = p.next; + } + return false; + } + } + void removeMessages(Handler h, int what, Object object) { if (h == null) { return; @@ -759,12 +776,14 @@ public final class MessageQueue { } } - void dump(Printer pw, String prefix) { + void dump(Printer pw, String prefix, Handler h) { synchronized (this) { long now = SystemClock.uptimeMillis(); int n = 0; for (Message msg = mMessages; msg != null; msg = msg.next) { - pw.println(prefix + "Message " + n + ": " + msg.toString(now)); + if (h == null || h == msg.target) { + pw.println(prefix + "Message " + n + ": " + msg.toString(now)); + } n++; } pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked() diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 0611f175e809..2b82c77d5909 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -936,6 +936,11 @@ public final class StrictMode { return this; } + Builder disable(int bit) { + mMask &= ~bit; + return this; + } + /** * Construct the VmPolicy instance. * @@ -1214,7 +1219,13 @@ public final class StrictMode { if (IS_USER_BUILD) { setCloseGuardEnabled(false); } else { - VmPolicy.Builder policyBuilder = new VmPolicy.Builder().detectAll().penaltyDropBox(); + VmPolicy.Builder policyBuilder = new VmPolicy.Builder().detectAll(); + if (!IS_ENG_BUILD) { + // Activity leak detection causes too much slowdown for userdebug because of the + // GCs. + policyBuilder = policyBuilder.disable(DETECT_VM_ACTIVITY_LEAKS); + } + policyBuilder = policyBuilder.penaltyDropBox(); if (IS_ENG_BUILD) { policyBuilder.penaltyLog(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 784ed7a25a47..de69df559ac2 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -410,6 +410,21 @@ public final class Settings { "android.settings.BLUETOOTH_SETTINGS"; /** + * Activity Action: Show settings to allow configuration of Assist Gesture. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_ASSIST_GESTURE_SETTINGS = + "android.settings.ASSIST_GESTURE_SETTINGS"; + + /** * Activity Action: Show settings to allow configuration of cast endpoints. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -5216,6 +5231,15 @@ public final class Settings { public static final String USER_SETUP_COMPLETE = "user_setup_complete"; /** + * Whether the current user has been set up via setup wizard (0 = false, 1 = true) + * This value differs from USER_SETUP_COMPLETE in that it can be reset back to 0 + * in case SetupWizard has been re-enabled on TV devices. + * + * @hide + */ + public static final String TV_USER_SETUP_COMPLETE = "tv_user_setup_complete"; + + /** * Prefix for category name that marks whether a suggested action from that category was * completed. * @hide @@ -6803,6 +6827,22 @@ public final class Settings { public static final String ASSIST_GESTURE_SENSITIVITY = "assist_gesture_sensitivity"; /** + * Whether the assist gesture should silence alerts. + * + * @hide + */ + public static final String ASSIST_GESTURE_SILENCE_ALERTS_ENABLED = + "assist_gesture_silence_alerts_enabled"; + + /** + * Whether the assist gesture should wake the phone. + * + * @hide + */ + public static final String ASSIST_GESTURE_WAKE_ENABLED = + "assist_gesture_wake_enabled"; + + /** * Whether Assist Gesture Deferred Setup has been completed * * @hide @@ -7096,6 +7136,9 @@ public final class Settings { AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_SENSITIVITY, + ASSIST_GESTURE_SETUP_COMPLETE, + ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, + ASSIST_GESTURE_WAKE_ENABLED, VR_DISPLAY_MODE, NOTIFICATION_BADGING }; @@ -9111,7 +9154,10 @@ public final class Settings { * <pre> * max_cached_processes (int) * background_settle_time (long) - * foreground_service_ui_min_time (long) + * fgservice_min_shown_time (long) + * fgservice_min_report_time (long) + * fgservice_screen_on_before_time (long) + * fgservice_screen_on_after_time (long) * content_provider_retain_time (long) * gc_timeout (long) * gc_min_interval (long) @@ -9917,6 +9963,16 @@ public final class Settings { public static final String ENABLE_EPHEMERAL_FEATURE = "enable_ephemeral_feature"; /** + * Toggle to enable/disable dexopt for instant applications. The default is for dexopt + * to be disabled. + * <p> + * Type: int (0 to disable, 1 to enable) + * + * @hide + */ + public static final String INSTANT_APP_DEXOPT_ENABLED = "instant_app_dexopt_enabled"; + + /** * The min period for caching installed instant apps in milliseconds. * <p> * Type: long @@ -10066,6 +10122,15 @@ public final class Settings { "backup_refactored_service_disabled"; /** + * Flag to set the waiting time for euicc factory reset inside System > Settings + * Type: long + * + * @hide + */ + public static final String EUICC_WIPING_TIMEOUT_MILLIS = + "euicc_wiping_timeout_millis"; + + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. * @@ -10651,6 +10716,13 @@ public final class Settings { */ public static final String ENABLE_CACHE_QUOTA_CALCULATION = "enable_cache_quota_calculation"; + + /** + * Whether the Deletion Helper no threshold toggle is available. + * @hide + */ + public static final String ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE = + "enable_deletion_helper_no_threshold_toggle"; } /** diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 9df315b7deab..394bd0ac70f7 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -23,9 +23,7 @@ import com.android.internal.os.HandlerCaller; import android.annotation.SdkConstant; import android.app.Activity; import android.app.Service; -import android.app.assist.AssistStructure; import android.content.Intent; -import android.os.Bundle; import android.os.CancellationSignal; import android.os.IBinder; import android.os.ICancellationSignal; @@ -35,9 +33,6 @@ import android.view.autofill.AutofillManager; import com.android.internal.os.SomeArgs; -import java.util.ArrayList; -import java.util.List; - /** * Top-level service of the current autofill service for a given user. * @@ -192,6 +187,11 @@ public abstract class AutofillService extends Service { * {@link SaveCallback#onSuccess()} or {@link SaveCallback#onFailure(CharSequence)}) * to notify the result of the request. * + * <p><b>NOTE: </b>to retrieve the actual value of the field, the service should call + * {@link android.app.assist.AssistStructure.ViewNode#getAutofillValue()}; if it calls + * {@link android.app.assist.AssistStructure.ViewNode#getText()} or other methods, there is no + * guarantee such method will return the most recent value of the field. + * * @param request the {@link SaveRequest request} to handle. * See {@link FillResponse} for examples of multiple-sections requests. * @param callback object used to notify the result of the request. @@ -207,19 +207,23 @@ public abstract class AutofillService extends Service { public void onDisconnected() { } - /** @hide */ - @Deprecated - public final void disableSelf() { - getSystemService(AutofillManager.class).disableOwnedAutofillServices(); - } - /** - * Returns the {@link FillEventHistory.Event events} since the last {@link FillResponse} was - * returned. + * Gets the events that happened after the last + * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} + * call. + * + * <p>This method is typically used to keep track of previous user actions to optimize further + * requests. For example, the service might return email addresses in alphabetical order by + * default, but change that order based on the address the user picked on previous requests. * - * <p>The history is not persisted over reboots. + * <p>The history is not persisted over reboots, and it's cleared every time the service + * replies to a {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} by calling + * {@link FillCallback#onSuccess(FillResponse)} or {@link FillCallback#onFailure(CharSequence)} + * (if the service doesn't call any of these methods, the history will clear out after some + * pre-defined time). Hence, the service should call {@link #getFillEventHistory()} before + * finishing the {@link FillCallback}. * - * @return The history or {@code null} if there are not events. + * @return The history or {@code null} if there are no events. */ @Nullable public final FillEventHistory getFillEventHistory() { AutofillManager afm = getSystemService(AutofillManager.class); diff --git a/core/java/android/service/autofill/FillContext.java b/core/java/android/service/autofill/FillContext.java index 6956c8ac7135..f8a87516854d 100644 --- a/core/java/android/service/autofill/FillContext.java +++ b/core/java/android/service/autofill/FillContext.java @@ -106,15 +106,15 @@ public final class FillContext implements Parcelable { } /** - * Finds {@link ViewNode}s that have the requested ids. + * Finds {@link ViewNode ViewNodes} that have the requested ids. * - * @param ids The ids of the node to find + * @param ids The ids of the node to find. * - * @return The nodes indexed in the same way as the ids + * @return The nodes indexed in the same way as the ids. * * @hide */ - @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId... ids) { + @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId[] ids) { final LinkedList<ViewNode> nodesToProcess = new LinkedList<>(); final ViewNode[] foundNodes = new AssistStructure.ViewNode[ids.length]; @@ -178,6 +178,30 @@ public final class FillContext implements Parcelable { return foundNodes; } + /** + * Finds the {@link ViewNode} that has the requested {@code id}, if any. + * + * @hide + */ + @Nullable public ViewNode findViewNodeByAutofillId(@NonNull AutofillId id) { + final LinkedList<ViewNode> nodesToProcess = new LinkedList<>(); + final int numWindowNodes = mStructure.getWindowNodeCount(); + for (int i = 0; i < numWindowNodes; i++) { + nodesToProcess.add(mStructure.getWindowNodeAt(i).getRootViewNode()); + } + while (!nodesToProcess.isEmpty()) { + final ViewNode node = nodesToProcess.removeFirst(); + if (id.equals(node.getAutofillId())) { + return node; + } + for (int i = 0; i < node.getChildCount(); i++) { + nodesToProcess.addLast(node.getChildAt(i)); + } + } + + return null; + } + public static final Parcelable.Creator<FillContext> CREATOR = new Parcelable.Creator<FillContext>() { @Override diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java index 3d72fccece9c..f7dc1c58ade1 100644 --- a/core/java/android/service/autofill/FillEventHistory.java +++ b/core/java/android/service/autofill/FillEventHistory.java @@ -33,7 +33,20 @@ import java.util.ArrayList; import java.util.List; /** - * Describes what happened after the latest call to {@link FillCallback#onSuccess(FillResponse)}. + * Describes what happened after the last + * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} + * call. + * + * <p>This history is typically used to keep track of previous user actions to optimize further + * requests. For example, the service might return email addresses in alphabetical order by + * default, but change that order based on the address the user picked on previous requests. + * + * <p>The history is not persisted over reboots, and it's cleared every time the service + * replies to a + * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} + * by calling {@link FillCallback#onSuccess(FillResponse)} or + * {@link FillCallback#onFailure(CharSequence)} (if the service doesn't call any of these methods, + * the history will clear out after some pre-defined time). */ public final class FillEventHistory implements Parcelable { /** @@ -41,6 +54,11 @@ public final class FillEventHistory implements Parcelable { */ private final int mServiceUid; + /** + * Not in parcel. The ID of the autofill session that created the {@link FillResponse}. + */ + private final int mSessionId; + @Nullable private final Bundle mClientState; @Nullable List<Event> mEvents; @@ -55,10 +73,17 @@ public final class FillEventHistory implements Parcelable { return mServiceUid; } + /** @hide */ + public int getSessionId() { + return mSessionId; + } + /** - * Returns the client state of the {@link FillResponse}. + * Returns the client state set in the previous {@link FillResponse}. * - * @return The client state set by the last {@link FillResponse} + * <p><b>NOTE: </b>the state is associated with the app that was autofilled in the previous + * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} + * , which is not necessary the same app being autofilled now. */ @Nullable public Bundle getClientState() { return mClientState; @@ -87,9 +112,10 @@ public final class FillEventHistory implements Parcelable { /** * @hide */ - public FillEventHistory(int serviceUid, @Nullable Bundle clientState) { + public FillEventHistory(int serviceUid, int sessionId, @Nullable Bundle clientState) { mClientState = clientState; mServiceUid = serviceUid; + mSessionId = sessionId; } @Override @@ -190,7 +216,7 @@ public final class FillEventHistory implements Parcelable { new Parcelable.Creator<FillEventHistory>() { @Override public FillEventHistory createFromParcel(Parcel parcel) { - FillEventHistory selection = new FillEventHistory(0, parcel.readBundle()); + FillEventHistory selection = new FillEventHistory(0, 0, parcel.readBundle()); int numEvents = parcel.readInt(); for (int i = 0; i < numEvents; i++) { diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java index fa3f55b99917..6ea7d5edb496 100644 --- a/core/java/android/service/autofill/SaveInfo.java +++ b/core/java/android/service/autofill/SaveInfo.java @@ -273,13 +273,24 @@ public final class SaveInfo implements Parcelable { * * <p>See {@link SaveInfo} for more info. * - * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty. + * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty, or if + * it contains any {@code null} entry. */ public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) { - Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0, - "must have at least one required id: " + Arrays.toString(requiredIds)); + // TODO: add CTS unit tests (not integration) to assert the null cases mType = type; - mRequiredIds = requiredIds; + mRequiredIds = assertValid(requiredIds); + } + + private AutofillId[] assertValid(AutofillId[] ids) { + Preconditions.checkArgument(ids != null && ids.length > 0, + "must have at least one id: " + Arrays.toString(ids)); + for (int i = 0; i < ids.length; i++) { + final AutofillId id = ids[i]; + Preconditions.checkArgument(id != null, + "cannot have null id: " + Arrays.toString(ids)); + } + return ids; } /** @@ -302,12 +313,14 @@ public final class SaveInfo implements Parcelable { * * @param ids The ids of the optional views. * @return This builder. + * + * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if + * it contains any {@code null} entry. */ - public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) { + public @NonNull Builder setOptionalIds(@NonNull AutofillId[] ids) { + // TODO: add CTS unit tests (not integration) to assert the null cases throwIfDestroyed(); - if (ids != null && ids.length != 0) { - mOptionalIds = ids; - } + mOptionalIds = assertValid(ids); return this; } @@ -421,7 +434,10 @@ public final class SaveInfo implements Parcelable { final Builder builder = new Builder(parcel.readInt(), parcel.readParcelableArray(null, AutofillId.class)); builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null)); - builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class)); + final AutofillId[] optionalIds = parcel.readParcelableArray(null, AutofillId.class); + if (optionalIds != null) { + builder.setOptionalIds(optionalIds); + } builder.setDescription(parcel.readCharSequence()); builder.setFlags(parcel.readInt()); return builder.build(); diff --git a/core/java/android/service/euicc/DeleteResult.java b/core/java/android/service/euicc/DeleteResult.java deleted file mode 100644 index 8be9ac9543df..000000000000 --- a/core/java/android/service/euicc/DeleteResult.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.service.euicc; - -import android.annotation.IntDef; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Result of a {@link EuiccService#onDeleteSubscription} operation. - * @hide - * - * TODO(b/35851809): Make this a SystemApi. - */ -public final class DeleteResult implements Parcelable { - - public static final Creator<DeleteResult> CREATOR = new Creator<DeleteResult>() { - @Override - public DeleteResult createFromParcel(Parcel in) { - return new DeleteResult(in); - } - - @Override - public DeleteResult[] newArray(int size) { - return new DeleteResult[size]; - } - }; - - /** @hide */ - @IntDef({ - RESULT_OK, - RESULT_GENERIC_ERROR, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} - - public static final int RESULT_OK = 0; - public static final int RESULT_GENERIC_ERROR = 1; - - /** Result of the operation - one of the RESULT_* constants. */ - public final @ResultCode int result; - - /** Implementation-defined detailed error code in case of a failure not covered here. */ - public final int detailedCode; - - private DeleteResult(int result, int detailedCode) { - this.result = result; - this.detailedCode = detailedCode; - } - - private DeleteResult(Parcel in) { - this.result = in.readInt(); - this.detailedCode = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(result); - dest.writeInt(detailedCode); - } - - /** Return a result indicating that the delete was successful. */ - public static DeleteResult success() { - return new DeleteResult(RESULT_OK, 0); - } - - /** - * Return a result indicating that an error occurred for which no other more specific error - * code has been defined. - * - * @param detailedCode an implemenation-defined detailed error code for debugging purposes. - */ - public static DeleteResult genericError(int detailedCode) { - return new DeleteResult(RESULT_GENERIC_ERROR, detailedCode); - } - - @Override - public int describeContents() { - return 0; - } -} diff --git a/core/java/android/service/euicc/DownloadResult.java b/core/java/android/service/euicc/DownloadResult.java deleted file mode 100644 index ad75bff96f10..000000000000 --- a/core/java/android/service/euicc/DownloadResult.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.service.euicc; - -import android.annotation.IntDef; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Result of a {@link EuiccService#onDownloadSubscription} operation. - * @hide - * - * TODO(b/35851809): Make this a SystemApi. - */ -public final class DownloadResult implements Parcelable { - - public static final Creator<DownloadResult> CREATOR = new Creator<DownloadResult>() { - @Override - public DownloadResult createFromParcel(Parcel in) { - return new DownloadResult(in); - } - - @Override - public DownloadResult[] newArray(int size) { - return new DownloadResult[size]; - } - }; - - /** @hide */ - @IntDef({ - RESULT_OK, - RESULT_GENERIC_ERROR, - RESULT_MUST_DEACTIVATE_SIM, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} - - public static final int RESULT_OK = 0; - public static final int RESULT_GENERIC_ERROR = 1; - public static final int RESULT_MUST_DEACTIVATE_SIM = 2; - - /** Result of the operation - one of the RESULT_* constants. */ - public final @ResultCode int result; - - /** Implementation-defined detailed error code in case of a failure not covered here. */ - public final int detailedCode; - - private DownloadResult(int result, int detailedCode) { - this.result = result; - this.detailedCode = detailedCode; - } - - private DownloadResult(Parcel in) { - this.result = in.readInt(); - this.detailedCode = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(result); - dest.writeInt(detailedCode); - } - - /** Return a result indicating that the download was successful. */ - public static DownloadResult success() { - return new DownloadResult(RESULT_OK, 0); - } - - /** - * Return a result indicating that an active SIM must be deactivated to perform the operation. - */ - public static DownloadResult mustDeactivateSim() { - return new DownloadResult(RESULT_MUST_DEACTIVATE_SIM, 0); - } - - /** - * Return a result indicating that an error occurred for which no other more specific error - * code has been defined. - * - * @param detailedCode an implemenation-defined detailed error code for debugging purposes. - */ - public static DownloadResult genericError(int detailedCode) { - return new DownloadResult(RESULT_GENERIC_ERROR, detailedCode); - } - - @Override - public int describeContents() { - return 0; - } -} diff --git a/core/java/android/service/euicc/EraseResult.java b/core/java/android/service/euicc/EraseResult.java deleted file mode 100644 index 1cce5a9213b5..000000000000 --- a/core/java/android/service/euicc/EraseResult.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.service.euicc; - -import android.annotation.IntDef; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Result of a {@link EuiccService#onEraseSubscriptions} operation. - * @hide - * - * TODO(b/35851809): Make this a SystemApi. - */ -public final class EraseResult implements Parcelable { - - public static final Creator<EraseResult> CREATOR = new Creator<EraseResult>() { - @Override - public EraseResult createFromParcel(Parcel in) { - return new EraseResult(in); - } - - @Override - public EraseResult[] newArray(int size) { - return new EraseResult[size]; - } - }; - - /** @hide */ - @IntDef({ - RESULT_OK, - RESULT_GENERIC_ERROR, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} - - public static final int RESULT_OK = 0; - public static final int RESULT_GENERIC_ERROR = 1; - - /** Result of the operation - one of the RESULT_* constants. */ - public final @ResultCode int result; - - /** Implementation-defined detailed error code in case of a failure not covered here. */ - public final int detailedCode; - - private EraseResult(int result, int detailedCode) { - this.result = result; - this.detailedCode = detailedCode; - } - - private EraseResult(Parcel in) { - this.result = in.readInt(); - this.detailedCode = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(result); - dest.writeInt(detailedCode); - } - - /** Return a result indicating that the erase was successful. */ - public static EraseResult success() { - return new EraseResult(RESULT_OK, 0); - } - - /** - * Return a result indicating that an error occurred for which no other more specific error - * code has been defined. - * - * @param detailedCode an implemenation-defined detailed error code for debugging purposes. - */ - public static EraseResult genericError(int detailedCode) { - return new EraseResult(RESULT_GENERIC_ERROR, detailedCode); - } - - @Override - public int describeContents() { - return 0; - } -} diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java index 3734904196a3..875f286e1a6b 100644 --- a/core/java/android/service/euicc/EuiccService.java +++ b/core/java/android/service/euicc/EuiccService.java @@ -25,6 +25,12 @@ import android.telephony.euicc.DownloadableSubscription; import android.telephony.euicc.EuiccInfo; import android.util.ArraySet; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + /** * Service interface linking the system with an eUICC local profile assistant (LPA) application. * @@ -90,6 +96,16 @@ public abstract class EuiccService extends Service { */ public static final String ACTION_RESOLVE_NO_PRIVILEGES = "android.service.euicc.action.RESOLVE_NO_PRIVILEGES"; + + /** Result code for a successful operation. */ + public static final int RESULT_OK = 0; + /** Result code indicating that an active SIM must be deactivated to perform the operation. */ + public static final int RESULT_MUST_DEACTIVATE_SIM = -1; + // New predefined codes should have negative values. + + /** Start of implementation-specific error results. */ + public static final int RESULT_FIRST_USER = 1; + /** * List of all valid resolution actions for validation purposes. * @hide @@ -106,10 +122,45 @@ public abstract class EuiccService extends Service { private final IEuiccService.Stub mStubWrapper; + private ThreadPoolExecutor mExecutor; + public EuiccService() { mStubWrapper = new IEuiccServiceWrapper(); } + @Override + @CallSuper + public void onCreate() { + super.onCreate(); + // We use a oneway AIDL interface to avoid blocking phone process binder threads on IPCs to + // an external process, but doing so means the requests are serialized by binder, which is + // not desired. Spin up a background thread pool to allow requests to be parallelized. + // TODO(b/38206971): Consider removing this if basic card-level functions like listing + // profiles are moved to the platform. + mExecutor = new ThreadPoolExecutor( + 4 /* corePoolSize */, + 4 /* maxPoolSize */, + 30, TimeUnit.SECONDS, /* keepAliveTime */ + new LinkedBlockingQueue<>(), /* workQueue */ + new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "EuiccService #" + mCount.getAndIncrement()); + } + } + ); + mExecutor.allowCoreThreadTimeOut(true); + } + + @Override + @CallSuper + public void onDestroy() { + mExecutor.shutdownNow(); + super.onDestroy(); + } + /** * If overriding this method, call through to the super method for any unknown actions. * {@inheritDoc} @@ -138,9 +189,8 @@ public abstract class EuiccService extends Service { * but is here to future-proof the APIs. * @param subscription A subscription whose metadata needs to be populated. * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the - * eUICC, perform this action automatically. Otherwise, - * {@link GetDownloadableSubscriptionMetadataResult#mustDeactivateSim()} should be returned - * to allow the user to consent to this operation first. + * eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM)} + * should be returned to allow the user to consent to this operation first. * @return The result of the operation. * @see android.telephony.euicc.EuiccManager#getDownloadableSubscriptionMetadata */ @@ -153,9 +203,8 @@ public abstract class EuiccService extends Service { * @param slotId ID of the SIM slot to use for the operation. This is currently not populated * but is here to future-proof the APIs. * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the - * eUICC, perform this action automatically. Otherwise, - * {@link GetDefaultDownloadableSubscriptionListResult#mustDeactivateSim()} should be - * returned to allow the user to consent to this operation first. + * eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM)} + * should be returned to allow the user to consent to this operation first. * @return The result of the list operation. * @see android.telephony.euicc.EuiccManager#getDefaultDownloadableSubscriptionList */ @@ -171,13 +220,13 @@ public abstract class EuiccService extends Service { * @param switchAfterDownload If true, the subscription should be enabled upon successful * download. * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the - * eUICC, perform this action automatically. Otherwise, - * {@link DownloadResult#mustDeactivateSim()} should be returned to allow the user to - * consent to this operation first. - * @return the result of the download operation. + * eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM} + * should be returned to allow the user to consent to this operation first. + * @return the result of the download operation. May be one of the predefined {@code RESULT_} + * constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}. * @see android.telephony.euicc.EuiccManager#downloadSubscription */ - public abstract DownloadResult onDownloadSubscription(int slotId, + public abstract int onDownloadSubscription(int slotId, DownloadableSubscription subscription, boolean switchAfterDownload, boolean forceDeactivateSim); @@ -211,10 +260,11 @@ public abstract class EuiccService extends Service { * @param slotId ID of the SIM slot to use for the operation. This is currently not populated * but is here to future-proof the APIs. * @param iccid the ICCID of the subscription to delete. - * @return the result of the delete operation. + * @return the result of the delete operation. May be one of the predefined {@code RESULT_} + * constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}. * @see android.telephony.euicc.EuiccManager#deleteSubscription */ - public abstract DeleteResult onDeleteSubscription(int slotId, String iccid); + public abstract int onDeleteSubscription(int slotId, String iccid); /** * Switch to the given subscription. @@ -225,13 +275,13 @@ public abstract class EuiccService extends Service { * profile should be deactivated and no profile should be activated to replace it - this is * equivalent to a physical SIM being ejected. * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the - * eUICC, perform this action automatically. Otherwise, - * {@link SwitchResult#mustDeactivateSim()} should be returned to allow the user to consent - * to this operation first. - * @return the result of the switch operation. + * eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM} + * should be returned to allow the user to consent to this operation first. + * @return the result of the switch operation. May be one of the predefined {@code RESULT_} + * constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}. * @see android.telephony.euicc.EuiccManager#switchToSubscription */ - public abstract SwitchResult onSwitchToSubscription(int slotId, @Nullable String iccid, + public abstract int onSwitchToSubscription(int slotId, @Nullable String iccid, boolean forceDeactivateSim); /** @@ -241,10 +291,11 @@ public abstract class EuiccService extends Service { * but is here to future-proof the APIs. * @param iccid the ICCID of the subscription to update. * @param nickname the new nickname to apply. - * @return the result of the update operation. + * @return the result of the update operation. May be one of the predefined {@code RESULT_} + * constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}. * @see android.telephony.euicc.EuiccManager#updateSubscriptionNickname */ - public abstract UpdateNicknameResult onUpdateSubscriptionNickname(int slotId, String iccid, + public abstract int onUpdateSubscriptionNickname(int slotId, String iccid, String nickname); /** @@ -255,10 +306,11 @@ public abstract class EuiccService extends Service { * * @param slotId ID of the SIM slot to use for the operation. This is currently not populated * but is here to future-proof the APIs. - * @return the result of the erase operation. + * @return the result of the erase operation. May be one of the predefined {@code RESULT_} + * constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}. * @see android.telephony.euicc.EuiccManager#eraseSubscriptions */ - public abstract EraseResult onEraseSubscriptions(int slotId); + public abstract int onEraseSubscriptions(int slotId); /** * Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}. @@ -268,23 +320,33 @@ public abstract class EuiccService extends Service { public void downloadSubscription(int slotId, DownloadableSubscription subscription, boolean switchAfterDownload, boolean forceDeactivateSim, IDownloadSubscriptionCallback callback) { - DownloadResult result = EuiccService.this.onDownloadSubscription( - slotId, subscription, switchAfterDownload, forceDeactivateSim); - try { - callback.onComplete(result); - } catch (RemoteException e) { - // Can't communicate with the phone process; ignore. - } + mExecutor.execute(new Runnable() { + @Override + public void run() { + int result = EuiccService.this.onDownloadSubscription( + slotId, subscription, switchAfterDownload, forceDeactivateSim); + try { + callback.onComplete(result); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); } @Override public void getEid(int slotId, IGetEidCallback callback) { - String eid = EuiccService.this.onGetEid(slotId); - try { - callback.onSuccess(eid); - } catch (RemoteException e) { - // Can't communicate with the phone process; ignore. - } + mExecutor.execute(new Runnable() { + @Override + public void run() { + String eid = EuiccService.this.onGetEid(slotId); + try { + callback.onSuccess(eid); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); } @Override @@ -292,93 +354,135 @@ public abstract class EuiccService extends Service { DownloadableSubscription subscription, boolean forceDeactivateSim, IGetDownloadableSubscriptionMetadataCallback callback) { - GetDownloadableSubscriptionMetadataResult result = - EuiccService.this.onGetDownloadableSubscriptionMetadata( - slotId, subscription, forceDeactivateSim); - try { - callback.onComplete(result); - } catch (RemoteException e) { - // Can't communicate with the phone process; ignore. - } + mExecutor.execute(new Runnable() { + @Override + public void run() { + GetDownloadableSubscriptionMetadataResult result = + EuiccService.this.onGetDownloadableSubscriptionMetadata( + slotId, subscription, forceDeactivateSim); + try { + callback.onComplete(result); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); } @Override public void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim, IGetDefaultDownloadableSubscriptionListCallback callback) { - GetDefaultDownloadableSubscriptionListResult result = - EuiccService.this.onGetDefaultDownloadableSubscriptionList( - slotId, forceDeactivateSim); - try { - callback.onComplete(result); - } catch (RemoteException e) { - // Can't communicate with the phone process; ignore. - } + mExecutor.execute(new Runnable() { + @Override + public void run() { + GetDefaultDownloadableSubscriptionListResult result = + EuiccService.this.onGetDefaultDownloadableSubscriptionList( + slotId, forceDeactivateSim); + try { + callback.onComplete(result); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); } @Override public void getEuiccProfileInfoList(int slotId, IGetEuiccProfileInfoListCallback callback) { - GetEuiccProfileInfoListResult result = - EuiccService.this.onGetEuiccProfileInfoList(slotId); - try { - callback.onComplete(result); - } catch (RemoteException e) { - // Can't communicate with the phone process; ignore. - } + mExecutor.execute(new Runnable() { + @Override + public void run() { + GetEuiccProfileInfoListResult result = + EuiccService.this.onGetEuiccProfileInfoList(slotId); + try { + callback.onComplete(result); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); } @Override public void getEuiccInfo(int slotId, IGetEuiccInfoCallback callback) { - EuiccInfo euiccInfo = EuiccService.this.onGetEuiccInfo(slotId); - try { - callback.onSuccess(euiccInfo); - } catch (RemoteException e) { - // Can't communicate with the phone process; ignore. - } + mExecutor.execute(new Runnable() { + @Override + public void run() { + EuiccInfo euiccInfo = EuiccService.this.onGetEuiccInfo(slotId); + try { + callback.onSuccess(euiccInfo); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); + } @Override public void deleteSubscription(int slotId, String iccid, IDeleteSubscriptionCallback callback) { - DeleteResult result = EuiccService.this.onDeleteSubscription(slotId, iccid); - try { - callback.onComplete(result); - } catch (RemoteException e) { - // Can't communicate with the phone process; ignore. - } + mExecutor.execute(new Runnable() { + @Override + public void run() { + int result = EuiccService.this.onDeleteSubscription(slotId, iccid); + try { + callback.onComplete(result); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); } @Override public void switchToSubscription(int slotId, String iccid, boolean forceDeactivateSim, ISwitchToSubscriptionCallback callback) { - SwitchResult result = - EuiccService.this.onSwitchToSubscription(slotId, iccid, forceDeactivateSim); - try { - callback.onComplete(result); - } catch (RemoteException e) { - // Can't communicate with the phone process; ignore. - } + mExecutor.execute(new Runnable() { + @Override + public void run() { + int result = + EuiccService.this.onSwitchToSubscription( + slotId, iccid, forceDeactivateSim); + try { + callback.onComplete(result); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); } @Override public void updateSubscriptionNickname(int slotId, String iccid, String nickname, IUpdateSubscriptionNicknameCallback callback) { - UpdateNicknameResult result = - EuiccService.this.onUpdateSubscriptionNickname(slotId, iccid, nickname); - try { - callback.onComplete(result); - } catch (RemoteException e) { - // Can't communicate with the phone process; ignore. - } + mExecutor.execute(new Runnable() { + @Override + public void run() { + int result = + EuiccService.this.onUpdateSubscriptionNickname(slotId, iccid, nickname); + try { + callback.onComplete(result); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); } @Override public void eraseSubscriptions(int slotId, IEraseSubscriptionsCallback callback) { - EraseResult result = EuiccService.this.onEraseSubscriptions(slotId); - try { - callback.onComplete(result); - } catch (RemoteException e) { - // Can't communicate with the phone process; ignore. - } + mExecutor.execute(new Runnable() { + @Override + public void run() { + int result = EuiccService.this.onEraseSubscriptions(slotId); + try { + callback.onComplete(result); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); } } } diff --git a/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java b/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java index 95569b27e763..5a24492007f3 100644 --- a/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java +++ b/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java @@ -15,15 +15,11 @@ */ package android.service.euicc; -import android.annotation.IntDef; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.telephony.euicc.DownloadableSubscription; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Result of a {@link EuiccService#onGetDefaultDownloadableSubscriptionList} operation. * @hide @@ -45,77 +41,54 @@ public final class GetDefaultDownloadableSubscriptionListResult implements Parce } }; - /** @hide */ - @IntDef({ - RESULT_OK, - RESULT_GENERIC_ERROR, - RESULT_MUST_DEACTIVATE_SIM, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} - - public static final int RESULT_OK = 0; - public static final int RESULT_MUST_DEACTIVATE_SIM = 1; - public static final int RESULT_GENERIC_ERROR = 2; - - /** Result of the operation - one of the RESULT_* constants. */ - public final @ResultCode int result; + /** + * Result of the operation. + * + * <p>May be one of the predefined {@code RESULT_} constants in EuiccService or any + * implementation-specific code starting with {@link EuiccService#RESULT_FIRST_USER}. + */ + public final int result; /** * The available {@link DownloadableSubscription}s (with filled-in metadata). * - * <p>Only non-null if {@link #result} is {@link #RESULT_OK}. + * <p>Only non-null if {@link #result} is {@link EuiccService#RESULT_OK}. */ @Nullable public final DownloadableSubscription[] subscriptions; - /** Implementation-defined detailed error code in case of a failure not covered here. */ - public final int detailedCode; - - private GetDefaultDownloadableSubscriptionListResult(int result, - @Nullable DownloadableSubscription[] subscriptions, int detailedCode) { + /** + * Construct a new {@link GetDefaultDownloadableSubscriptionListResult}. + * + * @param result Result of the operation. May be one of the predefined {@code RESULT_} constants + * in EuiccService or any implementation-specific code starting with + * {@link EuiccService#RESULT_FIRST_USER}. + * @param subscriptions The available subscriptions. Should only be provided if the result is + * {@link EuiccService#RESULT_OK}. + */ + public GetDefaultDownloadableSubscriptionListResult(int result, + @Nullable DownloadableSubscription[] subscriptions) { this.result = result; - this.subscriptions = subscriptions; - this.detailedCode = detailedCode; + if (this.result == EuiccService.RESULT_OK) { + this.subscriptions = subscriptions; + } else { + if (subscriptions != null) { + throw new IllegalArgumentException( + "Error result with non-null subscriptions: " + result); + } + this.subscriptions = null; + } } private GetDefaultDownloadableSubscriptionListResult(Parcel in) { this.result = in.readInt(); this.subscriptions = in.createTypedArray(DownloadableSubscription.CREATOR); - this.detailedCode = in.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(result); dest.writeTypedArray(subscriptions, flags); - dest.writeInt(detailedCode); - } - - /** Return a result indicating that the list operation was successful. */ - public static GetDefaultDownloadableSubscriptionListResult success( - DownloadableSubscription[] subscriptions) { - return new GetDefaultDownloadableSubscriptionListResult(RESULT_OK, subscriptions, - 0 /* detailedCode */); - } - - /** - * Return a result indicating that an active SIM must be deactivated to perform the operation. - */ - public static GetDefaultDownloadableSubscriptionListResult mustDeactivateSim() { - return new GetDefaultDownloadableSubscriptionListResult(RESULT_MUST_DEACTIVATE_SIM, - null /* subscription */, 0 /* detailedCode */); - } - - /** - * Return a result indicating that an error occurred for which no other more specific error - * code has been defined. - * - * @param detailedCode an implementation-defined detailed error code for debugging purposes. - */ - public static GetDefaultDownloadableSubscriptionListResult genericError(int detailedCode) { - return new GetDefaultDownloadableSubscriptionListResult(RESULT_GENERIC_ERROR, - null /* subscription */, detailedCode); } @Override diff --git a/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java b/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java index 99e7614cbf08..de8a30706945 100644 --- a/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java +++ b/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java @@ -15,15 +15,11 @@ */ package android.service.euicc; -import android.annotation.IntDef; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.telephony.euicc.DownloadableSubscription; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Result of a {@link EuiccService#onGetDownloadableSubscriptionMetadata} operation. * @hide @@ -45,77 +41,54 @@ public final class GetDownloadableSubscriptionMetadataResult implements Parcelab } }; - /** @hide */ - @IntDef({ - RESULT_OK, - RESULT_GENERIC_ERROR, - RESULT_MUST_DEACTIVATE_SIM, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} - - public static final int RESULT_OK = 0; - public static final int RESULT_MUST_DEACTIVATE_SIM = 1; - public static final int RESULT_GENERIC_ERROR = 2; - - /** Result of the operation - one of the RESULT_* constants. */ - public final @ResultCode int result; + /** + * Result of the operation. + * + * <p>May be one of the predefined {@code RESULT_} constants in EuiccService or any + * implementation-specific code starting with {@link EuiccService#RESULT_FIRST_USER}. + */ + public final int result; /** * The {@link DownloadableSubscription} with filled-in metadata. * - * <p>Only non-null if {@link #result} is {@link #RESULT_OK}. + * <p>Only non-null if {@link #result} is {@link EuiccService#RESULT_OK}. */ @Nullable public final DownloadableSubscription subscription; - /** Implementation-defined detailed error code in case of a failure not covered here. */ - public final int detailedCode; - - private GetDownloadableSubscriptionMetadataResult(int result, - @Nullable DownloadableSubscription subscription, int detailedCode) { + /** + * Construct a new {@link GetDownloadableSubscriptionMetadataResult}. + * + * @param result Result of the operation. May be one of the predefined {@code RESULT_} constants + * in EuiccService or any implementation-specific code starting with + * {@link EuiccService#RESULT_FIRST_USER}. + * @param subscription The subscription with filled-in metadata. Should only be provided if the + * result is {@link EuiccService#RESULT_OK}. + */ + public GetDownloadableSubscriptionMetadataResult(int result, + @Nullable DownloadableSubscription subscription) { this.result = result; - this.subscription = subscription; - this.detailedCode = detailedCode; + if (this.result == EuiccService.RESULT_OK) { + this.subscription = subscription; + } else { + if (subscription != null) { + throw new IllegalArgumentException( + "Error result with non-null subscription: " + result); + } + this.subscription = null; + } } private GetDownloadableSubscriptionMetadataResult(Parcel in) { this.result = in.readInt(); this.subscription = in.readTypedObject(DownloadableSubscription.CREATOR); - this.detailedCode = in.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(result); dest.writeTypedObject(this.subscription, flags); - dest.writeInt(detailedCode); - } - - /** Return a result indicating that the lookup was successful. */ - public static GetDownloadableSubscriptionMetadataResult success( - DownloadableSubscription subscription) { - return new GetDownloadableSubscriptionMetadataResult(RESULT_OK, subscription, - 0 /* detailedCode */); - } - - /** - * Return a result indicating that an active SIM must be deactivated to perform the operation. - */ - public static GetDownloadableSubscriptionMetadataResult mustDeactivateSim() { - return new GetDownloadableSubscriptionMetadataResult(RESULT_MUST_DEACTIVATE_SIM, - null /* subscription */, 0 /* detailedCode */); - } - - /** - * Return a result indicating that an error occurred for which no other more specific error - * code has been defined. - * - * @param detailedCode an implementation-defined detailed error code for debugging purposes. - */ - public static GetDownloadableSubscriptionMetadataResult genericError(int detailedCode) { - return new GetDownloadableSubscriptionMetadataResult(RESULT_GENERIC_ERROR, - null /* subscription */, detailedCode); } @Override diff --git a/core/java/android/service/euicc/GetEuiccProfileInfoListResult.java b/core/java/android/service/euicc/GetEuiccProfileInfoListResult.java index 5ac10fef3c23..7ad84888dc82 100644 --- a/core/java/android/service/euicc/GetEuiccProfileInfoListResult.java +++ b/core/java/android/service/euicc/GetEuiccProfileInfoListResult.java @@ -15,14 +15,10 @@ */ package android.service.euicc; -import android.annotation.IntDef; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Result of a {@link EuiccService#onGetEuiccProfileInfoList} operation. * @hide @@ -33,33 +29,24 @@ public final class GetEuiccProfileInfoListResult implements Parcelable { public static final Creator<GetEuiccProfileInfoListResult> CREATOR = new Creator<GetEuiccProfileInfoListResult>() { - @Override - public GetEuiccProfileInfoListResult createFromParcel(Parcel in) { - return new GetEuiccProfileInfoListResult(in); - } - - @Override - public GetEuiccProfileInfoListResult[] newArray(int size) { - return new GetEuiccProfileInfoListResult[size]; - } - }; + @Override + public GetEuiccProfileInfoListResult createFromParcel(Parcel in) { + return new GetEuiccProfileInfoListResult(in); + } - /** @hide */ - @IntDef({ - RESULT_OK, - RESULT_GENERIC_ERROR, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} + @Override + public GetEuiccProfileInfoListResult[] newArray(int size) { + return new GetEuiccProfileInfoListResult[size]; + } + }; - public static final int RESULT_OK = 0; - public static final int RESULT_GENERIC_ERROR = 1; - - /** Result of the operation - one of the RESULT_* constants. */ - public final @ResultCode int result; - - /** Implementation-defined detailed error code in case of a failure not covered here. */ - public final int detailedCode; + /** + * Result of the operation. + * + * <p>May be one of the predefined {@code RESULT_} constants in EuiccService or any + * implementation-specific code starting with {@link EuiccService#RESULT_FIRST_USER}. + */ + public final int result; /** The profile list (only upon success). */ @Nullable @@ -68,17 +55,37 @@ public final class GetEuiccProfileInfoListResult implements Parcelable { /** Whether the eUICC is removable. */ public final boolean isRemovable; - private GetEuiccProfileInfoListResult(int result, int detailedCode, EuiccProfileInfo[] profiles, - boolean isRemovable) { + /** + * Construct a new {@link GetEuiccProfileInfoListResult}. + * + * @param result Result of the operation. May be one of the predefined {@code RESULT_} constants + * in EuiccService or any implementation-specific code starting with + * {@link EuiccService#RESULT_FIRST_USER}. + * @param profiles the list of profiles. Should only be provided if the result is + * {@link EuiccService#RESULT_OK}. + * @param isRemovable whether the eUICC in this slot is removable. If true, the profiles + * returned here will only be considered accessible as long as this eUICC is present. + * Otherwise, they will remain accessible until the next time a response with isRemovable + * set to false is returned. + */ + public GetEuiccProfileInfoListResult( + int result, @Nullable EuiccProfileInfo[] profiles, boolean isRemovable) { this.result = result; - this.detailedCode = detailedCode; - this.profiles = profiles; this.isRemovable = isRemovable; + if (this.result == EuiccService.RESULT_OK) { + this.profiles = profiles; + } else { + if (profiles != null) { + throw new IllegalArgumentException( + "Error result with non-null profiles: " + result); + } + this.profiles = null; + } + } private GetEuiccProfileInfoListResult(Parcel in) { this.result = in.readInt(); - this.detailedCode = in.readInt(); this.profiles = in.createTypedArray(EuiccProfileInfo.CREATOR); this.isRemovable = in.readBoolean(); } @@ -86,41 +93,10 @@ public final class GetEuiccProfileInfoListResult implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(result); - dest.writeInt(detailedCode); dest.writeTypedArray(profiles, flags); dest.writeBoolean(isRemovable); } - /** - * Return a result indicating that the listing was successful. - * - * @param profiles the list of profiles - * @param isRemovable whether the eUICC in this slot is removable. If true, the profiles - * returned here will only be considered accessible as long as this eUICC is present. - * Otherwise, they will remain accessible until the next time a response with isRemovable - * set to false is returned. - */ - public static GetEuiccProfileInfoListResult success( - EuiccProfileInfo[] profiles, boolean isRemovable) { - return new GetEuiccProfileInfoListResult( - RESULT_OK, 0 /* detailedCode */, profiles, isRemovable); - } - - /** - * Return a result indicating that an error occurred for which no other more specific error - * code has been defined. - * - * @param detailedCode an implementation-defined detailed error code for debugging purposes. - * @param isRemovable whether the eUICC in this slot is removable. If true, only removable - * profiles will be made inaccessible. Otherwise, all embedded profiles will be - * considered inaccessible. - */ - public static GetEuiccProfileInfoListResult genericError( - int detailedCode, boolean isRemovable) { - return new GetEuiccProfileInfoListResult( - RESULT_GENERIC_ERROR, detailedCode, null /* profiles */, isRemovable); - } - @Override public int describeContents() { return 0; diff --git a/core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl b/core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl index 224cbd3bd641..4667066df866 100644 --- a/core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl +++ b/core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl @@ -16,9 +16,7 @@ package android.service.euicc; -import android.service.euicc.DeleteResult; - /** @hide */ oneway interface IDeleteSubscriptionCallback { - void onComplete(in DeleteResult result); + void onComplete(int result); }
\ No newline at end of file diff --git a/core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl b/core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl index 0677cbe3ca1c..6893c8559d9d 100644 --- a/core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl +++ b/core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl @@ -16,9 +16,7 @@ package android.service.euicc; -import android.service.euicc.DownloadResult; - /** @hide */ oneway interface IDownloadSubscriptionCallback { - void onComplete(in DownloadResult result); + void onComplete(int result); }
\ No newline at end of file diff --git a/core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl b/core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl index aa70e767242e..c975f1894aa2 100644 --- a/core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl +++ b/core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl @@ -16,9 +16,7 @@ package android.service.euicc; -import android.service.euicc.EraseResult; - /** @hide */ oneway interface IEraseSubscriptionsCallback { - void onComplete(in EraseResult result); + void onComplete(int result); }
\ No newline at end of file diff --git a/core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl b/core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl index 970adcd8bef5..0f91a6b29e26 100644 --- a/core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl +++ b/core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl @@ -16,9 +16,7 @@ package android.service.euicc; -import android.service.euicc.SwitchResult; - /** @hide */ oneway interface ISwitchToSubscriptionCallback { - void onComplete(in SwitchResult result); + void onComplete(int result); }
\ No newline at end of file diff --git a/core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl b/core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl index 439759d95747..66669330c8c0 100644 --- a/core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl +++ b/core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl @@ -16,9 +16,7 @@ package android.service.euicc; -import android.service.euicc.UpdateNicknameResult; - /** @hide */ oneway interface IUpdateSubscriptionNicknameCallback { - void onComplete(in UpdateNicknameResult result); + void onComplete(int result); }
\ No newline at end of file diff --git a/core/java/android/service/euicc/SwitchResult.java b/core/java/android/service/euicc/SwitchResult.java deleted file mode 100644 index f5dc4d3399c1..000000000000 --- a/core/java/android/service/euicc/SwitchResult.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.service.euicc; - -import android.annotation.IntDef; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Result of a {@link EuiccService#onSwitchToSubscription} operation. - * @hide - * - * TODO(b/35851809): Make this a SystemApi. - */ -public final class SwitchResult implements Parcelable { - - public static final Creator<SwitchResult> CREATOR = new Creator<SwitchResult>() { - @Override - public SwitchResult createFromParcel(Parcel in) { - return new SwitchResult(in); - } - - @Override - public SwitchResult[] newArray(int size) { - return new SwitchResult[size]; - } - }; - - /** @hide */ - @IntDef({ - RESULT_OK, - RESULT_GENERIC_ERROR, - RESULT_MUST_DEACTIVATE_SIM, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} - - public static final int RESULT_OK = 0; - public static final int RESULT_GENERIC_ERROR = 1; - public static final int RESULT_MUST_DEACTIVATE_SIM = 2; - - /** Result of the operation - one of the RESULT_* constants. */ - public final @ResultCode int result; - - /** Implementation-defined detailed error code in case of a failure not covered here. */ - public final int detailedCode; - - private SwitchResult(int result, int detailedCode) { - this.result = result; - this.detailedCode = detailedCode; - } - - private SwitchResult(Parcel in) { - this.result = in.readInt(); - this.detailedCode = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(result); - dest.writeInt(detailedCode); - } - - /** Return a result indicating that the switch was successful. */ - public static SwitchResult success() { - return new SwitchResult(RESULT_OK, 0); - } - - /** - * Return a result indicating that an active SIM must be deactivated to perform the operation. - */ - public static SwitchResult mustDeactivateSim() { - return new SwitchResult(RESULT_MUST_DEACTIVATE_SIM, 0); - } - - /** - * Return a result indicating that an error occurred for which no other more specific error - * code has been defined. - * - * @param detailedCode an implemenation-defined detailed error code for debugging purposes. - */ - public static SwitchResult genericError(int detailedCode) { - return new SwitchResult(RESULT_GENERIC_ERROR, detailedCode); - } - - @Override - public int describeContents() { - return 0; - } -} diff --git a/core/java/android/service/euicc/UpdateNicknameResult.aidl b/core/java/android/service/euicc/UpdateNicknameResult.aidl deleted file mode 100644 index 08b849127857..000000000000 --- a/core/java/android/service/euicc/UpdateNicknameResult.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.service.euicc; - -parcelable UpdateNicknameResult; diff --git a/core/java/android/service/euicc/UpdateNicknameResult.java b/core/java/android/service/euicc/UpdateNicknameResult.java deleted file mode 100644 index d871fc882933..000000000000 --- a/core/java/android/service/euicc/UpdateNicknameResult.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.service.euicc; - -import android.annotation.IntDef; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Result of a {@link EuiccService#onUpdateSubscriptionNickname} operation. - * @hide - * - * TODO(b/35851809): Make this a SystemApi. - */ -public final class UpdateNicknameResult implements Parcelable { - - public static final Creator<UpdateNicknameResult> CREATOR = - new Creator<UpdateNicknameResult>() { - @Override - public UpdateNicknameResult createFromParcel(Parcel in) { - return new UpdateNicknameResult(in); - } - - @Override - public UpdateNicknameResult[] newArray(int size) { - return new UpdateNicknameResult[size]; - } - }; - - /** @hide */ - @IntDef({ - RESULT_OK, - RESULT_GENERIC_ERROR, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} - - public static final int RESULT_OK = 0; - public static final int RESULT_GENERIC_ERROR = 1; - - /** Result of the operation - one of the RESULT_* constants. */ - public final @ResultCode int result; - - /** Implementation-defined detailed error code in case of a failure not covered here. */ - public final int detailedCode; - - private UpdateNicknameResult(int result, int detailedCode) { - this.result = result; - this.detailedCode = detailedCode; - } - - private UpdateNicknameResult(Parcel in) { - this.result = in.readInt(); - this.detailedCode = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(result); - dest.writeInt(detailedCode); - } - - /** Return a result indicating that the update was successful. */ - public static UpdateNicknameResult success() { - return new UpdateNicknameResult(RESULT_OK, 0); - } - - /** - * Return a result indicating that an error occurred for which no other more specific error - * code has been defined. - * - * @param detailedCode an implemenation-defined detailed error code for debugging purposes. - */ - public static UpdateNicknameResult genericError(int detailedCode) { - return new UpdateNicknameResult(RESULT_GENERIC_ERROR, detailedCode); - } - - @Override - public int describeContents() { - return 0; - } -} diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 539278fd47f1..e4d3142ccefe 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -17,25 +17,19 @@ package android.service.wallpaper; import android.annotation.Nullable; -import android.app.WallpaperColors; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.util.MergedConfiguration; -import android.view.WindowInsets; - -import com.android.internal.R; -import com.android.internal.os.HandlerCaller; -import com.android.internal.view.BaseIWindow; -import com.android.internal.view.BaseSurfaceHolder; - import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.Service; +import android.app.WallpaperColors; import android.app.WallpaperManager; import android.content.Context; import android.content.Intent; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Bundle; @@ -44,6 +38,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; +import android.util.MergedConfiguration; import android.view.Display; import android.view.Gravity; import android.view.IWindowSession; @@ -55,9 +50,14 @@ import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import com.android.internal.os.HandlerCaller; +import com.android.internal.view.BaseIWindow; +import com.android.internal.view.BaseSurfaceHolder; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -548,6 +548,7 @@ public abstract class WallpaperService extends Service { /** * Notifies the engine that wallpaper colors changed significantly. * This will trigger a {@link #onComputeWallpaperColors()} call. + * @hide */ public void invalidateColors() { try { @@ -562,8 +563,14 @@ public abstract class WallpaperService extends Service { * Notifies the system about what colors the wallpaper is using. * You might return null if no color information is available at the moment. In that case * you might want to call {@link #invalidateColors()} in a near future. + * <p> + * The simplest way of creating A {@link android.app.WallpaperColors} object is by using + * {@link android.app.WallpaperColors#fromBitmap(Bitmap)} or + * {@link android.app.WallpaperColors#fromDrawable(Drawable)}, but you can also specify + * your main colors and dark text support explicitly using one of the constructors. * - * @return List of wallpaper colors and their weights. + * @return Wallpaper colors. + * @hide */ public @Nullable WallpaperColors onComputeWallpaperColors() { return null; diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 3e9fab1aee27..cdb9b8229314 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE; import android.annotation.IntDef; import android.annotation.RequiresPermission; import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Point; @@ -854,6 +855,9 @@ public final class Display { /** * Returns whether this display can be used to display wide color gamut content. + * This does not necessarily mean the device itself can render wide color gamut + * content. To ensure wide color gamut content can be produced, refer to + * {@link Configuration#isScreenWideColorGamut()}. */ public boolean isWideColorGamut() { synchronized (this) { diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index caadc364a3fb..cb98c8816281 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -72,6 +72,15 @@ public abstract class DisplayEventReceiver { * Creates a display event receiver. * * @param looper The looper to use when invoking callbacks. + */ + public DisplayEventReceiver(Looper looper) { + this(looper, VSYNC_SOURCE_APP); + } + + /** + * Creates a display event receiver. + * + * @param looper The looper to use when invoking callbacks. * @param vsyncSource The source of the vsync tick. Must be on of the VSYNC_SOURCE_* values. */ public DisplayEventReceiver(Looper looper, int vsyncSource) { diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index 31cece49c79d..0a73949ef17a 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -62,6 +62,12 @@ public class HapticFeedbackConstants { public static final int VIRTUAL_KEY_RELEASE = 7; /** + * The user has performed a selection/insertion handle move on text field. + * @hide + */ + public static final int TEXT_HANDLE_MOVE = 8; + + /** * This is a private constant. Feel free to renumber as desired. * @hide */ diff --git a/core/java/android/view/IWallpaperVisibilityListener.aidl b/core/java/android/view/IWallpaperVisibilityListener.aidl new file mode 100644 index 000000000000..349f98493a43 --- /dev/null +++ b/core/java/android/view/IWallpaperVisibilityListener.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +/** + * Listener to be invoked when wallpaper visibility changes. + * {@hide} + */ +oneway interface IWallpaperVisibilityListener { + /** + * Method that will be invoked when wallpaper becomes visible or hidden. + * @param visible True if wallpaper is being displayed; false otherwise. + * @param displayId The id of the display where wallpaper visibility changed. + */ + void onWallpaperVisibilityChanged(boolean visible, int displayId); +} diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 2b73c14540d5..e576a0fbdb2b 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -39,6 +39,7 @@ import android.view.IDockedStackListener; import android.view.IOnKeyguardExitResult; import android.view.IPinnedStackListener; import android.view.IRotationWatcher; +import android.view.IWallpaperVisibilityListener; import android.view.IWindowSession; import android.view.IWindowSessionCallback; import android.view.KeyEvent; @@ -256,6 +257,19 @@ interface IWindowManager Bitmap screenshotWallpaper(); /** + * Registers a wallpaper visibility listener. + * @return Current visibility. + */ + boolean registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener, + int displayId); + + /** + * Remove a visibility watcher that was added using registerWallpaperVisibilityListener. + */ + void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener, + int displayId); + + /** * Used only for assist -- request a screenshot of the current application. */ boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver); diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 5f55bdc0e0d3..04fa637b72f2 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -224,6 +224,14 @@ public final class MotionEvent extends InputEvent implements Parcelable { * Constant for {@link #getActionMasked}: A movement has happened outside of the * normal bounds of the UI element. This does not provide a full gesture, * but only the initial location of the movement/touch. + * <p> + * Note: Because the location of any event will be outside the + * bounds of the view hierarchy, it will not get dispatched to + * any children of a ViewGroup by default. Therefore, + * movements with ACTION_OUTSIDE should be handled in either the + * root {@link View} or in the appropriate {@link Window.Callback} + * (e.g. {@link android.app.Activity} or {@link android.app.Dialog}). + * </p> */ public static final int ACTION_OUTSIDE = 4; diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 8bb3fa988a45..4f9dbd5ad7a0 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -52,7 +52,9 @@ public class Surface implements Parcelable { private static native long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture) throws OutOfResourcesException; + private static native long nativeCreateFromSurfaceControl(long surfaceControlNativeObject); + private static native long nativeGetFromSurfaceControl(long surfaceControlNativeObject); private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty) throws OutOfResourcesException; @@ -410,6 +412,9 @@ public class Surface implements Parcelable { * back from a client, converting it from the representation being managed * by the window manager to the representation the client uses to draw * in to it. + * + * @param other {@link SurfaceControl} to copy from. + * * @hide */ public void copyFrom(SurfaceControl other) { @@ -420,7 +425,39 @@ public class Surface implements Parcelable { long surfaceControlPtr = other.mNativeObject; if (surfaceControlPtr == 0) { throw new NullPointerException( - "SurfaceControl native object is null. Are you using a released SurfaceControl?"); + "null SurfaceControl native object. Are you using a released SurfaceControl?"); + } + long newNativeObject = nativeGetFromSurfaceControl(surfaceControlPtr); + + synchronized (mLock) { + if (mNativeObject != 0) { + nativeRelease(mNativeObject); + } + setNativeObjectLocked(newNativeObject); + } + } + + /** + * Gets a reference a surface created from this one. This surface now holds a reference + * to the same data as the original surface, and is -not- the owner. + * This is for use by the window manager when returning a window surface + * back from a client, converting it from the representation being managed + * by the window manager to the representation the client uses to draw + * in to it. + * + * @param other {@link SurfaceControl} to create surface from. + * + * @hide + */ + public void createFrom(SurfaceControl other) { + if (other == null) { + throw new IllegalArgumentException("other must not be null"); + } + + long surfaceControlPtr = other.mNativeObject; + if (surfaceControlPtr == 0) { + throw new NullPointerException( + "null SurfaceControl native object. Are you using a released SurfaceControl?"); } long newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr); diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 679a9cd92bc2..b035b7fd53ed 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -641,6 +641,16 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb mSurface.copyFrom(mSurfaceControl); } + if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion + < Build.VERSION_CODES.O) { + // Some legacy applications use the underlying native {@link Surface} object + // as a key to whether anything has changed. In these cases, updates to the + // existing {@link Surface} will be ignored when the size changes. + // Therefore, we must explicitly recreate the {@link Surface} in these + // cases. + mSurface.createFrom(mSurfaceControl); + } + if (visible && mSurface.isValid()) { if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) { mSurfaceCreated = true; @@ -828,6 +838,8 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d", System.identityHashCode(this), frameNumber)); } + mRTLastReportedPosition.setEmpty(); + if (mSurfaceControl == null) { return; } @@ -848,7 +860,6 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb Log.e(TAG, "Exception configuring surface", ex); } } - mRTLastReportedPosition.setEmpty(); } } diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index de4d03e9ec35..489de565acb6 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -996,6 +996,9 @@ public final class ThreadedRenderer { observer.mNative = null; } + /** Not actually public - internal use only. This doc to make lint happy */ + public static native void disableVsync(); + static native void setupShadersDiskCache(String cacheFile); private static native void nRotateProcessStatsBuffer(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 97f53313baaf..c329db4ff3e1 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -802,7 +802,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * {@hide} */ - public static final int LAST_APP_ACCESSIBILITY_ID = Integer.MAX_VALUE / 2; + public static final int LAST_APP_AUTOFILL_ID = Integer.MAX_VALUE / 2; /** * Attribute to find the autofilled highlight @@ -2045,6 +2045,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private SparseArray<Object> mKeyedTags; /** + * The next available accessibility id. + */ + private static int sNextAccessibilityViewId; + + /** * The animation currently associated with this view. * @hide */ @@ -2086,16 +2091,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.ExportedProperty(resolveId = true) int mID = NO_ID; - /** The ID of this view for accessibility and autofill purposes. + /** The ID of this view for autofill purposes. * <ul> * <li>== {@link #NO_ID}: ID has not been assigned yet - * <li>≤ {@link #LAST_APP_ACCESSIBILITY_ID}: View is not part of a activity. The ID is + * <li>≤ {@link #LAST_APP_AUTOFILL_ID}: View is not part of a activity. The ID is * unique in the process. This might change * over activity lifecycle events. - * <li>> {@link #LAST_APP_ACCESSIBILITY_ID}: View is part of a activity. The ID is + * <li>> {@link #LAST_APP_AUTOFILL_ID}: View is part of a activity. The ID is * unique in the activity. This stays the same * over activity lifecycle events. */ + private int mAutofillViewId = NO_ID; + + // ID for accessibility purposes. This ID must be unique for every window private int mAccessibilityViewId = NO_ID; private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED; @@ -7721,7 +7729,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mAutofillId == null) { // The autofill id needs to be unique, but its value doesn't matter, // so it's better to reuse the accessibility id to save space. - mAutofillId = new AutofillId(getAccessibilityViewId()); + mAutofillId = new AutofillId(getAutofillViewId()); } return mAutofillId; } @@ -7954,7 +7962,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private boolean isAutofillable() { return getAutofillType() != AUTOFILL_TYPE_NONE && isImportantForAutofill() - && getAccessibilityViewId() > LAST_APP_ACCESSIBILITY_ID; + && getAutofillViewId() > LAST_APP_AUTOFILL_ID; } private void populateVirtualStructure(ViewStructure structure, @@ -8472,12 +8480,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public int getAccessibilityViewId() { if (mAccessibilityViewId == NO_ID) { - mAccessibilityViewId = mContext.getNextAccessibilityId(); + mAccessibilityViewId = sNextAccessibilityViewId++; } return mAccessibilityViewId; } /** + * Gets the unique identifier of this view on the screen for autofill purposes. + * + * @return The view autofill id. + * + * @hide + */ + public int getAutofillViewId() { + if (mAutofillViewId == NO_ID) { + mAutofillViewId = mContext.getNextAutofillId(); + } + return mAutofillViewId; + } + + /** * Gets the unique identifier of the window in which this View reseides. * * @return The window accessibility id. @@ -12125,7 +12147,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (isAutofillable()) { AutofillManager afm = getAutofillManager(); - if (afm != null && getAccessibilityViewId() > LAST_APP_ACCESSIBILITY_ID) { + if (afm != null && getAutofillViewId() > LAST_APP_AUTOFILL_ID) { if (mVisibilityChangeForAutofillHandler != null) { mVisibilityChangeForAutofillHandler.removeMessages(0); } @@ -17555,16 +17577,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @return Returns a Parcelable object containing the view's current dynamic * state, or null if there is nothing interesting to save. - * @see #onRestoreInstanceState(android.os.Parcelable) - * @see #saveHierarchyState(android.util.SparseArray) - * @see #dispatchSaveInstanceState(android.util.SparseArray) + * @see #onRestoreInstanceState(Parcelable) + * @see #saveHierarchyState(SparseArray) + * @see #dispatchSaveInstanceState(SparseArray) * @see #setSaveEnabled(boolean) */ @CallSuper @Nullable protected Parcelable onSaveInstanceState() { mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; if (mStartActivityRequestWho != null || isAutofilled() - || mAccessibilityViewId > LAST_APP_ACCESSIBILITY_ID) { + || mAutofillViewId > LAST_APP_AUTOFILL_ID) { BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE); if (mStartActivityRequestWho != null) { @@ -17575,13 +17597,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, state.mSavedData |= BaseSavedState.IS_AUTOFILLED; } - if (mAccessibilityViewId > LAST_APP_ACCESSIBILITY_ID) { - state.mSavedData |= BaseSavedState.ACCESSIBILITY_ID; + if (mAutofillViewId > LAST_APP_AUTOFILL_ID) { + state.mSavedData |= BaseSavedState.AUTOFILL_ID; } state.mStartActivityRequestWhoSaved = mStartActivityRequestWho; state.mIsAutofilled = isAutofilled(); - state.mAccessibilityViewId = mAccessibilityViewId; + state.mAutofillViewId = mAutofillViewId; return state; } return BaseSavedState.EMPTY_STATE; @@ -17659,8 +17681,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((baseState.mSavedData & BaseSavedState.IS_AUTOFILLED) != 0) { setAutofilled(baseState.mIsAutofilled); } - if ((baseState.mSavedData & BaseSavedState.ACCESSIBILITY_ID) != 0) { - mAccessibilityViewId = baseState.mAccessibilityViewId; + if ((baseState.mSavedData & BaseSavedState.AUTOFILL_ID) != 0) { + mAutofillViewId = baseState.mAutofillViewId; } } } @@ -21484,7 +21506,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param accessibilityId The searched accessibility id. * @return The found view. */ - final <T extends View> T findViewByAccessibilityId(int accessibilityId) { + final <T extends View> T findViewByAccessibilityId(int accessibilityId) { if (accessibilityId < 0) { return null; } @@ -21496,11 +21518,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Performs the traversal to find a view by its unuque and stable accessibility id. + * Performs the traversal to find a view by its unique and stable accessibility id. * * <strong>Note:</strong>This method does not stop at the root namespace * boundary since the user can touch the screen at an arbitrary location - * potentially crossing the root namespace bounday which will send an + * potentially crossing the root namespace boundary which will send an * accessibility event to accessibility services and they should be able * to obtain the event source. Also accessibility ids are guaranteed to be * unique in the window. @@ -21517,6 +21539,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Performs the traversal to find a view by its autofill id. + * + * <strong>Note:</strong>This method does not stop at the root namespace + * boundary. + * + * @param autofillId The autofill id. + * @return The found view. + * @hide + */ + public <T extends View> T findViewByAutofillIdTraversal(int autofillId) { + if (getAutofillViewId() == autofillId) { + return (T) this; + } + return null; + } + + /** * Look for a child view with the given tag. If this view has the given * tag, return this view. * @@ -24982,13 +25021,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static class BaseSavedState extends AbsSavedState { static final int START_ACTIVITY_REQUESTED_WHO_SAVED = 0b1; static final int IS_AUTOFILLED = 0b10; - static final int ACCESSIBILITY_ID = 0b100; + static final int AUTOFILL_ID = 0b100; // Flags that describe what data in this state is valid int mSavedData; String mStartActivityRequestWhoSaved; boolean mIsAutofilled; - int mAccessibilityViewId; + int mAutofillViewId; /** * Constructor used when reading from a parcel. Reads the state of the superclass. @@ -25011,7 +25050,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mSavedData = source.readInt(); mStartActivityRequestWhoSaved = source.readString(); mIsAutofilled = source.readBoolean(); - mAccessibilityViewId = source.readInt(); + mAutofillViewId = source.readInt(); } /** @@ -25030,7 +25069,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, out.writeInt(mSavedData); out.writeString(mStartActivityRequestWhoSaved); out.writeBoolean(mIsAutofilled); - out.writeInt(mAccessibilityViewId); + out.writeInt(mAutofillViewId); } public static final Parcelable.Creator<BaseSavedState> CREATOR @@ -26208,9 +26247,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mTooltipInfo.mHideTooltipRunnable = this::hideTooltip; } mTooltipInfo.mTooltipText = tooltipText; - if (mTooltipInfo.mTooltipPopup != null && mTooltipInfo.mTooltipPopup.isShowing()) { - mTooltipInfo.mTooltipPopup.updateContent(mTooltipInfo.mTooltipText); - } } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 2e817eb91cc4..cb92a4ca3ad4 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1361,6 +1361,27 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return null; } + /** @hide */ + @Override + public View findViewByAutofillIdTraversal(int autofillId) { + View foundView = super.findViewByAutofillIdTraversal(autofillId); + if (foundView != null) { + return foundView; + } + + final int childrenCount = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < childrenCount; i++) { + View child = children[i]; + foundView = child.findViewByAutofillIdTraversal(autofillId); + if (foundView != null) { + return foundView; + } + } + + return null; + } + @Override public void dispatchWindowFocusChanged(boolean hasFocus) { super.dispatchWindowFocusChanged(hasFocus); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4a231efcdd07..86b19f4a0929 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -963,7 +963,8 @@ public final class ViewRootImpl implements ViewParent, || insets.top != 0 || insets.bottom != 0; final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets; final boolean wideGamut = - attrs.getColorMode() == ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT; + mContext.getResources().getConfiguration().isScreenWideColorGamut() + && attrs.getColorMode() == ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT; mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent, attrs.getTitle().toString()); @@ -2476,6 +2477,9 @@ public final class ViewRootImpl implements ViewParent, mInLayout = true; final View host = mView; + if (host == null) { + return; + } if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { Log.v(mTag, "Laying out " + host + " to (" + host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")"); @@ -2783,6 +2787,8 @@ public final class ViewRootImpl implements ViewParent, private void performDraw() { if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) { return; + } else if (mView == null) { + return; } final boolean fullRedrawNeeded = mFullRedrawNeeded; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index dfbf9620c82c..4c0a1902d6a4 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1391,6 +1391,13 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_TASK_SNAPSHOT = 0x00080000; /** + * Flag to indicate that this window should be ignored when determining what parts of the + * screen can be magnified. + * @hide + */ + public static final int PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT = 0x00100000; + + /** * Control flags that are private to the platform. * @hide */ diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java index 55aed529037d..4c9cf40beb8d 100644 --- a/core/java/android/view/WindowManagerInternal.java +++ b/core/java/android/view/WindowManagerInternal.java @@ -229,6 +229,11 @@ public abstract class WindowManagerInternal { public abstract boolean isKeyguardGoingAway(); /** + * @return Whether the keyguard is showing and not occluded. + */ + public abstract boolean isKeyguardShowingAndNotOccluded(); + + /** * Gets the frame of a window given its token. * * @param token The token. diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 3a3e17123d3c..69892d991ccd 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -971,14 +971,14 @@ public final class AccessibilityManager { } /** - * Notifies that the availability of the accessibility button in the system's navigation area + * Notifies that the visibility of the accessibility button in the system's navigation area * has changed. * - * @param available {@code true} if the accessibility button is available within the system + * @param shown {@code true} if the accessibility button is visible within the system * navigation area, {@code false} otherwise * @hide */ - public void notifyAccessibilityButtonAvailabilityChanged(boolean available) { + public void notifyAccessibilityButtonVisibilityChanged(boolean shown) { final IAccessibilityManager service; synchronized (mLock) { service = getServiceLocked(); @@ -987,9 +987,9 @@ public final class AccessibilityManager { } } try { - service.notifyAccessibilityButtonAvailabilityChanged(available); + service.notifyAccessibilityButtonVisibilityChanged(shown); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while dispatching accessibility button availability change", re); + Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re); } } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 06cb5dca8afa..3f499abd2e4d 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -64,7 +64,7 @@ interface IAccessibilityManager { void notifyAccessibilityButtonClicked(); - void notifyAccessibilityButtonAvailabilityChanged(boolean available); + void notifyAccessibilityButtonVisibilityChanged(boolean available); // Requires WRITE_SECURE_SETTINGS void performAccessibilityShortcut(); diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 310ec1c938d0..5b04f41c7ee2 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -246,20 +246,20 @@ public final class AutofillManager { /** * Finds views by traversing the hierarchies of the client. * - * @param viewIds The accessibility ids of the views to find + * @param viewIds The autofill ids of the views to find * * @return And array containing the views (empty if no views found). */ - @NonNull View[] findViewsByAccessibilityIdTraversal(@NonNull int[] viewIds); + @NonNull View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds); /** * Finds a view by traversing the hierarchies of the client. * - * @param viewId The accessibility id of the views to find + * @param viewId The autofill id of the views to find * * @return The view, or {@code null} if not found */ - @Nullable View findViewByAccessibilityIdTraversal(int viewId); + @Nullable View findViewByAutofillIdTraversal(int viewId); /** * Runs the specified action on the UI thread. @@ -795,11 +795,11 @@ public final class AutofillManager { } private static AutofillId getAutofillId(View view) { - return new AutofillId(view.getAccessibilityViewId()); + return new AutofillId(view.getAutofillViewId()); } private static AutofillId getAutofillId(View parent, int virtualId) { - return new AutofillId(parent.getAccessibilityViewId(), virtualId); + return new AutofillId(parent.getAutofillViewId(), virtualId); } private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, @@ -1039,7 +1039,7 @@ public final class AutofillManager { final int itemCount = ids.size(); int numApplied = 0; ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null; - final View[] views = client.findViewsByAccessibilityIdTraversal(getViewIds(ids)); + final View[] views = client.findViewsByAutofillIdTraversal(getViewIds(ids)); for (int i = 0; i < itemCount; i++) { final AutofillId id = ids.get(i); @@ -1232,7 +1232,7 @@ public final class AutofillManager { return null; } - return client.findViewByAccessibilityIdTraversal(autofillId.getViewId()); + return client.findViewByAutofillIdTraversal(autofillId.getViewId()); } /** @hide */ diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java index 1e8207a2e7b3..f712d5fa65ca 100644 --- a/core/java/android/widget/DayPickerView.java +++ b/core/java/android/widget/DayPickerView.java @@ -293,9 +293,19 @@ class DayPickerView extends ViewGroup { * @param setSelected whether to set the specified day as selected */ private void setDate(long timeInMillis, boolean animate, boolean setSelected) { + boolean dateClamped = false; + // Clamp the target day in milliseconds to the min or max if outside the range. + if (timeInMillis < mMinDate.getTimeInMillis()) { + timeInMillis = mMinDate.getTimeInMillis(); + dateClamped = true; + } else if (timeInMillis > mMaxDate.getTimeInMillis()) { + timeInMillis = mMaxDate.getTimeInMillis(); + dateClamped = true; + } + getTempCalendarForTime(timeInMillis); - if (setSelected) { + if (setSelected || dateClamped) { mSelectedDay.setTimeInMillis(timeInMillis); } @@ -353,13 +363,6 @@ class DayPickerView extends ViewGroup { public void onRangeChanged() { mAdapter.setRange(mMinDate, mMaxDate); - // Clamp the selected day to the new min/max. - if (mSelectedDay.before(mMinDate)) { - mSelectedDay.setTimeInMillis(mMinDate.getTimeInMillis()); - } else if (mSelectedDay.after(mMaxDate)) { - mSelectedDay.setTimeInMillis(mMaxDate.getTimeInMillis()); - } - // Changing the min/max date changes the selection position since we // don't really have stable IDs. Jumps immediately to the new position. setDate(mSelectedDay.getTimeInMillis(), false, false); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 0e6e3aee28b1..45e5f8adc468 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -2584,14 +2584,18 @@ public class Editor { if (offset == -1) { return; } + stopTextActionModeWithPreservingSelection(); - final boolean isOnSelection = mTextView.hasSelection() - && offset >= mTextView.getSelectionStart() && offset <= mTextView.getSelectionEnd(); - if (!isOnSelection) { - // Right clicked position is not on the selection. Remove the selection and move the - // cursor to the right clicked position. - Selection.setSelection((Spannable) mTextView.getText(), offset); - stopTextActionMode(); + if (mTextView.canSelectText()) { + final boolean isOnSelection = mTextView.hasSelection() + && offset >= mTextView.getSelectionStart() + && offset <= mTextView.getSelectionEnd(); + if (!isOnSelection) { + // Right clicked position is not on the selection. Remove the selection and move the + // cursor to the right clicked position. + Selection.setSelection((Spannable) mTextView.getText(), offset); + stopTextActionMode(); + } } if (shouldOfferToShowSuggestions()) { diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl index f4c18c300513..1bee6924454d 100644 --- a/core/java/com/android/internal/app/ISoundTriggerService.aidl +++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl @@ -16,6 +16,7 @@ package com.android.internal.app; +import android.app.PendingIntent; import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.SoundTrigger; import android.os.ParcelUuid; @@ -26,7 +27,6 @@ import android.os.ParcelUuid; */ interface ISoundTriggerService { - SoundTrigger.GenericSoundModel getSoundModel(in ParcelUuid soundModelId); void updateSoundModel(in SoundTrigger.GenericSoundModel soundModel); @@ -36,8 +36,17 @@ interface ISoundTriggerService { int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback, in SoundTrigger.RecognitionConfig config); - /** - * Stops recognition. - */ int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback); + + int loadGenericSoundModel(in SoundTrigger.GenericSoundModel soundModel); + int loadKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel soundModel); + + int startRecognitionForIntent(in ParcelUuid soundModelId, in PendingIntent callbackIntent, + in SoundTrigger.RecognitionConfig config); + + int stopRecognitionForIntent(in ParcelUuid soundModelId); + + int unloadSoundModel(in ParcelUuid soundModelId); + + boolean isRecognitionActive(in ParcelUuid parcelUuid); } diff --git a/core/java/com/android/internal/app/NightDisplayController.java b/core/java/com/android/internal/app/NightDisplayController.java index bb54085ac3bf..860c5c4c3d3b 100644 --- a/core/java/com/android/internal/app/NightDisplayController.java +++ b/core/java/com/android/internal/app/NightDisplayController.java @@ -182,6 +182,10 @@ public final class NightDisplayController { throw new IllegalArgumentException("Invalid autoMode: " + autoMode); } + if (getAutoMode() != autoMode) { + Secure.putLongForUser(mContext.getContentResolver(), + Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1L, mUserId); + } return Secure.putIntForUser(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mUserId); } diff --git a/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java index 56d60a13e159..9ac753b6d6ce 100644 --- a/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java +++ b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java @@ -61,7 +61,7 @@ import com.android.internal.graphics.palette.Palette.Swatch; * This means that the color space is divided into distinct colors, rather than representative * colors. */ -final class ColorCutQuantizer { +final class ColorCutQuantizer implements Quantizer { private static final String LOG_TAG = "ColorCutQuantizer"; private static final boolean LOG_TIMINGS = false; @@ -73,22 +73,22 @@ final class ColorCutQuantizer { private static final int QUANTIZE_WORD_WIDTH = 5; private static final int QUANTIZE_WORD_MASK = (1 << QUANTIZE_WORD_WIDTH) - 1; - final int[] mColors; - final int[] mHistogram; - final List<Swatch> mQuantizedColors; - final TimingLogger mTimingLogger; - final Palette.Filter[] mFilters; + int[] mColors; + int[] mHistogram; + List<Swatch> mQuantizedColors; + TimingLogger mTimingLogger; + Palette.Filter[] mFilters; private final float[] mTempHsl = new float[3]; /** - * Constructor. + * Execute color quantization. * * @param pixels histogram representing an image's pixel data * @param maxColors The maximum number of colors that should be in the result palette. * @param filters Set of filters to use in the quantization stage */ - ColorCutQuantizer(final int[] pixels, final int maxColors, final Palette.Filter[] filters) { + public void quantize(final int[] pixels, final int maxColors, final Palette.Filter[] filters) { mTimingLogger = LOG_TIMINGS ? new TimingLogger(LOG_TAG, "Creation") : null; mFilters = filters; @@ -160,7 +160,7 @@ final class ColorCutQuantizer { /** * @return the list of quantized colors */ - List<Swatch> getQuantizedColors() { + public List<Swatch> getQuantizedColors() { return mQuantizedColors; } diff --git a/core/java/com/android/internal/graphics/palette/Palette.java b/core/java/com/android/internal/graphics/palette/Palette.java index 9f1504a0495c..a4f9a596050c 100644 --- a/core/java/com/android/internal/graphics/palette/Palette.java +++ b/core/java/com/android/internal/graphics/palette/Palette.java @@ -613,6 +613,8 @@ public final class Palette { private final List<Palette.Filter> mFilters = new ArrayList<>(); private Rect mRegion; + private Quantizer mQuantizer; + /** * Construct a new {@link Palette.Builder} using a source {@link Bitmap} */ @@ -726,6 +728,18 @@ public final class Palette { } /** + * Set a specific quantization algorithm. {@link ColorCutQuantizer} will + * be used if unspecified. + * + * @param quantizer Quantizer implementation. + */ + @NonNull + public Palette.Builder setQuantizer(Quantizer quantizer) { + mQuantizer = quantizer; + return this; + } + + /** * Set a region of the bitmap to be used exclusively when calculating the palette. * <p>This only works when the original input is a {@link Bitmap}.</p> * @@ -818,17 +832,19 @@ public final class Palette { } // Now generate a quantizer from the Bitmap - final ColorCutQuantizer quantizer = new ColorCutQuantizer( - getPixelsFromBitmap(bitmap), - mMaxColors, - mFilters.isEmpty() ? null : mFilters.toArray(new Palette.Filter[mFilters.size()])); + if (mQuantizer == null) { + mQuantizer = new ColorCutQuantizer(); + } + mQuantizer.quantize(getPixelsFromBitmap(bitmap), + mMaxColors, mFilters.isEmpty() ? null : + mFilters.toArray(new Palette.Filter[mFilters.size()])); // If created a new bitmap, recycle it if (bitmap != mBitmap) { bitmap.recycle(); } - swatches = quantizer.getQuantizedColors(); + swatches = mQuantizer.getQuantizedColors(); if (logger != null) { logger.addSplit("Color quantization completed"); diff --git a/core/java/android/service/euicc/SwitchResult.aidl b/core/java/com/android/internal/graphics/palette/Quantizer.java index eb706a5c65d3..db60f2e9dc69 100644 --- a/core/java/android/service/euicc/SwitchResult.aidl +++ b/core/java/com/android/internal/graphics/palette/Quantizer.java @@ -11,9 +11,17 @@ * 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. + * limitations under the License */ -package android.service.euicc; +package com.android.internal.graphics.palette; -parcelable SwitchResult; +import java.util.List; + +/** + * Definition of an algorithm that receives pixels and outputs a list of colors. + */ +public interface Quantizer { + void quantize(final int[] pixels, final int maxColors, final Palette.Filter[] filters); + List<Palette.Swatch> getQuantizedColors(); +} diff --git a/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java b/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java new file mode 100644 index 000000000000..b0355350dc15 --- /dev/null +++ b/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.internal.graphics.palette; + +import android.util.Log; + +import com.android.internal.graphics.ColorUtils; +import com.android.internal.ml.clustering.KMeans; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * A quantizer that uses k-means + */ +public class VariationalKMeansQuantizer implements Quantizer { + + private static final String TAG = "KMeansQuantizer"; + private static final boolean DEBUG = false; + + /** + * Clusters closer than this value will me merged. + */ + private final float mMinClusterSqDistance; + + /** + * K-means can get stuck in local optima, this can be avoided by + * repeating it and getting the "best" execution. + */ + private final int mInitializations; + + /** + * Initialize KMeans with a fixed random state to have + * consistent results across multiple runs. + */ + private final KMeans mKMeans = new KMeans(new Random(0), 30, 0); + + private List<Palette.Swatch> mQuantizedColors; + + public VariationalKMeansQuantizer() { + this(0.25f /* cluster distance */); + } + + public VariationalKMeansQuantizer(float minClusterDistance) { + this(minClusterDistance, 1 /* initializations */); + } + + public VariationalKMeansQuantizer(float minClusterDistance, int initializations) { + mMinClusterSqDistance = minClusterDistance * minClusterDistance; + mInitializations = initializations; + } + + /** + * K-Means quantizer. + * + * @param pixels Pixels to quantize. + * @param maxColors Maximum number of clusters to extract. + * @param filters Colors that should be ignored + */ + @Override + public void quantize(int[] pixels, int maxColors, Palette.Filter[] filters) { + // Start by converting all colors to HSL. + // HLS is way more meaningful for clustering than RGB. + final float[] hsl = {0, 0, 0}; + final float[][] hslPixels = new float[pixels.length][3]; + for (int i = 0; i < pixels.length; i++) { + ColorUtils.colorToHSL(pixels[i], hsl); + // Normalize hue so all values go from 0 to 1. + hslPixels[i][0] = hsl[0] / 360f; + hslPixels[i][1] = hsl[1]; + hslPixels[i][2] = hsl[2]; + } + + final List<KMeans.Mean> optimalMeans = getOptimalKMeans(maxColors, hslPixels); + + // Ideally we should run k-means again to merge clusters but it would be too expensive, + // instead we just merge all clusters that are closer than a threshold. + for (int i = 0; i < optimalMeans.size(); i++) { + KMeans.Mean current = optimalMeans.get(i); + float[] currentCentroid = current.getCentroid(); + for (int j = i + 1; j < optimalMeans.size(); j++) { + KMeans.Mean compareTo = optimalMeans.get(j); + float[] compareToCentroid = compareTo.getCentroid(); + float sqDistance = KMeans.sqDistance(currentCentroid, compareToCentroid); + // Merge them + if (sqDistance < mMinClusterSqDistance) { + optimalMeans.remove(compareTo); + current.getItems().addAll(compareTo.getItems()); + for (int k = 0; k < currentCentroid.length; k++) { + currentCentroid[k] += (compareToCentroid[k] - currentCentroid[k]) / 2.0; + } + j--; + } + } + } + + // Convert data to final format, de-normalizing the hue. + mQuantizedColors = new ArrayList<>(); + for (KMeans.Mean mean : optimalMeans) { + if (mean.getItems().size() == 0) { + continue; + } + float[] centroid = mean.getCentroid(); + mQuantizedColors.add(new Palette.Swatch(new float[]{ + centroid[0] * 360f, + centroid[1], + centroid[2] + }, mean.getItems().size())); + } + } + + private List<KMeans.Mean> getOptimalKMeans(int k, float[][] inputData) { + List<KMeans.Mean> optimal = null; + double optimalScore = -Double.MAX_VALUE; + int runs = mInitializations; + while (runs > 0) { + if (DEBUG) { + Log.d(TAG, "k-means run: " + runs); + } + List<KMeans.Mean> means = mKMeans.predict(k, inputData); + double score = KMeans.score(means); + if (optimal == null || score > optimalScore) { + if (DEBUG) { + Log.d(TAG, "\tnew optimal score: " + score); + } + optimalScore = score; + optimal = means; + } + runs--; + } + + return optimal; + } + + @Override + public List<Palette.Swatch> getQuantizedColors() { + return mQuantizedColors; + } +} diff --git a/core/java/com/android/internal/ml/clustering/KMeans.java b/core/java/com/android/internal/ml/clustering/KMeans.java new file mode 100644 index 000000000000..4d5b3331e7b1 --- /dev/null +++ b/core/java/com/android/internal/ml/clustering/KMeans.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.internal.ml.clustering; + +import android.annotation.NonNull; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +/** + * Simple K-Means implementation + */ +public class KMeans { + + private static final boolean DEBUG = false; + private static final String TAG = "KMeans"; + private final Random mRandomState; + private final int mMaxIterations; + private float mSqConvergenceEpsilon; + + public KMeans() { + this(new Random()); + } + + public KMeans(Random random) { + this(random, 30 /* maxIterations */, 0.005f /* convergenceEpsilon */); + } + public KMeans(Random random, int maxIterations, float convergenceEpsilon) { + mRandomState = random; + mMaxIterations = maxIterations; + mSqConvergenceEpsilon = convergenceEpsilon * convergenceEpsilon; + } + + /** + * Runs k-means on the input data (X) trying to find k means. + * + * K-Means is known for getting stuck into local optima, so you might + * want to run it multiple time and argmax on {@link KMeans#score(List)} + * + * @param k The number of points to return. + * @param inputData Input data. + * @return An array of k Means, each representing a centroid and data points that belong to it. + */ + public List<Mean> predict(final int k, final float[][] inputData) { + checkDataSetSanity(inputData); + int dimension = inputData[0].length; + + final ArrayList<Mean> means = new ArrayList<>(); + for (int i = 0; i < k; i++) { + Mean m = new Mean(dimension); + for (int j = 0; j < dimension; j++) { + m.mCentroid[j] = mRandomState.nextFloat(); + } + means.add(m); + } + + // Iterate until we converge or run out of iterations + boolean converged = false; + for (int i = 0; i < mMaxIterations; i++) { + converged = step(means, inputData); + if (converged) { + if (DEBUG) Log.d(TAG, "Converged at iteration: " + i); + break; + } + } + if (!converged && DEBUG) Log.d(TAG, "Did not converge"); + + return means; + } + + /** + * Score calculates the inertia between means. + * This can be considered as an E step of an EM algorithm. + * + * @param means Means to use when calculating score. + * @return The score + */ + public static double score(@NonNull List<Mean> means) { + double score = 0; + final int meansSize = means.size(); + for (int i = 0; i < meansSize; i++) { + Mean mean = means.get(i); + for (int j = 0; j < meansSize; j++) { + Mean compareTo = means.get(j); + if (mean == compareTo) { + continue; + } + double distance = Math.sqrt(sqDistance(mean.mCentroid, compareTo.mCentroid)); + score += distance; + } + } + return score; + } + + @VisibleForTesting + public void checkDataSetSanity(float[][] inputData) { + if (inputData == null) { + throw new IllegalArgumentException("Data set is null."); + } else if (inputData.length == 0) { + throw new IllegalArgumentException("Data set is empty."); + } else if (inputData[0] == null) { + throw new IllegalArgumentException("Bad data set format."); + } + + final int dimension = inputData[0].length; + final int length = inputData.length; + for (int i = 1; i < length; i++) { + if (inputData[i] == null || inputData[i].length != dimension) { + throw new IllegalArgumentException("Bad data set format."); + } + } + } + + /** + * K-Means iteration. + * + * @param means Current means + * @param inputData Input data + * @return True if data set converged + */ + private boolean step(final ArrayList<Mean> means, final float[][] inputData) { + + // Clean up the previous state because we need to compute + // which point belongs to each mean again. + for (int i = means.size() - 1; i >= 0; i--) { + final Mean mean = means.get(i); + mean.mClosestItems.clear(); + } + for (int i = inputData.length - 1; i >= 0; i--) { + final float[] current = inputData[i]; + final Mean nearest = nearestMean(current, means); + nearest.mClosestItems.add(current); + } + + boolean converged = true; + // Move each mean towards the nearest data set points + for (int i = means.size() - 1; i >= 0; i--) { + final Mean mean = means.get(i); + if (mean.mClosestItems.size() == 0) { + continue; + } + + // Compute the new mean centroid: + // 1. Sum all all points + // 2. Average them + final float[] oldCentroid = mean.mCentroid; + mean.mCentroid = new float[oldCentroid.length]; + for (int j = 0; j < mean.mClosestItems.size(); j++) { + // Update each centroid component + for (int p = 0; p < mean.mCentroid.length; p++) { + mean.mCentroid[p] += mean.mClosestItems.get(j)[p]; + } + } + for (int j = 0; j < mean.mCentroid.length; j++) { + mean.mCentroid[j] /= mean.mClosestItems.size(); + } + + // We converged if the centroid didn't move for any of the means. + if (sqDistance(oldCentroid, mean.mCentroid) > mSqConvergenceEpsilon) { + converged = false; + } + } + return converged; + } + + @VisibleForTesting + public static Mean nearestMean(float[] point, List<Mean> means) { + Mean nearest = null; + float nearestDistance = Float.MAX_VALUE; + + final int meanCount = means.size(); + for (int i = 0; i < meanCount; i++) { + Mean next = means.get(i); + // We don't need the sqrt when comparing distances in euclidean space + // because they exist on both sides of the equation and cancel each other out. + float nextDistance = sqDistance(point, next.mCentroid); + if (nextDistance < nearestDistance) { + nearest = next; + nearestDistance = nextDistance; + } + } + return nearest; + } + + @VisibleForTesting + public static float sqDistance(float[] a, float[] b) { + float dist = 0; + final int length = a.length; + for (int i = 0; i < length; i++) { + dist += (a[i] - b[i]) * (a[i] - b[i]); + } + return dist; + } + + /** + * Definition of a mean, contains a centroid and points on its cluster. + */ + public static class Mean { + float[] mCentroid; + final ArrayList<float[]> mClosestItems = new ArrayList<>(); + + public Mean(int dimension) { + mCentroid = new float[dimension]; + } + + public Mean(float ...centroid) { + mCentroid = centroid; + } + + public float[] getCentroid() { + return mCentroid; + } + + public List<float[]> getItems() { + return mClosestItems; + } + + @Override + public String toString() { + return "Mean(centroid: " + Arrays.toString(mCentroid) + ", size: " + + mClosestItems.size() + ")"; + } + } +} diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java index 96b443d28abc..1f84061a5978 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; @@ -101,7 +102,7 @@ public class CollectionUtils { /** * Returns the given list, or an immutable empty list if the provided list is null * - * This can be used to guaranty null-safety without paying the price of extra allocations + * This can be used to guarantee null-safety without paying the price of extra allocations * * @see Collections#emptyList */ @@ -110,6 +111,17 @@ public class CollectionUtils { } /** + * Returns the given set, or an immutable empty set if the provided set is null + * + * This can be used to guarantee null-safety without paying the price of extra allocations + * + * @see Collections#emptySet + */ + public static @NonNull <T> Set<T> emptyIfNull(@Nullable Set<T> cur) { + return cur == null ? Collections.emptySet() : cur; + } + + /** * Returns the size of the given list, or 0 if the list is null */ public static int size(@Nullable Collection<?> cur) { diff --git a/core/java/com/android/internal/view/TooltipPopup.java b/core/java/com/android/internal/view/TooltipPopup.java index 52357ac486c2..3930214ea286 100644 --- a/core/java/com/android/internal/view/TooltipPopup.java +++ b/core/java/com/android/internal/view/TooltipPopup.java @@ -91,10 +91,6 @@ public class TooltipPopup { return mContentView.getParent() != null; } - public void updateContent(CharSequence tooltipText) { - mMessageView.setText(tooltipText); - } - private void computePosition(View anchorView, int anchorX, int anchorY, boolean fromTouch, WindowManager.LayoutParams outParams) { outParams.token = anchorView.getWindowToken(); diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 226e9e366cd4..1779ada24aac 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -1761,6 +1761,14 @@ android_media_AudioSystem_systemReady(JNIEnv *env, jobject thiz) return nativeToJavaStatus(AudioSystem::systemReady()); } +static jfloat +android_media_AudioSystem_getStreamVolumeDB(JNIEnv *env, jobject thiz, + jint stream, jint index, jint device) +{ + return (jfloat)AudioSystem::getStreamVolumeDB((audio_stream_type_t)stream, + (int)index, + (audio_devices_t)device); +} // ---------------------------------------------------------------------------- @@ -1814,6 +1822,7 @@ static const JNINativeMethod gMethods[] = { {"native_register_recording_callback", "()V", (void *)android_media_AudioSystem_registerRecordingCallback}, {"systemReady", "()I", (void *)android_media_AudioSystem_systemReady}, + {"getStreamVolumeDB", "(III)F", (void *)android_media_AudioSystem_getStreamVolumeDB}, }; diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp index 19f779f39b1d..cdd3c094e009 100644 --- a/core/jni/android_os_HwBinder.cpp +++ b/core/jni/android_os_HwBinder.cpp @@ -58,6 +58,29 @@ static struct fields_t { jmethodID onTransactID; } gFields; +struct JHwBinderHolder : public RefBase { + JHwBinderHolder() {} + + sp<JHwBinder> get(JNIEnv *env, jobject obj) { + Mutex::Autolock autoLock(mLock); + + sp<JHwBinder> binder = mBinder.promote(); + + if (binder == NULL) { + binder = new JHwBinder(env, obj); + mBinder = binder; + } + + return binder; + } + +private: + Mutex mLock; + wp<JHwBinder> mBinder; + + DISALLOW_COPY_AND_ASSIGN(JHwBinderHolder); +}; + // static void JHwBinder::InitClass(JNIEnv *env) { ScopedLocalRef<jclass> clazz( @@ -75,10 +98,10 @@ void JHwBinder::InitClass(JNIEnv *env) { } // static -sp<JHwBinder> JHwBinder::SetNativeContext( - JNIEnv *env, jobject thiz, const sp<JHwBinder> &context) { - sp<JHwBinder> old = - (JHwBinder *)env->GetLongField(thiz, gFields.contextID); +sp<JHwBinderHolder> JHwBinder::SetNativeContext( + JNIEnv *env, jobject thiz, const sp<JHwBinderHolder> &context) { + sp<JHwBinderHolder> old = + (JHwBinderHolder *)env->GetLongField(thiz, gFields.contextID); if (context != NULL) { context->incStrong(NULL /* id */); @@ -94,27 +117,27 @@ sp<JHwBinder> JHwBinder::SetNativeContext( } // static -sp<JHwBinder> JHwBinder::GetNativeContext( +sp<JHwBinder> JHwBinder::GetNativeBinder( JNIEnv *env, jobject thiz) { - return (JHwBinder *)env->GetLongField(thiz, gFields.contextID); + JHwBinderHolder *holder = + reinterpret_cast<JHwBinderHolder *>( + env->GetLongField(thiz, gFields.contextID)); + + return holder->get(env, thiz); } JHwBinder::JHwBinder(JNIEnv *env, jobject thiz) { jclass clazz = env->GetObjectClass(thiz); CHECK(clazz != NULL); - mClass = (jclass)env->NewGlobalRef(clazz); - mObject = env->NewWeakGlobalRef(thiz); + mObject = env->NewGlobalRef(thiz); } JHwBinder::~JHwBinder() { JNIEnv *env = AndroidRuntime::getJNIEnv(); - env->DeleteWeakGlobalRef(mObject); + env->DeleteGlobalRef(mObject); mObject = NULL; - - env->DeleteGlobalRef(mClass); - mClass = NULL; } status_t JHwBinder::onTransact( @@ -203,10 +226,10 @@ status_t JHwBinder::onTransact( using namespace android; static void releaseNativeContext(void *nativeContext) { - sp<JHwBinder> binder = (JHwBinder *)nativeContext; + sp<JHwBinderHolder> context = static_cast<JHwBinderHolder *>(nativeContext); - if (binder != NULL) { - binder->decStrong(NULL /* id */); + if (context != NULL) { + context->decStrong(NULL /* id */); } } @@ -217,8 +240,7 @@ static jlong JHwBinder_native_init(JNIEnv *env) { } static void JHwBinder_native_setup(JNIEnv *env, jobject thiz) { - sp<JHwBinder> context = new JHwBinder(env, thiz); - + sp<JHwBinderHolder> context = new JHwBinderHolder; JHwBinder::SetNativeContext(env, thiz, context); } @@ -246,7 +268,7 @@ static void JHwBinder_native_registerService( return; // XXX exception already pending? } - sp<hardware::IBinder> binder = JHwBinder::GetNativeContext(env, thiz); + sp<hardware::IBinder> binder = JHwBinder::GetNativeBinder(env, thiz); /* TODO(b/33440494) this is not right */ sp<hidl::base::V1_0::IBase> base = new hidl::base::V1_0::BpHwBase(binder); diff --git a/core/jni/android_os_HwBinder.h b/core/jni/android_os_HwBinder.h index fa8fe01d6e93..5352f1e607c2 100644 --- a/core/jni/android_os_HwBinder.h +++ b/core/jni/android_os_HwBinder.h @@ -24,13 +24,15 @@ namespace android { +struct JHwBinderHolder; + struct JHwBinder : public hardware::BHwBinder { static void InitClass(JNIEnv *env); - static sp<JHwBinder> SetNativeContext( - JNIEnv *env, jobject thiz, const sp<JHwBinder> &context); + static sp<JHwBinderHolder> SetNativeContext( + JNIEnv *env, jobject thiz, const sp<JHwBinderHolder> &context); - static sp<JHwBinder> GetNativeContext(JNIEnv *env, jobject thiz); + static sp<JHwBinder> GetNativeBinder(JNIEnv *env, jobject thiz); JHwBinder(JNIEnv *env, jobject thiz); @@ -45,7 +47,6 @@ protected: TransactCallback callback); private: - jclass mClass; jobject mObject; DISALLOW_COPY_AND_ASSIGN(JHwBinder); diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp index b21ea828f2a4..6ea809aa95ad 100644 --- a/core/jni/android_os_HwParcel.cpp +++ b/core/jni/android_os_HwParcel.cpp @@ -169,7 +169,6 @@ JHwParcel::JHwParcel(JNIEnv *env, jobject thiz) jclass clazz = env->GetObjectClass(thiz); CHECK(clazz != NULL); - mClass = (jclass)env->NewGlobalRef(clazz); mObject = env->NewWeakGlobalRef(thiz); } @@ -182,9 +181,6 @@ JHwParcel::~JHwParcel() { env->DeleteWeakGlobalRef(mObject); mObject = NULL; - - env->DeleteGlobalRef(mClass); - mClass = NULL; } hardware::Parcel *JHwParcel::getParcel() { @@ -542,7 +538,7 @@ static void JHwParcel_native_writeStrongBinder( env, FindClassOrDie(env, PACKAGE_PATH "/HwRemoteBinder")); if (env->IsInstanceOf(binderObj, hwBinderKlass.get())) { - binder = JHwBinder::GetNativeContext(env, binderObj); + binder = JHwBinder::GetNativeBinder(env, binderObj); } else if (env->IsInstanceOf(binderObj, hwRemoteBinderKlass.get())) { binder = JHwRemoteBinder::GetNativeContext( env, binderObj)->getBinder(); diff --git a/core/jni/android_os_HwParcel.h b/core/jni/android_os_HwParcel.h index f81de9bf30b7..f6e61004b0e3 100644 --- a/core/jni/android_os_HwParcel.h +++ b/core/jni/android_os_HwParcel.h @@ -53,7 +53,6 @@ protected: virtual ~JHwParcel(); private: - jclass mClass; jobject mObject; hardware::Parcel *mParcel; diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp index f2f8e52db9f5..9c2ee9cfec45 100644 --- a/core/jni/android_os_HwRemoteBinder.cpp +++ b/core/jni/android_os_HwRemoteBinder.cpp @@ -272,7 +272,6 @@ JHwRemoteBinder::JHwRemoteBinder( jclass clazz = env->GetObjectClass(thiz); CHECK(clazz != NULL); - mClass = (jclass)env->NewGlobalRef(clazz); mObject = env->NewWeakGlobalRef(thiz); } @@ -281,9 +280,6 @@ JHwRemoteBinder::~JHwRemoteBinder() { env->DeleteWeakGlobalRef(mObject); mObject = NULL; - - env->DeleteGlobalRef(mClass); - mClass = NULL; } sp<hardware::IBinder> JHwRemoteBinder::getBinder() const { diff --git a/core/jni/android_os_HwRemoteBinder.h b/core/jni/android_os_HwRemoteBinder.h index 77a02784926d..63aad0ab2923 100644 --- a/core/jni/android_os_HwRemoteBinder.h +++ b/core/jni/android_os_HwRemoteBinder.h @@ -68,7 +68,6 @@ protected: virtual ~JHwRemoteBinder(); private: - jclass mClass; jobject mObject; sp<hardware::IBinder> mBinder; diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index de67c50e7195..26b00344b789 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -29,6 +29,7 @@ #include <sys/types.h> #include <unistd.h> +#include <android-base/stringprintf.h> #include <binder/IInterface.h> #include <binder/IServiceManager.h> #include <binder/IPCThreadState.h> @@ -194,10 +195,34 @@ static void report_exception(JNIEnv* env, jthrowable excep, const char* msg) /* * It's an Error: Reraise the exception and ask the runtime to abort. */ + + // Try to get the exception string. Sometimes logcat isn't available, + // so try to add it to the abort message. + std::string exc_msg = "(Unknown exception message)"; + { + ScopedLocalRef<jclass> exc_class(env, env->GetObjectClass(excep)); + jmethodID method_id = env->GetMethodID(exc_class.get(), + "toString", + "()Ljava/lang/String;"); + ScopedLocalRef<jstring> jstr( + env, + reinterpret_cast<jstring>( + env->CallObjectMethod(excep, method_id))); + env->ExceptionClear(); // Just for good measure. + if (jstr.get() != nullptr) { + ScopedUtfChars jstr_utf(env, jstr.get()); + exc_msg = jstr_utf.c_str(); + } + } + env->Throw(excep); ALOGE("java.lang.Error thrown during binder transaction (stack trace follows) : "); env->ExceptionDescribe(); - env->FatalError("java.lang.Error thrown during binder transaction."); + + std::string error_msg = base::StringPrintf( + "java.lang.Error thrown during binder transaction: %s", + exc_msg.c_str()); + env->FatalError(error_msg.c_str()); } bail: diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index bde913487e8a..494e266dabcd 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -400,6 +400,16 @@ static void nativeAllocateBuffers(JNIEnv* /* env */ , jclass /* clazz */, static jlong nativeCreateFromSurfaceControl(JNIEnv* env, jclass clazz, jlong surfaceControlNativeObj) { + sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj)); + sp<Surface> surface(ctrl->createSurface()); + if (surface != NULL) { + surface->incStrong(&sRefBaseOwner); + } + return reinterpret_cast<jlong>(surface.get()); +} + +static jlong nativeGetFromSurfaceControl(JNIEnv* env, jclass clazz, + jlong surfaceControlNativeObj) { /* * This is used by the WindowManagerService just after constructing * a Surface and is necessary for returning the Surface reference to @@ -596,6 +606,8 @@ static const JNINativeMethod gSurfaceMethods[] = { (void*)nativeAllocateBuffers }, {"nativeCreateFromSurfaceControl", "(J)J", (void*)nativeCreateFromSurfaceControl }, + {"nativeGetFromSurfaceControl", "(J)J", + (void*)nativeGetFromSurfaceControl }, {"nativeReadFromParcel", "(JLandroid/os/Parcel;)J", (void*)nativeReadFromParcel }, {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 2657140cd634..286c1a2692da 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -926,6 +926,10 @@ static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode( return createBitmap(env, bitmap.release(), android::bitmap::kBitmapCreateFlag_Mutable); } +static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) { + RenderProxy::disableVsync(); +} + // ---------------------------------------------------------------------------- // FrameMetricsObserver // ---------------------------------------------------------------------------- @@ -1024,6 +1028,7 @@ static const JNINativeMethod gMethods[] = { (void*)android_view_ThreadedRenderer_copySurfaceInto }, { "nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;", (void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode }, + { "disableVsync", "()V", (void*)android_view_ThreadedRenderer_disableVsync }, }; int register_android_view_ThreadedRenderer(JNIEnv* env) { diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index d509c1232461..8f6db0096150 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -471,7 +471,7 @@ <string name="permlab_nfc" msgid="4423351274757876953">"controlar Comunicació de camp proper (NFC)"</string> <string name="permdesc_nfc" msgid="7120611819401789907">"Permet que l\'aplicació es comuniqui amb les etiquetes, les targetes i els lectors de Comunicació de camp proper (NFC)."</string> <string name="permlab_disableKeyguard" msgid="3598496301486439258">"desactivació del bloqueig de pantalla"</string> - <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"Permet que l\'aplicació desactivi el bloqueig del teclat i qualsevol element de seguretat de contrasenyes associat. Per exemple, el telèfon desactiva el bloqueig del teclat en rebre una trucada telefònica entrant i, a continuació, reactiva el bloqueig del teclat quan finalitza la trucada."</string> + <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"Permet que l\'aplicació desactivi el bloqueig del teclat i qualsevol element de seguretat de contrasenyes associat. Per exemple, el telèfon desactiva el bloqueig del teclat en rebre una trucada entrant i, a continuació, reactiva el bloqueig del teclat quan finalitza la trucada."</string> <string name="permlab_manageFingerprint" msgid="5640858826254575638">"Gestionar el maquinari d\'empremtes digitals"</string> <string name="permdesc_manageFingerprint" msgid="178208705828055464">"Permet que l\'aplicació invoqui mètodes per afegir i suprimir plantilles d\'empremtes digitals que es puguin fer servir."</string> <string name="permlab_useFingerprint" msgid="3150478619915124905">"Utilitzar el maquinari d\'empremtes digitals"</string> @@ -788,13 +788,13 @@ <string name="keyguard_accessibility_widget_reorder_end" msgid="7170190950870468320">"Ha finalitzat la reorganització del widget."</string> <string name="keyguard_accessibility_widget_deleted" msgid="4426204263929224434">"S\'ha suprimit el widget de <xliff:g id="WIDGET_INDEX">%1$s</xliff:g>."</string> <string name="keyguard_accessibility_expand_lock_area" msgid="519859720934178024">"Desplega l\'àrea de desbloqueig."</string> - <string name="keyguard_accessibility_slide_unlock" msgid="2959928478764697254">"Desbloqueig lliscant el dit"</string> + <string name="keyguard_accessibility_slide_unlock" msgid="2959928478764697254">"Desbloqueig lliscant"</string> <string name="keyguard_accessibility_pattern_unlock" msgid="1490840706075246612">"Desbloqueig mitjançant patró"</string> <string name="keyguard_accessibility_face_unlock" msgid="4817282543351718535">"Desbloqueig facial"</string> <string name="keyguard_accessibility_pin_unlock" msgid="2469687111784035046">"Desbloqueig mitjançant PIN"</string> <string name="keyguard_accessibility_password_unlock" msgid="7675777623912155089">"Desbloqueig mitjançant contrasenya"</string> <string name="keyguard_accessibility_pattern_area" msgid="7679891324509597904">"Àrea de patró"</string> - <string name="keyguard_accessibility_slide_area" msgid="6736064494019979544">"Àrea per lliscar el dit"</string> + <string name="keyguard_accessibility_slide_area" msgid="6736064494019979544">"Àrea per lliscar"</string> <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string> <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string> <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> @@ -811,7 +811,7 @@ <string name="js_dialog_before_unload_title" msgid="2619376555525116593">"Confirmació de la navegació"</string> <string name="js_dialog_before_unload_positive_button" msgid="3112752010600484130">"Surt d\'aquesta pàgina"</string> <string name="js_dialog_before_unload_negative_button" msgid="5614861293026099715">"Queda\'t en aquesta pàgina"</string> - <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nEstàs segur que vols sortir d\'aquesta pàgina?"</string> + <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nConfirmes que vols sortir d\'aquesta pàgina?"</string> <string name="save_password_label" msgid="6860261758665825069">"Confirma"</string> <string name="double_tap_toast" msgid="4595046515400268881">"Consell: Pica dos cops per ampliar i per reduir."</string> <string name="autofill_this_form" msgid="4616758841157816676">"Em. aut."</string> @@ -1075,7 +1075,7 @@ <string name="dump_heap_notification" msgid="2618183274836056542">"<xliff:g id="PROC">%1$s</xliff:g> ha superat el límit de memòria"</string> <string name="dump_heap_notification_detail" msgid="6901391084243999274">"S\'ha recopilat un procés \"heap dump\"; toca per compartir-lo"</string> <string name="dump_heap_title" msgid="5864292264307651673">"Vols compartir el \"heap dump\"?"</string> - <string name="dump_heap_text" msgid="4809417337240334941">"El procés <xliff:g id="PROC">%1$s</xliff:g> ha superat el límit de <xliff:g id="SIZE">%2$s</xliff:g> de memòria del procés. Hi ha un procés \"heap dump\" disponible perquè el comparteixis amb el desenvolupador. Vés amb compte: aquest \"heap dump\" pot contenir les dades personals a les quals l\'aplicació tingui accés."</string> + <string name="dump_heap_text" msgid="4809417337240334941">"El procés <xliff:g id="PROC">%1$s</xliff:g> ha superat el límit de <xliff:g id="SIZE">%2$s</xliff:g> de memòria del procés. Hi ha un procés \"heap dump\" disponible perquè el comparteixis amb el desenvolupador. Ves amb compte: aquest \"heap dump\" pot contenir les dades personals a les quals l\'aplicació tingui accés."</string> <string name="sendText" msgid="5209874571959469142">"Tria una acció per al text"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Volum del timbre"</string> <string name="volume_music" msgid="5421651157138628171">"Volum de multimèdia"</string> @@ -1259,7 +1259,7 @@ <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Permet que una aplicació demani permís per ignorar les optimitzacions de bateria per a l\'aplicació."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Piqueu dos cops per controlar el zoom"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"No s\'ha pogut afegir el widget."</string> - <string name="ime_action_go" msgid="8320845651737369027">"Vés"</string> + <string name="ime_action_go" msgid="8320845651737369027">"Ves"</string> <string name="ime_action_search" msgid="658110271822807811">"Cerca"</string> <string name="ime_action_send" msgid="2316166556349314424">"Envia"</string> <string name="ime_action_next" msgid="3138843904009813834">"Següent"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index e64cdd211b98..eab7b5d9f057 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1290,10 +1290,10 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN aktiveres af <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="1610714069627824309">"Tryk for at administrere netværket."</string> <string name="vpn_text_long" msgid="4907843483284977618">"Forbundet til <xliff:g id="SESSION">%s</xliff:g>. Tryk for at administrere netværket."</string> - <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Opretter forbindelse til altid aktiveret VPN…"</string> - <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Altid aktiveret VPN er forbundet"</string> - <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Forbindelsen til altid aktiveret VPN er afbrudt"</string> - <string name="vpn_lockdown_error" msgid="6009249814034708175">"Fejl i altid aktiveret VPN"</string> + <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Opretter forbindelse til konstant VPN…"</string> + <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Konstant VPN er forbundet"</string> + <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Forbindelsen til konstant VPN er afbrudt"</string> + <string name="vpn_lockdown_error" msgid="6009249814034708175">"Fejl i konstant VPN"</string> <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tryk for at konfigurere"</string> <string name="upload_file" msgid="2897957172366730416">"Vælg fil"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index b58df81acea3..e72e419a99e5 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -447,7 +447,7 @@ <string name="permlab_changeTetherState" msgid="5952584964373017960">"Tethering-Konnektivität ändern"</string> <string name="permdesc_changeTetherState" msgid="1524441344412319780">"Ermöglicht der App, den Status der Tethering-Konnektivität zu ändern"</string> <string name="permlab_accessWifiState" msgid="5202012949247040011">"WLAN-Verbindungen abrufen"</string> - <string name="permdesc_accessWifiState" msgid="5002798077387803726">"Ermöglicht der App, Informationen zu WLANs abzurufen, etwa ob ein WLAN aktiviert ist, und den Namen verbundener WLAN-Geräte."</string> + <string name="permdesc_accessWifiState" msgid="5002798077387803726">"Ermöglicht der App, Informationen zu WLAN-Netzwerken abzurufen, etwa ob ein WLAN aktiviert ist, und den Namen verbundener WLAN-Geräte."</string> <string name="permlab_changeWifiState" msgid="6550641188749128035">"WLAN-Verbindungen herstellen und trennen"</string> <string name="permdesc_changeWifiState" msgid="7137950297386127533">"Ermöglicht der App, eine Verbindung zu WLAN-Zugangspunkten herzustellen und solche zu trennen und Änderungen an der Gerätekonfiguration für WLAN-Netzwerke vorzunehmen."</string> <string name="permlab_changeWifiMulticastState" msgid="1368253871483254784">"WLAN-Multicast-Empfang zulassen"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 002997f5276e..97e85e16e906 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -90,7 +90,7 @@ <string name="serviceNotProvisioned" msgid="8614830180508686666">"Η υπηρεσία δεν προβλέπεται."</string> <string name="CLIRPermanent" msgid="3377371145926835671">"Δεν μπορείτε να αλλάξετε τη ρύθμιση του αναγνωριστικού καλούντος."</string> <string name="RestrictedOnDataTitle" msgid="1322504692764166532">"Δεν υπάρχει υπηρεσία δεδομένων"</string> - <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"Αδυναμία πραγματοποίησης κλήσεων έκτακτης ανάγκης"</string> + <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"Δεν επιτρέπονται οι κλήσεις έκτακτης ανάγκης"</string> <string name="RestrictedOnNormalTitle" msgid="3179574012752700984">"Δεν υπάρχει φωνητική υπηρεσία"</string> <string name="RestrictedOnAllVoiceTitle" msgid="158800171499150681">"Δεν υπάρχει φωνητική υπηρεσία/υπηρεσία έκτακτης ανάγκης"</string> <string name="RestrictedStateContent" msgid="4278821484643362350">"Δεν προσφέρεται προσωρινά από το δίκτυο κινητής τηλεφωνίας στην τοποθεσία σας"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 01d50efd918b..c064d1dfea14 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -1138,7 +1138,7 @@ <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Gonbidapena bidali da"</string> <string name="wifi_p2p_invitation_to_connect_title" msgid="4958803948658533637">"Konektatzeko gonbidapena"</string> <string name="wifi_p2p_from_message" msgid="570389174731951769">"Igorlea:"</string> - <string name="wifi_p2p_to_message" msgid="248968974522044099">"Nori:"</string> + <string name="wifi_p2p_to_message" msgid="248968974522044099">"Hartzailea:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Idatzi beharrezko PINa:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PINa:"</string> <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Tableta Wi-Fi saretik deskonektatuko da <xliff:g id="DEVICE_NAME">%1$s</xliff:g> gailura konektatuta dagoen bitartean"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 1717f5a4b1c3..eef1d77fc449 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -398,7 +398,7 @@ <string name="permdesc_accessCoarseLocation" product="default" msgid="7788009094906196995">"Այս հավելվածը կարող է ստանալ ձեր տեղադրության տվյալները ցանցային տարբեր աղբյուրներից, օրինակ՝ բջջային աշտարակներից և Wi-Fi ցանցերից: Այս տեղորոշման ծառայությունները պետք է միացված և հասանելի լինեն ձեր հեռախոսում, որպեսզի հավելվածը կարողանա օգտագործել դրանք:"</string> <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"փոխել ձեր աուդիո կարգավորումները"</string> <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Թույլ է տալիս հավելվածին փոփոխել ձայնանյութի գլոբալ կարգավորումները, ինչպես օրինակ` ձայնը և թե որ խոսափողն է օգտագործված արտածման համար:"</string> - <string name="permlab_recordAudio" msgid="3876049771427466323">"ձայնագրել ձայնանյութ"</string> + <string name="permlab_recordAudio" msgid="3876049771427466323">"ձայնագրել աուդիո ֆայլ"</string> <string name="permdesc_recordAudio" msgid="4245930455135321433">"Այս հավելվածը ցանկացած պահի կարող է ձայնագրել խոսափողի օգնությամբ:"</string> <string name="permlab_sim_communication" msgid="2935852302216852065">"ուղարկել հրամաններ SIM քարտին"</string> <string name="permdesc_sim_communication" msgid="5725159654279639498">"Թույլ է տալիս հավելվածին հրամաններ ուղարկել SIM-ին: Սա շատ վտանգավոր է:"</string> @@ -1420,7 +1420,7 @@ <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Համակցված բարձրախոսներ"</string> <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string> <string name="default_audio_route_category_name" msgid="3722811174003886946">"Համակարգ"</string> - <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-ի ձայնանյութ"</string> + <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-ի աուդիո ֆայլ"</string> <string name="wireless_display_route_description" msgid="9070346425023979651">"Անլար էկրան"</string> <string name="media_route_button_content_description" msgid="591703006349356016">"Հեռարձակում"</string> <string name="media_route_chooser_title" msgid="1751618554539087622">"Միանալ սարքին"</string> diff --git a/core/res/res/values-mcc302-mnc370/strings.xml b/core/res/res/values-mcc302-mnc370/strings.xml new file mode 100644 index 000000000000..f5b8496f9a0b --- /dev/null +++ b/core/res/res/values-mcc302-mnc370/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2017, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Template for showing mobile network operator name while WFC is active --> + <string-array name="wfcSpnFormats"> + <item>%s</item> + <item>%s Wi-Fi</item> + </string-array> +</resources> diff --git a/core/res/res/values-mcc302-mnc720/strings.xml b/core/res/res/values-mcc302-mnc720/strings.xml new file mode 100644 index 000000000000..f5b8496f9a0b --- /dev/null +++ b/core/res/res/values-mcc302-mnc720/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2017, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Template for showing mobile network operator name while WFC is active --> + <string-array name="wfcSpnFormats"> + <item>%s</item> + <item>%s Wi-Fi</item> + </string-array> +</resources> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index b35f770df132..dda9da437000 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -1041,7 +1041,7 @@ <string name="anr_application_process" msgid="6417199034861140083">"<xliff:g id="APPLICATION">%1$s</xliff:g> не реагира"</string> <string name="anr_process" msgid="6156880875555921105">"Процесот <xliff:g id="PROCESS">%1$s</xliff:g> не реагира"</string> <string name="force_close" msgid="8346072094521265605">"Во ред"</string> - <string name="report" msgid="4060218260984795706">"Извештај"</string> + <string name="report" msgid="4060218260984795706">"Пријави"</string> <string name="wait" msgid="7147118217226317732">"Почекај"</string> <string name="webpage_unresponsive" msgid="3272758351138122503">"Страницата не реагира.\n\nДали сакате да ја затворите?"</string> <string name="launch_warning_title" msgid="1547997780506713581">"Пренасочена апликација"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 52ffe6ce3591..a02e8cc88c01 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -90,7 +90,7 @@ <string name="serviceNotProvisioned" msgid="8614830180508686666">"ਸੇਵਾ ਪ੍ਰਬੰਧਿਤ ਨਹੀਂ ਹੈ।"</string> <string name="CLIRPermanent" msgid="3377371145926835671">"ਤੁਸੀਂ ਕਾਲਰ ID ਸੈਟਿੰਗ ਨਹੀਂ ਬਦਲ ਸਕਦੇ।"</string> <string name="RestrictedOnDataTitle" msgid="1322504692764166532">"ਕੋਈ ਡੈਟਾ ਸੇਵਾ ਨਹੀਂ"</string> - <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"ਕੋਈ ਸੰਕਟਕਾਲੀਨ ਕਾਲਿੰਗ ਨਹੀਂ"</string> + <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"ਸੰਕਟਕਾਲ ਵਿੱਚ ਕੋਈ ਕਾਲ ਨਹੀਂ"</string> <string name="RestrictedOnNormalTitle" msgid="3179574012752700984">"ਕੋਈ ਆਵਾਜ਼ੀ ਸੇਵਾ ਨਹੀਂ"</string> <string name="RestrictedOnAllVoiceTitle" msgid="158800171499150681">"ਕੋਈ ਆਵਾਜ਼ੀ/ਸੰਕਟਕਾਲੀਨ ਸੇਵਾ ਨਹੀਂ"</string> <string name="RestrictedStateContent" msgid="4278821484643362350">"ਤੁਹਾਡੇ ਟਿਕਾਣੇ \'ਤੇ ਅਸਥਾਈ ਤੌਰ \'ਤੇ ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਵੱਲੋਂ ਉਪਲਬਧ ਨਹੀਂ ਕਰਵਾਈ ਗਈ"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index c74178d890b7..0d5e332693e0 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -1623,7 +1623,7 @@ <string name="package_installed_device_owner" msgid="6875717669960212648">"Instalado pelo seu administrador"</string> <string name="package_updated_device_owner" msgid="1847154566357862089">"Atualizado pelo seu administrador"</string> <string name="package_deleted_device_owner" msgid="2307122077550236438">"Excluído pelo seu administrador"</string> - <string name="battery_saver_description" msgid="1960431123816253034">"A economia de bateria reduz o desempenho e os limites de vibração do dispositivo, os serviços de localização e a maioria dos dados em segundo plano para aumentar a duração da bateria. E-mails, mensagens e outros aplicativos que dependem de sincronização não serão atualizados, a não ser que você os abra.\n\nA economia de bateria é desligada automaticamente quando o dispositivo está sendo carregado."</string> + <string name="battery_saver_description" msgid="1960431123816253034">"A economia de bateria reduz o desempenho do dispositivo e limita a vibração, os serviços de localização e a maioria dos dados em segundo plano para aumentar a duração da bateria. E-mails, mensagens e outros apps que dependem de sincronização não serão atualizados, a não ser que você os abra.\n\nA economia de bateria é desligada automaticamente quando o dispositivo está sendo carregado."</string> <string name="data_saver_description" msgid="6015391409098303235">"Para ajudar a reduzir o uso de dados, a Economia de dados impede que alguns apps enviem ou recebam dados em segundo plano. Um app que você esteja usando no momento pode acessar dados, mas com menos frequência. Isso pode significar que as imagens não serão exibidas até que você toque nelas."</string> <string name="data_saver_enable_title" msgid="4674073932722787417">"Ativar Economia de dados?"</string> <string name="data_saver_enable_button" msgid="7147735965247211818">"Ativar"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index c74178d890b7..0d5e332693e0 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1623,7 +1623,7 @@ <string name="package_installed_device_owner" msgid="6875717669960212648">"Instalado pelo seu administrador"</string> <string name="package_updated_device_owner" msgid="1847154566357862089">"Atualizado pelo seu administrador"</string> <string name="package_deleted_device_owner" msgid="2307122077550236438">"Excluído pelo seu administrador"</string> - <string name="battery_saver_description" msgid="1960431123816253034">"A economia de bateria reduz o desempenho e os limites de vibração do dispositivo, os serviços de localização e a maioria dos dados em segundo plano para aumentar a duração da bateria. E-mails, mensagens e outros aplicativos que dependem de sincronização não serão atualizados, a não ser que você os abra.\n\nA economia de bateria é desligada automaticamente quando o dispositivo está sendo carregado."</string> + <string name="battery_saver_description" msgid="1960431123816253034">"A economia de bateria reduz o desempenho do dispositivo e limita a vibração, os serviços de localização e a maioria dos dados em segundo plano para aumentar a duração da bateria. E-mails, mensagens e outros apps que dependem de sincronização não serão atualizados, a não ser que você os abra.\n\nA economia de bateria é desligada automaticamente quando o dispositivo está sendo carregado."</string> <string name="data_saver_description" msgid="6015391409098303235">"Para ajudar a reduzir o uso de dados, a Economia de dados impede que alguns apps enviem ou recebam dados em segundo plano. Um app que você esteja usando no momento pode acessar dados, mas com menos frequência. Isso pode significar que as imagens não serão exibidas até que você toque nelas."</string> <string name="data_saver_enable_title" msgid="4674073932722787417">"Ativar Economia de dados?"</string> <string name="data_saver_enable_button" msgid="7147735965247211818">"Ativar"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 56349891e49d..d2f7b5a37c5f 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -210,7 +210,7 @@ <string name="reboot_to_update_prepare" msgid="6305853831955310890">"Подготовка обновлений…"</string> <string name="reboot_to_update_package" msgid="3871302324500927291">"Обработка обновлений…"</string> <string name="reboot_to_update_reboot" msgid="6428441000951565185">"Перезагрузка…"</string> - <string name="reboot_to_reset_title" msgid="4142355915340627490">"Сброс к заводским настройкам"</string> + <string name="reboot_to_reset_title" msgid="4142355915340627490">"Сбросить к заводским настройкам"</string> <string name="reboot_to_reset_message" msgid="2432077491101416345">"Перезагрузка…"</string> <string name="shutdown_progress" msgid="2281079257329981203">"Выключение..."</string> <string name="shutdown_confirm" product="tablet" msgid="3385745179555731470">"Планшетный ПК будет отключен."</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 76dc06ff15db..d1e152806236 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -90,7 +90,7 @@ <string name="serviceNotProvisioned" msgid="8614830180508686666">"సేవ కేటాయించబడలేదు."</string> <string name="CLIRPermanent" msgid="3377371145926835671">"మీరు కాలర్ ID సెట్టింగ్ను మార్చలేరు."</string> <string name="RestrictedOnDataTitle" msgid="1322504692764166532">"డేటా సేవ లేదు"</string> - <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"అత్యవసర కాలింగ్ లేదు"</string> + <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"అత్యవసర కాలింగ్ సదుపాయం లేదు"</string> <string name="RestrictedOnNormalTitle" msgid="3179574012752700984">"వాయిస్ సేవ లేదు"</string> <string name="RestrictedOnAllVoiceTitle" msgid="158800171499150681">"వాయిస్/అత్యవసర సేవ లేదు"</string> <string name="RestrictedStateContent" msgid="4278821484643362350">"మీ స్థానంలో మొబైల్ నెట్వర్క్ ద్వారా తాత్కాలికంగా అందించబడదు"</string> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 76cee70fa0e8..062da9584305 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2968,6 +2968,8 @@ <!-- Name of a font family to use for headlines. If empty, falls back to platform default --> <string name="config_headlineFontFamily" translatable="false"></string> + <!-- Name of a font family to use for headlines. Defaults to sans-serif-light --> + <string name="config_headlineFontFamilyLight" translatable="false">sans-serif-light</string> <!-- An array of packages that need to be treated as type system in battery settings --> <string-array translatable="false" name="config_batteryPackageTypeSystem"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 241886c97ec1..733890232c37 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3041,6 +3041,7 @@ <java-symbol type="bool" name="config_dozeAlwaysOnDisplayAvailable" /> <java-symbol type="integer" name="config_storageManagerDaystoRetainDefault" /> <java-symbol type="string" name="config_headlineFontFamily" /> + <java-symbol type="string" name="config_headlineFontFamilyLight" /> <java-symbol type="drawable" name="stat_sys_vitals" /> diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index d154730058ee..df2b35b39aa8 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -32,7 +32,8 @@ namespace uirenderer { OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t height) { LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer..."); - OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height); + OffscreenBuffer* buffer = mRenderState.layerPool().get( + mRenderState, width, height, mWideColorGamut); startRepaintLayer(buffer, Rect(width, height)); return buffer; } @@ -103,7 +104,8 @@ void BakedOpRenderer::endLayer() { OffscreenBuffer* BakedOpRenderer::copyToLayer(const Rect& area) { const uint32_t width = area.getWidth(); const uint32_t height = area.getHeight(); - OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height); + OffscreenBuffer* buffer = mRenderState.layerPool().get( + mRenderState, width, height, mWideColorGamut); if (!area.isEmpty() && width != 0 && height != 0) { mCaches.textureState().activateTexture(0); mCaches.textureState().bindTexture(buffer->texture.id()); diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h index 4d76a3df7a62..01ca36742d24 100644 --- a/libs/hwui/BakedOpRenderer.h +++ b/libs/hwui/BakedOpRenderer.h @@ -54,12 +54,13 @@ public: uint8_t spotShadowAlpha; }; - BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, + BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, bool wideColorGamut, const LightInfo& lightInfo) : mGlopReceiver(DefaultGlopReceiver) , mRenderState(renderState) , mCaches(caches) , mOpaque(opaque) + , mWideColorGamut(wideColorGamut) , mLightInfo(lightInfo) { } @@ -118,6 +119,7 @@ private: RenderState& mRenderState; Caches& mCaches; bool mOpaque; + bool mWideColorGamut; bool mHasDrawn = false; // render target state - setup by start/end layer/frame diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp index 4ef0f4bd2712..b073070b58b6 100644 --- a/libs/hwui/OpenGLReadback.cpp +++ b/libs/hwui/OpenGLReadback.cpp @@ -133,8 +133,7 @@ inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState, return CopyResult::DestinationInvalid; } - // TODO: Add support for RGBA_F16 destinations - if (bitmap->colorType() == kRGBA_F16_SkColorType) { + if (bitmap->colorType() == kRGBA_F16_SkColorType && !caches.extensions().hasFloatTextures()) { ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported"); return CopyResult::DestinationInvalid; } @@ -148,24 +147,34 @@ inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState, GLuint texture; GLenum format; + GLenum internalFormat; GLenum type; switch (bitmap->colorType()) { case kAlpha_8_SkColorType: format = GL_ALPHA; + internalFormat = GL_ALPHA; type = GL_UNSIGNED_BYTE; break; case kRGB_565_SkColorType: format = GL_RGB; + internalFormat = GL_RGB; type = GL_UNSIGNED_SHORT_5_6_5; break; case kARGB_4444_SkColorType: format = GL_RGBA; + internalFormat = GL_RGBA; type = GL_UNSIGNED_SHORT_4_4_4_4; break; + case kRGBA_F16_SkColorType: + format = GL_RGBA; + internalFormat = GL_RGBA16F; + type = GL_HALF_FLOAT; + break; case kN32_SkColorType: default: format = GL_RGBA; + internalFormat = GL_RGBA; type = GL_UNSIGNED_BYTE; break; } @@ -184,7 +193,7 @@ inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState, glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, format, destWidth, destHeight, + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, destWidth, destHeight, 0, format, type, nullptr); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); @@ -220,6 +229,7 @@ inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState, ortho.loadOrtho(destWidth, destHeight); renderState.render(glop, ortho); + // TODO: We should convert to linear space when the target is RGBA16F glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, type, bitmap->getPixels()); } diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index aad81df0b7e5..b5872485b136 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -69,6 +69,7 @@ bool Properties::waitForGpuCompletion = false; bool Properties::forceDrawFrame = false; bool Properties::filterOutTestOverhead = false; +bool Properties::disableVsync = false; static int property_get_int(const char* key, int defaultValue) { char buf[PROPERTY_VALUE_MAX] = {'\0',}; diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 9db64493928a..91b4a2d440e2 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -318,6 +318,12 @@ public: // any overhead they add static bool filterOutTestOverhead; + // Workaround a device lockup in edge cases by switching to async mode + // instead of the default vsync (b/38372997). Only system_server should hit this. + // Any existing RenderProxy & Surface combination will be unaffected, only things + // created after changing this. + static bool disableVsync; + // Used for testing only to change the render pipeline. #ifdef HWUI_GLES_WRAP_ENABLED static void overrideRenderPipelineType(RenderPipelineType); diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 959059fede0c..4ef31d59271e 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -120,6 +120,10 @@ void Texture::resetCachedParams() { void Texture::upload(GLint internalFormat, uint32_t width, uint32_t height, GLenum format, GLenum type, const void* pixels) { GL_CHECKPOINT(MODERATE); + + // We don't have color space information, we assume the data is gamma encoded + mIsLinear = false; + bool needsAlloc = updateLayout(width, height, internalFormat, format, GL_TEXTURE_2D); if (!mId) { glGenTextures(1, &mId); @@ -309,11 +313,16 @@ void Texture::upload(Bitmap& bitmap) { bool rgba16fNeedsConversion = bitmap.colorType() == kRGBA_F16_SkColorType && internalFormat != GL_RGBA16F; + // RGBA16F is always linear extended sRGB + if (internalFormat == GL_RGBA16F) { + mIsLinear = true; + } + mConnector.reset(); - // RGBA16F is always extended sRGB, alpha masks don't have color profiles + // Alpha masks don't have color profiles // If an RGBA16F bitmap needs conversion, we know the target will be sRGB - if (internalFormat != GL_RGBA16F && internalFormat != GL_ALPHA && !rgba16fNeedsConversion) { + if (!mIsLinear && internalFormat != GL_ALPHA && !rgba16fNeedsConversion) { SkColorSpace* colorSpace = bitmap.info().colorSpace(); // If the bitmap is sRGB we don't need conversion if (colorSpace != nullptr && !colorSpace->isSRGB()) { diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h index 55b74ed9e234..7f742e604838 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -88,7 +88,8 @@ public: * The image data is undefined after calling this. */ void resize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) { - upload(internalFormat, width, height, format, GL_UNSIGNED_BYTE, nullptr); + upload(internalFormat, width, height, format, + internalFormat == GL_RGBA16F ? GL_HALF_FLOAT : GL_UNSIGNED_BYTE, nullptr); } /** @@ -155,7 +156,7 @@ public: * Returns true if this texture uses a linear encoding format. */ constexpr bool isLinear() const { - return mInternalFormat == GL_RGBA16F; + return mIsLinear; } /** @@ -219,6 +220,9 @@ private: GLenum mMinFilter = GL_NEAREST_MIPMAP_LINEAR; GLenum mMagFilter = GL_LINEAR; + // Indicates whether the content of the texture is in linear space + bool mIsLinear = false; + Caches& mCaches; std::unique_ptr<ColorSpaceConnector> mConnector; diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 095d32e75911..6d5ef1d4ff1f 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -61,7 +61,7 @@ bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, + const Rect& contentDrawBounds, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo, const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) { @@ -85,7 +85,8 @@ bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, mRenderThread.getGrContext(), renderTargetDesc, &props)); SkiaPipeline::updateLighting(lightGeometry, lightInfo); - renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut, + contentDrawBounds, surface); layerUpdateQueue->clear(); // Draw visual debugging features diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index d5471de06aa3..aa29c8e3babc 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -35,7 +35,7 @@ public: bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, + const Rect& contentDrawBounds, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo, const std::vector< sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler) override; diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index bbbbd5ce51b2..0bab7932432c 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -72,16 +72,18 @@ void SkiaPipeline::unpinImages() { } void SkiaPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, bool opaque, + LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo) { updateLighting(lightGeometry, lightInfo); ATRACE_NAME("draw layers"); renderVectorDrawableCache(); - renderLayersImpl(*layerUpdateQueue, opaque); + renderLayersImpl(*layerUpdateQueue, opaque, wideColorGamut); layerUpdateQueue->clear(); } -void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) { +void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, + bool opaque, bool wideColorGamut) { + // TODO: Handle wide color gamut // Render all layers that need to be updated, in order. for (size_t i = 0; i < layers.entries().size(); i++) { RenderNode* layerNode = layers.entries()[i].renderNode.get(); @@ -129,12 +131,13 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) } bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, - const DamageAccumulator& damageAccumulator) { + const DamageAccumulator& damageAccumulator, bool wideColorGamut) { SkSurface* layer = node->getLayerSurface(); if (!layer || layer->width() != node->getWidth() || layer->height() != node->getHeight()) { SkImageInfo info = SkImageInfo::MakeN32Premul(node->getWidth(), node->getHeight()); SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); + // TODO: Handle wide color gamut requests node->setLayerSurface( SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, info, 0, &props)); @@ -203,13 +206,13 @@ void SkiaPipeline::renderVectorDrawableCache() { } void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, - const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds, - sk_sp<SkSurface> surface) { + const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut, + const Rect &contentDrawBounds, sk_sp<SkSurface> surface) { renderVectorDrawableCache(); // draw all layers up front - renderLayersImpl(layers, opaque); + renderLayersImpl(layers, opaque, wideColorGamut); // initialize the canvas for the current frame SkCanvas* canvas = surface->getCanvas(); @@ -227,7 +230,7 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli } } - renderFrameImpl(layers, clip, nodes, opaque, contentDrawBounds, canvas); + renderFrameImpl(layers, clip, nodes, opaque, wideColorGamut, contentDrawBounds, canvas); if (skpCaptureEnabled() && recordingPicture) { sk_sp<SkPicture> picture = recorder->finishRecordingAsPicture(); @@ -260,8 +263,8 @@ static Rect nodeBounds(RenderNode& node) { } void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip, - const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds, - SkCanvas* canvas) { + const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut, + const Rect &contentDrawBounds, SkCanvas* canvas) { SkAutoCanvasRestore saver(canvas, true); canvas->androidFramework_setDeviceClipRestriction(clip.roundOut()); @@ -388,7 +391,7 @@ void SkiaPipeline::renderOverdraw(const LayerUpdateQueue& layers, const SkRect& // each time a pixel would have been drawn. // Pass true for opaque so we skip the clear - the overdrawCanvas is already zero // initialized. - renderFrameImpl(layers, clip, nodes, true, contentDrawBounds, &overdrawCanvas); + renderFrameImpl(layers, clip, nodes, true, false, contentDrawBounds, &overdrawCanvas); sk_sp<SkImage> counts = offscreen->makeImageSnapshot(); // Draw overdraw colors to the canvas. The color filter will convert counts to colors. diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index 6f5e719fc2c2..19ffc463c121 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -39,15 +39,15 @@ public: void unpinImages() override; void renderLayers(const FrameBuilder::LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, bool opaque, + LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo) override; bool createOrUpdateLayer(RenderNode* node, - const DamageAccumulator& damageAccumulator) override; + const DamageAccumulator& damageAccumulator, bool wideColorGamut) override; void renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, - const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds, - sk_sp<SkSurface> surface); + const std::vector< sp<RenderNode> >& nodes, bool opaque, bool wideColorGamut, + const Rect &contentDrawBounds, sk_sp<SkSurface> surface); std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; } @@ -55,7 +55,7 @@ public: static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap); - static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque); + static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut); static bool skpCaptureEnabled() { return false; } @@ -110,8 +110,8 @@ protected: private: void renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip, - const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds, - SkCanvas* canvas); + const std::vector< sp<RenderNode> >& nodes, bool opaque, bool wideColorGamut, + const Rect &contentDrawBounds, SkCanvas* canvas); /** * Debugging feature. Draws a semi-transparent overlay on each pixel, indicating diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index f1ef9e60f997..e1ef71f7d3ab 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -66,7 +66,7 @@ bool SkiaVulkanPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, + const Rect& contentDrawBounds, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo, const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) { @@ -76,7 +76,8 @@ bool SkiaVulkanPipeline::draw(const Frame& frame, const SkRect& screenDirty, return false; } SkiaPipeline::updateLighting(lightGeometry, lightInfo); - renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer); + renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut, + contentDrawBounds, backBuffer); layerUpdateQueue->clear(); // Draw visual debugging features diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 3481312d3b21..263206d97571 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -33,7 +33,7 @@ public: bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, + const Rect& contentDrawBounds, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo, const std::vector< sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler) override; diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp index a9bbb273dbb5..90b27c8d8fb0 100644 --- a/libs/hwui/renderstate/OffscreenBufferPool.cpp +++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp @@ -35,17 +35,19 @@ namespace uirenderer { //////////////////////////////////////////////////////////////////////////////// OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches, - uint32_t viewportWidth, uint32_t viewportHeight) + uint32_t viewportWidth, uint32_t viewportHeight, bool wideColorGamut) : GpuMemoryTracker(GpuObjectType::OffscreenBuffer) , renderState(renderState) , viewportWidth(viewportWidth) , viewportHeight(viewportHeight) - , texture(caches) { + , texture(caches) + , wideColorGamut(wideColorGamut) { uint32_t width = computeIdealDimension(viewportWidth); uint32_t height = computeIdealDimension(viewportHeight); ATRACE_FORMAT("Allocate %ux%u HW Layer", width, height); caches.textureState().activateTexture(0); - texture.resize(width, height, caches.rgbaInternalFormat(), GL_RGBA); + texture.resize(width, height, + wideColorGamut ? GL_RGBA16F : caches.rgbaInternalFormat(), GL_RGBA); texture.blend = true; texture.setWrap(GL_CLAMP_TO_EDGE); // not setting filter on texture, since it's set when drawing, based on transform @@ -127,7 +129,10 @@ int OffscreenBufferPool::Entry::compare(const Entry& lhs, const Entry& rhs) { int deltaInt = int(lhs.width) - int(rhs.width); if (deltaInt != 0) return deltaInt; - return int(lhs.height) - int(rhs.height); + deltaInt = int(lhs.height) - int(rhs.height); + if (deltaInt != 0) return deltaInt; + + return int(lhs.wideColorGamut) - int(rhs.wideColorGamut); } void OffscreenBufferPool::clear() { @@ -139,10 +144,10 @@ void OffscreenBufferPool::clear() { } OffscreenBuffer* OffscreenBufferPool::get(RenderState& renderState, - const uint32_t width, const uint32_t height) { + const uint32_t width, const uint32_t height, bool wideColorGamut) { OffscreenBuffer* layer = nullptr; - Entry entry(width, height); + Entry entry(width, height, wideColorGamut); auto iter = mPool.find(entry); if (iter != mPool.end()) { @@ -154,7 +159,8 @@ OffscreenBuffer* OffscreenBufferPool::get(RenderState& renderState, layer->viewportHeight = height; mSize -= layer->getSizeInBytes(); } else { - layer = new OffscreenBuffer(renderState, Caches::getInstance(), width, height); + layer = new OffscreenBuffer(renderState, Caches::getInstance(), + width, height, wideColorGamut); } return layer; @@ -174,7 +180,7 @@ OffscreenBuffer* OffscreenBufferPool::resize(OffscreenBuffer* layer, return layer; } putOrDelete(layer); - return get(renderState, width, height); + return get(renderState, width, height, layer->wideColorGamut); } void OffscreenBufferPool::dump() { diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h index 26d4e3654a48..d9422c9edd69 100644 --- a/libs/hwui/renderstate/OffscreenBufferPool.h +++ b/libs/hwui/renderstate/OffscreenBufferPool.h @@ -43,7 +43,7 @@ class RenderState; class OffscreenBuffer : GpuMemoryTracker { public: OffscreenBuffer(RenderState& renderState, Caches& caches, - uint32_t viewportWidth, uint32_t viewportHeight); + uint32_t viewportWidth, uint32_t viewportHeight, bool wideColorGamut = false); ~OffscreenBuffer(); Rect getTextureCoordinates(); @@ -68,6 +68,8 @@ public: uint32_t viewportHeight; Texture texture; + bool wideColorGamut = false; + // Portion of layer that has been drawn to. Used to minimize drawing area when // drawing back to screen / parent FBO. Region region; @@ -90,7 +92,7 @@ public: ~OffscreenBufferPool(); WARN_UNUSED_RESULT OffscreenBuffer* get(RenderState& renderState, - const uint32_t width, const uint32_t height); + const uint32_t width, const uint32_t height, bool wideColorGamut = false); WARN_UNUSED_RESULT OffscreenBuffer* resize(OffscreenBuffer* layer, const uint32_t width, const uint32_t height); @@ -122,14 +124,16 @@ private: struct Entry { Entry() {} - Entry(const uint32_t layerWidth, const uint32_t layerHeight) + Entry(const uint32_t layerWidth, const uint32_t layerHeight, bool wideColorGamut) : width(OffscreenBuffer::computeIdealDimension(layerWidth)) - , height(OffscreenBuffer::computeIdealDimension(layerHeight)) {} + , height(OffscreenBuffer::computeIdealDimension(layerHeight)) + , wideColorGamut(wideColorGamut) {} explicit Entry(OffscreenBuffer* layer) : layer(layer) , width(layer->texture.width()) - , height(layer->texture.height()) { + , height(layer->texture.height()) + , wideColorGamut(layer->wideColorGamut) { } static int compare(const Entry& lhs, const Entry& rhs); @@ -149,6 +153,7 @@ private: OffscreenBuffer* layer = nullptr; uint32_t width = 0; uint32_t height = 0; + bool wideColorGamut = false; }; // struct Entry std::multiset<Entry> mPool; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index a79bf359913b..779924883016 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -421,7 +421,7 @@ void CanvasContext::draw() { SkRect windowDirty = computeDirtyRect(frame, &dirty); bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, - mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes, &(profiler())); + mContentDrawBounds, mOpaque, mWideColorGamut, mLightInfo, mRenderNodes, &(profiler())); waitOnFences(); @@ -563,7 +563,8 @@ void CanvasContext::buildLayer(RenderNode* node) { // purposes when the frame is actually drawn node->setPropertyFieldsDirty(RenderNode::GENERIC); - mRenderPipeline->renderLayers(mLightGeometry, &mLayerUpdateQueue, mOpaque, mLightInfo); + mRenderPipeline->renderLayers(mLightGeometry, &mLayerUpdateQueue, + mOpaque, mWideColorGamut, mLightInfo); node->incStrong(nullptr); mPrefetchedLayers.insert(node); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 76623f9ae954..b1f405040a59 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -76,7 +76,7 @@ public: * @return true if the layer has been created or updated */ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& dmgAccumulator) { - return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator); + return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator, mWideColorGamut); } /** diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index ecf686c5b40c..d6240e7064cf 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -357,6 +357,9 @@ bool EglManager::makeCurrent(EGLSurface surface, EGLint* errOut) { } } mCurrentSurface = surface; + if (Properties::disableVsync) { + eglSwapInterval(mEglDisplay, 0); + } return true; } diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index 46ac0d23637d..f9b6e384d211 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -59,7 +59,7 @@ public: virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, + const Rect& contentDrawBounds, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo, const std::vector< sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler) = 0; @@ -73,11 +73,11 @@ public: virtual bool isContextReady() = 0; virtual void onDestroyHardwareResources() = 0; virtual void renderLayers(const FrameBuilder::LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, bool opaque, + LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo) = 0; virtual TaskManager* getTaskManager() = 0; virtual bool createOrUpdateLayer(RenderNode* node, - const DamageAccumulator& damageAccumulator) = 0; + const DamageAccumulator& damageAccumulator, bool wideColorGamut) = 0; virtual bool pinImages(std::vector<SkImage*>& mutableImages) = 0; virtual bool pinImages(LsaVector<sk_sp<Bitmap>>& images) = 0; virtual void unpinImages() = 0; diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp index e15d0eb33403..7283eb123d6a 100644 --- a/libs/hwui/renderthread/OpenGLPipeline.cpp +++ b/libs/hwui/renderthread/OpenGLPipeline.cpp @@ -58,7 +58,7 @@ Frame OpenGLPipeline::getFrame() { bool OpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, + const Rect& contentDrawBounds, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo, const std::vector< sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler) { @@ -77,7 +77,7 @@ bool OpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const S frameBuilder.deferRenderNodeScene(renderNodes, contentDrawBounds); BakedOpRenderer renderer(caches, mRenderThread.renderState(), - opaque, lightInfo); + opaque, wideColorGamut, lightInfo); frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); ProfileRenderer profileRenderer(renderer); profiler->draw(profileRenderer); @@ -184,14 +184,14 @@ void OpenGLPipeline::onDestroyHardwareResources() { } void OpenGLPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, bool opaque, + LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo) { static const std::vector< sp<RenderNode> > emptyNodeList; auto& caches = Caches::getInstance(); FrameBuilder frameBuilder(*layerUpdateQueue, lightGeometry, caches); layerUpdateQueue->clear(); - BakedOpRenderer renderer(caches, mRenderThread.renderState(), - opaque, lightInfo); + // TODO: Handle wide color gamut contexts + BakedOpRenderer renderer(caches, mRenderThread.renderState(), opaque, wideColorGamut, lightInfo); LOG_ALWAYS_FATAL_IF(renderer.didDraw(), "shouldn't draw in buildlayer case"); frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); } @@ -205,12 +205,13 @@ static bool layerMatchesWH(OffscreenBuffer* layer, int width, int height) { } bool OpenGLPipeline::createOrUpdateLayer(RenderNode* node, - const DamageAccumulator& damageAccumulator) { + const DamageAccumulator& damageAccumulator, bool wideColorGamut) { RenderState& renderState = mRenderThread.renderState(); OffscreenBufferPool& layerPool = renderState.layerPool(); bool transformUpdateNeeded = false; if (node->getLayer() == nullptr) { - node->setLayer(layerPool.get(renderState, node->getWidth(), node->getHeight())); + node->setLayer(layerPool.get(renderState, + node->getWidth(), node->getHeight(), wideColorGamut)); transformUpdateNeeded = true; } else if (!layerMatchesWH(node->getLayer(), node->getWidth(), node->getHeight())) { // TODO: remove now irrelevant, currently enqueued damage (respecting damage ordering) diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h index 0e8c3f553bc7..4ca19fb6245c 100644 --- a/libs/hwui/renderthread/OpenGLPipeline.h +++ b/libs/hwui/renderthread/OpenGLPipeline.h @@ -36,7 +36,7 @@ public: bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, + const Rect& contentDrawBounds, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo, const std::vector< sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler) override; @@ -50,11 +50,11 @@ public: bool isContextReady() override; void onDestroyHardwareResources() override; void renderLayers(const FrameBuilder::LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, bool opaque, + LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo) override; TaskManager* getTaskManager() override; bool createOrUpdateLayer(RenderNode* node, - const DamageAccumulator& damageAccumulator) override; + const DamageAccumulator& damageAccumulator, bool wideColorGamut) override; bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; } bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override; void unpinImages() override; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index ec56c313f62a..80c2955400d8 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -18,6 +18,7 @@ #include "DeferredLayerUpdater.h" #include "DisplayList.h" +#include "Properties.h" #include "Readback.h" #include "Rect.h" #include "renderthread/CanvasContext.h" @@ -709,6 +710,10 @@ void RenderProxy::onBitmapDestroyed(uint32_t pixelRefId) { thread.queue(task); } +void RenderProxy::disableVsync() { + Properties::disableVsync = true; +} + void RenderProxy::post(RenderTask* task) { mRenderThread.queue(task); } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index e1e2808e734f..31f0ce67e1a7 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -138,6 +138,8 @@ public: static int copyGraphicBufferInto(GraphicBuffer* buffer, SkBitmap* bitmap); static void onBitmapDestroyed(uint32_t pixelRefId); + + ANDROID_API static void disableVsync(); private: RenderThread& mRenderThread; CanvasContext* mContext; diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp index 398e7a89be1e..a5e85df22c8e 100644 --- a/libs/hwui/tests/microbench/FrameBuilderBench.cpp +++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp @@ -83,7 +83,7 @@ void BM_FrameBuilder_deferAndRender(benchmark::State& state) { sLightGeometry, caches); frameBuilder.deferRenderNode(*node); - BakedOpRenderer renderer(caches, renderState, true, sLightInfo); + BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo); frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); benchmark::DoNotOptimize(&renderer); } @@ -142,7 +142,7 @@ void BM_FrameBuilder_deferAndRender_scene(benchmark::State& state) { sLightGeometry, Caches::getInstance()); frameBuilder.deferRenderNode(*node); - BakedOpRenderer renderer(caches, renderState, true, sLightInfo); + BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo); frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); benchmark::DoNotOptimize(&renderer); } diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp index c46592c8867a..b0ef11f26bdd 100644 --- a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp +++ b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp @@ -37,7 +37,7 @@ const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50}; class ValidatingBakedOpRenderer : public BakedOpRenderer { public: ValidatingBakedOpRenderer(RenderState& renderState, std::function<void(const Glop& glop)> validator) - : BakedOpRenderer(Caches::getInstance(), renderState, true, sLightInfo) + : BakedOpRenderer(Caches::getInstance(), renderState, true, false, sLightInfo) , mValidator(validator) { mGlopReceiver = ValidatingGlopReceiver; } diff --git a/libs/hwui/tests/unit/BakedOpRendererTests.cpp b/libs/hwui/tests/unit/BakedOpRendererTests.cpp index 380062a36d45..603599ceb88a 100644 --- a/libs/hwui/tests/unit/BakedOpRendererTests.cpp +++ b/libs/hwui/tests/unit/BakedOpRendererTests.cpp @@ -24,7 +24,8 @@ using namespace android::uirenderer; const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 }; RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, startRepaintLayer_clear) { - BakedOpRenderer renderer(Caches::getInstance(), renderThread.renderState(), true, sLightInfo); + BakedOpRenderer renderer(Caches::getInstance(), renderThread.renderState(), + true, false, sLightInfo); OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200u, 200u); layer.dirty(Rect(200, 200)); diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp index 6c42ca1f2c2e..19d7ef59e397 100644 --- a/libs/hwui/tests/unit/LeakCheckTests.cpp +++ b/libs/hwui/tests/unit/LeakCheckTests.cpp @@ -45,7 +45,7 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(LeakCheck, saveLayer_overdrawRejection) { FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometery, Caches::getInstance()); frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - BakedOpRenderer renderer(caches, renderState, true, sLightInfo); + BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo); frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); } @@ -62,6 +62,6 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(LeakCheck, saveLayerUnclipped_simple) { FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometery, Caches::getInstance()); frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - BakedOpRenderer renderer(caches, renderState, true, sLightInfo); + BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo); frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); } diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp index 6cd595af6d2f..919852f6b2d7 100644 --- a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp +++ b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp @@ -41,6 +41,19 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, construct) { EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes()); } +RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, constructWideColorGamut) { + OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 49u, 149u, true); + EXPECT_EQ(49u, layer.viewportWidth); + EXPECT_EQ(149u, layer.viewportHeight); + + EXPECT_EQ(64u, layer.texture.width()); + EXPECT_EQ(192u, layer.texture.height()); + + EXPECT_TRUE(layer.wideColorGamut); + + EXPECT_EQ(64u * 192u * 8u, layer.getSizeInBytes()); +} + RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, getTextureCoordinates) { OffscreenBuffer layerAligned(renderThread.renderState(), Caches::getInstance(), 256u, 256u); EXPECT_EQ(Rect(0, 1, 1, 0), @@ -88,6 +101,47 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, getPutClear) { EXPECT_EQ(0u, pool.getCount()); } +RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, getPutClearWideColorGamut) { + OffscreenBufferPool pool; + + auto layer = pool.get(renderThread.renderState(), 100u, 200u, true); + EXPECT_EQ(100u, layer->viewportWidth); + EXPECT_EQ(200u, layer->viewportHeight); + EXPECT_TRUE(layer->wideColorGamut); + + ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize()); + + pool.putOrDelete(layer); + ASSERT_EQ(layer->getSizeInBytes(), pool.getSize()); + + auto layer2 = pool.get(renderThread.renderState(), 102u, 202u, true); + EXPECT_EQ(layer, layer2) << "layer should be recycled"; + ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer"; + + pool.putOrDelete(layer2); + EXPECT_EQ(1u, pool.getCount()); + pool.clear(); + EXPECT_EQ(0u, pool.getSize()); + EXPECT_EQ(0u, pool.getCount()); + + // add non wide gamut layer + auto layer3 = pool.get(renderThread.renderState(), 100u, 200u); + EXPECT_FALSE(layer3->wideColorGamut); + pool.putOrDelete(layer3); + EXPECT_EQ(1u, pool.getCount()); + + auto layer4 = pool.get(renderThread.renderState(), 100u, 200u, true); + EXPECT_TRUE(layer4->wideColorGamut); + EXPECT_EQ(1u, pool.getCount()); + ASSERT_NE(layer3, layer4); + + pool.putOrDelete(layer4); + + pool.clear(); + EXPECT_EQ(0u, pool.getSize()); + EXPECT_EQ(0u, pool.getCount()); +} + RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resize) { OffscreenBufferPool pool; @@ -123,6 +177,43 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resize) { pool.putOrDelete(layer2); } +RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resizeWideColorGamut) { + OffscreenBufferPool pool; + + auto layer = pool.get(renderThread.renderState(), 64u, 64u, true); + + // resize in place + ASSERT_EQ(layer, pool.resize(layer, 60u, 55u)); + EXPECT_EQ(60u, layer->viewportWidth); + EXPECT_EQ(55u, layer->viewportHeight); + EXPECT_EQ(64u, layer->texture.width()); + EXPECT_EQ(64u, layer->texture.height()); + + EXPECT_TRUE(layer->wideColorGamut); + EXPECT_EQ(64u * 64u * 8u, layer->getSizeInBytes()); + + // resized to use different object in pool + auto layer2 = pool.get(renderThread.renderState(), 128u, 128u, true); + pool.putOrDelete(layer2); + ASSERT_EQ(1u, pool.getCount()); + + // add a non-wide gamut layer + auto layer3 = pool.get(renderThread.renderState(), 128u, 128u); + pool.putOrDelete(layer3); + ASSERT_EQ(2u, pool.getCount()); + + ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u)); + EXPECT_EQ(120u, layer2->viewportWidth); + EXPECT_EQ(125u, layer2->viewportHeight); + EXPECT_EQ(128u, layer2->texture.width()); + EXPECT_EQ(128u, layer2->texture.height()); + + EXPECT_TRUE(layer2->wideColorGamut); + EXPECT_EQ(128u * 128u * 8u, layer2->getSizeInBytes()); + + pool.putOrDelete(layer2); +} + RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, putAndDestroy) { OffscreenBufferPool pool; // layer too big to return to the pool @@ -153,3 +244,4 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, clear) { EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer)); } + diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index 686d06f9cd85..4c3e182ced2f 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -450,7 +450,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) { LayerUpdateQueue layerUpdateQueue; layerUpdateQueue.enqueueLayerWithDamage(child.get(), android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT)); - SkiaPipeline::renderLayersImpl(layerUpdateQueue, true); + SkiaPipeline::renderLayersImpl(layerUpdateQueue, true, false); EXPECT_EQ(1, drawCounter); //assert index 0 is drawn on the layer RenderNodeDrawable drawable(parent.get(), surfaceLayer1->getCanvas(), true); diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index a895cbad838e..b397b151ad76 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -51,7 +51,8 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) { auto surface = SkSurface::MakeRasterN32Premul(1, 1); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, + opaque, false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); } @@ -72,10 +73,12 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) { auto surface = SkSurface::MakeRasterN32Premul(2, 2); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, + true, false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, + false, false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned int)SK_ColorTRANSPARENT); ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); } @@ -94,7 +97,8 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { auto surface = SkSurface::MakeRasterN32Premul(2, 2); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, + true, false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surface, 1, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorRED); @@ -135,7 +139,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) { lightGeometry.center = { 0.0f, 0.0f, 0.0f }; BakedOpRenderer::LightInfo lightInfo; auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); - pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, lightInfo); + pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, false, lightInfo); ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorRED); ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 1), SK_ColorWHITE); @@ -166,32 +170,38 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); // Single draw, should be white. - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, + false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE); // 1 Overdraw, should be blue blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, + false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffd0d0ff); // 2 Overdraw, should be green blended onto white renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, + false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffd0ffd0); // 3 Overdraw, should be pink blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, + false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffffc0c0); // 4 Overdraw, should be red blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, + false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffff8080); // 5 Overdraw, should be red blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, + false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffff8080); } @@ -278,7 +288,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) { SkRect dirty = SkRect::MakeWH(800, 600); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); sk_sp<DeferLayer<DeferTestCanvas>> surface(new DeferLayer<DeferTestCanvas>()); - pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false, contentDrawBounds, surface); EXPECT_EQ(4, surface->canvas()->mDrawCounter); } @@ -308,7 +318,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) { SkRect dirty = SkRect::MakeLTRB(10, 20, 30, 40); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); sk_sp<DeferLayer<ClippedTestCanvas>> surface(new DeferLayer<ClippedTestCanvas>()); - pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false, SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface); EXPECT_EQ(1, surface->canvas()->mDrawCounter); } @@ -339,7 +349,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) { SkRect dirty = SkRect::MakeLTRB(10, 10, 40, 40); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); sk_sp<DeferLayer<ClipReplaceTestCanvas>> surface(new DeferLayer<ClipReplaceTestCanvas>()); - pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false, SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface); EXPECT_EQ(1, surface->canvas()->mDrawCounter); } diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index e36ceb8a3e13..c1e81c54f117 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -194,6 +194,12 @@ public final class AudioAttributes implements Parcelable { * @see #SUPPRESSIBLE_USAGES */ public final static int SUPPRESSIBLE_CALL = 2; + /** + * @hide + * Denotes a usage that is never going to be muted, even in Total Silence. + * @see #SUPPRESSIBLE_USAGES + */ + public final static int SUPPRESSIBLE_NEVER = 3; /** * @hide @@ -211,6 +217,7 @@ public final class AudioAttributes implements Parcelable { SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_COMMUNICATION_INSTANT,SUPPRESSIBLE_NOTIFICATION); SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_COMMUNICATION_DELAYED,SUPPRESSIBLE_NOTIFICATION); SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_EVENT, SUPPRESSIBLE_NOTIFICATION); + SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_ACCESSIBILITY, SUPPRESSIBLE_NEVER); } /** diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 47ecf329058c..6ef3091dcc70 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -758,6 +758,8 @@ public class AudioSystem public static native int systemReady(); + public static native float getStreamVolumeDB(int stream, int index, int device); + // Items shared with audio service /** diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index 7f8140adaed2..92ffae0fe560 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -15,7 +15,9 @@ */ package android.media.soundtrigger; +import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR; +import android.app.PendingIntent; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -23,6 +25,10 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.hardware.soundtrigger.SoundTrigger; +import android.hardware.soundtrigger.SoundTrigger.SoundModel; +import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; +import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.os.Handler; import android.os.ParcelUuid; import android.os.RemoteException; @@ -178,4 +184,144 @@ public final class SoundTriggerManager { return mGenericSoundModel; } } + + + /** + * Default message type. + * @hide + */ + public static final int FLAG_MESSAGE_TYPE_UNKNOWN = -1; + /** + * Contents of EXTRA_MESSAGE_TYPE extra for a RecognitionEvent. + * @hide + */ + public static final int FLAG_MESSAGE_TYPE_RECOGNITION_EVENT = 0; + /** + * Contents of EXTRA_MESSAGE_TYPE extra for recognition error events. + * @hide + */ + public static final int FLAG_MESSAGE_TYPE_RECOGNITION_ERROR = 1; + /** + * Contents of EXTRA_MESSAGE_TYPE extra for a recognition paused events. + * @hide + */ + public static final int FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED = 2; + /** + * Contents of EXTRA_MESSAGE_TYPE extra for recognition resumed events. + * @hide + */ + public static final int FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED = 3; + + /** + * Extra key in the intent for the type of the message. + * @hide + */ + public static final String EXTRA_MESSAGE_TYPE = "android.media.soundtrigger.MESSAGE_TYPE"; + /** + * Extra key in the intent that holds the RecognitionEvent parcelable. + * @hide + */ + public static final String EXTRA_RECOGNITION_EVENT = "android.media.soundtrigger.RECOGNITION_EVENT"; + /** + * Extra key in the intent that holds the status in an error message. + * @hide + */ + public static final String EXTRA_STATUS = "android.media.soundtrigger.STATUS"; + + /** + * Loads a given sound model into the sound trigger. Note the model will be unloaded if there is + * an error/the system service is restarted. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) + public int loadSoundModel(SoundModel soundModel) { + if (soundModel == null) { + return STATUS_ERROR; + } + + try { + switch (soundModel.type) { + case SoundModel.TYPE_GENERIC_SOUND: + return mSoundTriggerService.loadGenericSoundModel( + (GenericSoundModel) soundModel); + case SoundModel.TYPE_KEYPHRASE: + return mSoundTriggerService.loadKeyphraseSoundModel( + (KeyphraseSoundModel) soundModel); + default: + Slog.e(TAG, "Unkown model type"); + return STATUS_ERROR; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Starts recognition on the given model id. All events from the model will be sent to the + * PendingIntent. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) + public int startRecognition(UUID soundModelId, PendingIntent callbackIntent, + RecognitionConfig config) { + if (soundModelId == null || callbackIntent == null || config == null) { + return STATUS_ERROR; + } + try { + return mSoundTriggerService.startRecognitionForIntent(new ParcelUuid(soundModelId), + callbackIntent, config); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Stops the given model's recognition. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) + public int stopRecognition(UUID soundModelId) { + if (soundModelId == null) { + return STATUS_ERROR; + } + try { + return mSoundTriggerService.stopRecognitionForIntent(new ParcelUuid(soundModelId)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes the given model from memory. Will also stop any pending recognitions. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) + public int unloadSoundModel(UUID soundModelId) { + if (soundModelId == null) { + return STATUS_ERROR; + } + try { + return mSoundTriggerService.unloadSoundModel( + new ParcelUuid(soundModelId)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns true if the given model has had detection started on it. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) + public boolean isRecognitionActive(UUID soundModelId) { + if (soundModelId == null) { + return false; + } + try { + return mSoundTriggerService.isRecognitionActive( + new ParcelUuid(soundModelId)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index 52fadfaad984..ff115f70fb43 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -176,6 +176,7 @@ BufferItem* JNIImageReaderContext::getBufferItem() { } void JNIImageReaderContext::returnBufferItem(BufferItem* buffer) { + buffer->mGraphicBuffer = nullptr; mBuffers.push_back(buffer); } diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index 22f53eabb99b..e38cca739253 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -57,6 +57,7 @@ import java.lang.InterruptedException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Random; +import java.util.concurrent.atomic.AtomicBoolean; public class CaptivePortalLoginActivity extends Activity { private static final String TAG = CaptivePortalLoginActivity.class.getSimpleName(); @@ -82,6 +83,8 @@ public class CaptivePortalLoginActivity extends Activity { private ConnectivityManager mCm; private boolean mLaunchBrowser = false; private MyWebViewClient mWebViewClient; + // Ensures that done() happens once exactly, handling concurrent callers with atomic operations. + private final AtomicBoolean isDone = new AtomicBoolean(false); @Override protected void onCreate(Bundle savedInstanceState) { @@ -178,13 +181,13 @@ public class CaptivePortalLoginActivity extends Activity { } private void done(Result result) { + if (isDone.getAndSet(true)) { + // isDone was already true: done() already called + return; + } if (DBG) { Log.d(TAG, String.format("Result %s for %s", result.name(), mUrl.toString())); } - if (mNetworkCallback != null) { - mCm.unregisterNetworkCallback(mNetworkCallback); - mNetworkCallback = null; - } logMetricsEvent(result.metricsEvent); switch (result) { case DISMISSED: @@ -244,8 +247,8 @@ public class CaptivePortalLoginActivity extends Activity { public void onDestroy() { super.onDestroy(); if (mNetworkCallback != null) { + // mNetworkCallback is not null if mUrl is not null. mCm.unregisterNetworkCallback(mNetworkCallback); - mNetworkCallback = null; } if (mLaunchBrowser) { // Give time for this network to become default. After 500ms just proceed. diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java index 2a4ab0f550e5..3b29a6cd7b6c 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java @@ -20,6 +20,8 @@ import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameI import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress; import static com.android.internal.util.ArrayUtils.isEmpty; +import static com.android.internal.util.CollectionUtils.emptyIfNull; +import static com.android.internal.util.CollectionUtils.size; import android.annotation.NonNull; import android.annotation.Nullable; @@ -154,6 +156,25 @@ public class DeviceDiscoveryService extends Service { onReadyToShowUI(); } + // If filtering to get single device by mac address, also search in the set of already + // bonded devices to allow linking those directly + String singleMacAddressFilter = null; + if (mRequest.isSingleDevice()) { + int numFilters = size(mBluetoothFilters); + for (int i = 0; i < numFilters; i++) { + BluetoothDeviceFilter filter = mBluetoothFilters.get(i); + if (!TextUtils.isEmpty(filter.getAddress())) { + singleMacAddressFilter = filter.getAddress(); + break; + } + } + } + if (singleMacAddressFilter != null) { + for (BluetoothDevice dev : emptyIfNull(mBluetoothAdapter.getBondedDevices())) { + onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters)); + } + } + if (shouldScan(mBluetoothFilters)) { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothDevice.ACTION_FOUND); @@ -211,6 +232,8 @@ public class DeviceDiscoveryService extends Service { } private void onDeviceFound(@Nullable DeviceFilterPair device) { + if (device == null) return; + if (mDevicesFound.contains(device)) { return; } @@ -444,12 +467,9 @@ public class DeviceDiscoveryService extends Service { } for (int i = 0; i < scanResults.size(); i++) { - DeviceFilterPair<android.net.wifi.ScanResult> deviceFilterPair = - DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters); - if (deviceFilterPair != null) onDeviceFound(deviceFilterPair); + onDeviceFound(DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters)); } } - } } } diff --git a/packages/Osu2/Android.mk b/packages/Osu2/Android.mk new file mode 100644 index 000000000000..05586f014ce7 --- /dev/null +++ b/packages/Osu2/Android.mk @@ -0,0 +1,18 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_PROGUARD_ENABLED := disabled +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := Osu2 +LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true + +include $(BUILD_PACKAGE) + +######################## +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/packages/Osu2/AndroidManifest.xml b/packages/Osu2/AndroidManifest.xml new file mode 100644 index 000000000000..236b120b950c --- /dev/null +++ b/packages/Osu2/AndroidManifest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.osu"> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> + <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.INTERNET" /> + + <application + android:enabled="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name"> + <activity android:name=".MainActivity" android:exported="true"> + </activity> + </application> + +</manifest> diff --git a/packages/Osu2/res/layout/activity_main.xml b/packages/Osu2/res/layout/activity_main.xml new file mode 100644 index 000000000000..f9504c9a46c8 --- /dev/null +++ b/packages/Osu2/res/layout/activity_main.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + +</LinearLayout> diff --git a/packages/Osu2/res/mipmap-hdpi/ic_launcher.png b/packages/Osu2/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..cde69bcccec6 --- /dev/null +++ b/packages/Osu2/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/Osu2/res/mipmap-mdpi/ic_launcher.png b/packages/Osu2/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..c133a0cbd379 --- /dev/null +++ b/packages/Osu2/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/Osu2/res/mipmap-xhdpi/ic_launcher.png b/packages/Osu2/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..bfa42f0e7b91 --- /dev/null +++ b/packages/Osu2/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/Osu2/res/mipmap-xxhdpi/ic_launcher.png b/packages/Osu2/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..324e72cdd748 --- /dev/null +++ b/packages/Osu2/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/Osu2/res/mipmap-xxxhdpi/ic_launcher.png b/packages/Osu2/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..aee44e138434 --- /dev/null +++ b/packages/Osu2/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/Osu2/res/values-w820dp/dimens.xml b/packages/Osu2/res/values-w820dp/dimens.xml new file mode 100644 index 000000000000..63fc81644461 --- /dev/null +++ b/packages/Osu2/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ +<resources> + <!-- Example customization of dimensions originally defined in res/values/dimens.xml + (such as screen margins) for screens with more than 820dp of available width. This + would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> + <dimen name="activity_horizontal_margin">64dp</dimen> +</resources> diff --git a/packages/Osu2/res/values/colors.xml b/packages/Osu2/res/values/colors.xml new file mode 100644 index 000000000000..3ab3e9cbce07 --- /dev/null +++ b/packages/Osu2/res/values/colors.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="colorPrimary">#3F51B5</color> + <color name="colorPrimaryDark">#303F9F</color> + <color name="colorAccent">#FF4081</color> +</resources> diff --git a/packages/Osu2/res/values/dimens.xml b/packages/Osu2/res/values/dimens.xml new file mode 100644 index 000000000000..47c82246738c --- /dev/null +++ b/packages/Osu2/res/values/dimens.xml @@ -0,0 +1,5 @@ +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> +</resources> diff --git a/packages/Osu2/res/values/strings.xml b/packages/Osu2/res/values/strings.xml new file mode 100644 index 000000000000..e5b1af6edd0c --- /dev/null +++ b/packages/Osu2/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">Passpoint Online Sign-Up</string> +</resources> diff --git a/core/java/android/service/euicc/DeleteResult.aidl b/packages/Osu2/src/com/android/osu/Constants.java index 3da8b49fb9ff..cd046d857b4c 100644 --- a/core/java/android/service/euicc/DeleteResult.aidl +++ b/packages/Osu2/src/com/android/osu/Constants.java @@ -14,6 +14,11 @@ * limitations under the License. */ -package android.service.euicc; +package com.android.osu; -parcelable DeleteResult; +public final class Constants { + public static final String INTENT_EXTRA_COMMAND = "com.android.osu.extra.COMMAND"; + public static final String INTENT_EXTRA_OSU_PROVIDER = "com.android.osu.extra.OSU_PROVIDER"; + + public static final String COMMAND_PROVISION = "Provision"; +}
\ No newline at end of file diff --git a/packages/Osu2/src/com/android/osu/MainActivity.java b/packages/Osu2/src/com/android/osu/MainActivity.java new file mode 100644 index 000000000000..4e2136b99c74 --- /dev/null +++ b/packages/Osu2/src/com/android/osu/MainActivity.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.osu; + +import android.app.Activity; +import android.content.Intent; +import android.net.wifi.hotspot2.OsuProvider; +import android.os.Bundle; +import android.util.Log; + +/** + * Main entry point for the OSU (Online Sign-Up) app. + */ +public class MainActivity extends Activity { + private static final String TAG = "OSU_MainActivity"; + private OsuService mService; + + @Override + protected void onCreate(Bundle saveInstanceState) { + super.onCreate(saveInstanceState); + + Intent intent = getIntent(); + if (intent == null) { + Log.e(TAG, "Intent not provided"); + finish(); + } + + if (!intent.hasExtra(Constants.INTENT_EXTRA_COMMAND)) { + Log.e(TAG, "Command not provided"); + finish(); + } + + String command = intent.getStringExtra(Constants.INTENT_EXTRA_COMMAND); + switch (command) { + case Constants.COMMAND_PROVISION: + if (!startProvisionService(intent.getParcelableExtra( + Constants.INTENT_EXTRA_OSU_PROVIDER))) { + finish(); + } + break; + default: + Log.e(TAG, "Unknown command: '" + command + "'"); + finish(); + break; + } + } + + /** + * Start the {@link ProvisionService} to perform provisioning tasks. + * + * @return true if service is started + */ + private boolean startProvisionService(OsuProvider provider) { + if (provider == null) { + Log.e(TAG, "OSU Provider not provided"); + return false; + } + mService = new ProvisionService(this, provider); + mService.start(); + return true; + } +} diff --git a/packages/Osu2/src/com/android/osu/NetworkConnection.java b/packages/Osu2/src/com/android/osu/NetworkConnection.java new file mode 100644 index 000000000000..9f5b929e96d3 --- /dev/null +++ b/packages/Osu2/src/com/android/osu/NetworkConnection.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.osu; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Network; +import android.net.NetworkInfo; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiSsid; +import android.os.Handler; +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; + +/** + * Responsible for setup/monitor on a Wi-Fi connection. + */ +public class NetworkConnection { + private static final String TAG = "OSU_NetworkConnection"; + + private final WifiManager mWifiManager; + private final Callbacks mCallbacks; + private final int mNetworkId; + private boolean mConnected = false; + + /** + * Callbacks on Wi-Fi connection state changes. + */ + public interface Callbacks { + /** + * Invoked when network connection is established with IP connectivity. + * + * @param network {@link Network} associated with the connected network. + */ + public void onConnected(Network network); + + /** + * Invoked when the targeted network is disconnected. + */ + public void onDisconnected(); + + /** + * Invoked when network connection is not established within the pre-defined timeout. + */ + public void onTimeout(); + } + + /** + * Create an instance of {@link NetworkConnection} for the specified Wi-Fi network. + * The Wi-Fi network (specified by its SSID) will be added/enabled as part of this object + * creation. + * + * {@link #teardown} will need to be invoked once you're done with this connection, + * to remove the given Wi-Fi network from the framework. + * + * @param context The application context + * @param handler The handler to dispatch the processing of received broadcast intents + * @param ssid The SSID to connect to + * @param nai The network access identifier associated with the AP + * @param callbacks The callbacks to be invoked on network change events + * @throws IOException when failed to add/enable the specified Wi-Fi network + */ + public NetworkConnection(Context context, Handler handler, WifiSsid ssid, String nai, + Callbacks callbacks) throws IOException { + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + mCallbacks = callbacks; + mNetworkId = connect(ssid, nai); + + // TODO(zqiu): setup alarm to timed out the connection attempt. + + IntentFilter filter = new IntentFilter(); + filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + handleNetworkStateChanged( + intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO), + intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)); + } + } + }; + // Provide a Handler so that the onReceive call will be run on the specified handler + // thread instead of the main thread. + context.registerReceiver(receiver, filter, null, handler); + } + + /** + * Teardown the network connection by removing the network. + */ + public void teardown() { + mWifiManager.removeNetwork(mNetworkId); + } + + /** + * Connect to a OSU Wi-Fi network specified by the given SSID. The security type of the Wi-Fi + * network is either open or OSEN (OSU Server-only authenticated layer 2 Encryption Network). + * When network access identifier is provided, OSEN is used. + * + * @param ssid The SSID to connect to + * @param nai Network access identifier of the network + * + * @return unique ID associated with the network + * @throws IOException + */ + private int connect(WifiSsid ssid, String nai) throws IOException { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = "\"" + ssid.toString() + "\""; + if (TextUtils.isEmpty(nai)) { + config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + } else { + // TODO(zqiu): configuration setup for OSEN. + } + int networkId = mWifiManager.addNetwork(config); + if (networkId < 0) { + throw new IOException("Failed to add OSU network"); + } + if (!mWifiManager.enableNetwork(networkId, true)) { + throw new IOException("Failed to enable OSU network"); + } + return networkId; + } + + /** + * Handle network state changed events. + * + * @param networkInfo {@link NetworkInfo} indicating the current network state + * @param wifiInfo {@link WifiInfo} associated with the current network when connected + */ + private void handleNetworkStateChanged(NetworkInfo networkInfo, WifiInfo wifiInfo) { + if (networkInfo == null) { + Log.e(TAG, "NetworkInfo not provided for network state changed event"); + return; + } + switch (networkInfo.getDetailedState()) { + case CONNECTED: + handleConnectedEvent(wifiInfo); + break; + case DISCONNECTED: + handleDisconnectedEvent(); + break; + default: + Log.d(TAG, "Ignore uninterested state: " + networkInfo.getDetailedState()); + break; + } + } + + /** + * Handle network connected event. + * + * @param wifiInfo {@link WifiInfo} associated with the current connection + */ + private void handleConnectedEvent(WifiInfo wifiInfo) { + if (mConnected) { + // No-op if already connected. + return; + } + if (wifiInfo == null) { + Log.e(TAG, "WifiInfo not provided for connected event"); + return; + } + if (wifiInfo.getNetworkId() != mNetworkId) { + return; + } + Network network = mWifiManager.getCurrentNetwork(); + if (network == null) { + Log.e(TAG, "Current network is not set"); + return; + } + mConnected = true; + mCallbacks.onConnected(network); + } + + /** + * Handle network disconnected event. + */ + private void handleDisconnectedEvent() { + if (!mConnected) { + // No-op if not connected, most likely a disconnect event for a different network. + return; + } + mConnected = false; + mCallbacks.onDisconnected(); + } +} diff --git a/core/java/android/service/euicc/EraseResult.aidl b/packages/Osu2/src/com/android/osu/OsuService.java index e28a097ebb65..46a3c843f1dd 100644 --- a/core/java/android/service/euicc/EraseResult.aidl +++ b/packages/Osu2/src/com/android/osu/OsuService.java @@ -14,6 +14,20 @@ * limitations under the License. */ -package android.service.euicc; +package com.android.osu; -parcelable EraseResult; +/** + * Abstraction for services that can be performed by the OSU app, such as provisioning, + * subscription remediation, and etc. + */ +public interface OsuService { + /** + * Start the service. + */ + public void start(); + + /** + * Stop the service. + */ + public void stop(); +} diff --git a/packages/Osu2/src/com/android/osu/ProvisionService.java b/packages/Osu2/src/com/android/osu/ProvisionService.java new file mode 100644 index 000000000000..b1d43b2b0ea9 --- /dev/null +++ b/packages/Osu2/src/com/android/osu/ProvisionService.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.osu; + +import android.content.Context; +import android.net.Network; +import android.net.wifi.hotspot2.OsuProvider; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import java.io.IOException; + +/** + * Service responsible for performing Passpoint subscription provisioning tasks. + * This service will run on a separate thread to avoid blocking on the Main thread. + */ +public class ProvisionService implements OsuService { + private static final String TAG = "OSU_ProvisionService"; + private static final int COMMAND_START = 1; + private static final int COMMAND_STOP = 2; + + private final Context mContext; + private final HandlerThread mHandlerThread; + private final ServiceHandler mServiceHandler; + private final OsuProvider mProvider; + + private boolean mStarted = false; + private NetworkConnection mNetworkConnection = null; + + public ProvisionService(Context context, OsuProvider provider) { + mContext = context; + mProvider = provider; + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mServiceHandler = new ServiceHandler(mHandlerThread.getLooper()); + } + + @Override + public void start() { + mServiceHandler.sendMessage(mServiceHandler.obtainMessage(COMMAND_START)); + } + + @Override + public void stop() { + mServiceHandler.sendMessage(mServiceHandler.obtainMessage(COMMAND_STOP)); + } + + /** + * Handler class for handling commands to the ProvisionService. + */ + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case COMMAND_START: + if (mStarted) { + Log.e(TAG, "Service already started"); + return; + } + try { + // Initiate network connection to the OSU AP. + mNetworkConnection = new NetworkConnection( + mContext, this, mProvider.getOsuSsid(), + mProvider.getNetworkAccessIdentifier(), new NetworkCallbacks()); + mStarted = true; + } catch (IOException e) { + // TODO(zqiu): broadcast failure event via LocalBroadcastManager. + } + break; + case COMMAND_STOP: + if (!mStarted) { + Log.e(TAG, "Service not started"); + return; + } + Log.e(TAG, "Stop provision service"); + break; + default: + Log.e(TAG, "Unknown command: " + msg.what); + break; + } + } + } + + private final class NetworkCallbacks implements NetworkConnection.Callbacks { + @Override + public void onConnected(Network network) { + Log.d(TAG, "Connected to OSU AP"); + } + + @Override + public void onDisconnected() { + } + + @Override + public void onTimeout() { + } + } +} diff --git a/packages/Osu2/tests/Android.mk b/packages/Osu2/tests/Android.mk new file mode 100644 index 000000000000..4b6e0e60652b --- /dev/null +++ b/packages/Osu2/tests/Android.mk @@ -0,0 +1,43 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_CERTIFICATE := platform + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_JAVA_LIBRARIES := android.test.runner + +LOCAL_JACK_FLAGS := --multi-dex native + +LOCAL_PACKAGE_NAME := OsuTests +LOCAL_COMPATIBILITY_SUITE := device-tests + +LOCAL_INSTRUMENTATION_FOR := Osu2 + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test \ + mockito-target-minus-junit4 \ + frameworks-base-testutils + +# Code coverage puts us over the dex limit, so enable multi-dex for coverage-enabled builds +ifeq (true,$(EMMA_INSTRUMENT)) +LOCAL_JACK_FLAGS := --multi-dex native +LOCAL_DX_FLAGS := --multi-dex +endif # EMMA_INSTRUMENT + +include $(BUILD_PACKAGE) diff --git a/packages/Osu2/tests/AndroidManifest.xml b/packages/Osu2/tests/AndroidManifest.xml new file mode 100644 index 000000000000..e22c1122958a --- /dev/null +++ b/packages/Osu2/tests/AndroidManifest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.osu.tests"> + + <application> + <uses-library android:name="android.test.runner" /> + <activity android:label="OsuTestDummyLabel" + android:name="OsuTestDummyName"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.osu" + android:label="OSU App Tests"> + </instrumentation> + +</manifest> diff --git a/packages/Osu2/tests/README.md b/packages/Osu2/tests/README.md new file mode 100644 index 000000000000..dbfa79c3d26f --- /dev/null +++ b/packages/Osu2/tests/README.md @@ -0,0 +1,45 @@ +# OSU Unit Tests +This package contains unit tests for the OSU app based on the +[Android Testing Support Library](http://developer.android.com/tools/testing-support-library/index.html). +The test cases are built using the [JUnit](http://junit.org/) and [Mockito](http://mockito.org/) +libraries. + +## Running Tests +The easiest way to run tests is simply run + +``` +frameworks/base/packages/Osu2/tests/runtests.sh +``` + +`runtests.sh` will build the test project and all of its dependencies and push the APK to the +connected device. It will then run the tests on the device. + +To enable syncing data to the device for first time after clean reflash: +1. adb disable-verity +2. adb reboot +3. adb remount + +See below for a few example of options to limit which tests are run. +See the +[AndroidJUnitRunner Documentation](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html) +for more details on the supported options. + +``` +runtests.sh -e package com.android.osu +runtests.sh -e class com.android.osu.NetworkConnectionTest +``` + +If you manually build and push the test APK to the device you can run tests using + +``` +adb shell am instrument -w 'com.android.osu.tests/android.support.test.runner.AndroidJUnitRunner' +``` + +## Adding Tests +Tests can be added by adding classes to the src directory. JUnit4 style test cases can +be written by simply annotating test methods with `org.junit.Test`. + +## Debugging Tests +If you are trying to debug why tests are not doing what you expected, you can add android log +statements and use logcat to view them. The beginning and end of every tests is automatically logged +with the tag `TestRunner`. diff --git a/packages/Osu2/tests/runtests.sh b/packages/Osu2/tests/runtests.sh new file mode 100755 index 000000000000..3513f5b8fc64 --- /dev/null +++ b/packages/Osu2/tests/runtests.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +if [ -z $ANDROID_BUILD_TOP ]; then + echo "You need to source and lunch before you can use this script" + exit 1 +fi + +echo "Running tests" + +set -e # fail early + +echo "+ mmma -j32 $ANDROID_BUILD_TOP/frameworks/base/packages/Osu2/tests" +# NOTE Don't actually run the command above since this shell doesn't inherit functions from the +# caller. +make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-base-packages-Osu2-tests + +set -x # print commands + +adb root +adb wait-for-device + +adb install -r -g "$OUT/data/app/OsuTests/OsuTests.apk" + +adb shell am instrument -w "$@" 'com.android.osu.tests/android.support.test.runner.AndroidJUnitRunner' diff --git a/packages/Osu2/tests/src/com/android/osu/NetworkConnectionTest.java b/packages/Osu2/tests/src/com/android/osu/NetworkConnectionTest.java new file mode 100644 index 000000000000..2753249aa32e --- /dev/null +++ b/packages/Osu2/tests/src/com/android/osu/NetworkConnectionTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.osu; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkInfo; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiSsid; +import android.os.Handler; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; + +import java.io.IOException; + +/** + * Unit tests for {@link com.android.osu.NetworkConnection}. + */ +@SmallTest +public class NetworkConnectionTest { + private static final String TEST_SSID = "TEST SSID"; + private static final String TEST_SSID_WITH_QUOTES = "\"" + TEST_SSID + "\""; + private static final int TEST_NETWORK_ID = 1; + + @Mock Context mContext; + @Mock Handler mHandler; + @Mock WifiManager mWifiManager; + @Mock NetworkConnection.Callbacks mCallbacks; + + @Before + public void setUp() throws Exception { + initMocks(this); + when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager); + } + + /** + * Verify that an IOException will be thrown when failed to add the network. + * + * @throws Exception + */ + @Test(expected = IOException.class) + public void networkAddFailed() throws Exception { + when(mWifiManager.addNetwork(any(WifiConfiguration.class))).thenReturn(-1); + new NetworkConnection(mContext, mHandler, WifiSsid.createFromAsciiEncoded(TEST_SSID), + null, mCallbacks); + } + + /** + * Verify that an IOException will be thrown when failed to enable the network. + * + * @throws Exception + */ + @Test(expected = IOException.class) + public void networkEnableFailed() throws Exception { + when(mWifiManager.addNetwork(any(WifiConfiguration.class))).thenReturn(TEST_NETWORK_ID); + when(mWifiManager.enableNetwork(eq(TEST_NETWORK_ID), eq(true))).thenReturn(false); + new NetworkConnection(mContext, mHandler, WifiSsid.createFromAsciiEncoded(TEST_SSID), + null, mCallbacks); + } + + /** + * Verify that the connection is established after receiving a + * WifiManager.NETWORK_STATE_CHANGED_ACTION intent indicating that we are connected. + * + * @throws Exception + */ + @Test + public void openNetworkConnectionEstablished() throws Exception { + when(mWifiManager.addNetwork(any(WifiConfiguration.class))).thenReturn(TEST_NETWORK_ID); + when(mWifiManager.enableNetwork(eq(TEST_NETWORK_ID), eq(true))).thenReturn(true); + NetworkConnection connection = new NetworkConnection(mContext, mHandler, + WifiSsid.createFromAsciiEncoded(TEST_SSID), null, mCallbacks); + + // Verify the WifiConfiguration being added. + ArgumentCaptor<WifiConfiguration> wifiConfig = + ArgumentCaptor.forClass(WifiConfiguration.class); + verify(mWifiManager).addNetwork(wifiConfig.capture()); + assertEquals(wifiConfig.getValue().SSID, TEST_SSID_WITH_QUOTES); + + // Capture the BroadcastReceiver. + ArgumentCaptor<BroadcastReceiver> receiver = + ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mContext).registerReceiver(receiver.capture(), any(), any(), any()); + + // Setup intent. + Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); + NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""); + networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", ""); + intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo); + WifiInfo wifiInfo = new WifiInfo(); + wifiInfo.setNetworkId(TEST_NETWORK_ID); + intent.putExtra(WifiManager.EXTRA_WIFI_INFO, wifiInfo); + + // Send intent to the receiver. + Network network = new Network(0); + when(mWifiManager.getCurrentNetwork()).thenReturn(network); + receiver.getValue().onReceive(mContext, intent); + + // Verify we are connected. + verify(mCallbacks).onConnected(eq(network)); + } +} diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java index 653a45351691..81f7315dd422 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java @@ -16,6 +16,8 @@ package com.android.printspooler.model; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; @@ -29,21 +31,27 @@ import android.os.AsyncTask; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.print.PageRange; import android.print.PrintAttributes; -import android.print.PrintAttributes.MediaSize; import android.print.PrintAttributes.Margins; +import android.print.PrintAttributes.MediaSize; import android.print.PrintDocumentInfo; import android.util.ArrayMap; import android.util.Log; import android.view.View; + import com.android.internal.annotations.GuardedBy; import com.android.printspooler.renderer.IPdfRenderer; import com.android.printspooler.renderer.PdfManipulationService; import com.android.printspooler.util.BitmapSerializeUtils; +import com.android.printspooler.util.PageRangeUtils; + import dalvik.system.CloseGuard; + import libcore.io.IoUtils; import java.io.IOException; +import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; @@ -69,8 +77,9 @@ public final class PageContentRepository { private RenderSpec mLastRenderSpec; - private int mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX; - private int mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX; + @Nullable private PageRange mScheduledPreloadVisiblePages; + @Nullable private PageRange[] mScheduledPreloadSelectedPages; + @Nullable private PageRange[] mScheduledPreloadWrittenPages; private int mState; @@ -129,14 +138,24 @@ public final class PageContentRepository { } } - public void startPreload(int firstShownPage, int lastShownPage) { + /** + * Preload selected, written pages around visiblePages. + * + * @param visiblePages The pages currently visible + * @param selectedPages The pages currently selected (e.g. they might become visible by + * scrolling) + * @param writtenPages The pages currently in the document + */ + public void startPreload(@NonNull PageRange visiblePages, @NonNull PageRange[] selectedPages, + @NonNull PageRange[] writtenPages) { // If we do not have a render spec we have no clue what size the // preloaded bitmaps should be, so just take a note for what to do. if (mLastRenderSpec == null) { - mScheduledPreloadFirstShownPage = firstShownPage; - mScheduledPreloadLastShownPage = lastShownPage; + mScheduledPreloadVisiblePages = visiblePages; + mScheduledPreloadSelectedPages = selectedPages; + mScheduledPreloadWrittenPages = writtenPages; } else if (mState == STATE_OPENED) { - mRenderer.startPreload(firstShownPage, lastShownPage, mLastRenderSpec); + mRenderer.startPreload(visiblePages, selectedPages, writtenPages, mLastRenderSpec); } } @@ -225,11 +244,12 @@ public final class PageContentRepository { // We tired to preload but didn't know the bitmap size, now // that we know let us do the work. - if (mScheduledPreloadFirstShownPage != INVALID_PAGE_INDEX - && mScheduledPreloadLastShownPage != INVALID_PAGE_INDEX) { - startPreload(mScheduledPreloadFirstShownPage, mScheduledPreloadLastShownPage); - mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX; - mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX; + if (mScheduledPreloadVisiblePages != null) { + startPreload(mScheduledPreloadVisiblePages, mScheduledPreloadSelectedPages, + mScheduledPreloadWrittenPages); + mScheduledPreloadVisiblePages = null; + mScheduledPreloadSelectedPages = null; + mScheduledPreloadWrittenPages = null; } if (mState == STATE_OPENED) { @@ -526,10 +546,45 @@ public final class PageContentRepository { mDestroyed = true; } - public void startPreload(int firstShownPage, int lastShownPage, RenderSpec renderSpec) { + /** + * How many pages are {@code pages} before pageNum. E.g. page 5 in [0-1], [4-7] has the + * index 4. + * + * @param pageNum The number of the page to find + * @param pages A normalized array of page ranges + * + * @return The index or {@link #INVALID_PAGE_INDEX} if not found + */ + private int findIndexOfPage(int pageNum, @NonNull PageRange[] pages) { + int pagesBefore = 0; + for (int i = 0; i < pages.length; i++) { + if (pages[i].contains(pageNum)) { + return pagesBefore + pageNum - pages[i].getStart(); + } else { + pagesBefore += pages[i].getSize(); + } + } + + return INVALID_PAGE_INDEX; + } + + void startPreload(@NonNull PageRange visiblePages, @NonNull PageRange[] selectedPages, + @NonNull PageRange[] writtenPages, RenderSpec renderSpec) { + if (PageRangeUtils.isAllPages(selectedPages)) { + selectedPages = new PageRange[]{new PageRange(0, mPageCount - 1)}; + } + if (DEBUG) { - Log.i(LOG_TAG, "Preloading pages around [" + firstShownPage - + "-" + lastShownPage + "]"); + Log.i(LOG_TAG, "Preloading pages around " + visiblePages + " from " + + Arrays.toString(selectedPages)); + } + + int firstVisiblePageIndex = findIndexOfPage(visiblePages.getStart(), selectedPages); + int lastVisiblePageIndex = findIndexOfPage(visiblePages.getEnd(), selectedPages); + + if (firstVisiblePageIndex == INVALID_PAGE_INDEX + || lastVisiblePageIndex == INVALID_PAGE_INDEX) { + return; } final int bitmapSizeInBytes = renderSpec.bitmapWidth * renderSpec.bitmapHeight @@ -537,28 +592,33 @@ public final class PageContentRepository { final int maxCachedPageCount = mPageContentCache.getMaxSizeInBytes() / bitmapSizeInBytes; final int halfPreloadCount = (maxCachedPageCount - - (lastShownPage - firstShownPage)) / 2 - 1; + - (lastVisiblePageIndex - firstVisiblePageIndex)) / 2 - 1; - final int excessFromStart; - if (firstShownPage - halfPreloadCount < 0) { - excessFromStart = halfPreloadCount - firstShownPage; - } else { - excessFromStart = 0; - } + final int fromIndex = Math.max(firstVisiblePageIndex - halfPreloadCount, 0); + final int toIndex = lastVisiblePageIndex + halfPreloadCount; - final int excessFromEnd; - if (lastShownPage + halfPreloadCount >= mPageCount) { - excessFromEnd = (lastShownPage + halfPreloadCount) - mPageCount; - } else { - excessFromEnd = 0; + if (DEBUG) { + Log.i(LOG_TAG, "fromIndex=" + fromIndex + " toIndex=" + toIndex); } - final int fromIndex = Math.max(firstShownPage - halfPreloadCount - excessFromEnd, 0); - final int toIndex = Math.min(lastShownPage + halfPreloadCount + excessFromStart, - mPageCount - 1); + int previousRangeSizes = 0; + for (int rangeNum = 0; rangeNum < selectedPages.length; rangeNum++) { + PageRange range = selectedPages[rangeNum]; + + int thisRangeStart = Math.max(0, fromIndex - previousRangeSizes); + int thisRangeEnd = Math.min(range.getSize(), toIndex - previousRangeSizes + 1); + + for (int i = thisRangeStart; i < thisRangeEnd; i++) { + if (PageRangeUtils.contains(writtenPages, range.getStart() + i)) { + if (DEBUG) { + Log.i(LOG_TAG, "Preloading " + (range.getStart() + i)); + } + + renderPage(range.getStart() + i, renderSpec, null); + } + } - for (int i = fromIndex; i <= toIndex; i++) { - renderPage(i, renderSpec, null); + previousRangeSizes += range.getSize(); } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java index e172948fffd6..ad46b60f2f5b 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java @@ -16,6 +16,7 @@ package com.android.printspooler.ui; +import android.annotation.NonNull; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -24,8 +25,8 @@ import android.os.Handler; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.print.PageRange; -import android.print.PrintAttributes.MediaSize; import android.print.PrintAttributes.Margins; +import android.print.PrintAttributes.MediaSize; import android.print.PrintDocumentInfo; import android.support.v7.widget.RecyclerView.Adapter; import android.support.v7.widget.RecyclerView.ViewHolder; @@ -33,11 +34,12 @@ import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; +import android.view.View.MeasureSpec; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; -import android.view.View.MeasureSpec; import android.widget.TextView; + import com.android.printspooler.R; import com.android.printspooler.model.OpenDocumentCallback; import com.android.printspooler.model.PageContentRepository; @@ -45,6 +47,7 @@ import com.android.printspooler.model.PageContentRepository.PageContentProvider; import com.android.printspooler.util.PageRangeUtils; import com.android.printspooler.widget.PageContentView; import com.android.printspooler.widget.PreviewPageFrame; + import dalvik.system.CloseGuard; import java.util.ArrayList; @@ -797,14 +800,16 @@ public final class PageAdapter extends Adapter<ViewHolder> { page.setTag(null); } - public void startPreloadContent(PageRange pageRangeInAdapter) { - final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart()); - final int startPageInFile = computePageIndexInFile(startPageInDocument); - final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd()); - final int endPageInFile = computePageIndexInFile(endPageInDocument); - if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) { - mPageContentRepository.startPreload(startPageInFile, endPageInFile); + void startPreloadContent(@NonNull PageRange visiblePagesInAdapter) { + int startVisibleDocument = computePageIndexInDocument(visiblePagesInAdapter.getStart()); + int endVisibleDocument = computePageIndexInDocument(visiblePagesInAdapter.getEnd()); + if (startVisibleDocument == INVALID_PAGE_INDEX + || endVisibleDocument == INVALID_PAGE_INDEX) { + return; } + + mPageContentRepository.startPreload(new PageRange(startVisibleDocument, endVisibleDocument), + mSelectedPages, mWrittenPages); } public void stopPreloadContent() { diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java index a36f5837ecb1..17d820a983f9 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java +++ b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java @@ -426,7 +426,7 @@ public final class PageRangeUtils { // be based off the start of the written ones instead of zero. // The written pages are always non-null and not empty. final int offset = -pagesWrittenToFile[0].getStart(); - PageRangeUtils.offset(pagesInDocRequested, offset); + PageRangeUtils.offset(pagesInDocRequested.clone(), offset); return pagesInDocRequested; } else if (Arrays.equals(pagesInDocRequested, ALL_PAGES_RANGE) && isAllPages(pagesWrittenToFile, pageCount)) { diff --git a/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml b/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml index 4aa8569466f1..4b81a3c9c460 100644 --- a/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml +++ b/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml @@ -18,7 +18,7 @@ android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" - android:tint="?android:attr/colorAccent"> + android:tint="?android:attr/colorControlNormal"> <path android:fillColor="#FF000000" android:pathData="M20,5L4,5c-1.1,0 -1.99,0.9 -1.99,2L2,17c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 diff --git a/packages/SystemUI/res/drawable/ic_mode_edit.xml b/packages/SettingsLib/res/drawable/ic_mode_edit.xml index 2ad31195ca57..2ad31195ca57 100644 --- a/packages/SystemUI/res/drawable/ic_mode_edit.xml +++ b/packages/SettingsLib/res/drawable/ic_mode_edit.xml diff --git a/packages/SettingsLib/res/layout/preference_two_target.xml b/packages/SettingsLib/res/layout/preference_two_target.xml index 69b7204a8678..7000940d3720 100644 --- a/packages/SettingsLib/res/layout/preference_two_target.xml +++ b/packages/SettingsLib/res/layout/preference_two_target.xml @@ -32,7 +32,8 @@ android:background="?android:attr/selectableItemBackground" android:gravity="start|center_vertical" android:clipToPadding="false" - android:paddingStart="?android:attr/listPreferredItemPaddingStart"> + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> <LinearLayout android:id="@+id/icon_frame" diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index fbdb394976dc..438cbbbb16fe 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -282,7 +282,7 @@ <string name="show_all_anrs" msgid="28462979638729082">"Tots els errors sense resposta"</string> <string name="show_all_anrs_summary" msgid="641908614413544127">"Informa que una aplicació en segon pla no respon"</string> <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Mostra avisos del canal de notificacions"</string> - <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra un avís a la pantalla quan una app publica una notificació sense canal vàlid"</string> + <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra un avís a la pantalla quan una aplicació publica una notificació sense un canal vàlid"</string> <string name="force_allow_on_external" msgid="3215759785081916381">"Força permís d\'aplicacions a l\'emmagatzem. extern"</string> <string name="force_allow_on_external_summary" msgid="3640752408258034689">"Permet que qualsevol aplicació es pugui escriure en un dispositiu d’emmagatzematge extern, independentment dels valors definits"</string> <string name="force_resizable_activities" msgid="8615764378147824985">"Força l\'ajust de la mida de les activitats"</string> @@ -376,6 +376,6 @@ <string name="active_input_method_subtypes" msgid="3596398805424733238">"Mètodes d\'introducció actius"</string> <string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"Utilitza els idiomes del sistema"</string> <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"No s\'ha pogut obrir la configuració de: <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string> - <string name="ime_security_warning" msgid="4135828934735934248">"Pot ser que aquest mètode d\'entrada pugui recopilar tot el que escriviu, incloses dades personals, com ara contrasenyes i números de targetes de crèdit. Ve de l\'aplicació <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Voleu utilitzar aquest mètode d\'entrada?"</string> + <string name="ime_security_warning" msgid="4135828934735934248">"Pot ser que aquest mètode d\'introducció pugui recopilar tot el que escriviu, incloses dades personals, com ara contrasenyes i números de targetes de crèdit. Ve de l\'aplicació <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Voleu utilitzar aquest mètode d\'introducció?"</string> <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Nota: després de reiniciar, l\'aplicació no s\'iniciarà fins que no desbloquegis el telèfon"</string> </resources> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 66e55f9191e3..29490122fb9e 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -28,7 +28,7 @@ <string name="wifi_disabled_by_recommendation_provider" msgid="5168315140978066096">"No se estableció conexión debido a la mala calidad de la red"</string> <string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Error de conexión Wi-Fi"</string> <string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problema de autenticación"</string> - <string name="wifi_cant_connect" msgid="5410016875644565884">"No se puede establecer conexión"</string> + <string name="wifi_cant_connect" msgid="5410016875644565884">"No se puede establecer la conexión"</string> <string name="wifi_cant_connect_to_ap" msgid="1222553274052685331">"No se puede establecer conexión con \"<xliff:g id="AP_NAME">%1$s</xliff:g>\""</string> <string name="wifi_check_password_try_again" msgid="516958988102584767">"Revisa la contraseña y vuelve a intentarlo"</string> <string name="wifi_not_in_range" msgid="1136191511238508967">"Fuera de alcance"</string> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index 09dae27da47f..eb20fdc5ad60 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -86,7 +86,7 @@ <string name="bluetooth_pairing_accept" msgid="6163520056536604875">"페어링"</string> <string name="bluetooth_pairing_accept_all_caps" msgid="6061699265220789149">"페어링"</string> <string name="bluetooth_pairing_decline" msgid="4185420413578948140">"취소"</string> - <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"페어링하면 연결 시 주소록 및 통화 기록에 액세스할 수 있습니다."</string> + <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"페어링하면 연결 시 연락처 및 통화 기록에 액세스할 수 있습니다."</string> <string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>와(과) 페어링하지 못했습니다."</string> <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"PIN 또는 패스키가 잘못되어 <xliff:g id="DEVICE_NAME">%1$s</xliff:g>와(과) 페어링하지 못했습니다."</string> <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>와(과) 통신할 수 없습니다."</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index f9d193058306..675a6d09ebea 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -90,7 +90,7 @@ <string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"Não foi possível parear com <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"Não foi possível parear com <xliff:g id="DEVICE_NAME">%1$s</xliff:g> por causa de um PIN ou senha incorretos."</string> <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"Não é possível se comunicar com <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> - <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"Emparelhamento rejeitado por <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> + <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"Pareamento rejeitado por <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi desligado."</string> <string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fi desconectado"</string> <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Uma barra de Wi-Fi."</string> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 414ff31865f4..736579fc6c83 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -281,8 +281,8 @@ <string name="app_process_limit_title" msgid="4280600650253107163">"Limite proc. em 2º plano"</string> <string name="show_all_anrs" msgid="28462979638729082">"Mostrar todos os ANR"</string> <string name="show_all_anrs_summary" msgid="641908614413544127">"Mostrar erro \"Aplic. não Resp.\" p/ aplic. 2º plano"</string> - <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Mostrar avisos do canal de notif."</string> - <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra um aviso no ecrã quando uma aplic. publica uma notific. sem um canal válido"</string> + <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Mostrar avisos do canal de notificações"</string> + <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra um aviso no ecrã quando uma aplicação publica uma notificação sem o canal ser válido"</string> <string name="force_allow_on_external" msgid="3215759785081916381">"Forçar perm. de aplicações no armazenamento ext."</string> <string name="force_allow_on_external_summary" msgid="3640752408258034689">"Torna qualquer aplicação elegível para ser gravada no armazenamento externo, independentemente dos valores do manifesto"</string> <string name="force_resizable_activities" msgid="8615764378147824985">"Forçar as atividades a serem redimensionáveis"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index f9d193058306..675a6d09ebea 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -90,7 +90,7 @@ <string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"Não foi possível parear com <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"Não foi possível parear com <xliff:g id="DEVICE_NAME">%1$s</xliff:g> por causa de um PIN ou senha incorretos."</string> <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"Não é possível se comunicar com <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> - <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"Emparelhamento rejeitado por <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> + <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"Pareamento rejeitado por <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi desligado."</string> <string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fi desconectado"</string> <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Uma barra de Wi-Fi."</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 61f280cab7f6..ff6c838536e0 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -59,7 +59,7 @@ <string name="bluetooth_profile_headset" msgid="7815495680863246034">"Звонки"</string> <string name="bluetooth_profile_opp" msgid="9168139293654233697">"Профиль OPP"</string> <string name="bluetooth_profile_hid" msgid="3680729023366986480">"Профиль HID"</string> - <string name="bluetooth_profile_pan" msgid="3391606497945147673">"Интернет-доступ"</string> + <string name="bluetooth_profile_pan" msgid="3391606497945147673">"Доступ к Интернету"</string> <string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Обмен контактами"</string> <string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Использовать для обмена контактами"</string> <string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Профиль PAN"</string> @@ -83,12 +83,12 @@ <string name="bluetooth_headset_profile_summary_use_for" msgid="8705753622443862627">"Использовать для аудиоустройства телефона"</string> <string name="bluetooth_opp_profile_summary_use_for" msgid="1255674547144769756">"Используется для передачи файлов"</string> <string name="bluetooth_hid_profile_summary_use_for" msgid="232727040453645139">"Использовать для ввода"</string> - <string name="bluetooth_pairing_accept" msgid="6163520056536604875">"Подключить"</string> - <string name="bluetooth_pairing_accept_all_caps" msgid="6061699265220789149">"ПОДКЛЮЧИТЬ"</string> + <string name="bluetooth_pairing_accept" msgid="6163520056536604875">"Добавить"</string> + <string name="bluetooth_pairing_accept_all_caps" msgid="6061699265220789149">"ДОБАВИТЬ"</string> <string name="bluetooth_pairing_decline" msgid="4185420413578948140">"Отмена"</string> <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"Сопряжение обеспечивает доступ к вашим контактам и журналу звонков при подключении."</string> - <string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"Не удалось подключиться к устройству \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string> - <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"Не удалось подключиться к устройству \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\", так как введен неверный PIN-код или пароль."</string> + <string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"Не удалось установить сопряжение с устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string> + <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"Не удалось установить сопряжение с устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\", так как введен неверный PIN-код или пароль."</string> <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"Не удается установить соединение с устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string> <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> не разрешает сопряжение."</string> <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi выключен"</string> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index d29d8d481d9b..edd02c9f238a 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -88,7 +88,7 @@ <string name="bluetooth_pairing_decline" msgid="4185420413578948140">"取消"</string> <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"配對完成後,所配對的裝置即可在連線後存取你的聯絡人和通話紀錄。"</string> <string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配對。"</string> - <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配對,因為 PIN 或密碼金鑰不正確。"</string> + <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配對,因為 PIN 碼或密碼金鑰不正確。"</string> <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 通訊。"</string> <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」拒絕配對要求。"</string> <string name="accessibility_wifi_off" msgid="1166761729660614716">"已關閉 Wi-Fi。"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java new file mode 100644 index 000000000000..9554e8128122 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015 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.settingslib; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v14.preference.PreferenceDialogFragment; +import android.support.v7.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.View; + +public class CustomDialogPreference extends DialogPreference { + + private CustomPreferenceDialogFragment mFragment; + + public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public CustomDialogPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CustomDialogPreference(Context context) { + super(context); + } + + public boolean isDialogOpen() { + return getDialog() != null && getDialog().isShowing(); + } + + public Dialog getDialog() { + return mFragment != null ? mFragment.getDialog() : null; + } + + protected void onPrepareDialogBuilder(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + } + + protected void onDialogClosed(boolean positiveResult) { + } + + protected void onClick(DialogInterface dialog, int which) { + } + + protected void onBindDialogView(View view) { + } + + private void setFragment(CustomPreferenceDialogFragment fragment) { + mFragment = fragment; + } + + public static class CustomPreferenceDialogFragment extends PreferenceDialogFragment { + + public static CustomPreferenceDialogFragment newInstance(String key) { + final CustomPreferenceDialogFragment fragment = new CustomPreferenceDialogFragment(); + final Bundle b = new Bundle(1); + b.putString(ARG_KEY, key); + fragment.setArguments(b); + return fragment; + } + + private CustomDialogPreference getCustomizablePreference() { + return (CustomDialogPreference) getPreference(); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + super.onPrepareDialogBuilder(builder); + getCustomizablePreference().setFragment(this); + getCustomizablePreference().onPrepareDialogBuilder(builder, this); + } + + @Override + public void onDialogClosed(boolean positiveResult) { + getCustomizablePreference().onDialogClosed(positiveResult); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + getCustomizablePreference().onBindDialogView(view); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + super.onClick(dialog, which); + getCustomizablePreference().onClick(dialog, which); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java new file mode 100644 index 000000000000..692d2110e498 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2015 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.settingslib; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v14.preference.EditTextPreferenceDialogFragment; +import android.support.v7.preference.EditTextPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.EditText; + +public class CustomEditTextPreference extends EditTextPreference { + + private CustomPreferenceDialogFragment mFragment; + + public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public CustomEditTextPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CustomEditTextPreference(Context context) { + super(context); + } + + public EditText getEditText() { + return mFragment != null ? (EditText) mFragment.getDialog().findViewById(android.R.id.edit) + : null; + } + + public boolean isDialogOpen() { + return getDialog() != null && getDialog().isShowing(); + } + + public Dialog getDialog() { + return mFragment != null ? mFragment.getDialog() : null; + } + + protected void onPrepareDialogBuilder(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + } + + protected void onDialogClosed(boolean positiveResult) { + } + + protected void onClick(DialogInterface dialog, int which) { + } + + protected void onBindDialogView(View view) { + } + + private void setFragment(CustomPreferenceDialogFragment fragment) { + mFragment = fragment; + } + + public static class CustomPreferenceDialogFragment extends EditTextPreferenceDialogFragment { + + public static CustomPreferenceDialogFragment newInstance(String key) { + final CustomPreferenceDialogFragment fragment = new CustomPreferenceDialogFragment(); + final Bundle b = new Bundle(1); + b.putString(ARG_KEY, key); + fragment.setArguments(b); + return fragment; + } + + private CustomEditTextPreference getCustomizablePreference() { + return (CustomEditTextPreference) getPreference(); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + getCustomizablePreference().onBindDialogView(view); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + super.onPrepareDialogBuilder(builder); + getCustomizablePreference().setFragment(this); + getCustomizablePreference().onPrepareDialogBuilder(builder, this); + } + + @Override + public void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + getCustomizablePreference().onDialogClosed(positiveResult); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + super.onClick(dialog, which); + getCustomizablePreference().onClick(dialog, which); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index b21f2fa860b1..576782319e1e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -35,7 +35,7 @@ public class Utils { private static String sServicesSystemSharedLibPackageName; private static String sSharedSystemSharedLibPackageName; - public static final int[] WIFI_PIE_FOR_BADGING = { + static final int[] WIFI_PIE_FOR_BADGING = { com.android.internal.R.drawable.ic_signal_wifi_badged_0_bars, com.android.internal.R.drawable.ic_signal_wifi_badged_1_bar, com.android.internal.R.drawable.ic_signal_wifi_badged_2_bars, @@ -294,12 +294,7 @@ public class Utils { }); } - /** - * Returns the resource id for the given badge or {@link View.NO_ID} if no badge is to be shown. - * - * @throws IllegalArgumentException if the given badge value is not supported. - */ - public static int getWifiBadgeResource(int badge) { + private static int getWifiBadgeResource(int badge) { switch (badge) { case NetworkBadging.BADGING_NONE: return View.NO_ID; diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java index 474de9074062..1cbb7450f7e1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java +++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java @@ -88,21 +88,16 @@ public class ZoneGetter { private static final String XMLTAG_TIMEZONE = "timezone"; public static CharSequence getTimeZoneOffsetAndName(Context context, TimeZone tz, Date now) { - final Locale locale = Locale.getDefault(); - final CharSequence gmtText = getGmtOffsetText(context, locale, tz, now); - final TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale); - final ZoneGetterData data = new ZoneGetterData(context); - - final boolean useExemplarLocationForLocalNames = - shouldUseExemplarLocationForLocalNames(data, timeZoneNames); - final CharSequence zoneName = getTimeZoneDisplayName(data, timeZoneNames, - useExemplarLocationForLocalNames, tz, tz.getID()); - if (zoneName == null) { + Locale locale = Locale.getDefault(); + CharSequence gmtText = getGmtOffsetText(context, locale, tz, now); + TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale); + String zoneNameString = getZoneLongName(timeZoneNames, tz, now); + if (zoneNameString == null) { return gmtText; } // We don't use punctuation here to avoid having to worry about localizing that too! - return TextUtils.concat(gmtText, " ", zoneName); + return TextUtils.concat(gmtText, " ", zoneNameString); } public static List<Map<String, Object>> getZonesList(Context context) { diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java index 9d09737a14dc..e067de1a5e7a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java @@ -34,6 +34,8 @@ public final class CategoryKey { public static final String CATEGORY_SOUND = "com.android.settings.category.ia.sound"; public static final String CATEGORY_STORAGE = "com.android.settings.category.ia.storage"; public static final String CATEGORY_SECURITY = "com.android.settings.category.ia.security"; + public static final String CATEGORY_SECURITY_LOCKSCREEN = + "com.android.settings.category.ia.lockscreen"; public static final String CATEGORY_ACCOUNT = "com.android.settings.category.ia.accounts"; public static final String CATEGORY_SYSTEM = "com.android.settings.category.ia.system"; public static final String CATEGORY_SYSTEM_LANGUAGE = diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java index f52a6b5cac56..04a3d1f81561 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java @@ -165,6 +165,9 @@ public class TileUtils { * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the * custom view which should be displayed for the preference. The custom view will be inflated * as a remote view. + * + * This also can be used with {@link META_DATA_PREFERENCE_SUMMARY_URI} above, by setting the id + * of the summary TextView to '@android:id/summary'. */ public static final String META_DATA_PREFERENCE_CUSTOM_VIEW = "com.android.settings.custom_view"; @@ -315,6 +318,7 @@ public class TileUtils { PackageManager pm = context.getPackageManager(); List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent, PackageManager.GET_META_DATA, user.getIdentifier()); + Map<String, IContentProvider> providerMap = new HashMap<>(); for (ResolveInfo resolved : results) { if (!resolved.system) { // Do not allow any app to add to settings, only system ones. @@ -346,7 +350,7 @@ public class TileUtils { tile.priority = usePriority ? resolved.priority : 0; tile.metaData = activityInfo.metaData; updateTileData(context, tile, activityInfo, activityInfo.applicationInfo, - pm); + pm, providerMap); if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title); addedCache.put(key, tile); @@ -361,14 +365,14 @@ public class TileUtils { } private static boolean updateTileData(Context context, Tile tile, - ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) { + ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm, + Map<String, IContentProvider> providerMap) { if (applicationInfo.isSystemApp()) { int icon = 0; Pair<String, Integer> iconFromUri = null; CharSequence title = null; String summary = null; String keyHint = null; - Uri uri = null; RemoteViews remoteViews = null; // Get the activity's meta-data @@ -414,6 +418,15 @@ public class TileUtils { if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) { int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW); remoteViews = new RemoteViews(applicationInfo.packageName, layoutId); + if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) { + String uriString = metaData.getString( + META_DATA_PREFERENCE_SUMMARY_URI); + String overrideSummary = getTextFromUri(context, uriString, providerMap, + META_DATA_PREFERENCE_SUMMARY); + if (overrideSummary != null) { + remoteViews.setTextViewText(android.R.id.summary, overrideSummary); + } + } } } } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 0f9b2ff4d5f2..edb322680c69 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -540,7 +540,10 @@ public class AccessPoint implements Comparable<AccessPoint> { } } - mSeen = seen; + // Only replace the previous value if we have a recent scan result to use + if (seen != 0) { + mSeen = seen; + } } /** @@ -984,8 +987,10 @@ public class AccessPoint implements Comparable<AccessPoint> { security = getSecurity(result); if (security == SECURITY_PSK) pskType = getPskType(result); - mRssi = result.level; - mSeen = result.timestamp; + + mScanResultCache.put(result.BSSID, result); + updateRssi(); + mSeen = result.timestamp; // even if the timestamp is old it is still valid } public void saveWifiState(Bundle savedState) { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index ec94841c7d0d..0d67ad03cb10 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -12,17 +12,13 @@ package com.android.settingslib.wifi; import android.content.Intent; import android.net.NetworkInfo; -import android.net.NetworkKey; -import android.net.WifiKey; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.util.Log; import java.util.List; public class WifiStatusTracker { - private static final String TAG = "WifiStatusTracker"; private final WifiManager mWifiManager; public boolean enabled; @@ -32,7 +28,6 @@ public class WifiStatusTracker { public String ssid; public int rssi; public int level; - public NetworkKey networkKey; public WifiStatusTracker(WifiManager wifiManager) { mWifiManager = wifiManager; @@ -54,32 +49,19 @@ public class WifiStatusTracker { connecting = networkInfo != null && !networkInfo.isConnected() && networkInfo.isConnectedOrConnecting(); connected = networkInfo != null && networkInfo.isConnected(); - WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null - ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) - : mWifiManager.getConnectionInfo(); - // If Connected grab the signal strength and ssid. - if (connected && info != null) { - ssid = getSsid(info); - String bssid = info.getBSSID(); - if ((ssid != null) && (bssid != null)) { - // Reuse existing network key object if possible. - if ((networkKey == null) - || !networkKey.wifiKey.ssid.equals(ssid) - || !networkKey.wifiKey.bssid.equals(bssid)) { - try { - networkKey = new NetworkKey( - new WifiKey(ssid, bssid)); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Cannot create NetworkKey", e); - } - } + if (connected) { + // try getting it out of the intent first + WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null + ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) + : mWifiManager.getConnectionInfo(); + if (info != null) { + ssid = getSsid(info); } else { - networkKey = null; + ssid = null; } - } else { + } else if (!connected) { ssid = null; - networkKey = null; } } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { // Default to -200 as its below WifiManager.MIN_RSSI. diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 20cc5a623358..9083d90b9085 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -144,6 +144,7 @@ public class WifiTracker { @VisibleForTesting Scanner mScanner; + private boolean mStaleScanResults = false; public WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved, boolean includeScans) { @@ -348,7 +349,11 @@ public class WifiTracker { * Stop tracking wifi networks and scores. * * <p>This should always be called when done with a WifiTracker (if startTracking was called) to - * ensure proper cleanup and prevent any further callbacks from occuring. + * ensure proper cleanup and prevent any further callbacks from occurring. + * + * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents + * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit + * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION). */ @MainThread public void stopTracking() { @@ -365,6 +370,7 @@ public class WifiTracker { mWorkHandler.removePendingMessages(); mMainHandler.removePendingMessages(); } + mStaleScanResults = true; } private void unregisterAndClearScoreCache() { @@ -730,6 +736,11 @@ public class WifiTracker { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); + + if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { + mStaleScanResults = false; + } + if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)); @@ -840,7 +851,9 @@ public class WifiTracker { switch (msg.what) { case MSG_UPDATE_ACCESS_POINTS: - updateAccessPointsLocked(); + if (!mStaleScanResults) { + updateAccessPointsLocked(); + } break; case MSG_UPDATE_NETWORK_INFO: updateNetworkInfo((NetworkInfo) msg.obj); diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java index 703e9d29e6ac..a3345ee58b7d 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java @@ -47,9 +47,9 @@ public class ZoneGetterTest { } @Test - public void getTimeZoneOffsetAndName_setLondon_returnLondon() { - // Check it will ends with 'London', not 'British Summer Time' or sth else - testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "London"); + public void getTimeZoneOffsetAndName_setLondon_returnBritishSummerTime() { + // Check it will ends with 'British Summer Time', not 'London' or sth else + testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "British Summer Time"); } @Test diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java index 46ea31993c38..340ef016d74d 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -747,4 +748,36 @@ public class WifiTrackerTest { verifyNoMoreInteractions(mockWifiListener); } + + @Test + public void stopTrackingShouldSetStaleBitWhichPreventsCallbacksUntilNextScanResult() + throws Exception { + WifiTracker tracker = createMockedWifiTracker(); + startTracking(tracker); + tracker.stopTracking(); + + CountDownLatch latch1 = new CountDownLatch(1); + tracker.mMainHandler.post(() -> { + latch1.countDown(); + }); + assertTrue("Latch 1 timed out", latch1.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); + + startTracking(tracker); + + tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION)); + tracker.mReceiver.onReceive( + mContext, new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION)); + tracker.mReceiver.onReceive( + mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION)); + + CountDownLatch latch2 = new CountDownLatch(1); + tracker.mMainHandler.post(() -> { + latch2.countDown(); + }); + assertTrue("Latch 2 timed out", latch2.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); + + verify(mockWifiListener, never()).onAccessPointsChanged(); + + sendScanResultsAndProcess(tracker); // verifies onAccessPointsChanged is invoked + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java index 40353e7489b4..9fc8a9607cba 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java @@ -54,13 +54,14 @@ public class CategoryKeyTest { allKeys.add(CategoryKey.CATEGORY_SOUND); allKeys.add(CategoryKey.CATEGORY_STORAGE); allKeys.add(CategoryKey.CATEGORY_SECURITY); + allKeys.add(CategoryKey.CATEGORY_SECURITY_LOCKSCREEN); allKeys.add(CategoryKey.CATEGORY_ACCOUNT); allKeys.add(CategoryKey.CATEGORY_SYSTEM); allKeys.add(CategoryKey.CATEGORY_SYSTEM_LANGUAGE); allKeys.add(CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT); // DO NOT REMOVE ANYTHING ABOVE - assertThat(allKeys.size()).isEqualTo(13); + assertThat(allKeys.size()).isEqualTo(14); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java index a2e0e2c5b46b..7cfb32d7bdb0 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java @@ -36,6 +36,7 @@ import android.provider.Settings.Global; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Pair; +import android.widget.RemoteViews; import com.android.settingslib.R; import com.android.settingslib.TestConfig; @@ -51,6 +52,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.internal.ShadowExtractor; import java.util.ArrayList; import java.util.Collections; @@ -68,9 +70,13 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; @RunWith(RobolectricTestRunner.class) -@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +@Config(manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = {TileUtilsTest.TileUtilsShadowRemoteViews.class}) public class TileUtilsTest { @Mock @@ -364,6 +370,86 @@ public class TileUtilsTest { assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference); } + @Test + public void getTilesForIntent_summaryUriSpecified_shouldOverrideRemoteViewSummary() + throws RemoteException { + Intent intent = new Intent(); + Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>(); + List<Tile> outTiles = new ArrayList<>(); + List<ResolveInfo> info = new ArrayList<>(); + ResolveInfo resolveInfo = newInfo(true, null /* category */, null, + null, URI_GET_SUMMARY); + resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view", + R.layout.user_preference); + info.add(resolveInfo); + + when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt())) + .thenReturn(info); + + // Mock the content provider interaction. + Bundle bundle = new Bundle(); + bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY, "new summary text"); + when(mIContentProvider.call(anyString(), + eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY), + any())).thenReturn(bundle); + when(mContentResolver.acquireUnstableProvider(anyString())) + .thenReturn(mIContentProvider); + when(mContentResolver.acquireUnstableProvider(any(Uri.class))) + .thenReturn(mIContentProvider); + + TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, + null /* defaultCategory */, outTiles, false /* usePriority */, + false /* checkCategory */); + + assertThat(outTiles.size()).isEqualTo(1); + Tile tile = outTiles.get(0); + assertThat(tile.remoteViews).isNotNull(); + assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference); + // Make sure the summary TextView got a new text string. + TileUtilsShadowRemoteViews shadowRemoteViews = + (TileUtilsShadowRemoteViews) ShadowExtractor.extract(tile.remoteViews); + assertThat(shadowRemoteViews.overrideViewId).isEqualTo(android.R.id.summary); + assertThat(shadowRemoteViews.overrideText).isEqualTo("new summary text"); + } + + @Test + public void getTilesForIntent_providerUnavailable_shouldNotOverrideRemoteViewSummary() + throws RemoteException { + Intent intent = new Intent(); + Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>(); + List<Tile> outTiles = new ArrayList<>(); + List<ResolveInfo> info = new ArrayList<>(); + ResolveInfo resolveInfo = newInfo(true, null /* category */, null, + null, URI_GET_SUMMARY); + resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view", + R.layout.user_preference); + info.add(resolveInfo); + + when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt())) + .thenReturn(info); + + // Mock the content provider interaction. + Bundle bundle = new Bundle(); + bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY, "new summary text"); + when(mIContentProvider.call(anyString(), + eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY), + any())).thenReturn(bundle); + + TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, + null /* defaultCategory */, outTiles, false /* usePriority */, + false /* checkCategory */); + + assertThat(outTiles.size()).isEqualTo(1); + Tile tile = outTiles.get(0); + assertThat(tile.remoteViews).isNotNull(); + assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference); + // Make sure the summary TextView didn't get any text view updates. + TileUtilsShadowRemoteViews shadowRemoteViews = + (TileUtilsShadowRemoteViews) ShadowExtractor.extract(tile.remoteViews); + assertThat(shadowRemoteViews.overrideViewId).isNull(); + assertThat(shadowRemoteViews.overrideText).isNull(); + } + public static ResolveInfo newInfo(boolean systemApp, String category) { return newInfo(systemApp, category, null); } @@ -423,4 +509,17 @@ public class TileUtilsTest { info.activityInfo.metaData.putString(key, value); } } + + @Implements(RemoteViews.class) + public static class TileUtilsShadowRemoteViews { + + private Integer overrideViewId; + private CharSequence overrideText; + + @Implementation + public void setTextViewText(int viewId, CharSequence text) { + overrideViewId = viewId; + overrideText = text; + } + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index b777d412ca6c..06d00be8aee8 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -45,8 +45,8 @@ import android.util.Log; import com.android.ims.ImsConfig; import com.android.internal.content.PackageHelper; +import com.android.internal.telephony.Phone; import com.android.internal.telephony.RILConstants; -import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; @@ -2617,9 +2617,9 @@ class DatabaseHelper extends SQLiteOpenHelper { loadSetting(stmt, Settings.Global.PREFERRED_NETWORK_MODE, type); // Set the preferred cdma subscription source to target desired value or default - // value defined in CdmaSubscriptionSourceManager + // value defined in Phone type = SystemProperties.getInt("ro.telephony.default_cdma_sub", - CdmaSubscriptionSourceManager.PREFERRED_CDMA_SUBSCRIPTION); + Phone.PREFERRED_CDMA_SUBSCRIPTION); loadSetting(stmt, Settings.Global.CDMA_SUBSCRIPTION_MODE, type); loadIntegerSetting(stmt, Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, diff --git a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java index 4a5d8b460517..2d794fb01ad6 100644 --- a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java +++ b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java @@ -21,6 +21,7 @@ import android.app.WallpaperManager; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; +import android.support.v4.graphics.ColorUtils; import android.util.Log; import android.util.SparseArray; @@ -41,15 +42,12 @@ public class ColorExtractor implements WallpaperManager.OnColorsChangedListener private static final String TAG = "ColorExtractor"; - @VisibleForTesting - static final int FALLBACK_COLOR = 0xff83888d; + public static final int FALLBACK_COLOR = 0xff83888d; private int mMainFallbackColor = FALLBACK_COLOR; private int mSecondaryFallbackColor = FALLBACK_COLOR; private final SparseArray<GradientColors[]> mGradientColors; private final ArrayList<OnColorsChangedListener> mOnColorsChangedListeners; - // Colors to return when the wallpaper isn't visible - private final GradientColors mWpHiddenColors; private final Context mContext; private final ExtractionType mExtractionType; @@ -60,9 +58,6 @@ public class ColorExtractor implements WallpaperManager.OnColorsChangedListener @VisibleForTesting public ColorExtractor(Context context, ExtractionType extractionType) { mContext = context; - mWpHiddenColors = new GradientColors(); - mWpHiddenColors.setMainColor(FALLBACK_COLOR); - mWpHiddenColors.setSecondaryColor(FALLBACK_COLOR); mExtractionType = extractionType; mGradientColors = new SparseArray<>(); @@ -123,7 +118,6 @@ public class ColorExtractor implements WallpaperManager.OnColorsChangedListener if (which != WallpaperManager.FLAG_LOCK && which != WallpaperManager.FLAG_SYSTEM) { throw new IllegalArgumentException("which should be FLAG_SYSTEM or FLAG_NORMAL"); } - return mGradientColors.get(which)[type]; } @@ -134,7 +128,6 @@ public class ColorExtractor implements WallpaperManager.OnColorsChangedListener GradientColors[] lockColors = mGradientColors.get(WallpaperManager.FLAG_LOCK); extractInto(colors, lockColors[TYPE_NORMAL], lockColors[TYPE_DARK], lockColors[TYPE_EXTRA_DARK]); - changed = true; } if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { @@ -149,7 +142,7 @@ public class ColorExtractor implements WallpaperManager.OnColorsChangedListener } } - private void triggerColorsChanged(int which) { + protected void triggerColorsChanged(int which) { for (OnColorsChangedListener listener: mOnColorsChangedListeners) { listener.onColorsChanged(this, which); } @@ -258,4 +251,4 @@ public class ColorExtractor implements WallpaperManager.OnColorsChangedListener public interface OnColorsChangedListener { void onColorsChanged(ColorExtractor colorExtractor, int which); } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java index 5b4b3ed77f3a..d9719f356f14 100644 --- a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java +++ b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java @@ -29,6 +29,8 @@ import android.util.Range; import com.google.android.colorextraction.ColorExtractor.GradientColors; +import java.util.List; + /** * Implementation of tonal color extraction */ @@ -40,9 +42,6 @@ public class Tonal implements ExtractionType { private static final float FIT_WEIGHT_S = 1.0f; private static final float FIT_WEIGHT_L = 10.0f; - // When extracting the main color, only consider colors - // present in at least MIN_COLOR_OCCURRENCE of the image - private static final float MIN_COLOR_OCCURRENCE = 0.1f; private static final boolean DEBUG = true; // Temporary variable to avoid allocations @@ -61,7 +60,10 @@ public class Tonal implements ExtractionType { @NonNull GradientColors outColorsNormal, @NonNull GradientColors outColorsDark, @NonNull GradientColors outColorsExtraDark) { - if (inWallpaperColors.getColors().size() == 0) { + final List<Color> mainColors = inWallpaperColors.getMainColors(); + final int mainColorsSize = mainColors.size(); + + if (mainColorsSize == 0) { return false; } // Tonal is not really a sort, it takes a color from the extracted @@ -69,30 +71,18 @@ public class Tonal implements ExtractionType { // palettes. The best fit is tweaked to be closer to the source color // and replaces the original palette - // First find the most representative color in the image - populationSort(inWallpaperColors); - // Calculate total - int total = 0; - for (Pair<Color, Integer> weightedColor : inWallpaperColors.getColors()) { - total += weightedColor.second; - } - - // Get bright colors that occur often enough in this image - Pair<Color, Integer> bestColor = null; - float[] hsl = new float[3]; - for (Pair<Color, Integer> weightedColor : inWallpaperColors.getColors()) { - float colorOccurrence = weightedColor.second / (float) total; - if (colorOccurrence < MIN_COLOR_OCCURRENCE) { - break; - } - - int colorValue = weightedColor.first.toArgb(); + // Get the most preeminent, non-blacklisted color. + Color bestColor = null; + final float[] hsl = new float[3]; + for (int i = 0; i < mainColorsSize; i++) { + final Color color = mainColors.get(i); + final int colorValue = color.toArgb(); ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue), Color.blue(colorValue), hsl); // Stop when we find a color that meets our criteria if (!isBlacklisted(hsl)) { - bestColor = weightedColor; + bestColor = color; break; } } @@ -102,7 +92,7 @@ public class Tonal implements ExtractionType { return false; } - int colorValue = bestColor.first.toArgb(); + int colorValue = bestColor.toArgb(); ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue), Color.blue(colorValue), hsl); @@ -111,7 +101,7 @@ public class Tonal implements ExtractionType { hsl[0] /= 360f; // Find the palette that contains the closest color - TonalPalette palette = findTonalPalette(hsl[0]); + TonalPalette palette = findTonalPalette(hsl[0], hsl[1]); if (palette == null) { Log.w(TAG, "Could not find a tonal palette!"); return false; @@ -212,10 +202,6 @@ public class Tonal implements ExtractionType { return false; } - private static void populationSort(@NonNull WallpaperColors wallpaperColors) { - wallpaperColors.getColors().sort((a, b) -> b.second - a.second); - } - /** * Offsets all colors by a delta, clamping values that go beyond what's * supported on the color space. @@ -265,11 +251,19 @@ public class Tonal implements ExtractionType { } @Nullable - private static TonalPalette findTonalPalette(float h) { + private static TonalPalette findTonalPalette(float h, float s) { + // Fallback to a grey palette if the color is too desaturated. + // This avoids hue shifts. + if (s < 0.05f) { + return GREY_PALETTE; + } + TonalPalette best = null; float error = Float.POSITIVE_INFINITY; - for (TonalPalette candidate : TONAL_PALETTES) { + for (int i = 0; i < TONAL_PALETTES.length; i++) { + final TonalPalette candidate = TONAL_PALETTES[i]; + if (h >= candidate.minHue && h <= candidate.maxHue) { best = candidate; break; @@ -339,271 +333,280 @@ public class Tonal implements ExtractionType { // a best fit. Each palette is defined as 22 HSL colors private static final TonalPalette[] TONAL_PALETTES = { new TonalPalette( - new float[]{0.991f, 0.9833333333333333f, 0f, 0f, 0f, 0.01134380453752181f, - 0.015625000000000003f, 0.024193548387096798f, 0.027397260273972573f, - 0.017543859649122865f}, - new float[]{1f, 1f, 1f, 1f, 0.8434782608695652f, 1f, 1f, 1f, 1f, 1f}, - new float[]{0.2f, 0.27450980392156865f, 0.34901960784313724f, - 0.4235294117647059f, 0.5490196078431373f, 0.6254901960784314f, - 0.6862745098039216f, 0.7568627450980392f, 0.8568627450980393f, - 0.9254901960784314f} + new float[] {1f, 1f, 0.991f, 0.991f, 0.9833333333333333f, 0f, 0f, 0f, + 0.01134380453752181f, 0.015625000000000003f, 0.024193548387096798f, + 0.027397260273972573f, 0.017543859649122865f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 0.8434782608695652f, 1f, 1f, 1f, 1f, + 1f}, + new float[] {0.04f, 0.09f, 0.14f, 0.2f, 0.27450980392156865f, + 0.34901960784313724f, 0.4235294117647059f, 0.5490196078431373f, + 0.6254901960784314f, 0.6862745098039216f, 0.7568627450980392f, + 0.8568627450980393f, 0.9254901960784314f} ), new TonalPalette( - new float[]{0.6385767790262171f, 0.6301169590643275f, 0.6223958333333334f, - 0.6151079136690647f, 0.6065400843881856f, 0.5986964618249534f, - 0.5910746812386157f, 0.5833333333333334f, 0.5748031496062993f, - 0.5582010582010583f}, - new float[]{1f, 1f, 0.9014084507042253f, 0.8128654970760234f, - 0.7979797979797981f, 0.7816593886462883f, 0.778723404255319f, - 1f, 1f, 1f}, - new float[]{0.17450980392156862f, 0.2235294117647059f, 0.2784313725490196f, - 0.3352941176470588f, 0.388235294117647f, 0.44901960784313727f, - 0.5392156862745098f, 0.6509803921568628f, 0.7509803921568627f, - 0.8764705882352941f} + new float[] {0.638f, 0.638f, 0.6385767790262171f, 0.6301169590643275f, + 0.6223958333333334f, 0.6151079136690647f, 0.6065400843881856f, + 0.5986964618249534f, 0.5910746812386157f, 0.5833333333333334f, + 0.5748031496062993f, 0.5582010582010583f}, + new float[] {1f, 1f, 1f, 1f, 0.9014084507042253f, 0.8128654970760234f, + 0.7979797979797981f, 0.7816593886462883f, 0.778723404255319f, 1f, 1f, + 1f}, + new float[] {0.05f, 0.12f, 0.17450980392156862f, 0.2235294117647059f, + 0.2784313725490196f, 0.3352941176470588f, 0.388235294117647f, + 0.44901960784313727f, 0.5392156862745098f, 0.6509803921568628f, + 0.7509803921568627f, 0.8764705882352941f} ), new TonalPalette( - new float[]{0.5669934640522876f, 0.5748031496062993f, + new float[] {0.563f, 0.569f, 0.5666f, 0.5669934640522876f, 0.5748031496062993f, 0.5595238095238095f, 0.5473118279569893f, 0.5393258426966292f, 0.5315955766192734f, 0.524031007751938f, 0.5154711673699016f, 0.508080808080808f, 0.5f}, - new float[]{1f, 1f, 1f, 1f, 1f, 1f, 0.8847736625514403f, 1f, 1f, 1f}, - new float[]{0.2f, 0.24901960784313726f, 0.27450980392156865f, - 0.30392156862745096f, 0.34901960784313724f, 0.4137254901960784f, - 0.47647058823529415f, 0.5352941176470588f, 0.6764705882352942f, 0.8f} + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 0.8847736625514403f, 1f, 1f, + 1f}, + new float[] {0.07f, 0.12f, 0.16f, 0.2f, 0.24901960784313726f, + 0.27450980392156865f, 0.30392156862745096f, 0.34901960784313724f, + 0.4137254901960784f, 0.47647058823529415f, 0.5352941176470588f, + 0.6764705882352942f, 0.8f} ), new TonalPalette( - new float[]{0.5082304526748972f, 0.5069444444444444f, 0.5f, 0.5f, - 0.5f, 0.48724954462659376f, 0.4800347222222222f, - 0.4755134281200632f, 0.4724409448818897f, 0.4671052631578947f}, - new float[]{1f, 0.8888888888888887f, 0.9242424242424242f, 1f, 1f, - 0.8133333333333332f, 0.7868852459016393f, 1f, 1f, 1f}, - new float[]{0.1588235294117647f, 0.21176470588235297f, - 0.25882352941176473f, 0.3f, 0.34901960784313724f, + new float[] {0.508f, 0.511f, 0.508f, 0.508f, 0.5082304526748972f, + 0.5069444444444444f, 0.5f, 0.5f, 0.5f, 0.48724954462659376f, + 0.4800347222222222f, 0.4755134281200632f, 0.4724409448818897f, + 0.4671052631578947f}, + new float[] {1f, 1f, 1f, 1f, 1f, 0.8888888888888887f, 0.9242424242424242f, 1f, + 1f, 0.8133333333333332f, 0.7868852459016393f, 1f, 1f, 1f}, + new float[] {0.04f, 0.06f, 0.08f, 0.12f, 0.1588235294117647f, + 0.21176470588235297f, 0.25882352941176473f, 0.3f, 0.34901960784313724f, 0.44117647058823534f, 0.5215686274509804f, 0.5862745098039216f, 0.7509803921568627f, 0.8509803921568627f} ), new TonalPalette( - new float[]{0.3333333333333333f, 0.3333333333333333f, + new float[] {0.333f, 0.333f, 0.333f, 0.3333333333333333f, 0.3333333333333333f, 0.34006734006734f, 0.34006734006734f, 0.34006734006734f, 0.34259259259259256f, 0.3475783475783476f, 0.34767025089605735f, 0.3467741935483871f, 0.3703703703703704f}, - new float[]{0.6703296703296703f, 0.728813559322034f, + new float[] {0.70f, 0.72f, 0.69f, 0.6703296703296703f, 0.728813559322034f, 0.5657142857142856f, 0.5076923076923077f, 0.3944223107569721f, 0.6206896551724138f, 0.8931297709923666f, 1f, 1f, 1f}, - new float[]{0.1784313725490196f, 0.23137254901960785f, + new float[] {0.05f, 0.08f, 0.1784313725490196f, 0.23137254901960785f, 0.3431372549019608f, 0.38235294117647056f, 0.49215686274509807f, 0.6588235294117647f, 0.7431372549019608f, 0.8176470588235294f, 0.8784313725490196f, 0.9294117647058824f} ), new TonalPalette( - new float[]{0.162280701754386f, 0.15032679738562088f, + new float[] {0.161f, 0.163f, 0.163f, 0.162280701754386f, 0.15032679738562088f, 0.15879265091863518f, 0.16236559139784948f, 0.17443868739205526f, - 0.17824074074074076f, 0.18674698795180725f, - 0.18692449355432778f, 0.1946778711484594f, 0.18604651162790695f}, - new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, - new float[]{0.14901960784313725f, 0.2f, 0.24901960784313726f, - 0.30392156862745096f, 0.3784313725490196f, 0.4235294117647059f, - 0.48823529411764705f, 0.6450980392156863f, 0.7666666666666666f, - 0.8313725490196078f} + 0.17824074074074076f, 0.18674698795180725f, 0.18692449355432778f, + 0.1946778711484594f, 0.18604651162790695f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[] {0.05f, 0.08f, 0.11f, 0.14901960784313725f, 0.2f, + 0.24901960784313726f, 0.30392156862745096f, 0.3784313725490196f, + 0.4235294117647059f, 0.48823529411764705f, 0.6450980392156863f, + 0.7666666666666666f, 0.8313725490196078f} ), new TonalPalette( - new float[]{0.10619469026548674f, 0.11924686192468618f, - 0.13046448087431692f, 0.14248366013071895f, 0.1506024096385542f, - 0.16220238095238093f, 0.16666666666666666f, + new float[] {0.108f, 0.105f, 0.105f, 0.105f, 0.10619469026548674f, + 0.11924686192468618f, 0.13046448087431692f, 0.14248366013071895f, + 0.1506024096385542f, 0.16220238095238093f, 0.16666666666666666f, 0.16666666666666666f, 0.162280701754386f, 0.15686274509803924f}, - new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, - new float[]{0.44313725490196076f, 0.46862745098039216f, - 0.47843137254901963f, 0.5f, 0.5117647058823529f, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[] {0.17f, 0.22f, 0.28f, 0.35f, 0.44313725490196076f, + 0.46862745098039216f, 0.47843137254901963f, 0.5f, 0.5117647058823529f, 0.5607843137254902f, 0.6509803921568628f, 0.7509803921568627f, 0.8509803921568627f, 0.9f} ), new TonalPalette( - new float[]{0.03561253561253561f, 0.05098039215686275f, - 0.07516339869281045f, 0.09477124183006536f, 0.1150326797385621f, - 0.134640522875817f, 0.14640522875816991f, 0.1582397003745319f, - 0.15773809523809523f, 0.15359477124183002f}, - new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, - new float[]{0.4588235294117647f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, - 0.5f, 0.6509803921568628f, 0.7803921568627451f, 0.9f} + new float[] {0.036f, 0.036f, 0.036f, 0.036f, 0.03561253561253561f, + 0.05098039215686275f, 0.07516339869281045f, 0.09477124183006536f, + 0.1150326797385621f, 0.134640522875817f, 0.14640522875816991f, + 0.1582397003745319f, 0.15773809523809523f, 0.15359477124183002f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[] {0.19f, 0.26f, 0.34f, 0.39f, 0.4588235294117647f, 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, 0.6509803921568628f, 0.7803921568627451f, 0.9f} ), new TonalPalette( - new float[]{0.9596491228070175f, 0.9593837535014005f, + new float[] {0.955f, 0.961f, 0.958f, 0.9596491228070175f, 0.9593837535014005f, 0.9514767932489452f, 0.943859649122807f, 0.9396825396825397f, 0.9395424836601307f, 0.9393939393939394f, 0.9362745098039216f, 0.9754098360655739f, 0.9824561403508771f}, - new float[]{0.84070796460177f, 0.8206896551724138f, + new float[] {0.87f, 0.85f, 0.85f, 0.84070796460177f, 0.8206896551724138f, 0.7979797979797981f, 0.7661290322580644f, 0.9051724137931036f, 1f, 1f, 1f, 1f, 1f}, - new float[]{0.22156862745098038f, 0.2843137254901961f, + new float[] {0.06f, 0.11f, 0.16f, 0.22156862745098038f, 0.2843137254901961f, 0.388235294117647f, 0.48627450980392156f, 0.5450980392156863f, 0.6f, 0.6764705882352942f, 0.8f, 0.8803921568627451f, 0.9254901960784314f} ), new TonalPalette( - new float[]{0.841025641025641f, 0.8333333333333334f, + new float[] {0.866f, 0.855f, 0.841025641025641f, 0.8333333333333334f, 0.8285256410256411f, 0.821522309711286f, 0.8083333333333333f, 0.8046594982078853f, 0.8005822416302766f, 0.7842377260981912f, 0.7771084337349398f, 0.7747747747747749f}, - new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 0.737142857142857f, 0.6434108527131781f, 0.46835443037974644f}, - new float[]{0.12745098039215685f, 0.15490196078431373f, + new float[] {0.05f, 0.08f, 0.12745098039215685f, 0.15490196078431373f, 0.20392156862745098f, 0.24901960784313726f, 0.3137254901960784f, - 0.36470588235294116f, 0.44901960784313727f, - 0.6568627450980392f, 0.7470588235294118f, 0.8450980392156863f} + 0.36470588235294116f, 0.44901960784313727f, 0.6568627450980392f, + 0.7470588235294118f, 0.8450980392156863f} ), new TonalPalette( - new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f}, - new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f}, - new float[]{0.14901960784313725f, 0.2f, 0.2980392156862745f, 0.4f, - 0.4980392156862745f, 0.6196078431372549f, 0.7176470588235294f, - 0.8196078431372549f, 0.9176470588235294f, 0.9490196078431372f} - ), - new TonalPalette( - new float[]{0.955952380952381f, 0.9681069958847737f, - 0.9760479041916167f, 0.9873563218390804f, 0f, 0f, + new float[] {0.925f, 0.93f, 0.938f, 0.947f, 0.955952380952381f, + 0.9681069958847737f, 0.9760479041916167f, 0.9873563218390804f, 0f, 0f, 0.009057971014492771f, 0.026748971193415648f, 0.041666666666666616f, 0.05303030303030304f}, - new float[]{1f, 0.8350515463917526f, 0.6929460580912863f, + new float[] {1f, 1f, 1f, 1f, 1f, 0.8350515463917526f, 0.6929460580912863f, 0.6387665198237885f, 0.6914893617021276f, 0.7583892617449666f, 0.8070175438596495f, 0.9310344827586209f, 1f, 1f}, - new float[]{0.27450980392156865f, 0.3803921568627451f, - 0.4725490196078432f, 0.5549019607843138f, 0.6313725490196078f, - 0.707843137254902f, 0.7764705882352941f, 0.8294117647058823f, - 0.9058823529411765f, 0.9568627450980391f} + new float[] {0.10f, 0.13f, 0.17f, 0.2f, 0.27450980392156865f, + 0.3803921568627451f, 0.4725490196078432f, 0.5549019607843138f, + 0.6313725490196078f, 0.707843137254902f, 0.7764705882352941f, + 0.8294117647058823f, 0.9058823529411765f, 0.9568627450980391f} ), new TonalPalette( - new float[]{0.7514619883040936f, 0.7679738562091503f, + new float[] {0.733f, 0.736f, 0.744f, 0.7514619883040936f, 0.7679738562091503f, 0.7802083333333333f, 0.7844311377245509f, 0.796875f, 0.8165618448637316f, 0.8487179487179487f, 0.8582375478927203f, 0.8562091503267975f, 0.8666666666666667f}, - new float[]{1f, 1f, 0.8163265306122449f, 0.6653386454183268f, + new float[] {1f, 1f, 1f, 1f, 1f, 0.8163265306122449f, 0.6653386454183268f, 0.7547169811320753f, 0.929824561403509f, 0.9558823529411766f, 0.9560439560439562f, 1f, 1f}, - new float[]{0.2235294117647059f, 0.3f, 0.38431372549019605f, - 0.492156862745098f, 0.5843137254901961f, 0.6647058823529411f, - 0.7333333333333334f, 0.8215686274509804f, 0.9f, + new float[] {0.07f, 0.12f, 0.17f, 0.2235294117647059f, 0.3f, + 0.38431372549019605f, 0.492156862745098f, 0.5843137254901961f, + 0.6647058823529411f, 0.7333333333333334f, 0.8215686274509804f, 0.9f, 0.9411764705882353f} ), new TonalPalette( - new float[]{0.6666666666666666f, 0.6666666666666666f, + new float[] {0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f}, - new float[]{0.24590163934426232f, 0.17880794701986752f, + new float[] {0.25f, 0.24590163934426232f, 0.17880794701986752f, 0.14606741573033713f, 0.13761467889908252f, 0.14893617021276592f, - 0.16756756756756758f, 0.20312500000000017f, - 0.26086956521739135f, 0.29999999999999966f, 0.5000000000000004f}, - new float[]{0.2392156862745098f, 0.296078431372549f, + 0.16756756756756758f, 0.20312500000000017f, 0.26086956521739135f, + 0.29999999999999966f, 0.5000000000000004f}, + new float[] {0.18f, 0.2392156862745098f, 0.296078431372549f, 0.34901960784313724f, 0.4274509803921569f, 0.5392156862745098f, 0.6372549019607843f, 0.7490196078431373f, 0.8196078431372549f, 0.8823529411764706f, 0.9372549019607843f} ), new TonalPalette( - new float[]{0.9678571428571429f, 0.9944812362030905f, 0f, 0f, + new float[] {0.938f, 0.944f, 0.952f, 0.961f, 0.9678571428571429f, + 0.9944812362030905f, 0f, 0f, 0.0047348484848484815f, 0.00316455696202532f, 0f, 0.9980392156862745f, 0.9814814814814816f, 0.9722222222222221f}, - new float[]{1f, 0.7023255813953488f, 0.6638655462184874f, + new float[] {1f, 1f, 1f, 1f, 1f, 0.7023255813953488f, 0.6638655462184874f, 0.6521739130434782f, 0.7719298245614035f, 0.8315789473684211f, 0.6867469879518071f, 0.7264957264957265f, 0.8181818181818182f, 0.8181818181818189f}, - new float[]{0.27450980392156865f, 0.4215686274509804f, + new float[] {0.08f, 0.13f, 0.18f, 0.23f, 0.27450980392156865f, + 0.4215686274509804f, 0.4666666666666667f, 0.503921568627451f, 0.5529411764705883f, 0.6274509803921569f, 0.6745098039215687f, 0.7705882352941176f, 0.892156862745098f, 0.9568627450980391f} ), new TonalPalette( - new float[]{0.9052287581699346f, 0.9112021857923498f, 0.9270152505446624f, - 0.9343137254901961f, 0.9391534391534391f, 0.9437984496124031f, - 0.943661971830986f, 0.9438943894389439f, 0.9426229508196722f, - 0.9444444444444444f}, - new float[]{1f, 0.8133333333333332f, 0.7927461139896375f, 0.7798165137614679f, - 0.7777777777777779f, 0.8190476190476191f, 0.8255813953488372f, - 0.8211382113821142f, 0.8133333333333336f, 0.8000000000000006f}, - new float[]{0.2f, 0.29411764705882354f, 0.3784313725490196f, - 0.42745098039215684f, 0.4764705882352941f, 0.5882352941176471f, - 0.6627450980392157f, 0.7588235294117647f, 0.8529411764705882f, - 0.9411764705882353f} + new float[] {0.88f, 0.888f, 0.897f, 0.9052287581699346f, 0.9112021857923498f, + 0.9270152505446624f, 0.9343137254901961f, 0.9391534391534391f, + 0.9437984496124031f, 0.943661971830986f, 0.9438943894389439f, + 0.9426229508196722f, 0.9444444444444444f}, + new float[] {1f, 1f, 1f, 1f, 0.8133333333333332f, 0.7927461139896375f, + 0.7798165137614679f, 0.7777777777777779f, 0.8190476190476191f, + 0.8255813953488372f, 0.8211382113821142f, 0.8133333333333336f, + 0.8000000000000006f}, + new float[] {0.08f, 0.12f, 0.16f, 0.2f, 0.29411764705882354f, + 0.3784313725490196f, 0.42745098039215684f, 0.4764705882352941f, + 0.5882352941176471f, 0.6627450980392157f, 0.7588235294117647f, + 0.8529411764705882f, 0.9411764705882353f} ), new TonalPalette( - new float[]{0.6884057971014492f, 0.6974789915966387f, 0.7079889807162534f, - 0.7154471544715447f, 0.7217741935483872f, 0.7274143302180687f, - 0.7272727272727273f, 0.7258064516129031f, 0.7252252252252251f, - 0.7333333333333333f}, - new float[]{0.8214285714285715f, 0.6878612716763006f, 0.6080402010050251f, - 0.5774647887323943f, 0.5391304347826086f, 0.46724890829694316f, - 0.4680851063829788f, 0.462686567164179f, 0.45679012345678977f, - 0.4545454545454551f}, - new float[]{0.2196078431372549f, 0.33921568627450976f, 0.39019607843137255f, - 0.4176470588235294f, 0.45098039215686275f, + new float[] {0.669f, 0.680f, 0.6884057971014492f, 0.6974789915966387f, + 0.7079889807162534f, 0.7154471544715447f, 0.7217741935483872f, + 0.7274143302180687f, 0.7272727272727273f, 0.7258064516129031f, + 0.7252252252252251f, 0.7333333333333333f}, + new float[] {0.81f, 0.81f, 0.8214285714285715f, 0.6878612716763006f, + 0.6080402010050251f, 0.5774647887323943f, 0.5391304347826086f, + 0.46724890829694316f, 0.4680851063829788f, 0.462686567164179f, + 0.45679012345678977f, 0.4545454545454551f}, + new float[] {0.12f, 0.16f, 0.2196078431372549f, 0.33921568627450976f, + 0.39019607843137255f, 0.4176470588235294f, 0.45098039215686275f, 0.5509803921568628f, 0.6313725490196078f, 0.7372549019607844f, 0.8411764705882353f, 0.9352941176470588f} ), new TonalPalette( - new float[]{0.6470588235294118f, 0.6516666666666667f, 0.6464174454828661f, + new float[] {0.6470588235294118f, 0.6516666666666667f, 0.6464174454828661f, 0.6441441441441442f, 0.6432748538011696f, 0.6416666666666667f, 0.6402439024390243f, 0.6412429378531074f, 0.6435185185185186f, 0.6428571428571429f}, - new float[]{0.8095238095238095f, 0.6578947368421053f, 0.5721925133689839f, + new float[] {0.8095238095238095f, 0.6578947368421053f, 0.5721925133689839f, 0.5362318840579711f, 0.5f, 0.4424778761061947f, 0.44086021505376327f, - 0.44360902255639095f, - 0.4499999999999997f, 0.4375000000000006f}, - new float[]{0.16470588235294117f, 0.2980392156862745f, 0.36666666666666664f, + 0.44360902255639095f, 0.4499999999999997f, 0.4375000000000006f}, + new float[] {0.16470588235294117f, 0.2980392156862745f, 0.36666666666666664f, 0.40588235294117647f, 0.44705882352941173f, 0.5568627450980392f, 0.6352941176470588f, 0.7392156862745098f, 0.8431372549019608f, 0.9372549019607843f} ), new TonalPalette( - new float[]{0.46732026143790845f, 0.4718614718614719f, 0.4793650793650794f, - 0.48071625344352614f, 0.4829683698296837f, 0.484375f, - 0.4841269841269842f, 0.48444444444444457f, 0.48518518518518516f, - 0.4907407407407408f}, - new float[]{1f, 1f, 1f, 1f, 1f, 0.6274509803921569f, 0.41832669322709176f, - 0.41899441340782106f, 0.4128440366972478f, - 0.4090909090909088f}, - new float[]{0.1f, 0.15098039215686274f, 0.20588235294117646f, + new float[] {0.469f, 0.46732026143790845f, 0.4718614718614719f, + 0.4793650793650794f, 0.48071625344352614f, 0.4829683698296837f, + 0.484375f, 0.4841269841269842f, 0.48444444444444457f, + 0.48518518518518516f, 0.4907407407407408f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 0.6274509803921569f, 0.41832669322709176f, + 0.41899441340782106f, 0.4128440366972478f, 0.4090909090909088f}, + new float[] {0.07f, 0.1f, 0.15098039215686274f, 0.20588235294117646f, 0.2372549019607843f, 0.26862745098039215f, 0.4f, 0.5078431372549019f, 0.6490196078431372f, 0.7862745098039216f, 0.9137254901960784f} ), new TonalPalette( - new float[]{0.5444444444444444f, 0.5555555555555556f, 0.5555555555555556f, - 0.553763440860215f, 0.5526315789473684f, 0.5555555555555556f, - 0.5555555555555555f, 0.5555555555555556f, 0.5512820512820514f, - 0.5666666666666667f}, - new float[]{0.24590163934426232f, 0.19148936170212766f, 0.1791044776119403f, - 0.18343195266272191f, 0.18446601941747576f, + new float[] {0.542f, 0.5444444444444444f, 0.5555555555555556f, + 0.5555555555555556f, 0.553763440860215f, 0.5526315789473684f, + 0.5555555555555556f, 0.5555555555555555f, 0.5555555555555556f, + 0.5512820512820514f, 0.5666666666666667f}, + new float[] {0.25f, 0.24590163934426232f, 0.19148936170212766f, + 0.1791044776119403f, 0.18343195266272191f, 0.18446601941747576f, 0.1538461538461539f, 0.15625000000000003f, 0.15328467153284678f, 0.15662650602409653f, 0.151515151515151f}, - new float[]{0.1196078431372549f, 0.1843137254901961f, 0.2627450980392157f, + new float[] {0.05f, 0.1196078431372549f, 0.1843137254901961f, + 0.2627450980392157f, 0.33137254901960783f, 0.403921568627451f, 0.5411764705882354f, 0.6235294117647059f, 0.7313725490196079f, 0.8372549019607843f, 0.9352941176470588f} ), new TonalPalette( - new float[]{0.022222222222222223f, 0.02469135802469136f, 0.031249999999999997f, + new float[] {0.022222222222222223f, 0.02469135802469136f, 0.031249999999999997f, 0.03947368421052631f, 0.04166666666666668f, 0.043650793650793655f, 0.04411764705882352f, 0.04166666666666652f, 0.04444444444444459f, 0.05555555555555529f}, - new float[]{0.33333333333333337f, 0.2783505154639175f, 0.2580645161290323f, + new float[] {0.33333333333333337f, 0.2783505154639175f, 0.2580645161290323f, 0.25675675675675674f, 0.2528735632183908f, 0.17500000000000002f, 0.15315315315315312f, 0.15189873417721522f, 0.15789473684210534f, 0.15789473684210542f}, - new float[]{0.08823529411764705f, 0.19019607843137254f, 0.2431372549019608f, + new float[] {0.08823529411764705f, 0.19019607843137254f, 0.2431372549019608f, 0.2901960784313725f, 0.3411764705882353f, 0.47058823529411764f, 0.5647058823529412f, 0.6901960784313725f, 0.8137254901960784f, 0.9254901960784314f} ), new TonalPalette( - new float[]{0.050884955752212385f, 0.07254901960784313f, 0.0934640522875817f, + new float[] {0.027f, 0.03f, 0.038f, 0.044f, 0.050884955752212385f, + 0.07254901960784313f, 0.0934640522875817f, 0.10457516339869281f, 0.11699346405228758f, 0.1255813953488372f, 0.1268939393939394f, 0.12533333333333332f, 0.12500000000000003f, 0.12777777777777777f}, - new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, - new float[]{0.44313725490196076f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5784313725490196f, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[] {0.25f, 0.3f, 0.35f, 0.4f, 0.44313725490196076f, 0.5f, 0.5f, 0.5f, + 0.5f, 0.5784313725490196f, 0.6549019607843137f, 0.7549019607843137f, 0.8509803921568627f, 0.9411764705882353f} ) }; + private static final TonalPalette GREY_PALETTE = new TonalPalette( + new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f}, + new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f}, + new float[]{0.08f, 0.11f, 0.14901960784313725f, 0.2f, 0.2980392156862745f, 0.4f, + 0.4980392156862745f, 0.6196078431372549f, 0.7176470588235294f, + 0.8196078431372549f, 0.9176470588235294f, 0.9490196078431372f} + ); + @SuppressWarnings("WeakerAccess") @VisibleForTesting static final ColorRange[] BLACKLISTED_COLORS = new ColorRange[] { diff --git a/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java b/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java index fd698d050a0c..b5f4a8c80c53 100644 --- a/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java +++ b/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java @@ -39,7 +39,7 @@ import org.junit.Test; import org.junit.runner.RunWith; /** - * Tests tonal palette generation. + * Tests color extraction generation. */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -101,14 +101,12 @@ public class ColorExtractorTest { }; ColorExtractor extractor = new ColorExtractor(mContext, type); - assertEquals("Extracted colors not being used!", - extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_NORMAL), - colorsExpectedNormal); - assertEquals("Extracted colors not being used!", - extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_DARK), - colorsExpectedDark); - assertEquals("Extracted colors not being used!", - extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_EXTRA_DARK), - colorsExpectedExtraDark); + GradientColors colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM, + ColorExtractor.TYPE_NORMAL); + assertEquals("Extracted colors not being used!", colors, colorsExpectedNormal); + colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_DARK); + assertEquals("Extracted colors not being used!", colors, colorsExpectedDark); + colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_EXTRA_DARK); + assertEquals("Extracted colors not being used!", colors, colorsExpectedExtraDark); } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DozeServicePlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DozeServicePlugin.java new file mode 100644 index 000000000000..3ca5690af474 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DozeServicePlugin.java @@ -0,0 +1,21 @@ +package com.android.systemui.plugins; + +import com.android.systemui.plugins.annotations.ProvidesInterface; + +@ProvidesInterface(action = DozeServicePlugin.ACTION, version = DozeServicePlugin.VERSION) +public interface DozeServicePlugin extends Plugin { + String ACTION = "com.android.systemui.action.PLUGIN_DOZE"; + int VERSION = 1; + + public interface RequestDoze { + void onRequestShowDoze(); + + void onRequestHideDoze(); + } + + void onDreamingStarted(); + + void onDreamingStopped(); + + void setDozeRequester(RequestDoze requester); +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java index 3cd5d89ea142..f6cf03562014 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java @@ -22,6 +22,7 @@ import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @ProvidesInterface(version = NotificationSwipeActionHelper.VERSION) @DependsOn(target = SnoozeOption.class) @@ -56,19 +57,17 @@ public interface NotificationSwipeActionHelper { public boolean swipedFastEnough(float translation, float velocity); @ProvidesInterface(version = SnoozeOption.VERSION) - public static class SnoozeOption { - public static final int VERSION = 1; - public int snoozeForMinutes; - public SnoozeCriterion criterion; - public CharSequence description; - public CharSequence confirmation; - - public SnoozeOption(SnoozeCriterion crit, int minsToSnoozeFor, CharSequence desc, - CharSequence confirm) { - criterion = crit; - snoozeForMinutes = minsToSnoozeFor; - description = desc; - confirmation = confirm; - } + public interface SnoozeOption { + public static final int VERSION = 2; + + public SnoozeCriterion getSnoozeCriterion(); + + public CharSequence getDescription(); + + public CharSequence getConfirmation(); + + public int getMinutesToSnoozeFor(); + + public AccessibilityAction getAccessibilityAction(); } } diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml index 9a9716277662..501d0a57148a 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml @@ -53,7 +53,7 @@ android:gravity="center" android:layout_centerHorizontal="true" android:layout_marginRight="72dp" - androidprv:scaledTextSize="28" + androidprv:scaledTextSize="@integer/scaled_password_text_size" android:textColor="?attr/bgProtectTextColor" android:contentDescription="@string/keyguard_accessibility_pin_area" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml index f0dec762abc6..c4732e42b5ad 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml @@ -66,7 +66,7 @@ android:gravity="center" android:layout_centerHorizontal="true" android:layout_marginRight="72dp" - androidprv:scaledTextSize="28" + androidprv:scaledTextSize="@integer/scaled_password_text_size" android:textColor="?attr/bgProtectTextColor" android:contentDescription="@string/keyguard_accessibility_sim_pin_area" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml index 119b3ee11335..1c7defd6399a 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml @@ -67,7 +67,7 @@ android:gravity="center" android:layout_centerHorizontal="true" android:layout_marginRight="72dp" - androidprv:scaledTextSize="28" + androidprv:scaledTextSize="@integer/scaled_password_text_size" android:textColor="?attr/bgProtectTextColor" android:contentDescription="@string/keyguard_accessibility_sim_puk_area" /> diff --git a/packages/SystemUI/res-keyguard/values/attrs.xml b/packages/SystemUI/res-keyguard/values/attrs.xml index d3d60a1e18d2..802bd308d407 100644 --- a/packages/SystemUI/res-keyguard/values/attrs.xml +++ b/packages/SystemUI/res-keyguard/values/attrs.xml @@ -41,7 +41,4 @@ <declare-styleable name="CarrierText"> <attr name="allCaps" format="boolean" /> </declare-styleable> - - <attr name="pinDividerColor" format="color" /> - <attr name="pinDeleteColor" format="color" /> </resources> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 41c723e3daf7..a721dd0609dc 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -48,6 +48,8 @@ <!-- The size of the dots in the PIN unlock method. --> <dimen name="password_dot_size">9dp</dimen> + <!-- The size of PIN text in the PIN unlock method. --> + <integer name="scaled_password_text_size">40</integer> <!-- The padding between chars of the password view. --> <dimen name="password_char_padding">8dp</dimen> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index d7ff349b52bf..0c96b0b221ab 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -32,8 +32,8 @@ <item name="android:singleLine">true</item> <item name="android:gravity">center_horizontal|center_vertical</item> <item name="android:background">@null</item> - <item name="android:textSize">36sp</item> - <item name="android:fontFamily">sans-serif-light</item> + <item name="android:textSize">32sp</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamilyLight</item> <item name="android:textColor">?attr/bgProtectTextColor</item> <item name="android:paddingBottom">-16dp</item> </style> @@ -53,7 +53,7 @@ </style> <style name="widget_big_thin"> <item name="android:textSize">@dimen/widget_big_font_size</item> - <item name="android:fontFamily">sans-serif-light</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamilyLight</item> </style> <style name="BouncerSecurityContainer"> diff --git a/packages/SystemUI/res/color/qs_background_dark.xml b/packages/SystemUI/res/color/qs_background_dark.xml index 1aa732f43286..62e495974623 100644 --- a/packages/SystemUI/res/color/qs_background_dark.xml +++ b/packages/SystemUI/res/color/qs_background_dark.xml @@ -16,5 +16,5 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:alpha="0.93" - android:color="?android:attr/colorPrimaryDark"/> + android:color="?android:attr/colorBackgroundFloating"/> </selector> diff --git a/packages/SystemUI/res/drawable/qs_background_primary.xml b/packages/SystemUI/res/drawable/qs_background_primary.xml index 416583013e64..03bba53946da 100644 --- a/packages/SystemUI/res/drawable/qs_background_primary.xml +++ b/packages/SystemUI/res/drawable/qs_background_primary.xml @@ -15,6 +15,6 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android"> <shape> - <solid android:color="?android:attr/colorBackgroundFloating"/> + <solid android:color="@color/qs_background_dark"/> </shape> </inset> diff --git a/packages/SystemUI/res/layout/navigation_bar_window.xml b/packages/SystemUI/res/layout/navigation_bar_window.xml index 051bf3ac3faf..6fa46d4ba3f8 100644 --- a/packages/SystemUI/res/layout/navigation_bar_window.xml +++ b/packages/SystemUI/res/layout/navigation_bar_window.xml @@ -16,11 +16,11 @@ ** limitations under the License. */ --> -<FrameLayout +<com.android.systemui.statusbar.phone.NavigationBarFrame xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/navigation_bar_frame" android:layout_height="match_parent" android:layout_width="match_parent"> -</FrameLayout> +</com.android.systemui.statusbar.phone.NavigationBarFrame> diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml index b70f24b4d552..3209f27f7e00 100644 --- a/packages/SystemUI/res/layout/notification_snooze.xml +++ b/packages/SystemUI/res/layout/notification_snooze.xml @@ -20,12 +20,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:clickable="true" android:background="@color/notification_guts_bg_color" android:theme="@*android:style/Theme.DeviceDefault.Light"> <RelativeLayout - android:layout_width="match_parent" android:id="@+id/notification_snooze" + android:layout_width="match_parent" android:layout_height="@dimen/snooze_snackbar_min_height"> <TextView diff --git a/packages/SystemUI/res/layout/tv_pip_controls.xml b/packages/SystemUI/res/layout/tv_pip_controls.xml index 61ac6f6991ff..0b7bce13d761 100644 --- a/packages/SystemUI/res/layout/tv_pip_controls.xml +++ b/packages/SystemUI/res/layout/tv_pip_controls.xml @@ -22,24 +22,24 @@ <com.android.systemui.pip.tv.PipControlButtonView android:id="@+id/full_button" - android:layout_width="100dp" + android:layout_width="@dimen/picture_in_picture_button_width" android:layout_height="wrap_content" android:src="@drawable/ic_fullscreen_white_24dp" android:text="@string/pip_fullscreen" /> <com.android.systemui.pip.tv.PipControlButtonView android:id="@+id/close_button" - android:layout_width="100dp" + android:layout_width="@dimen/picture_in_picture_button_width" android:layout_height="wrap_content" - android:layout_marginStart="-50dp" + android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" android:src="@drawable/ic_close_white" android:text="@string/pip_close" /> <com.android.systemui.pip.tv.PipControlButtonView android:id="@+id/play_pause_button" - android:layout_width="100dp" + android:layout_width="@dimen/picture_in_picture_button_width" android:layout_height="wrap_content" - android:layout_marginStart="-50dp" + android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" android:src="@drawable/ic_pause_white" android:text="@string/pip_pause" android:visibility="gone" /> diff --git a/packages/SystemUI/res/layout/tv_pip_custom_control.xml b/packages/SystemUI/res/layout/tv_pip_custom_control.xml new file mode 100644 index 000000000000..dd0fce466576 --- /dev/null +++ b/packages/SystemUI/res/layout/tv_pip_custom_control.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2017, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<com.android.systemui.pip.tv.PipControlButtonView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="@dimen/picture_in_picture_button_width" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" /> diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 90761994cde6..18ffd0fac417 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -16,12 +16,12 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/volume_dialog" - android:layout_width="@dimen/volume_dialog_panel_width" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/volume_dialog_margin_bottom" - android:layout_gravity="center_vertical|end" + android:background="@drawable/volume_dialog_background" android:paddingTop="@dimen/volume_dialog_padding_top" - android:translationZ="8dp" > + android:translationZ="4dp" > <LinearLayout android:id="@+id/volume_dialog_content" @@ -57,7 +57,6 @@ android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="1" - android:visibility="gone" android:textAppearance="@style/TextAppearance.Volume.Header" /> <com.android.keyguard.AlphaOptimizedImageButton xmlns:android="http://schemas.android.com/apk/res/android" diff --git a/packages/SystemUI/res/layout/volume_dialog_wrapped.xml b/packages/SystemUI/res/layout/volume_dialog_wrapped.xml deleted file mode 100644 index 57489fd5f8d1..000000000000 --- a/packages/SystemUI/res/layout/volume_dialog_wrapped.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<com.android.systemui.HardwareUiLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginTop="@dimen/top_padding" - android:layout_marginBottom="@dimen/bottom_padding"> - - <include layout="@layout/volume_dialog"/> - -</com.android.systemui.HardwareUiLayout> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index c3a9eebb32e9..39744542a678 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -555,7 +555,7 @@ <string name="notification_channel_disabled" msgid="2139193533791840539">"Aurrerantzean ez duzu jasoko horrelako jakinarazpenik"</string> <string name="notification_num_channels" msgid="2048144408999179471">"Jakinarazpenen <xliff:g id="NUMBER">%d</xliff:g> kategoria"</string> <string name="notification_default_channel_desc" msgid="2506053815870808359">"Aplikazio honek ez du jakinarazpen-kategoriarik"</string> - <string name="notification_unblockable_desc" msgid="3561016061737896906">"Aplikazio honen jakinarazpenak ezin dira desaktibatu"</string> + <string name="notification_unblockable_desc" msgid="3561016061737896906">"Ezin dira desaktibatu aplikazio honen jakinarazpenak"</string> <plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663"> <item quantity="other">Aplikazio honen 1/<xliff:g id="NUMBER_1">%d</xliff:g> jakinarazpen-kategoria</item> <item quantity="one">Aplikazio honen 1/<xliff:g id="NUMBER_0">%d</xliff:g> jakinarazpen-kategoria</item> diff --git a/packages/SystemUI/res/values-tvdpi/dimens.xml b/packages/SystemUI/res/values-tvdpi/dimens.xml index 5327cee7cae8..4d978aacc65f 100644 --- a/packages/SystemUI/res/values-tvdpi/dimens.xml +++ b/packages/SystemUI/res/values-tvdpi/dimens.xml @@ -24,4 +24,8 @@ <fraction name="battery_subpixel_smoothing_right">10%</fraction> <dimen name="battery_margin_bottom">1px</dimen> + + <!-- The dimensions to user for picture-in-picture action buttons. --> + <dimen name="picture_in_picture_button_width">100dp</dimen> + <dimen name="picture_in_picture_button_start_margin">-50dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 3ef28bda8488..0ab44ed85ec8 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -116,11 +116,11 @@ <color name="segmented_buttons_background">#14FFFFFF</color><!-- 8% white --> <color name="dark_mode_icon_color_single_tone">#99000000</color> - <color name="dark_mode_icon_color_dual_tone_background">#3d000000</color> + <color name="dark_mode_icon_color_dual_tone_background">#4d000000</color> <color name="dark_mode_icon_color_dual_tone_fill">#7a000000</color> <color name="light_mode_icon_color_single_tone">#ffffff</color> - <color name="light_mode_icon_color_dual_tone_background">#4dffffff</color> + <color name="light_mode_icon_color_dual_tone_background">#5dffffff</color> <color name="light_mode_icon_color_dual_tone_fill">#ffffff</color> <color name="volume_settings_icon_color">#7fffffff</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index d3cbe4aa578f..74b0702ce5f6 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -262,6 +262,9 @@ <!-- Doze: alpha to apply to small icons when dozing --> <integer name="doze_small_icon_alpha">222</integer><!-- 87% of 0xff --> + <!-- Doze: whether the double tap sensor reports 2D touch coordinates --> + <bool name="doze_double_tap_reports_touch_coordinates">false</bool> + <!-- Hotspot tile: number of days to show after feature is used. --> <integer name="days_to_show_hotspot_tile">30</integer> @@ -395,4 +398,8 @@ it has been expanded to reveal its children. --> <bool name="config_showGroupNotificationBgWhenExpanded">false</bool> + <!-- Whether to artificially interpret all signal strengths as + one bar higher than they actually are --> + <bool name="config_inflateSignalStrength">false</bool> + </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index fc9e585400c6..7f7b968f7403 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -216,6 +216,8 @@ <!-- The width of the panel that holds the quick settings. --> <dimen name="qs_panel_width">@dimen/notification_panel_width</dimen> + <dimen name="volume_dialog_panel_width">@dimen/standard_notification_panel_width</dimen> + <!-- Gravity for the notification panel --> <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top --> @@ -800,7 +802,6 @@ <dimen name="hwui_edge_margin">16dp</dimen> - <dimen name="volume_dialog_panel_width">315dp</dimen> <dimen name="global_actions_panel_width">125dp</dimen> <dimen name="global_actions_top_padding">100dp</dimen> @@ -823,5 +824,10 @@ <!-- Intended corner radius when drawing the mobile signal --> <dimen name="stat_sys_mobile_signal_corner_radius">0.75dp</dimen> + <!-- How far to inset the rounded edges --> + <dimen name="stat_sys_mobile_signal_circle_inset">0.9dp</dimen> + + <!-- Width of the hollow triangle for empty signal state --> + <dimen name="mobile_signal_empty_strokewidth">2dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 3c6ad23c9f07..b27deddb4dd0 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -78,5 +78,13 @@ <item type="id" name="action_move_tl_50" /> <item type="id" name="action_move_tl_30" /> <item type="id" name="action_move_rb_full" /> + + <!-- Accessibility actions for the notification menu --> + <item type="id" name="action_snooze_undo"/> + <item type="id" name="action_snooze_15_min"/> + <item type="id" name="action_snooze_30_min"/> + <item type="id" name="action_snooze_1_hour"/> + <item type="id" name="action_snooze_2_hours"/> + <item type="id" name="action_snooze_assistant_suggestion_1"/> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index f942be6a72d1..5274b6476aba 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -295,8 +295,6 @@ <item name="darkIconTheme">@style/DualToneDarkTheme</item> <item name="bgProtectTextColor">?android:attr/textColorPrimaryInverse</item> <item name="bgProtectSecondaryTextColor">?android:attr/textColorSecondaryInverse</item> - <item name="pinDividerColor">@color/pin_divider_color</item> - <item name="pinDeleteColor">@color/pin_delete_color</item> <item name="*android:lockPatternStyle">@style/LockPatternStyle</item> </style> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java index 6a86bb2b491f..ce3068d765a8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java @@ -16,12 +16,19 @@ package com.android.keyguard; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.UserHandle; import android.util.AttributeSet; import android.view.View; import android.widget.Button; import android.telephony.SubscriptionManager; +import android.telephony.SubscriptionInfo; import android.telephony.euicc.EuiccManager; +import android.util.Log; import java.lang.ref.WeakReference; @@ -30,8 +37,26 @@ import java.lang.ref.WeakReference; * the device with no cellular service. */ class KeyguardEsimArea extends Button implements View.OnClickListener { + private static final String ACTION_DISABLE_ESIM = "com.android.keyguard.disable_esim"; + private static final String TAG = "KeyguardEsimArea"; + private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; + private EuiccManager mEuiccManager; + private BroadcastReceiver mReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_DISABLE_ESIM.equals(intent.getAction())) { + int resultCode = getResultCode(); + if (resultCode != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) { + // TODO (b/62680294): Surface more info. to the end users for this failure. + Log.e(TAG, "Error disabling esim, result code = " + resultCode); + } + } + } + }; + public KeyguardEsimArea(Context context) { this(context, null); } @@ -51,15 +76,39 @@ class KeyguardEsimArea extends Button implements View.OnClickListener { } @Override - public void onClick(View v) { - // STOPSHIP(b/37353596): use EuiccManager API to disable current carrier. + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_DISABLE_ESIM), + PERMISSION_SELF, null /* scheduler */); } public static boolean isEsimLocked(Context context, int subId) { EuiccManager euiccManager = (EuiccManager) context.getSystemService(Context.EUICC_SERVICE); - return euiccManager.isEnabled() - && SubscriptionManager.from(context).getActiveSubscriptionInfo(subId).isEmbedded(); + if (!euiccManager.isEnabled()) { + return false; + } + SubscriptionInfo sub = SubscriptionManager.from(context).getActiveSubscriptionInfo(subId); + return sub != null && sub.isEmbedded(); } + @Override + protected void onDetachedFromWindow() { + mContext.unregisterReceiver(mReceiver); + super.onDetachedFromWindow(); + } + + @Override + public void onClick(View v) { + Intent intent = new Intent(mContext, KeyguardEsimArea.class); + intent.setAction(ACTION_DISABLE_ESIM); + intent.setPackage(mContext.getPackageName()); + PendingIntent callbackIntent = PendingIntent.getBroadcast( + mContext, + 0 /* requestCode */, + intent, + PendingIntent.FLAG_UPDATE_CURRENT); + mEuiccManager + .switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID, callbackIntent); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java index 7e811731c87d..d8bebabf0c5e 100644 --- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java +++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java @@ -135,7 +135,9 @@ public class PasswordTextView extends View { } mDrawPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG); mDrawPaint.setTextAlign(Paint.Align.CENTER); - mDrawPaint.setTypeface(Typeface.create("sans-serif-light", 0)); + mDrawPaint.setTypeface(Typeface.create( + context.getString(com.android.internal.R.string.config_headlineFontFamilyLight), + 0)); mShowPassword = Settings.System.getInt(mContext.getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1) == 1; mAppearInterpolator = AnimationUtils.loadInterpolator(mContext, diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index 6476416a4e72..c4de63bdd303 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -235,6 +235,7 @@ public class BatteryMeterView extends LinearLayout implements scaledLayoutParams.setMargins(0, 0, 0, marginBottom); mBatteryIconView.setLayoutParams(scaledLayoutParams); + FontSizeUtils.updateFontSize(mBatteryPercentView, R.dimen.qs_time_expanded_size); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 73a2a43adb8f..1b694b372124 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -28,6 +28,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.util.Preconditions; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.assist.AssistManager; +import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.fragments.FragmentService; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.PluginDependencyProvider; @@ -38,9 +39,9 @@ import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; -import com.android.systemui.statusbar.phone.StatusBarWindowManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl; +import com.android.systemui.statusbar.phone.StatusBarWindowManager; import com.android.systemui.statusbar.policy.AccessibilityController; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.BatteryController; @@ -77,7 +78,6 @@ import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; -import com.android.systemui.tuner.TunablePadding; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerServiceImpl; @@ -86,8 +86,6 @@ import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; import com.android.systemui.volume.VolumeDialogControllerImpl; -import com.google.android.colorextraction.ColorExtractor; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; @@ -109,6 +107,7 @@ import java.util.function.Consumer; * services, registered receivers, etc. */ public class Dependency extends SystemUI { + private static final String TAG = "Dependency"; /** * Key for getting a background Looper for background work. @@ -268,7 +267,7 @@ public class Dependency extends SystemUI { mProviders.put(AccessibilityManagerWrapper.class, () -> new AccessibilityManagerWrapper(mContext)); - mProviders.put(ColorExtractor.class, () -> new ColorExtractor(mContext)); + mProviders.put(SysuiColorExtractor.class, () -> new SysuiColorExtractor(mContext)); mProviders.put(TunablePaddingService.class, () -> new TunablePaddingService()); diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java index c4740af9e043..b35efb2e828d 100644 --- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java +++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java @@ -118,7 +118,8 @@ public class RoundedCorners extends SystemUI implements Tunable { | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED , PixelFormat.TRANSLUCENT); - lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS + | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT; lp.setTitle("RoundedOverlay"); lp.gravity = Gravity.TOP; return lp; diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java new file mode 100644 index 000000000000..5f393d0ff1e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.colorextraction; + +import android.app.WallpaperManager; +import android.content.Context; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; +import android.view.Display; +import android.view.IWallpaperVisibilityListener; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; + +import com.android.internal.annotations.VisibleForTesting; + +import com.google.android.colorextraction.ColorExtractor; +import com.google.android.colorextraction.types.ExtractionType; +import com.google.android.colorextraction.types.Tonal; + +/** + * ColorExtractor aware of wallpaper visibility + */ +public class SysuiColorExtractor extends ColorExtractor { + private static final String TAG = "SysuiColorExtractor"; + private boolean mWallpaperVisible; + // Colors to return when the wallpaper isn't visible + private final GradientColors mWpHiddenColors; + + public SysuiColorExtractor(Context context) { + this(context, new Tonal(), true); + } + + @VisibleForTesting + public SysuiColorExtractor(Context context, ExtractionType type, boolean registerVisibility) { + super(context, type); + + mWpHiddenColors = new GradientColors(); + mWpHiddenColors.setMainColor(FALLBACK_COLOR); + mWpHiddenColors.setSecondaryColor(FALLBACK_COLOR); + + if (registerVisibility) { + try { + IWindowManager windowManagerService = WindowManagerGlobal.getWindowManagerService(); + Handler handler = Handler.getMain(); + boolean visible = windowManagerService.registerWallpaperVisibilityListener( + new IWallpaperVisibilityListener.Stub() { + @Override + public void onWallpaperVisibilityChanged(boolean newVisibility, + int displayId) throws RemoteException { + handler.post(() -> setWallpaperVisible(newVisibility)); + } + }, Display.DEFAULT_DISPLAY); + setWallpaperVisible(visible); + } catch (RemoteException e) { + Log.w(TAG, "Can't listen to wallpaper visibility changes", e); + } + } + } + + /** + * Get TYPE_NORMAL colors when wallpaper is visible, or fallback otherwise. + * + * @param which FLAG_LOCK or FLAG_SYSTEM + * @return colors + */ + @Override + public GradientColors getColors(int which) { + return getColors(which, TYPE_NORMAL); + } + + /** + * Wallpaper colors when the wallpaper is visible, fallback otherwise. + * + * @param which FLAG_LOCK or FLAG_SYSTEM + * @param type TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK + * @return colors + */ + @Override + public GradientColors getColors(int which, int type) { + return getColors(which, type, false /* ignoreVisibility */); + } + + /** + * Get TYPE_NORMAL colors, possibly ignoring wallpaper visibility. + * + * @param which FLAG_LOCK or FLAG_SYSTEM + * @param ignoreWallpaperVisibility whether you want fallback colors or not if the wallpaper + * isn't visible + * @return + */ + public GradientColors getColors(int which, boolean ignoreWallpaperVisibility) { + return getColors(which, TYPE_NORMAL, ignoreWallpaperVisibility); + } + + /** + * + * @param which FLAG_LOCK or FLAG_SYSTEM + * @param type TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK + * @param ignoreWallpaperVisibility true if true wallpaper colors should be returning + * if it's visible or not + * @return colors + */ + public GradientColors getColors(int which, int type, boolean ignoreWallpaperVisibility) { + if (mWallpaperVisible || ignoreWallpaperVisibility) { + return super.getColors(which, type); + } else { + return mWpHiddenColors; + } + } + + @VisibleForTesting + void setWallpaperVisible(boolean visible) { + if (mWallpaperVisible != visible) { + mWallpaperVisible = visible; + triggerColorsChanged(WallpaperManager.FLAG_SYSTEM); + } + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java index 3e424d05373a..5aaa6c78f5fd 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java @@ -38,6 +38,8 @@ public interface DozeHost { void setAnimateWakeup(boolean animateWakeup); + void onDoubleTap(float x, float y); + interface Callback { default void onNotificationHeadsUp() {} default void onPowerSaveChanged(boolean active) {} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 73f522244a60..23da716706d3 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -81,17 +81,18 @@ public class DozeSensors { mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION), null /* setting */, dozeParameters.getPulseOnSigMotion(), - DozeLog.PULSE_REASON_SENSOR_SIGMOTION), + DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */), mPickupSensor = new TriggerSensor( mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE), Settings.Secure.DOZE_PULSE_ON_PICK_UP, config.pulseOnPickupAvailable(), - DozeLog.PULSE_REASON_SENSOR_PICKUP), + DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */), new TriggerSensor( findSensorWithType(config.doubleTapSensorType()), Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP, true /* configured */, - DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP) + DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP, + dozeParameters.doubleTapReportsTouchCoordinates()) }; mProxSensor = new ProxSensor(); @@ -207,16 +208,19 @@ public class DozeSensors { final boolean mConfigured; final int mPulseReason; final String mSetting; + final boolean mReportsTouchCoordinates; private boolean mRequested; private boolean mRegistered; private boolean mDisabled; - public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason) { + public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason, + boolean reportsTouchCoordinates) { mSensor = sensor; mSetting = setting; mConfigured = configured; mPulseReason = pulseReason; + mReportsTouchCoordinates = reportsTouchCoordinates; } public void setListening(boolean listen) { @@ -276,7 +280,13 @@ public class DozeSensors { } mRegistered = false; - mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck); + float screenX = -1; + float screenY = -1; + if (mReportsTouchCoordinates && event.values.length >= 2) { + screenX = event.values[0]; + screenY = event.values[1]; + } + mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY); updateListener(); // reregister, this sensor only fires once })); } @@ -309,7 +319,12 @@ public class DozeSensors { * Called when a sensor requests a pulse * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP} * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity. + * @param screenX the location on the screen where the sensor fired or -1 + * if the sensor doesn't support reporting screen locations. + * @param screenY the location on the screen where the sensor fired or -1 + * if the sensor doesn't support reporting screen locations. */ - void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck); + void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck, + float screenX, float screenY); } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index 3271caf598cf..d9fb087a6041 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -16,23 +16,27 @@ package com.android.systemui.doze; +import android.content.Context; import android.os.PowerManager; import android.os.SystemClock; import android.service.dreams.DreamService; import android.util.Log; import com.android.systemui.Dependency; -import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.DozeServicePlugin; import com.android.systemui.plugins.PluginManager; - +import com.android.systemui.plugins.DozeServicePlugin.RequestDoze; +import com.android.systemui.plugins.PluginListener; import java.io.FileDescriptor; import java.io.PrintWriter; -public class DozeService extends DreamService implements DozeMachine.Service { +public class DozeService extends DreamService + implements DozeMachine.Service, RequestDoze, PluginListener<DozeServicePlugin> { private static final String TAG = "DozeService"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private DozeMachine mDozeMachine; + private DozeServicePlugin mDozePlugin; public DozeService() { setDebug(DEBUG); @@ -48,23 +52,44 @@ public class DozeService extends DreamService implements DozeMachine.Service { finish(); return; } - + Dependency.get(PluginManager.class).addPluginListener(this, + DozeServicePlugin.class, false /* Allow multiple */); mDozeMachine = new DozeFactory().assembleMachine(this); } @Override + public void onPluginConnected(DozeServicePlugin plugin, Context pluginContext) { + mDozePlugin = plugin; + mDozePlugin.setDozeRequester(this); + } + + @Override + public void onPluginDisconnected(DozeServicePlugin plugin) { + if (mDozePlugin != null) { + mDozePlugin.onDreamingStopped(); + mDozePlugin = null; + } + } + + @Override public void onDreamingStarted() { super.onDreamingStarted(); mDozeMachine.requestState(DozeMachine.State.INITIALIZED); startDozing(); setDozeScreenBrightness(getResources().getInteger( com.android.internal.R.integer.config_screenBrightnessDoze)); + if (mDozePlugin != null) { + mDozePlugin.onDreamingStarted(); + } } @Override public void onDreamingStopped() { super.onDreamingStopped(); mDozeMachine.requestState(DozeMachine.State.FINISH); + if (mDozePlugin != null) { + mDozePlugin.onDreamingStopped(); + } } @Override @@ -79,4 +104,18 @@ public class DozeService extends DreamService implements DozeMachine.Service { PowerManager pm = getSystemService(PowerManager.class); pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:NODOZE"); } + + @Override + public void onRequestShowDoze() { + if (mDozeMachine != null) { + mDozeMachine.requestState(DozeMachine.State.DOZE_AOD); + } + } + + @Override + public void onRequestHideDoze() { + if (mDozeMachine != null) { + mDozeMachine.requestState(DozeMachine.State.DOZE); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 693a690d7f59..ae936db7b0e2 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -98,12 +98,14 @@ public class DozeTriggers implements DozeMachine.Part { requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */); } - private void onSensor(int pulseReason, boolean sensorPerformedProxCheck) { + private void onSensor(int pulseReason, boolean sensorPerformedProxCheck, + float screenX, float screenY) { boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP; boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP; if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) { if (isDoubleTap) { + mDozeHost.onDoubleTap(screenX, screenY); mMachine.wakeUp(); } else { mDozeHost.extendPulse(); diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java index 880951036661..4ff10e975b1a 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java @@ -34,12 +34,14 @@ public class ExtensionFragmentListener<T extends FragmentBase> implements Consum private final FragmentHostManager mFragmentHostManager; private final String mTag; private final Extension<T> mExtension; + private final int mId; private String mOldClass; private ExtensionFragmentListener(View view, String tag, int id, Extension<T> extension) { mTag = tag; mFragmentHostManager = FragmentHostManager.get(view); mExtension = extension; + mId = id; mFragmentHostManager.getFragmentManager().beginTransaction() .replace(id, (Fragment) mExtension.get(), mTag) .commit(); @@ -49,7 +51,7 @@ public class ExtensionFragmentListener<T extends FragmentBase> implements Consum public void accept(T extension) { try { Fragment.class.cast(extension); - mFragmentHostManager.getExtensionManager().setCurrentExtension(mTag, + mFragmentHostManager.getExtensionManager().setCurrentExtension(mId, mTag, mOldClass, extension.getClass().getName(), mExtension.getContext()); mOldClass = extension.getClass().getName(); } catch (ClassCastException e) { diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index 871f113ac95e..f8f364da089b 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -266,16 +266,14 @@ public class FragmentHostManager { class ExtensionFragmentManager { private final ArrayMap<String, Context> mExtensionLookup = new ArrayMap<>(); - public void setCurrentExtension(@NonNull String tag, @Nullable String oldClass, + public void setCurrentExtension(int id, @NonNull String tag, @Nullable String oldClass, @NonNull String currentClass, @Nullable Context context) { - Fragment fragment = getFragmentManager().findFragmentByTag(tag); if (oldClass != null) { mExtensionLookup.remove(oldClass); } mExtensionLookup.put(currentClass, context); getFragmentManager().beginTransaction() - .replace(((View) fragment.getView().getParent()).getId(), - instantiate(context, currentClass, null), tag) + .replace(id, instantiate(context, currentClass, null), tag) .commit(); reloadFragments(); } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index e8dcf6c1f0b2..80a641870f5b 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -25,6 +25,7 @@ import com.android.internal.telephony.TelephonyProperties; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dependency; import com.android.systemui.HardwareUiLayout; +import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.volume.VolumeDialogImpl; @@ -1227,7 +1228,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn mClickListener = clickListener; mLongClickListener = longClickListener; mGradientDrawable = new GradientDrawable(mContext); - mColorExtractor = Dependency.get(ColorExtractor.class); + mColorExtractor = Dependency.get(SysuiColorExtractor.class); // Window initialization Window window = getWindow(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java index 32b5862e2b6b..af2b7677dcad 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java @@ -38,6 +38,7 @@ import android.util.Log; import android.view.View; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.R; /** * Bouncer between work activities and the activity used to confirm credentials before unlocking @@ -83,6 +84,7 @@ public class WorkLockActivity extends Activity { // Blank out the activity. When it is on-screen it will look like a Recents thumbnail with // redaction switched on. final View blankView = new View(this); + blankView.setContentDescription(getString(R.string.accessibility_desc_work_lock)); blankView.setBackgroundColor(getPrimaryColor()); setContentView(blankView); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 0373d77940d7..ebf4b5d83029 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -93,6 +93,7 @@ public class PipManager implements BasePipManager { ComponentName topPipActivity = PipUtils.getTopPinnedActivity(mContext, mActivityManager); mMenuController.hideMenu(); + mTouchHandler.onActivityUnpinned(topPipActivity); mNotificationController.onActivityUnpinned(topPipActivity); SystemServicesProxy.getInstance(mContext).setPipVisibility(topPipActivity != null); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 0f69f471bc24..a5ee19824590 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -427,11 +427,7 @@ public class PipMenuActivity extends Activity { } else { actionsContainer.setVisibility(View.VISIBLE); if (mActionsGroup != null) { - // Hide extra views - for (int i = mActions.size(); i < mActionsGroup.getChildCount(); i++) { - mActionsGroup.getChildAt(i).setVisibility(View.GONE); - } - // Add needed views + // Ensure we have as many buttons as actions final LayoutInflater inflater = LayoutInflater.from(this); while (mActionsGroup.getChildCount() < mActions.size()) { final ImageView actionView = (ImageView) inflater.inflate( @@ -439,6 +435,13 @@ public class PipMenuActivity extends Activity { mActionsGroup.addView(actionView); } + // Update the visibility of all views + for (int i = 0; i < mActionsGroup.getChildCount(); i++) { + mActionsGroup.getChildAt(i).setVisibility(i < mActions.size() + ? View.VISIBLE + : View.GONE); + } + // Recreate the layout final boolean isLandscapePip = stackBounds != null && (stackBounds.width() > stackBounds.height()); @@ -460,10 +463,9 @@ public class PipMenuActivity extends Activity { Log.w(TAG, "Failed to send action", e); } }); - } else { - actionView.setAlpha(DISABLED_ACTION_ALPHA); } actionView.setEnabled(action.isEnabled()); + actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); // Update the margin between actions LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index ddaeb04f8443..3682ae655f7c 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -25,6 +25,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.IActivityManager; +import android.content.ComponentName; import android.content.Context; import android.graphics.Point; import android.graphics.PointF; @@ -116,7 +117,7 @@ public class PipTouchHandler { }; // Behaviour states - private int mMenuState; + private int mMenuState = MENU_STATE_NONE; private boolean mIsMinimized; private boolean mIsImeShowing; private int mImeHeight; @@ -212,14 +213,15 @@ public class PipTouchHandler { } public void onActivityPinned() { - // Reset some states once we are pinned - mMenuState = MENU_STATE_NONE; + cleanUp(); + mShowPipMenuOnAnimationEnd = true; + } - if (mIsMinimized) { - setMinimizedStateInternal(false); + public void onActivityUnpinned(ComponentName topPipActivity) { + if (topPipActivity == null) { + // Clean up state after the last PiP activity is removed + cleanUp(); } - cleanUpDismissTarget(); - mShowPipMenuOnAnimationEnd = true; } public void onPinnedStackAnimationEnded() { @@ -729,6 +731,16 @@ public class PipTouchHandler { mDismissViewController.destroyDismissTarget(); } + /** + * Resets some states related to the touch handling. + */ + private void cleanUp() { + if (mIsMinimized) { + setMinimizedStateInternal(false); + } + cleanUpDismissTarget(); + } + public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java index 40a63d7d92c0..b21cd95626a7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorInflater; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -33,6 +34,7 @@ import com.android.systemui.R; * A view containing PIP controls including fullscreen, close, and media controls. */ public class PipControlButtonView extends RelativeLayout { + private OnFocusChangeListener mFocusChangeListener; private ImageView mIconImageView; ImageView mButtonImageView; @@ -122,18 +124,37 @@ public class PipControlButtonView extends RelativeLayout { } /** + * Sets the drawable for the button with the given drawable. + */ + public void setImageDrawable(Drawable d) { + mIconImageView.setImageDrawable(d); + } + + /** * Sets the drawable for the button with the given resource id. */ public void setImageResource(int resId) { - mIconImageView.setImageResource(resId); + if (resId != 0) { + mIconImageView.setImageResource(resId); + } + } + + /** + * Sets the text for description the with the given string. + */ + public void setText(CharSequence text) { + mButtonImageView.setContentDescription(text); + mDescriptionTextView.setText(text); } /** * Sets the text for description the with the given resource id. */ public void setText(int resId) { - mButtonImageView.setContentDescription(getContext().getString(resId)); - mDescriptionTextView.setText(resId); + if (resId != 0) { + mButtonImageView.setContentDescription(getContext().getString(resId)); + mDescriptionTextView.setText(resId); + } } private static void cancelAnimator(Animator animator) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java index acea3b6b12ad..10206d492e3a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java @@ -16,12 +16,20 @@ package com.android.systemui.pip.tv; +import android.app.ActivityManager; +import android.app.PendingIntent.CanceledException; +import android.app.RemoteAction; import android.content.Context; +import android.graphics.Color; import android.media.session.MediaController; import android.media.session.PlaybackState; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; import android.view.View; import android.view.Gravity; import android.view.LayoutInflater; +import android.widget.ImageView; import android.widget.LinearLayout; import android.util.AttributeSet; @@ -30,11 +38,19 @@ import com.android.systemui.R; import static android.media.session.PlaybackState.ACTION_PAUSE; import static android.media.session.PlaybackState.ACTION_PLAY; +import java.util.ArrayList; +import java.util.List; + /** * A view containing PIP controls including fullscreen, close, and media controls. */ public class PipControlsView extends LinearLayout { + + private static final String TAG = PipControlsView.class.getSimpleName(); + + private static final float DISABLED_ACTION_ALPHA = 0.54f; + /** * An interface to listen user action. */ @@ -47,19 +63,23 @@ public class PipControlsView extends LinearLayout { private MediaController mMediaController; - final PipManager mPipManager = PipManager.getInstance(); - Listener mListener; + private final PipManager mPipManager = PipManager.getInstance(); + private final LayoutInflater mLayoutInflater; + private final Handler mHandler; + private Listener mListener; private PipControlButtonView mFullButtonView; private PipControlButtonView mCloseButtonView; private PipControlButtonView mPlayPauseButtonView; + private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>(); + private List<RemoteAction> mCustomActions = new ArrayList<>(); private PipControlButtonView mFocusedChild; private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() { @Override public void onPlaybackStateChanged(PlaybackState state) { - updatePlayPauseView(); + updateUserActions(); } }; @@ -95,9 +115,10 @@ public class PipControlsView extends LinearLayout { public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - LayoutInflater inflater = (LayoutInflater) getContext() + mLayoutInflater = (LayoutInflater) getContext() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.tv_pip_controls, this); + mLayoutInflater.inflate(R.layout.tv_pip_controls, this); + mHandler = new Handler(); setOrientation(LinearLayout.HORIZONTAL); setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); @@ -176,21 +197,74 @@ public class PipControlsView extends LinearLayout { if (mMediaController != null) { mMediaController.registerCallback(mMediaControllerCallback); } - updatePlayPauseView(); + updateUserActions(); } - private void updatePlayPauseView() { - int state = mPipManager.getPlaybackState(); - if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) { + /** + * Updates the actions for the PIP. If there are no custom actions, then the media session + * actions are shown. + */ + private void updateUserActions() { + if (!mCustomActions.isEmpty()) { + // Ensure we have as many buttons as actions + while (mCustomButtonViews.size() < mCustomActions.size()) { + PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate( + R.layout.tv_pip_custom_control, this, false); + addView(buttonView); + mCustomButtonViews.add(buttonView); + } + + // Update the visibility of all views + for (int i = 0; i < mCustomButtonViews.size(); i++) { + mCustomButtonViews.get(i).setVisibility(i < mCustomActions.size() + ? View.VISIBLE + : View.GONE); + } + + // Update the state and visibility of the action buttons, and hide the rest + for (int i = 0; i < mCustomActions.size(); i++) { + final RemoteAction action = mCustomActions.get(i); + PipControlButtonView actionView = mCustomButtonViews.get(i); + + // TODO: Check if the action drawable has changed before we reload it + action.getIcon().loadDrawableAsync(getContext(), d -> { + d.setTint(Color.WHITE); + actionView.setImageDrawable(d); + }, mHandler); + actionView.setText(action.getContentDescription()); + if (action.isEnabled()) { + actionView.setOnClickListener(v -> { + try { + action.getActionIntent().send(); + } catch (CanceledException e) { + Log.w(TAG, "Failed to send action", e); + } + }); + } + actionView.setEnabled(action.isEnabled()); + actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); + } + + // Hide the media session buttons mPlayPauseButtonView.setVisibility(View.GONE); } else { - mPlayPauseButtonView.setVisibility(View.VISIBLE); - if (state == PipManager.PLAYBACK_STATE_PLAYING) { - mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white); - mPlayPauseButtonView.setText(R.string.pip_pause); + int state = mPipManager.getPlaybackState(); + if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) { + mPlayPauseButtonView.setVisibility(View.GONE); } else { - mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white); - mPlayPauseButtonView.setText(R.string.pip_play); + mPlayPauseButtonView.setVisibility(View.VISIBLE); + if (state == PipManager.PLAYBACK_STATE_PLAYING) { + mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white); + mPlayPauseButtonView.setText(R.string.pip_pause); + } else { + mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white); + mPlayPauseButtonView.setText(R.string.pip_play); + } + } + + // Hide all the custom action buttons + for (int i = 0; i < mCustomButtonViews.size(); i++) { + mCustomButtonViews.get(i).setVisibility(View.GONE); } } } @@ -203,6 +277,9 @@ public class PipControlsView extends LinearLayout { mCloseButtonView.reset(); mPlayPauseButtonView.reset(); mFullButtonView.requestFocus(); + for (int i = 0; i < mCustomButtonViews.size(); i++) { + mCustomButtonViews.get(i).reset(); + } } /** @@ -213,6 +290,15 @@ public class PipControlsView extends LinearLayout { } /** + * Updates the set of activity-defined actions. + */ + public void setActions(List<RemoteAction> actions) { + mCustomActions.clear(); + mCustomActions.addAll(actions); + updateUserActions(); + } + + /** * Returns the focused control button view to animate focused button. */ PipControlButtonView getFocusedButton() { diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index f98310dd7aa1..ca58080c0260 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; +import android.app.RemoteAction; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -124,6 +125,7 @@ public class PipManager implements BasePipManager { private MediaController mPipMediaController; private String[] mLastPackagesResourceGranted; private PipNotification mPipNotification; + private ParceledListSlice mCustomActions; private final PinnedStackListener mPinnedStackListener = new PinnedStackListener(); @@ -187,7 +189,14 @@ public class PipManager implements BasePipManager { } @Override - public void onActionsChanged(ParceledListSlice actions) {} + public void onActionsChanged(ParceledListSlice actions) { + mCustomActions = actions; + mHandler.post(() -> { + for (int i = mListeners.size() - 1; i >= 0; --i) { + mListeners.get(i).onPipMenuActionsChanged(mCustomActions); + } + }); + } } private PipManager() { } @@ -432,6 +441,7 @@ public class PipManager implements BasePipManager { } Intent intent = new Intent(mContext, PipMenuActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(PipMenuActivity.EXTRA_CUSTOM_ACTIONS, mCustomActions); mContext.startActivity(intent); } @@ -690,6 +700,8 @@ public class PipManager implements BasePipManager { void onPipActivityClosed(); /** Invoked when the PIP menu gets shown. */ void onShowPipMenu(); + /** Invoked when the PIP menu actions change. */ + void onPipMenuActionsChanged(ParceledListSlice actions); /** Invoked when the PIPed activity is about to return back to the fullscreen. */ void onMoveToFullscreen(); /** Invoked when we are above to start resizing the Pip. */ diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java index ce1bea19ef60..82018ce9ddbe 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java @@ -19,22 +19,27 @@ package com.android.systemui.pip.tv; import android.animation.Animator; import android.animation.AnimatorInflater; import android.app.Activity; +import android.content.Intent; +import android.content.pm.ParceledListSlice; import android.os.Bundle; import android.view.View; import com.android.systemui.R; +import java.util.Collections; /** * Activity to show the PIP menu to control PIP. */ public class PipMenuActivity extends Activity implements PipManager.Listener { private static final String TAG = "PipMenuActivity"; + static final String EXTRA_CUSTOM_ACTIONS = "custom_actions"; + private final PipManager mPipManager = PipManager.getInstance(); private Animator mFadeInAnimation; private Animator mFadeOutAnimation; - private View mPipControlsView; + private PipControlsView mPipControlsView; private boolean mRestorePipSizeWhenClose; @Override @@ -51,6 +56,15 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { mFadeOutAnimation = AnimatorInflater.loadAnimator( this, R.anim.tv_pip_menu_fade_out_animation); mFadeOutAnimation.setTarget(mPipControlsView); + + onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS)); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS)); } private void restorePipAndFinish() { @@ -96,6 +110,12 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { } @Override + public void onPipMenuActionsChanged(ParceledListSlice actions) { + boolean hasCustomActions = actions != null && !actions.getList().isEmpty(); + mPipControlsView.setActions(hasCustomActions ? actions.getList() : Collections.EMPTY_LIST); + } + + @Override public void onShowPipMenu() { } @Override diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java index c8f418554904..f0745a0791ef 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java @@ -23,6 +23,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Icon; @@ -81,6 +82,11 @@ public class PipNotification { } @Override + public void onPipMenuActionsChanged(ParceledListSlice actions) { + // no-op. + } + + @Override public void onMoveToFullscreen() { dismissPipNotification(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java index eaf715ffc4c9..5b3ec08ce752 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java @@ -35,9 +35,7 @@ public class CellTileView extends SignalTileView { public CellTileView(Context context) { super(context); mSignalDrawable = new SignalDrawable(mContext); - float dark = Utils.getColorAttr(context, android.R.attr.colorForeground) == 0xff000000 - ? 1 : 0; - mSignalDrawable.setDarkIntensity(dark); + mSignalDrawable.setDarkIntensity(isDark(mContext)); mSignalDrawable.setIntrinsicSize(context.getResources().getDimensionPixelSize( R.dimen.qs_tile_icon_size)); } @@ -50,6 +48,10 @@ public class CellTileView extends SignalTileView { } } + private static int isDark(Context context) { + return Utils.getColorAttr(context, android.R.attr.colorForeground) == 0xff000000 ? 1 : 0; + } + public static class SignalIcon extends Icon { private final int mState; @@ -64,7 +66,11 @@ public class CellTileView extends SignalTileView { @Override public Drawable getDrawable(Context context) { - return null; + //TODO: Not the optimal solution to create this drawable + SignalDrawable d = new SignalDrawable(context); + d.setDarkIntensity(isDark(context)); + d.setLevel(getState()); + return d; } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java index f124e8686749..697db5fabcd8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java @@ -73,6 +73,7 @@ public class QSDetail extends LinearLayout { private int mOpenY; private boolean mAnimatingOpen; private boolean mSwitchState; + private View mFooter; public QSDetail(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -116,9 +117,10 @@ public class QSDetail extends LinearLayout { mDetailDoneButton.setOnClickListener(doneListener); } - public void setQsPanel(QSPanel panel, QuickStatusBarHeader header) { + public void setQsPanel(QSPanel panel, QuickStatusBarHeader header, View footer) { mQsPanel = panel; mHeader = header; + mFooter = footer; mHeader.setCallback(mQsPanelCallback); mQsPanel.setCallback(mQsPanelCallback); } @@ -214,6 +216,7 @@ public class QSDetail extends LinearLayout { mDetailAdapter = null; listener = mTeardownDetailWhenDone; mHeader.setVisibility(View.VISIBLE); + mFooter.setVisibility(View.VISIBLE); mQsPanel.setGridContentVisibility(true); mQsPanelCallback.onScanStateChanged(false); } @@ -345,6 +348,7 @@ public class QSDetail extends LinearLayout { if (mDetailAdapter != null) { mQsPanel.setGridContentVisibility(false); mHeader.setVisibility(View.INVISIBLE); + mFooter.setVisibility(View.INVISIBLE); } mAnimatingOpen = false; checkPendingAnimations(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 3f00276a6506..90275c50b54a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -80,7 +80,7 @@ public class QSFragment extends Fragment implements QS { mFooter = view.findViewById(R.id.qs_footer); mContainer = view.findViewById(id.quick_settings_container); - mQSDetail.setQsPanel(mQSPanel, mHeader); + mQSDetail.setQsPanel(mQSPanel, mHeader, mFooter); // If the quick settings row is not shown, then there is no need for the animation from // the row to the full QS panel. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index d434f2f7032d..56fca1f14fba 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -27,6 +27,7 @@ import android.text.SpannableStringBuilder; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.util.Log; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -244,8 +245,9 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic mDialog = new SystemUIDialog(mContext); mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); - View dialogView = LayoutInflater.from(mContext) - .inflate(R.layout.quick_settings_footer_dialog, null, false); + View dialogView = LayoutInflater.from( + new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_Dialog)) + .inflate(R.layout.quick_settings_footer_dialog, null, false); mDialog.setView(dialogView); mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this); 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 2d0fe6f63219..4d0e60d505f6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -32,6 +32,7 @@ import android.widget.Switch; import com.android.systemui.R; import com.android.systemui.plugins.qs.*; +import com.android.systemui.plugins.qs.QSTile.BooleanState; public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { @@ -44,6 +45,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private String mAccessibilityClass; private boolean mTileState; private boolean mCollapsedView; + private boolean mClicked; public QSTileBaseView(Context context, QSIconView icon) { this(context, icon, false); @@ -153,7 +155,11 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setContentDescription(state.contentDescription); mAccessibilityClass = state.expandedAccessibilityClassName; if (state instanceof QSTile.BooleanState) { - mTileState = ((QSTile.BooleanState) state).value; + boolean newState = ((BooleanState) state).value; + if (mTileState != newState) { + mClicked = false; + mTileState = newState; + } } } @@ -173,15 +179,22 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } @Override + public boolean performClick() { + mClicked = true; + return super.performClick(); + } + + @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); if (!TextUtils.isEmpty(mAccessibilityClass)) { event.setClassName(mAccessibilityClass); if (Switch.class.getName().equals(mAccessibilityClass)) { + boolean b = mClicked ? !mTileState : mTileState; String label = getResources() - .getString(!mTileState ? R.string.switch_bar_on : R.string.switch_bar_off); + .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off); event.setContentDescription(label); - event.setChecked(!mTileState); + event.setChecked(b); } } } @@ -192,10 +205,11 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { if (!TextUtils.isEmpty(mAccessibilityClass)) { info.setClassName(mAccessibilityClass); if (Switch.class.getName().equals(mAccessibilityClass)) { + boolean b = mClicked ? !mTileState : mTileState; String label = getResources() - .getString(mTileState ? R.string.switch_bar_on : R.string.switch_bar_off); + .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off); info.setText(label); - info.setChecked(mTileState); + info.setChecked(b); info.setCheckable(true); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java index 603f66beca63..315a815af100 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java @@ -18,12 +18,14 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.widget.ImageView; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.plugins.qs.QSTile.SlashState; import com.android.systemui.qs.SlashDrawable; public class SlashImageView extends ImageView { - private SlashDrawable mSlash; + @VisibleForTesting + protected SlashDrawable mSlash; public SlashImageView(Context context) { super(context); @@ -38,10 +40,13 @@ public class SlashImageView extends ImageView { @Override public void setImageDrawable(Drawable drawable) { - if (mSlash != null) { - mSlash.setDrawable(drawable); - } else { + if (drawable == null) { + mSlash = null; + super.setImageDrawable(null); + } else if (mSlash == null) { super.setImageDrawable(drawable); + } else { + mSlash.setDrawable(drawable); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 703be27ad173..36677a46ed9f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -183,12 +183,6 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { @Override public void onBluetoothDevicesChanged() { - mUiHandler.post(new Runnable() { - @Override - public void run() { - mDetailAdapter.updateItems(); - } - }); refreshState(); if (isShowingDetail()) { mDetailAdapter.updateItems(); @@ -202,6 +196,9 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback { + // We probably won't ever have space in the UI for more than 20 devices, so don't + // get info for them. + private static final int MAX_DEVICES = 20; private QSDetailItems mItems; @Override @@ -264,13 +261,14 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { final Collection<CachedBluetoothDevice> devices = mController.getDevices(); if (devices != null) { int connectedDevices = 0; + int count = 0; for (CachedBluetoothDevice device : devices) { - if (device.getBondState() == BluetoothDevice.BOND_NONE) continue; + if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue; final Item item = new Item(); item.icon = R.drawable.ic_qs_bluetooth_on; item.line1 = device.getName(); item.tag = device; - int state = device.getMaxConnectionState(); + int state = mController.getMaxConnectionState(device); if (state == BluetoothProfile.STATE_CONNECTED) { item.icon = R.drawable.ic_qs_bluetooth_connected; item.line2 = mContext.getString(R.string.quick_settings_connected); @@ -284,6 +282,9 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } else { items.add(item); } + if (++count == MAX_DEVICES) { + break; + } } } mItems.setItems(items.toArray(new Item[items.size()])); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index e6c3520c4d62..81ec6a7c72a9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -141,7 +141,11 @@ public class CellularTile extends QSTileImpl<SignalState> { && mDataController.isMobileDataEnabled(); state.icon = new SignalIcon(cb.mobileSignalIconId); - state.state = Tile.STATE_ACTIVE; + if (cb.airplaneModeEnabled) { + state.state = Tile.STATE_INACTIVE; + } else { + state.state = Tile.STATE_ACTIVE; + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index cb9093ac4928..73915092e8b4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -32,17 +32,16 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.wifi.AccessPoint; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.R.string; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.DetailAdapter; -import com.android.systemui.qs.QSDetailItems; -import com.android.systemui.qs.QSDetailItems.Item; -import com.android.systemui.qs.QSHost; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.SignalState; -import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.qs.QSDetailItems; +import com.android.systemui.qs.QSDetailItems.Item; +import com.android.systemui.qs.QSHost; import com.android.systemui.qs.SignalTileView; +import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.AccessPointController; import com.android.systemui.statusbar.policy.NetworkController.IconState; @@ -188,7 +187,7 @@ public class WifiTile extends QSTileImpl<SignalState> { minimalContentDescription.append(removeDoubleQuotes(cb.enabledDesc)); } } - state.contentDescription = minimalContentDescription; + state.contentDescription = minimalContentDescription.toString(); state.dualLabelContentDescription = r.getString( R.string.accessibility_quick_settings_open_settings, getTileLabel()); state.expandedAccessibilityClassName = Switch.class.getName(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 86b1d3b7efce..4de121488ecc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -72,6 +72,7 @@ import com.android.systemui.recents.events.ui.RecentsDrawnEvent; import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent; import com.android.systemui.recents.events.ui.StackViewScrolledEvent; +import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent; import com.android.systemui.recents.events.ui.UserInteractionEvent; import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; @@ -420,8 +421,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails; loader.loadTasks(this, loadPlan, loadOpts); TaskStack stack = loadPlan.getTaskStack(); - mRecentsView.onReload(mIsVisible, stack.getTaskCount() == 0); - mRecentsView.updateStack(stack, true /* setStackViewTasks */); + mRecentsView.onReload(stack, mIsVisible); // Update the nav bar scrim, but defer the animation until the enter-window event boolean animateNavBarScrim = !launchState.launchedViaDockGesture; @@ -755,6 +755,10 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD ssp.removeTask(event.task.key.id); } + public final void onBusEvent(TaskViewDismissedEvent event) { + mRecentsView.updateScrimOpacity(); + } + public final void onBusEvent(AllTaskViewsDismissedEvent event) { SystemServicesProxy ssp = Recents.getSystemServices(); if (ssp.hasDockedTask()) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 319b4638713a..9ca756c5431f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -31,6 +31,7 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.ArraySet; import android.util.AttributeSet; +import android.util.MathUtils; import android.view.AppTransitionAnimationSpec; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -46,6 +47,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsActivityLaunchState; @@ -78,6 +80,7 @@ import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecC import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture; import com.android.systemui.stackdivider.WindowManagerProxy; import com.android.systemui.statusbar.FlingAnimationUtils; +import com.android.systemui.statusbar.phone.ScrimController; import com.google.android.colorextraction.ColorExtractor; import com.google.android.colorextraction.drawable.GradientDrawable; @@ -95,11 +98,12 @@ public class RecentsView extends FrameLayout implements ColorExtractor.OnColorsC private static final String TAG = "RecentsView"; private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200; - private static final float DEFAULT_SCRIM_ALPHA = 0.8f; private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134; private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100; + private static final int BUSY_RECENTS_TASK_COUNT = 3; + private TaskStackView mTaskStackView; private TextView mStackActionButton; private TextView mEmptyView; @@ -111,9 +115,9 @@ public class RecentsView extends FrameLayout implements ColorExtractor.OnColorsC Rect mSystemInsets = new Rect(); private int mDividerSize; - private final float mScrimAlpha; + private float mBusynessFactor; private GradientDrawable mBackgroundScrim; - private final ColorExtractor mColorExtractor; + private final SysuiColorExtractor mColorExtractor; private Animator mBackgroundScrimAnimator; private RecentsTransitionHelper mTransitionHelper; @@ -142,11 +146,9 @@ public class RecentsView extends FrameLayout implements ColorExtractor.OnColorsC mDividerSize = ssp.getDockedDividerSize(context); mTouchHandler = new RecentsViewTouchHandler(this); mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f); - mScrimAlpha = DEFAULT_SCRIM_ALPHA; mBackgroundScrim = new GradientDrawable(context); mBackgroundScrim.setCallback(this); - mBackgroundScrim.setAlpha((int) (mScrimAlpha * 255)); - mColorExtractor = Dependency.get(ColorExtractor.class); + mColorExtractor = Dependency.get(SysuiColorExtractor.class); LayoutInflater inflater = LayoutInflater.from(context); if (RecentsDebugFlags.Static.EnableStackActionButton) { @@ -167,9 +169,10 @@ public class RecentsView extends FrameLayout implements ColorExtractor.OnColorsC /** * Called from RecentsActivity when it is relaunched. */ - public void onReload(boolean isResumingFromVisible, boolean isTaskStackEmpty) { - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); + public void onReload(TaskStack stack, boolean isResumingFromVisible) { + final RecentsConfiguration config = Recents.getConfiguration(); + final RecentsActivityLaunchState launchState = config.getLaunchState(); + final boolean isTaskStackEmpty = stack.getTaskCount() == 0; if (mTaskStackView == null) { isResumingFromVisible = false; @@ -184,17 +187,19 @@ public class RecentsView extends FrameLayout implements ColorExtractor.OnColorsC // Update the stack mTaskStackView.onReload(isResumingFromVisible); + updateStack(stack, true /* setStackViewTasks */); + updateBusyness(); if (isResumingFromVisible) { // If we are already visible, then restore the background scrim - animateBackgroundScrim(1f, DEFAULT_UPDATE_SCRIM_DURATION); + animateBackgroundScrim(getOpaqueScrimAlpha(), DEFAULT_UPDATE_SCRIM_DURATION); } else { // If we are already occluded by the app, then set the final background scrim alpha now. // Otherwise, defer until the enter animation completes to animate the scrim alpha with // the tasks for the home animation. if (launchState.launchedViaDockGesture || launchState.launchedFromApp || isTaskStackEmpty) { - mBackgroundScrim.setAlpha((int) (mScrimAlpha * 255)); + mBackgroundScrim.setAlpha((int) (getOpaqueScrimAlpha() * 255)); } else { mBackgroundScrim.setAlpha(0); } @@ -218,13 +223,40 @@ public class RecentsView extends FrameLayout implements ColorExtractor.OnColorsC } /** + * Animates the scrim opacity based on how many tasks are visible. + * Called from {@link RecentsActivity} when tasks are dismissed. + */ + public void updateScrimOpacity() { + if (updateBusyness()) { + animateBackgroundScrim(getOpaqueScrimAlpha(), DEFAULT_UPDATE_SCRIM_DURATION); + } + } + + /** + * Updates the busyness factor. + * + * @return True if it changed. + */ + private boolean updateBusyness() { + final int taskCount = mTaskStackView.getStack().getStackTaskCount(); + final float busyness = Math.min(taskCount, BUSY_RECENTS_TASK_COUNT) + / (float) BUSY_RECENTS_TASK_COUNT; + if (mBusynessFactor == busyness) { + return false; + } else { + mBusynessFactor = busyness; + return true; + } + } + + /** * Returns the current TaskStack. */ public TaskStack getStack() { return mTaskStackView.getStack(); } - /* + /** * Returns the window background scrim. */ public Drawable getBackgroundScrim() { @@ -618,7 +650,7 @@ public class RecentsView extends FrameLayout implements ColorExtractor.OnColorsC RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp && getStack().getTaskCount() > 0) { - animateBackgroundScrim(1f, + animateBackgroundScrim(getOpaqueScrimAlpha(), TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION); } } @@ -778,13 +810,25 @@ public class RecentsView extends FrameLayout implements ColorExtractor.OnColorsC } /** + * Scrim alpha based on how busy recents is: + * Scrim will be {@link ScrimController#GRADIENT_SCRIM_ALPHA} when the stack is empty, + * and {@link ScrimController#GRADIENT_SCRIM_ALPHA_BUSY} when it's full. + * + * @return Alpha from 0 to 1. + */ + private float getOpaqueScrimAlpha() { + return MathUtils.map(0, 1, ScrimController.GRADIENT_SCRIM_ALPHA, + ScrimController.GRADIENT_SCRIM_ALPHA_BUSY, mBusynessFactor); + } + + /** * Animates the background scrim to the given {@param alpha}. */ private void animateBackgroundScrim(float alpha, int duration) { Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator); // Calculate the absolute alpha to animate from - int fromAlpha = mBackgroundScrim.getAlpha(); - int toAlpha = (int) (alpha * mScrimAlpha * 255); + final int fromAlpha = mBackgroundScrim.getAlpha(); + final int toAlpha = (int) (alpha * 255); mBackgroundScrimAnimator = ObjectAnimator.ofInt(mBackgroundScrim, Utilities.DRAWABLE_ALPHA, fromAlpha, toAlpha); mBackgroundScrimAnimator.setDuration(duration); @@ -829,17 +873,24 @@ public class RecentsView extends FrameLayout implements ColorExtractor.OnColorsC } @Override - public void onColorsChanged(ColorExtractor extractor, int which) { + public void onColorsChanged(ColorExtractor colorExtractor, int which) { if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { - mBackgroundScrim.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM)); + // Recents doesn't care about the wallpaper being visible or not, it always + // wants to scrim with wallpaper colors + mBackgroundScrim.setColors( + mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, + ColorExtractor.TYPE_DARK, true)); } } public void onStart() { mColorExtractor.addOnColorsChangedListener(this); + // Getting system scrim colors ignoring wallpaper visibility since it should never be grey. + ColorExtractor.GradientColors systemColors = mColorExtractor.getColors( + ColorExtractor.TYPE_DARK, WallpaperManager.FLAG_SYSTEM, true); // We don't want to interpolate colors because we're defining the initial state. // Gradient should be set/ready when you open "Recents". - mBackgroundScrim.setColors(mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM), false); + mBackgroundScrim.setColors(systemColors, false); } public void onStop() { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index a7b845e2e62c..a2409d1f5583 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -50,6 +50,7 @@ import android.os.Process; import android.os.UserHandle; import android.provider.MediaStore; import android.util.DisplayMetrics; +import android.util.Slog; import android.view.Display; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -100,6 +101,7 @@ class SaveImageInBackgroundData { * An AsyncTask that saves an image to the media store in the background. */ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { + private static final String TAG = "SaveImageInBackgroundTask"; private static final String SCREENSHOTS_DIR_NAME = "Screenshots"; private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png"; @@ -303,6 +305,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { } catch (Exception e) { // IOException/UnsupportedOperationException may be thrown if external storage is not // mounted + Slog.e(TAG, "unable to save screenshot", e); mParams.clearImage(); mParams.errorMsgResId = R.string.screenshot_failed_to_save_text; } @@ -379,8 +382,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { * An AsyncTask that deletes an image from the media store in the background. */ class DeleteImageInBackgroundTask extends AsyncTask<Uri, Void, Void> { - private static final String TAG = "DeleteImageInBackgroundTask"; - private Context mContext; DeleteImageInBackgroundTask(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index bae6a275800a..eec818b2c9b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -303,7 +303,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public boolean performClick() { - if (mWasActivatedOnDown || !mNeedsDimming) { + if (mWasActivatedOnDown || !mNeedsDimming || isTouchExplorationEnabled()) { return super.performClick(); } return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 67ea25870a45..97e2f6d3e6f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -465,7 +465,8 @@ public class NotificationShelf extends ActivatableNotificationView implements || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) { iconState.hidden = true; } - int shelfColor = icon.getStaticDrawableColor(); + int backgroundColor = getBackgroundColorWithoutTint(); + int shelfColor = icon.getContrastedStaticDrawableColor(backgroundColor); if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) { int iconColor = row.getVisibleNotificationHeader().getOriginalIconColor(); shelfColor = NotificationUtils.interpolateColors(iconColor, shelfColor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java index 0e5e4161c886..c45ca54024db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java @@ -21,11 +21,14 @@ import java.util.List; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.Typeface; +import android.os.Bundle; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.text.SpannableString; @@ -35,6 +38,9 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -45,6 +51,10 @@ import com.android.systemui.R; public class NotificationSnooze extends LinearLayout implements NotificationGuts.GutsContent, View.OnClickListener { + /** + * If this changes more number increases, more assistant action resId's should be defined for + * accessibility purposes, see {@link #setSnoozeOptions(List)} + */ private static final int MAX_ASSISTANT_SUGGESTIONS = 1; private NotificationGuts mGutsContainer; private NotificationSwipeActionHelper mSnoozeListener; @@ -79,16 +89,60 @@ public class NotificationSnooze extends LinearLayout mDivider = findViewById(R.id.divider); mDivider.setAlpha(0f); mSnoozeOptionContainer = (ViewGroup) findViewById(R.id.snooze_options); + mSnoozeOptionContainer.setVisibility(View.INVISIBLE); mSnoozeOptionContainer.setAlpha(0f); // Create the different options based on list mSnoozeOptions = getDefaultSnoozeOptions(); createOptionViews(); - // Default to first option in list setSelected(mDefaultOption); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + if (mGutsContainer != null && mGutsContainer.isExposed()) { + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + event.getText().add(mSelectedOptionText.getText()); + } + } + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.addAction(new AccessibilityAction(R.id.action_snooze_undo, + getResources().getString(R.string.snooze_undo))); + int count = mSnoozeOptions.size(); + for (int i = 0; i < count; i++) { + AccessibilityAction action = mSnoozeOptions.get(i).getAccessibilityAction(); + if (action != null) { + info.addAction(action); + } + } + } + + @Override + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (super.performAccessibilityActionInternal(action, arguments)) { + return true; + } + if (action == R.id.action_snooze_undo) { + undoSnooze(mUndoButton); + return true; + } + for (int i = 0; i < mSnoozeOptions.size(); i++) { + SnoozeOption so = mSnoozeOptions.get(i); + if (so.getAccessibilityAction() != null + && so.getAccessibilityAction().getId() == action) { + setSelected(so); + return true; + } + } + return false; + } + public void setSnoozeOptions(final List<SnoozeCriterion> snoozeList) { if (snoozeList == null) { return; @@ -98,7 +152,10 @@ public class NotificationSnooze extends LinearLayout final int count = Math.min(MAX_ASSISTANT_SUGGESTIONS, snoozeList.size()); for (int i = 0; i < count; i++) { SnoozeCriterion sc = snoozeList.get(i); - mSnoozeOptions.add(new SnoozeOption(sc, 0, sc.getExplanation(), sc.getConfirmation())); + AccessibilityAction action = new AccessibilityAction( + R.id.action_snooze_assistant_suggestion_1, sc.getExplanation()); + mSnoozeOptions.add(new NotificationSnoozeOption(sc, 0, sc.getExplanation(), + sc.getConfirmation(), action)); } createOptionViews(); } @@ -117,15 +174,16 @@ public class NotificationSnooze extends LinearLayout private ArrayList<SnoozeOption> getDefaultSnoozeOptions() { ArrayList<SnoozeOption> options = new ArrayList<>(); - options.add(createOption(15 /* minutes */)); - options.add(createOption(30 /* minutes */)); - mDefaultOption = createOption(60 /* minutes */); + + options.add(createOption(15 /* minutes */, R.id.action_snooze_15_min)); + options.add(createOption(30 /* minutes */, R.id.action_snooze_30_min)); + mDefaultOption = createOption(60 /* minutes */, R.id.action_snooze_1_hour); options.add(mDefaultOption); - options.add(createOption(60 * 2 /* minutes */)); + options.add(createOption(60 * 2 /* minutes */, R.id.action_snooze_2_hours)); return options; } - private SnoozeOption createOption(int minutes) { + private SnoozeOption createOption(int minutes, int accessibilityActionId) { Resources res = getResources(); boolean showInHours = minutes >= 60; int pluralResId = showInHours @@ -137,7 +195,9 @@ public class NotificationSnooze extends LinearLayout SpannableString string = new SpannableString(resultText); string.setSpan(new StyleSpan(Typeface.BOLD), resultText.length() - description.length(), resultText.length(), 0 /* flags */); - return new SnoozeOption(null, minutes, description, string); + AccessibilityAction action = new AccessibilityAction(accessibilityActionId, description); + return new NotificationSnoozeOption(null, minutes, description, string, + action); } private void createOptionViews() { @@ -149,7 +209,7 @@ public class NotificationSnooze extends LinearLayout TextView tv = (TextView) inflater.inflate(R.layout.notification_snooze_option, mSnoozeOptionContainer, false); mSnoozeOptionContainer.addView(tv); - tv.setText(option.description); + tv.setText(option.getDescription()); tv.setTag(option); tv.setOnClickListener(this); } @@ -184,18 +244,36 @@ public class NotificationSnooze extends LinearLayout mDivider.getAlpha(), show ? 1f : 0f); ObjectAnimator optionAnim = ObjectAnimator.ofFloat(mSnoozeOptionContainer, View.ALPHA, mSnoozeOptionContainer.getAlpha(), show ? 1f : 0f); + mSnoozeOptionContainer.setVisibility(View.VISIBLE); mExpandAnimation = new AnimatorSet(); mExpandAnimation.playTogether(dividerAnim, optionAnim); mExpandAnimation.setDuration(150); mExpandAnimation.setInterpolator(show ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT); + mExpandAnimation.addListener(new AnimatorListenerAdapter() { + boolean cancelled = false; + + @Override + public void onAnimationCancel(Animator animation) { + cancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (!show && !cancelled) { + mSnoozeOptionContainer.setVisibility(View.INVISIBLE); + mSnoozeOptionContainer.setAlpha(0f); + } + } + }); mExpandAnimation.start(); } private void setSelected(SnoozeOption option) { mSelectedOption = option; - mSelectedOptionText.setText(option.confirmation); + mSelectedOptionText.setText(option.getConfirmation()); showSnoozeOptions(false); hideSelectedOption(); + sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } @Override @@ -212,20 +290,24 @@ public class NotificationSnooze extends LinearLayout showSnoozeOptions(!mExpanded); } else { // Undo snooze was selected - mSelectedOption = null; - int[] parentLoc = new int[2]; - int[] targetLoc = new int[2]; - mGutsContainer.getLocationOnScreen(parentLoc); - v.getLocationOnScreen(targetLoc); - final int centerX = v.getWidth() / 2; - final int centerY = v.getHeight() / 2; - final int x = targetLoc[0] - parentLoc[0] + centerX; - final int y = targetLoc[1] - parentLoc[1] + centerY; - showSnoozeOptions(false); - mGutsContainer.closeControls(x, y, false /* save */, false /* force */); + undoSnooze(v); } } + private void undoSnooze(View v) { + mSelectedOption = null; + int[] parentLoc = new int[2]; + int[] targetLoc = new int[2]; + mGutsContainer.getLocationOnScreen(parentLoc); + v.getLocationOnScreen(targetLoc); + final int centerX = v.getWidth() / 2; + final int centerY = v.getHeight() / 2; + final int x = targetLoc[0] - parentLoc[0] + centerX; + final int y = targetLoc[1] - parentLoc[1] + centerY; + showSnoozeOptions(false); + mGutsContainer.closeControls(x, y, false /* save */, false /* force */); + } + @Override public int getActualHeight() { return mExpanded ? getHeight() : mCollapsedHeight; @@ -270,4 +352,48 @@ public class NotificationSnooze extends LinearLayout public boolean isLeavebehind() { return true; } + + public class NotificationSnoozeOption implements SnoozeOption { + private SnoozeCriterion mCriterion; + private int mMinutesToSnoozeFor; + private CharSequence mDescription; + private CharSequence mConfirmation; + private AccessibilityAction mAction; + + public NotificationSnoozeOption(SnoozeCriterion sc, int minToSnoozeFor, + CharSequence description, + CharSequence confirmation, AccessibilityAction action) { + mCriterion = sc; + mMinutesToSnoozeFor = minToSnoozeFor; + mDescription = description; + mConfirmation = confirmation; + mAction = action; + } + + @Override + public SnoozeCriterion getSnoozeCriterion() { + return mCriterion; + } + + @Override + public CharSequence getDescription() { + return mDescription; + } + + @Override + public CharSequence getConfirmation() { + return mConfirmation; + } + + @Override + public int getMinutesToSnoozeFor() { + return mMinutesToSnoozeFor; + } + + @Override + public AccessibilityAction getAccessibilityAction() { + return mAction; + } + + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java index 169019f2cdb3..25f3e25f2051 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java @@ -16,19 +16,15 @@ package com.android.systemui.statusbar; -import android.annotation.ColorInt; import android.annotation.DrawableRes; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; -import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.net.NetworkBadging; import android.telephony.SubscriptionInfo; import android.util.ArraySet; import android.util.AttributeSet; @@ -80,10 +76,8 @@ public class SignalClusterView extends LinearLayout implements NetworkController private boolean mEthernetVisible = false; private int mEthernetIconId = 0; private int mLastEthernetIconId = -1; - private int mWifiBadgeId = -1; private boolean mWifiVisible = false; private int mWifiStrengthId = 0; - private int mLastWifiBadgeId = -1; private int mLastWifiStrengthId = -1; private boolean mWifiIn; private boolean mWifiOut; @@ -291,7 +285,6 @@ public class SignalClusterView extends LinearLayout implements NetworkController boolean activityIn, boolean activityOut, String description, boolean isTransient) { mWifiVisible = statusIcon.visible && !mBlockWifi; mWifiStrengthId = statusIcon.icon; - mWifiBadgeId = statusIcon.iconOverlay; mWifiDescription = statusIcon.contentDescription; mWifiIn = activityIn && mActivityEnabled && mWifiVisible; mWifiOut = activityOut && mActivityEnabled && mWifiVisible; @@ -428,7 +421,6 @@ public class SignalClusterView extends LinearLayout implements NetworkController mWifi.setImageDrawable(null); mWifiDark.setImageDrawable(null); mLastWifiStrengthId = -1; - mLastWifiBadgeId = -1; } for (PhoneState state : mPhoneStates) { @@ -484,16 +476,10 @@ public class SignalClusterView extends LinearLayout implements NetworkController (mEthernetVisible ? "VISIBLE" : "GONE"))); if (mWifiVisible) { - if (mWifiStrengthId != mLastWifiStrengthId || mWifiBadgeId != mLastWifiBadgeId) { - if (mWifiBadgeId == -1) { - setIconForView(mWifi, mWifiStrengthId); - setIconForView(mWifiDark, mWifiStrengthId); - } else { - setBadgedWifiIconForView(mWifi, mWifiStrengthId, mWifiBadgeId); - setBadgedWifiIconForView(mWifiDark, mWifiStrengthId, mWifiBadgeId); - } + if (mWifiStrengthId != mLastWifiStrengthId) { + setIconForView(mWifi, mWifiStrengthId); + setIconForView(mWifiDark, mWifiStrengthId); mLastWifiStrengthId = mWifiStrengthId; - mLastWifiBadgeId = mWifiBadgeId; } mWifiGroup.setContentDescription(mWifiDescription); mWifiGroup.setVisibility(View.VISIBLE); @@ -558,10 +544,6 @@ public class SignalClusterView extends LinearLayout implements NetworkController // Using the imageView's context to retrieve the Drawable so that theme is preserved. Drawable icon = imageView.getContext().getDrawable(iconId); - setScaledIcon(imageView, icon); - } - - private void setScaledIcon(ImageView imageView, Drawable icon) { if (mIconScaleFactor == 1.f) { imageView.setImageDrawable(icon); } else { @@ -569,33 +551,6 @@ public class SignalClusterView extends LinearLayout implements NetworkController } } - /** - * Creates and sets a LayerDrawable from the given ids on the given view. - * - * <p>This method will also scale the icon by {@link #mIconScaleFactor} if appropriate. - */ - private void setBadgedWifiIconForView(ImageView imageView, @DrawableRes int wifiPieId, - @DrawableRes int badgeId) { - // TODO(sghuman): Delete this method and revert to N badging logic - // Using the imageView's context to retrieve the Drawable so that theme is preserved.; - LayerDrawable icon = new LayerDrawable(new Drawable[] { - imageView.getContext().getDrawable(wifiPieId), - imageView.getContext().getDrawable(NetworkBadging.BADGING_NONE)}); - - // The LayerDrawable shares an underlying state so we must mutate the object to change the - // color between the light and dark themes. - icon.mutate().setTint(getColorAttr(imageView.getContext(), R.attr.singleToneColor)); - - setScaledIcon(imageView, icon); - } - - /** Returns the given color attribute value, or white if not defined. */ - @ColorInt private static int getColorAttr(Context context, int attr) { - TypedArray ta = context.obtainStyledAttributes(new int[] {attr}); - @ColorInt int colorAccent = ta.getColor(0, Color.WHITE); - ta.recycle(); - return colorAccent; - } @Override public void onDarkChanged(Rect tintArea, float darkIntensity, int tint) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 3c7ddb502145..1a47e4428e49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -35,6 +35,7 @@ import android.graphics.drawable.Icon; import android.os.Parcelable; import android.os.UserHandle; import android.service.notification.StatusBarNotification; +import android.support.v4.graphics.ColorUtils; import android.text.TextUtils; import android.util.AttributeSet; import android.util.FloatProperty; @@ -46,6 +47,7 @@ import android.view.accessibility.AccessibilityEvent; import android.view.animation.Interpolator; import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.util.NotificationColorUtil; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.notification.NotificationIconDozeHelper; @@ -127,6 +129,8 @@ public class StatusBarIconView extends AnimatedImageView { setColorInternal(newColor); }; private final NotificationIconDozeHelper mDozer; + private int mContrastedDrawableColor; + private int mCachedContrastBackgroundColor = NO_COLOR; public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) { this(context, slot, sbn, false); @@ -528,6 +532,7 @@ public class StatusBarIconView extends AnimatedImageView { public void setStaticDrawableColor(int color) { mDrawableColor = color; setColorInternal(color); + updateContrastedStaticColor(); mIconColor = color; mDozer.setColor(color); } @@ -580,6 +585,42 @@ public class StatusBarIconView extends AnimatedImageView { return mDrawableColor; } + /** + * A drawable color that passes GAR on a specific background. + * This value is cached. + * + * @param backgroundColor Background to test against. + * @return GAR safe version of {@link StatusBarIconView#getStaticDrawableColor()}. + */ + int getContrastedStaticDrawableColor(int backgroundColor) { + if (mCachedContrastBackgroundColor != backgroundColor) { + mCachedContrastBackgroundColor = backgroundColor; + updateContrastedStaticColor(); + } + return mContrastedDrawableColor; + } + + private void updateContrastedStaticColor() { + if (mCachedContrastBackgroundColor == NO_COLOR) { + return; + } + // We'll modify the color if it doesn't pass GAR + int contrastedColor = mDrawableColor; + if (!NotificationColorUtil.satisfiesTextContrast(mCachedContrastBackgroundColor, + contrastedColor)) { + float[] hsl = new float[3]; + ColorUtils.colorToHSL(mDrawableColor, hsl); + // This is basically a light grey, pushing the color will only distort it. + // Best thing to do in here is to fallback to the default color. + if (hsl[1] < 0.2f) { + contrastedColor = Notification.COLOR_DEFAULT; + } + contrastedColor = NotificationColorUtil.resolveContrastColor(mContext, + contrastedColor, mCachedContrastBackgroundColor); + } + mContrastedDrawableColor = contrastedColor; + } + public void setVisibleState(int state) { setVisibleState(state, true /* animate */, null /* endRunnable */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java index 7eaa290c9d23..bf926c625e4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java @@ -48,6 +48,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ public class NotificationInflater { + public static final String TAG = "NotificationInflater"; @VisibleForTesting static final int FLAG_REINFLATE_ALL = ~0; private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0; @@ -315,7 +316,8 @@ public class NotificationInflater { return cancellationSignal; } - private static void applyRemoteView(final InflationProgress result, + @VisibleForTesting + static void applyRemoteView(final InflationProgress result, final int reInflateFlags, int inflationId, final ExpandableNotificationRow row, final boolean redactAmbient, boolean isNewView, @@ -325,6 +327,7 @@ public class NotificationInflater { NotificationViewWrapper existingWrapper, final HashMap<Integer, CancellationSignal> runningInflations, ApplyCallback applyCallback) { + RemoteViews newContentView = applyCallback.getRemoteView(); RemoteViews.OnViewAppliedListener listener = new RemoteViews.OnViewAppliedListener() { @@ -343,12 +346,31 @@ public class NotificationInflater { @Override public void onError(Exception e) { - runningInflations.remove(inflationId); - handleInflationError(runningInflations, e, entry.notification, callback); + // Uh oh the async inflation failed. Due to some bugs (see b/38190555), this could + // actually also be a system issue, so let's try on the UI thread again to be safe. + try { + View newView = existingView; + if (isNewView) { + newView = newContentView.apply( + result.packageContext, + parentLayout, + remoteViewClickHandler); + } else { + newContentView.reapply( + result.packageContext, + existingView, + remoteViewClickHandler); + } + Log.wtf(TAG, "Async Inflation failed but normal inflation finished normally.", + e); + onViewApplied(newView); + } catch (Exception anotherException) { + runningInflations.remove(inflationId); + handleInflationError(runningInflations, e, entry.notification, callback); + } } }; CancellationSignal cancellationSignal; - RemoteViews newContentView = applyCallback.getRemoteView(); if (isNewView) { cancellationSignal = newContentView.applyAsync( result.packageContext, @@ -620,14 +642,16 @@ public class NotificationInflater { } } - private static class InflationProgress { + @VisibleForTesting + static class InflationProgress { private RemoteViews newContentView; private RemoteViews newHeadsUpView; private RemoteViews newExpandedView; private RemoteViews newAmbientView; private RemoteViews newPublicView; - private Context packageContext; + @VisibleForTesting + Context packageContext; private View inflatedContentView; private View inflatedHeadsUpView; @@ -636,7 +660,8 @@ public class NotificationInflater { private View inflatedPublicView; } - private abstract static class ApplyCallback { + @VisibleForTesting + abstract static class ApplyCallback { public abstract void setResultView(View v); public abstract RemoteViews getRemoteView(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index d7e7abe4fd9c..6b7397b3f8ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -157,6 +157,10 @@ public class DozeParameters { return 2 * getPulseVisibleDuration(); } + public boolean doubleTapReportsTouchCoordinates() { + return mContext.getResources().getBoolean(R.bool.doze_double_tap_reports_touch_coordinates); + } + /** * Parses a spec of the form `1,2,3,!5,*`. The resulting object will match numbers that are diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index bd59fb03d59a..82e6a3562c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -106,6 +106,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { mPanel.setPanelScrimMinFraction((float) expandedHeight / mPanel.getMaxPanelHeight()); mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight); + mPanel.startExpandingFromPeek(); // This call needs to be after the expansion start otherwise we will get a // flicker of one frame as it's not expanded yet. mHeadsUpManager.unpinAll(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFrame.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFrame.java new file mode 100644 index 000000000000..741f7839f455 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFrame.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import static android.view.MotionEvent.ACTION_OUTSIDE; + +import android.annotation.AttrRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +import com.android.systemui.statusbar.policy.DeadZone; + +public class NavigationBarFrame extends FrameLayout { + + private DeadZone mDeadZone = null; + + public NavigationBarFrame(@NonNull Context context) { + super(context); + } + + public NavigationBarFrame(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public NavigationBarFrame(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setDeadZone(@NonNull DeadZone deadZone) { + mDeadZone = deadZone; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (event.getAction() == ACTION_OUTSIDE) { + if (mDeadZone != null) { + return mDeadZone.onTouchEvent(event); + } + } + return super.dispatchTouchEvent(event); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index cb3222d6d287..a6cd472f2686 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -249,9 +249,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav if (mGestureHelper.onTouchEvent(event)) { return true; } - if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) { - mDeadZone.poke(event); - } return super.onTouchEvent(event); } @@ -612,9 +609,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav public void reorient() { updateCurrentView(); - getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); - mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone); + ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone); mDeadZone.setDisplayRotation(mCurrentRotation); // force the low profile & disabled states into compliance diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index b970b1b15ac1..16d85bec4829 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -304,10 +304,10 @@ public abstract class PanelView extends FrameLayout { trackMovement(event); if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) || mPeekAnimator != null) { - cancelHeightAnimator(); - cancelPeek(); mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning) || mPeekAnimator != null; + cancelHeightAnimator(); + cancelPeek(); onTrackingStarted(); } if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()) { @@ -407,6 +407,10 @@ public abstract class PanelView extends FrameLayout { return Math.abs(yDiff) >= Math.abs(xDiff); } + protected void startExpandingFromPeek() { + mStatusBar.handlePeekToExpandTransistion(); + } + protected void startExpandMotion(float newX, float newY, boolean startTracking, float expandedHeight) { mInitialOffsetOnTouch = expandedHeight; @@ -608,6 +612,9 @@ public abstract class PanelView extends FrameLayout { protected void cancelHeightAnimator() { if (mHeightAnimator != null) { + if (mHeightAnimator.isRunning()) { + mPanelUpdateWhenAnimatorEnds = false; + } mHeightAnimator.cancel(); } endClosing(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index f502bb5c5dfc..419aefe3e9e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -36,6 +36,7 @@ import android.view.animation.PathInterpolator; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.ScrimView; @@ -57,7 +58,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED = new PathInterpolator(0.3f, 0f, 0.8f, 1f); // Default alpha value for most scrims, if unsure use this constant - public static final float GRADIENT_SCRIM_ALPHA = 0.60f; + public static final float GRADIENT_SCRIM_ALPHA = 0.45f; // A scrim varies its opacity based on a busyness factor, for example // how many notifications are currently visible. public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.90f; @@ -78,11 +79,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private final View mHeadsUpScrim; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final ColorExtractor mColorExtractor; + private final SysuiColorExtractor mColorExtractor; private ColorExtractor.GradientColors mLockColors; - private ColorExtractor.GradientColors mLockColorsDark; private ColorExtractor.GradientColors mSystemColors; - private ColorExtractor.GradientColors mSystemColorsDark; private boolean mNeedsDrawableColorUpdate; protected float mScrimBehindAlpha; @@ -131,15 +130,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mLightBarController = lightBarController; mScrimBehindAlpha = context.getResources().getFloat(R.dimen.scrim_behind_alpha); - mColorExtractor = Dependency.get(ColorExtractor.class); + mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); - mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK); - mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM); - // Darker gradient for the top scrim (mScrimInFront) - mLockColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, - ColorExtractor.TYPE_DARK); - mSystemColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, - ColorExtractor.TYPE_DARK); + mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, + ColorExtractor.TYPE_DARK, true /* ignoreVisibility */); + mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, + ColorExtractor.TYPE_DARK, true /* ignoreVisibility */); mNeedsDrawableColorUpdate = true; updateHeadsUpScrim(false); @@ -308,13 +304,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mNeedsDrawableColorUpdate = false; if (mKeyguardShowing) { // Always animate color changes if we're seeing the keyguard - mScrimInFront.setColors(mLockColorsDark); + mScrimInFront.setColors(mLockColors); mScrimBehind.setColors(mLockColors); } else { // Only animate scrim color if the scrim view is actually visible boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0; boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0; - mScrimInFront.setColors(mSystemColorsDark, animateScrimInFront); + mScrimInFront.setColors(mSystemColors, animateScrimInFront); mScrimBehind.setColors(mSystemColors, animateScrimBehind); } } @@ -659,18 +655,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, @Override public void onColorsChanged(ColorExtractor colorExtractor, int which) { if ((which & WallpaperManager.FLAG_LOCK) != 0) { - mLockColors = colorExtractor.getColors(WallpaperManager.FLAG_LOCK, - ColorExtractor.TYPE_NORMAL); - mLockColorsDark = colorExtractor.getColors(WallpaperManager.FLAG_LOCK, - ColorExtractor.TYPE_DARK); + mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, + ColorExtractor.TYPE_DARK, true /* ignoreVisibility */); mNeedsDrawableColorUpdate = true; scheduleUpdate(); } if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { - mSystemColors = colorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, - ColorExtractor.TYPE_NORMAL); - mSystemColorsDark = colorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, - ColorExtractor.TYPE_DARK); + mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, + ColorExtractor.TYPE_DARK, mKeyguardShowing); mNeedsDrawableColorUpdate = true; scheduleUpdate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java index 5ebcde789cb1..deea521b7c2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java @@ -21,13 +21,15 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.Canvas; import android.graphics.ColorFilter; +import android.graphics.Matrix; import android.graphics.Paint; -import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.Path.Direction; import android.graphics.Path.FillType; import android.graphics.Path.Op; +import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Handler; import android.util.LayoutDirection; @@ -63,6 +65,7 @@ public class SignalDrawable extends Drawable { private static final int STATE_EMPTY = 1; private static final int STATE_CUT = 2; private static final int STATE_CARRIER_CHANGE = 3; + private static final int STATE_AIRPLANE = 4; private static final long DOT_DELAY = 1000; @@ -81,21 +84,32 @@ public class SignalDrawable extends Drawable { {-1.9f / VIEWPORT, -1.9f / VIEWPORT}, }; - // Rounded corners are achieved by arcing a circle of radius `mCornerRadius` from its tangent - // points along the curve. On the top and left corners of the triangle, the tangents are as - // follows: + // Rounded corners are achieved by arcing a circle of radius `R` from its tangent points along + // the curve (curve ≡ triangle). On the top and left corners of the triangle, the tangents are + // as follows: // 1) Along the straight lines (y = 0 and x = width): - // Ps = R / tan(45) + // Ps = circleOffset + R // 2) Along the diagonal line (y = x): // Pd = √((Ps^2) / 2) - // - // I.e., the points where we stop the straight lines lie at (starting drawing from the bottom- - // right corner and counter-clockwise): (width, height - Radius), (width, Ps), (width - Pd, Pd), - // (Pd, height - Pd), (Ps, height), and finally (width - Radius, height). - private static final double TAN_THETA = Math.tan(Math.PI / 8.f); - private final float mCornerRadius; - private final float mCircleOffsetStraight; - private final float mCircleOffsetDiag; + // or (remember: sin(π/4) ≈ 0.7071) + // Pd = (circleOffset + R - 0.7071, height - R - 0.7071) + // Where Pd is the (x,y) coords of the point that intersects the circle at the bottom + // left of the triangle + private static final float RADIUS_RATIO = 0.75f / 17f; + private static final float DIAG_OFFSET_MULTIPLIER = 0.707107f; + // How far the circle defining the corners is inset from the edges + private final float mAppliedCornerInset; + + // The easiest way to understand this is as if we set Style.STROKE and draw the triangle, + // but that is only theoretically right. Instead, draw the triangle and clip out a smaller + // one inset by this amount. + private final float mEmptyStrokeWidth; + private static final float INV_TAN = 1f / (float) Math.tan(Math.PI / 8f); + private final float mEmptyDiagInset; // == mEmptyStrokeWidth * INV_TAN + + // Where the top and left points of the triangle would be if not for rounding + private final PointF mVirtualTop = new PointF(); + private final PointF mVirtualLeft = new PointF(); private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mForegroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -106,6 +120,10 @@ public class SignalDrawable extends Drawable { private final Path mFullPath = new Path(); private final Path mForegroundPath = new Path(); private final Path mXPath = new Path(); + // Cut out when STATE_EMPTY + private final Path mCutPath = new Path(); + // Draws the slash when in airplane mode + private final SlashArtist mSlash = new SlashArtist(); private final Handler mHandler; private float mOldDarkIntensity = -1; private float mNumLevels = 1; @@ -126,13 +144,17 @@ public class SignalDrawable extends Drawable { mLightModeFillColor = Utils.getDefaultColor(context, R.color.light_mode_icon_color_dual_tone_fill); mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size); + + // mCutPath parameters + mEmptyStrokeWidth = context.getResources() + .getDimensionPixelSize(R.dimen.mobile_signal_empty_strokewidth); + mEmptyDiagInset = mEmptyStrokeWidth * INV_TAN; + mHandler = new Handler(); setDarkIntensity(0); - mCornerRadius = context.getResources() - .getDimensionPixelSize(R.dimen.stat_sys_mobile_signal_corner_radius); - mCircleOffsetStraight = mCornerRadius / (float) TAN_THETA; - mCircleOffsetDiag = (float) Math.sqrt(Math.pow(mCircleOffsetStraight, 2.f) / 2.f); + mAppliedCornerInset = context.getResources() + .getDimensionPixelSize(R.dimen.stat_sys_mobile_signal_circle_inset); } public void setIntrinsicSize(int size) { @@ -226,38 +248,43 @@ public class SignalDrawable extends Drawable { } mFullPath.reset(); mFullPath.setFillType(FillType.WINDING); - float width = getBounds().width(); - float height = getBounds().height(); - float padding = (int) (PAD * width); // Stay on pixel boundary + + final float width = getBounds().width(); + final float height = getBounds().height(); + final float padding = Math.round(PAD * width); + final float cornerRadius = RADIUS_RATIO * height; + // Offset from circle where the hypotenuse meets the circle + final float diagOffset = DIAG_OFFSET_MULTIPLIER * cornerRadius; // 1 - Bottom right, above corner - mFullPath.moveTo(width - padding, height - padding - mCornerRadius); + mFullPath.moveTo(width - padding, height - padding - cornerRadius); // 2 - Line to top right, below corner - mFullPath.lineTo(width - padding, padding + mCircleOffsetStraight); + mFullPath.lineTo(width - padding, padding + cornerRadius + mAppliedCornerInset); // 3 - Arc to top right, on hypotenuse mFullPath.arcTo( - width - padding - (2 * mCornerRadius), - padding + mCircleOffsetStraight - mCornerRadius, + width - padding - (2 * cornerRadius), + padding + mAppliedCornerInset, width - padding, - padding + mCircleOffsetStraight + mCornerRadius, + padding + mAppliedCornerInset + (2 * cornerRadius), 0.f, -135.f, false ); // 4 - Line to bottom left, on hypotenuse - mFullPath.lineTo(padding + mCircleOffsetDiag, height - padding - mCircleOffsetDiag); + mFullPath.lineTo(padding + mAppliedCornerInset + cornerRadius - diagOffset, + height - padding - cornerRadius - diagOffset); // 5 - Arc to bottom left, on leg mFullPath.arcTo( - padding + mCircleOffsetStraight - mCornerRadius, - height - padding - (2 * mCornerRadius), - padding + mCircleOffsetStraight + mCornerRadius, + padding + mAppliedCornerInset, + height - padding - (2 * cornerRadius), + padding + mAppliedCornerInset + ( 2 * cornerRadius), height - padding, -135.f, -135.f, false ); // 6 - Line to bottom rght, before corner - mFullPath.lineTo(width - padding - mCornerRadius, height - padding); + mFullPath.lineTo(width - padding - cornerRadius, height - padding); // 7 - Arc to beginning (bottom right, above corner) mFullPath.arcTo( - width - padding - (2 * mCornerRadius), - height - padding - (2 * mCornerRadius), + width - padding - (2 * cornerRadius), + height - padding - (2 * cornerRadius), width - padding, height - padding, 90.f, -90.f, false @@ -290,10 +317,37 @@ public class SignalDrawable extends Drawable { mFullPath.rLineTo(0, cut); } - mPaint.setStyle(mState == STATE_EMPTY ? Style.STROKE : Style.FILL); - mForegroundPaint.setStyle(mState == STATE_EMPTY ? Style.STROKE : Style.FILL); - - if (mState != STATE_CARRIER_CHANGE) { + if (mState == STATE_EMPTY) { + // Where the corners would be if this were a real triangle + mVirtualTop.set( + width - padding, + (padding + cornerRadius + mAppliedCornerInset) - (INV_TAN * cornerRadius)); + mVirtualLeft.set( + (padding + cornerRadius + mAppliedCornerInset) - (INV_TAN * cornerRadius), + height - padding); + + // Cut out a smaller triangle from the center of mFullPath + mCutPath.reset(); + mCutPath.setFillType(FillType.WINDING); + mCutPath.moveTo(width - padding - mEmptyStrokeWidth, + height - padding - mEmptyStrokeWidth); + mCutPath.lineTo(width - padding - mEmptyStrokeWidth, + mVirtualTop.y + mEmptyDiagInset); + mCutPath.lineTo(mVirtualLeft.x + mEmptyDiagInset, + height - padding - mEmptyStrokeWidth); + mCutPath.lineTo(width - padding - mEmptyStrokeWidth, + height - padding - mEmptyStrokeWidth); + + // In empty state, draw the full path as the foreground paint + mForegroundPath.set(mFullPath); + mFullPath.reset(); + mForegroundPath.op(mCutPath, Path.Op.DIFFERENCE); + } else if (mState == STATE_AIRPLANE) { + // Airplane mode is slashed, full-signal + mForegroundPath.set(mFullPath); + mFullPath.reset(); + mSlash.draw((int) height, (int) width, canvas, mForegroundPaint); + } else if (mState != STATE_CARRIER_CHANGE) { mForegroundPath.reset(); int sigWidth = Math.round(calcFit(mLevel / (mNumLevels - 1)) * (width - 2 * padding)); mForegroundPath.addRect(padding, padding, padding + sigWidth, height - padding, @@ -403,4 +457,65 @@ public class SignalDrawable extends Drawable { public static int getEmptyState(int numLevels) { return (STATE_EMPTY << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT); } + + public static int getAirplaneModeState(int numLevels) { + return (STATE_AIRPLANE << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT); + } + + private final class SlashArtist { + // These values are derived in un-rotated (vertical) orientation + private static final float SLASH_WIDTH = 1.8384776f; + private static final float SLASH_HEIGHT = 22f; + private static final float CENTER_X = 10.65f; + private static final float CENTER_Y = 15.869239f; + private static final float SCALE = 24f; + + // Bottom is derived during animation + private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE; + private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE; + private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE; + private static final float BOTTOM = (CENTER_Y + (SLASH_HEIGHT / 2)) / SCALE; + // Draw the slash washington-monument style; rotate to no-u-turn style + private static final float ROTATION = -45f; + + private final Path mPath = new Path(); + private final RectF mSlashRect = new RectF(); + + void draw(int height, int width, @NonNull Canvas canvas, Paint paint) { + Matrix m = new Matrix(); + updateRect( + scale(LEFT, width), + scale(TOP, height), + scale(RIGHT, width), + scale(BOTTOM, height)); + + mPath.reset(); + // Draw the slash vertically + mPath.addRect(mSlashRect, Direction.CW); + m.setRotate(ROTATION, width / 2, height / 2); + mPath.transform(m); + canvas.drawPath(mPath, paint); + + // Rotate back to vertical, and draw the cut-out rect next to this one + m.setRotate(-ROTATION, width / 2, height / 2); + mPath.transform(m); + m.setTranslate(mSlashRect.width(), 0); + mPath.transform(m); + mPath.addRect(mSlashRect, Direction.CW); + m.setRotate(ROTATION, width / 2, height / 2); + mPath.transform(m); + canvas.clipOutPath(mPath); + } + + void updateRect(float left, float top, float right, float bottom) { + mSlashRect.left = left; + mSlashRect.top = top; + mSlashRect.right = right; + mSlashRect.bottom = bottom; + } + + private float scale(float frac, int width) { + return frac * width; + } + } } 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 e8b6e079e261..14d0b7ea86b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -32,7 +32,6 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCE import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; -import android.R.style; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.NonNull; @@ -49,6 +48,7 @@ import android.app.PendingIntent; import android.app.RemoteInput; import android.app.StatusBarManager; import android.app.TaskStackBuilder; +import android.app.WallpaperColors; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -104,7 +104,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; import android.provider.Settings; -import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.service.vr.IVrManager; @@ -140,6 +139,7 @@ import android.widget.RemoteViews; import android.widget.TextView; import android.widget.Toast; +import com.android.internal.graphics.ColorUtils; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; @@ -169,6 +169,7 @@ import com.android.systemui.UiOffloadThread; import com.android.systemui.assist.AssistManager; import com.android.systemui.classifier.FalsingLog; import com.android.systemui.classifier.FalsingManager; +import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.fragments.ExtensionFragmentListener; @@ -213,7 +214,6 @@ import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.RowInflaterTask; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -303,12 +303,13 @@ public class StatusBar extends SystemUI implements DemoMode, private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action"; public static final String TAG = "StatusBar"; - public static final boolean DEBUG = true; + public static final boolean DEBUG = false; public static final boolean SPEW = false; public static final boolean DUMPTRUCK = true; // extra dumpsys info public static final boolean DEBUG_GESTURES = false; public static final boolean DEBUG_MEDIA = false; public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false; + public static final boolean DEBUG_CAMERA_LIFT = true; // false once b/62623620 is fixed public static final boolean DEBUG_WINDOW_STATE = false; @@ -661,7 +662,7 @@ public class StatusBar extends SystemUI implements DemoMode, // Tracks notifications currently visible in mNotificationStackScroller and // emits visibility events via NoMan on changes. - private final Runnable mVisibilityReporter = new Runnable() { + protected final Runnable mVisibilityReporter = new Runnable() { private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications = new ArraySet<>(); private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications = @@ -734,7 +735,7 @@ public class StatusBar extends SystemUI implements DemoMode, private HashMap<String, Entry> mPendingNotifications = new HashMap<>(); private boolean mClearAllEnabled; @Nullable private View mAmbientIndicationContainer; - private ColorExtractor mColorExtractor; + private SysuiColorExtractor mColorExtractor; private ForegroundServiceController mForegroundServiceController; private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) { @@ -781,7 +782,7 @@ public class StatusBar extends SystemUI implements DemoMode, mOverlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); - mColorExtractor = Dependency.get(ColorExtractor.class); + mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); @@ -1300,10 +1301,6 @@ public class StatusBar extends SystemUI implements DemoMode, } public void onOverlayChanged() { - final boolean usingDarkTheme = isUsingDarkTheme(); - if (DEBUG) { - Log.d(TAG, "Updating theme because overlay changed. Is theme dark? " + usingDarkTheme); - } reevaluateStyles(); // Clock and bottom icons @@ -2843,6 +2840,17 @@ public class StatusBar extends SystemUI implements DemoMode, updateTheme(); } + public boolean isUsingDarkText() { + OverlayInfo themeInfo = null; + try { + themeInfo = mOverlayManager.getOverlayInfo("com.android.systemui.theme.lightwallpaper", + mCurrentUserId); + } catch (RemoteException e) { + e.printStackTrace(); + } + return themeInfo != null && themeInfo.isEnabled(); + } + public boolean isUsingDarkTheme() { OverlayInfo themeInfo = null; try { @@ -3448,7 +3456,9 @@ public class StatusBar extends SystemUI implements DemoMode, pw.println(Settings.Global.zenModeToString(mZenMode)); pw.print(" mUseHeadsUp="); pw.println(mUseHeadsUp); - dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions()); + if (mStatusBarView != null) { + dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions()); + } pw.print(" mMediaSessionManager="); pw.println(mMediaSessionManager); @@ -3514,7 +3524,9 @@ public class StatusBar extends SystemUI implements DemoMode, pw.println(" mGroupManager: null"); } - mLightBarController.dump(fd, pw, args); + if (mLightBarController != null) { + mLightBarController.dump(fd, pw, args); + } if (KeyguardUpdateMonitor.getInstance(mContext) != null) { KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args); @@ -3822,6 +3834,17 @@ public class StatusBar extends SystemUI implements DemoMode, } } + void handlePeekToExpandTransistion() { + try { + // consider the transition from peek to expanded to be a panel open, + // but not one that clears notification effects. + int notificationLoad = mNotificationData.getActiveNotifications().size(); + mBarService.onPanelRevealed(false, notificationLoad); + } catch (RemoteException ex) { + // Won't fail unless the world has ended. + } + } + /** * The LEDs are turned off when the notification panel is shown, even just a little bit. * See also StatusBar.setPanelExpanded for another place where we attempt to do this. @@ -3837,8 +3860,6 @@ public class StatusBar extends SystemUI implements DemoMode, int notificationLoad = mNotificationData.getActiveNotifications().size(); if (pinnedHeadsUp && isPanelFullyCollapsed()) { notificationLoad = 1; - } else { - mMetricsLogger.histogram("note_load", notificationLoad); } mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad); } else { @@ -4512,30 +4533,49 @@ public class StatusBar extends SystemUI implements DemoMode, * Switches theme from light to dark and vice-versa. */ private void updateTheme() { - boolean useDarkTheme; - // Ignore visibility since we calculate the theme based on the real colors, - // not the current state. + + int which; if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { - useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK) - .supportsDarkText(); + which = WallpaperManager.FLAG_LOCK; } else { - useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM) - .supportsDarkText(); + which = WallpaperManager.FLAG_SYSTEM; } - // Enable/Disable dark overlay - if (isUsingDarkTheme() != useDarkTheme) { - if (DEBUG) { - Log.d(TAG, "Switching theme to: " + (useDarkTheme ? "Dark" : "Light")); + // Gradient defines if text color should be light or dark. + final boolean useDarkText = mColorExtractor.getColors(which, true /* ignoreVisibility */) + .supportsDarkText(); + // And wallpaper defines if QS should be light or dark. + boolean useDarkTheme = false; + final WallpaperManager wallpaperManager = mContext.getSystemService(WallpaperManager.class); + if (wallpaperManager != null) { + WallpaperColors wallpaperColors = wallpaperManager + .getWallpaperColors(WallpaperManager.FLAG_SYSTEM); + if (wallpaperColors != null) { + final int mainColor = wallpaperColors.getPrimaryColor().toArgb(); + final float[] hsl = new float[3]; + ColorUtils.colorToHSL(mainColor, hsl); + useDarkTheme = hsl[2] < 0.2f; } + } + + // Enable/disable dark UI. + if (isUsingDarkTheme() != useDarkTheme) { try { mOverlayManager.setEnabled("com.android.systemui.theme.dark", useDarkTheme, mCurrentUserId); } catch (RemoteException e) { Log.w(TAG, "Can't change theme", e); - return; } - mStatusBarWindowManager.setKeyguardDark(useDarkTheme); + } + // Enable/disable dark text overlay. + if (isUsingDarkText() != useDarkText) { + try { + mOverlayManager.setEnabled("com.android.systemui.theme.lightwallpaper", + useDarkText, mCurrentUserId); + mStatusBarWindowManager.setKeyguardDark(useDarkText); + } catch (RemoteException e) { + Log.w(TAG, "Can't change theme", e); + } } } @@ -5154,11 +5194,14 @@ public class StatusBar extends SystemUI implements DemoMode, public void onCameraLaunchGestureDetected(int source) { mLastCameraLaunchSource = source; if (mStartedGoingToSleep) { + if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Finish going to sleep before launching camera"); mLaunchCameraOnFinishedGoingToSleep = true; return; } if (!mNotificationPanel.canCameraGestureBeLaunched( mStatusBarKeyguardViewManager.isShowing() && mExpandedVisible)) { + if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Can't launch camera right now, mExpandedVisible: " + + mExpandedVisible); return; } if (!mDeviceInteractive) { @@ -5178,12 +5221,14 @@ public class StatusBar extends SystemUI implements DemoMode, mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L); } if (mScreenTurningOn || mStatusBarKeyguardViewManager.isScreenTurnedOn()) { + if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera"); mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source); } else { // We need to defer the camera launch until the screen comes on, since otherwise // we will dismiss us too early since we are waiting on an activity to be drawn and // incorrectly get notified because of the screen on event (which resumes and pauses // some activities) + if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Deferring until screen turns on"); mLaunchCameraOnScreenTurningOn = true; } } @@ -5199,6 +5244,12 @@ public class StatusBar extends SystemUI implements DemoMode, mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD || mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; + // When in wake-and-unlock we may not have received a change to mState + // but we still should not be dozing, manually set to false. + if (mFingerprintUnlockController.getMode() == + FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) { + mDozing = false; + } mStatusBarWindowManager.setDozing(mDozing); mStatusBarKeyguardViewManager.setDozing(mDozing); updateDozingState(); @@ -5325,6 +5376,37 @@ public class StatusBar extends SystemUI implements DemoMode, mAnimateWakeup = animateWakeup; } + @Override + public void onDoubleTap(float screenX, float screenY) { + if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null + && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) { + mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2); + float viewX = screenX - mTmpInt2[0]; + float viewY = screenY - mTmpInt2[1]; + if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth() + && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) { + dispatchDoubleTap(viewX, viewY); + } + } + } + + public void dispatchDoubleTap(float viewX, float viewY) { + dispatchTap(mAmbientIndicationContainer, viewX, viewY); + dispatchTap(mAmbientIndicationContainer, viewX, viewY); + } + + private void dispatchTap(View view, float x, float y) { + long now = SystemClock.elapsedRealtime(); + dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_DOWN); + dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_UP); + } + + private void dispatchTouchEvent(View view, float x, float y, long now, int action) { + MotionEvent ev = MotionEvent.obtain(now, now, action, x, y, 0 /* meta */); + view.dispatchTouchEvent(ev); + ev.recycle(); + } + private boolean shouldAnimateWakeup() { return mAnimateWakeup; } @@ -5993,11 +6075,12 @@ public class StatusBar extends SystemUI implements DemoMode, } public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) { - if (snoozeOption.criterion != null) { - mNotificationListener.snoozeNotification(sbn.getKey(), snoozeOption.criterion.getId()); + if (snoozeOption.getSnoozeCriterion() != null) { + mNotificationListener.snoozeNotification(sbn.getKey(), + snoozeOption.getSnoozeCriterion().getId()); } else { mNotificationListener.snoozeNotification(sbn.getKey(), - snoozeOption.snoozeForMinutes * 60 * 1000); + snoozeOption.getMinutesToSnoozeFor() * 60 * 1000); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java index df30e20ca582..9daa199ee92a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java @@ -37,6 +37,9 @@ public interface BluetoothController extends CallbackController<Callback>, Dumpa void disconnect(CachedBluetoothDevice device); boolean canConfigBluetooth(); + int getMaxConnectionState(CachedBluetoothDevice device); + int getBondState(CachedBluetoothDevice device); + public interface Callback { void onBluetoothStateChange(boolean enabled); void onBluetoothDevicesChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 36d24b370b00..dc4b1158c1cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.policy; import android.app.ActivityManager; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.os.Handler; import android.os.Looper; @@ -33,8 +35,10 @@ import com.android.systemui.Dependency; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; +import java.util.WeakHashMap; public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback, CachedBluetoothDevice.Callback { @@ -44,18 +48,22 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private final LocalBluetoothManager mLocalBluetoothManager; private final UserManager mUserManager; private final int mCurrentUser; + private final WeakHashMap<CachedBluetoothDevice, ActuallyCachedState> mCachedState = + new WeakHashMap<>(); + private final Handler mBgHandler; private boolean mEnabled; private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED; private CachedBluetoothDevice mLastDevice; - private final H mHandler = new H(); + private final H mHandler = new H(Looper.getMainLooper()); private int mState; public BluetoothControllerImpl(Context context, Looper bgLooper) { mLocalBluetoothManager = Dependency.get(LocalBluetoothManager.class); + mBgHandler = new Handler(bgLooper); if (mLocalBluetoothManager != null) { - mLocalBluetoothManager.getEventManager().setReceiverHandler(new Handler(bgLooper)); + mLocalBluetoothManager.getEventManager().setReceiverHandler(mBgHandler); mLocalBluetoothManager.getEventManager().registerCallback(this); onBluetoothStateChanged( mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState()); @@ -106,6 +114,16 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } @Override + public int getBondState(CachedBluetoothDevice device) { + return getCachedState(device).mBondState; + } + + @Override + public int getMaxConnectionState(CachedBluetoothDevice device) { + return getCachedState(device).mMaxConnectionState; + } + + @Override public void addCallback(Callback cb) { mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); @@ -225,12 +243,14 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @Override public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { + mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); } @Override public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); } @@ -243,11 +263,44 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @Override public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { + mCachedState.remove(cachedDevice); mLastDevice = cachedDevice; updateConnected(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } + private ActuallyCachedState getCachedState(CachedBluetoothDevice device) { + ActuallyCachedState state = mCachedState.get(device); + if (state == null) { + state = new ActuallyCachedState(device, mHandler); + mBgHandler.post(state); + mCachedState.put(device, state); + return state; + } + return state; + } + + private static class ActuallyCachedState implements Runnable { + + private final WeakReference<CachedBluetoothDevice> mDevice; + private final Handler mUiHandler; + private int mBondState = BluetoothDevice.BOND_NONE; + private int mMaxConnectionState = BluetoothProfile.STATE_DISCONNECTED; + + private ActuallyCachedState(CachedBluetoothDevice device, Handler uiHandler) { + mDevice = new WeakReference<>(device); + mUiHandler = uiHandler; + } + + @Override + public void run() { + mBondState = mDevice.get().getBondState(); + mMaxConnectionState = mDevice.get().getMaxConnectionState(); + mUiHandler.removeMessages(H.MSG_PAIRED_DEVICES_CHANGED); + mUiHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); + } + } + private final class H extends Handler { private final ArrayList<BluetoothController.Callback> mCallbacks = new ArrayList<>(); @@ -256,6 +309,10 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private static final int MSG_ADD_CALLBACK = 3; private static final int MSG_REMOVE_CALLBACK = 4; + public H(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java index 4c879c68f2ff..13ee23fb7af9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java @@ -127,6 +127,7 @@ public class DeadZone extends View { final int action = event.getAction(); if (action == MotionEvent.ACTION_OUTSIDE) { poke(event); + return true; } else if (action == MotionEvent.ACTION_DOWN) { if (DEBUG) { Slog.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); @@ -158,7 +159,7 @@ public class DeadZone extends View { return false; } - public void poke(MotionEvent event) { + private void poke(MotionEvent event) { mLastPokeTime = event.getEventTime(); if (DEBUG) Slog.v(TAG, "poked! size=" + getSize(mLastPokeTime)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 3f5f5a0ed5ae..874f0d9d5b5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -31,8 +31,10 @@ import android.os.Message; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.support.annotation.VisibleForTesting; import com.android.systemui.R; +import com.android.systemui.util.Utils; import java.util.ArrayList; import java.util.List; @@ -141,7 +143,8 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio /** * Returns true if there currently exist active high power location requests. */ - private boolean areActiveHighPowerLocationRequests() { + @VisibleForTesting + protected boolean areActiveHighPowerLocationRequests() { List<AppOpsManager.PackageOps> packages = mAppOpsManager.getPackagesForOps(mHighPowerRequestAppOpArray); // AppOpsManager can return null when there is no requested data. @@ -205,16 +208,14 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio } private void locationActiveChanged() { - for (LocationChangeCallback cb : mSettingsChangeCallbacks) { - cb.onLocationActiveChanged(mAreActiveLocationRequests); - } + Utils.safeForeach(mSettingsChangeCallbacks, + cb -> cb.onLocationActiveChanged(mAreActiveLocationRequests)); } private void locationSettingsChanged() { boolean isEnabled = isLocationEnabled(); - for (LocationChangeCallback cb : mSettingsChangeCallbacks) { - cb.onLocationSettingsChanged(isEnabled); - } + Utils.safeForeach(mSettingsChangeCallbacks, + cb -> cb.onLocationSettingsChanged(isEnabled)); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index 67b5596e34c9..bd7fee0f7f52 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -235,6 +235,9 @@ public class MobileSignalController extends SignalController< } private int getNumLevels() { + if (mConfig.inflateSignalStrengths) { + return SignalStrength.NUM_SIGNAL_STRENGTH_BINS + 1; + } return SignalStrength.NUM_SIGNAL_STRENGTH_BINS; } @@ -243,7 +246,11 @@ public class MobileSignalController extends SignalController< if (mCurrentState.iconGroup == TelephonyIcons.CARRIER_NETWORK_CHANGE) { return SignalDrawable.getCarrierChangeState(getNumLevels()); } else if (mCurrentState.connected) { - return SignalDrawable.getState(mCurrentState.level, getNumLevels(), + int level = mCurrentState.level; + if (mConfig.inflateSignalStrengths) { + level++; + } + return SignalDrawable.getState(level, getNumLevels(), mCurrentState.inetCondition == 0); } else if (mCurrentState.enabled) { return SignalDrawable.getEmptyState(getNumLevels()); @@ -254,6 +261,10 @@ public class MobileSignalController extends SignalController< @Override public int getQsCurrentIconId() { + if (mCurrentState.airplaneMode) { + return SignalDrawable.getAirplaneModeState(getNumLevels()); + } + return getCurrentIconId(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index c02ce0ea6335..2771011c6997 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -67,29 +67,15 @@ public interface NetworkController extends CallbackController<SignalCallback>, D public static class IconState { public final boolean visible; - public final int icon; - - /** - * Optional iconOverlay resource id. - * - * <p>Set to -1 if not present. - */ - public final int iconOverlay; - public final String contentDescription; - public IconState(boolean visible, int icon, int iconOverlay, String contentDescription) { + public IconState(boolean visible, int icon, String contentDescription) { this.visible = visible; this.icon = icon; - this.iconOverlay = iconOverlay; this.contentDescription = contentDescription; } - public IconState(boolean visible, int icon, String contentDescription) { - this(visible, icon, -1 /* iconOverlay */, contentDescription); - } - public IconState(boolean visible, int icon, int contentDescription, Context context) { this(visible, icon, context.getString(contentDescription)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index c21f444d0bf3..c217bda935c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -24,7 +24,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; -import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Bundle; @@ -92,7 +91,6 @@ public class NetworkControllerImpl extends BroadcastReceiver private final DataSaverController mDataSaverController; private final CurrentUserTracker mUserTracker; private Config mConfig; - private final NetworkScoreManager mNetworkScoreManager; // Subcontrollers. @VisibleForTesting @@ -149,12 +147,9 @@ public class NetworkControllerImpl extends BroadcastReceiver public NetworkControllerImpl(Context context, Looper bgLooper, DeviceProvisionedController deviceProvisionedController) { this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE), - context.getSystemService(NetworkScoreManager.class), (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE), (WifiManager) context.getSystemService(Context.WIFI_SERVICE), - SubscriptionManager.from(context), - Config.readConfig(context), - bgLooper, + SubscriptionManager.from(context), Config.readConfig(context), bgLooper, new CallbackHandler(), new AccessPointControllerImpl(context, bgLooper), new DataUsageController(context), @@ -165,12 +160,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @VisibleForTesting NetworkControllerImpl(Context context, ConnectivityManager connectivityManager, - NetworkScoreManager networkScoreManager, - TelephonyManager telephonyManager, - WifiManager wifiManager, - SubscriptionManager subManager, - Config config, - Looper bgLooper, + TelephonyManager telephonyManager, WifiManager wifiManager, + SubscriptionManager subManager, Config config, Looper bgLooper, CallbackHandler callbackHandler, AccessPointControllerImpl accessPointController, DataUsageController dataUsageController, @@ -193,7 +184,6 @@ public class NetworkControllerImpl extends BroadcastReceiver // wifi mWifiManager = wifiManager; - mNetworkScoreManager = networkScoreManager; mLocale = mContext.getResources().getConfiguration().locale; mAccessPoints = accessPointController; @@ -207,7 +197,7 @@ public class NetworkControllerImpl extends BroadcastReceiver } }); mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature, - mCallbackHandler, this, mNetworkScoreManager); + mCallbackHandler, this); mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this); @@ -968,6 +958,7 @@ public class NetworkControllerImpl extends BroadcastReceiver boolean show4gForLte = false; boolean hideLtePlus = false; boolean hspaDataDistinguishable; + boolean inflateSignalStrengths = false; static Config readConfig(Context context) { Config config = new Config(); @@ -980,6 +971,7 @@ public class NetworkControllerImpl extends BroadcastReceiver config.hspaDataDistinguishable = res.getBoolean(R.bool.config_hspa_data_distinguishable); config.hideLtePlus = res.getBoolean(R.bool.config_hideLtePlus); + config.inflateSignalStrengths = res.getBoolean(R.bool.config_inflateSignalStrength); return config; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index 2104cb1421aa..28196246f44c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -17,50 +17,33 @@ package com.android.systemui.statusbar.policy; import android.content.Context; import android.content.Intent; -import android.database.ContentObserver; -import android.net.NetworkBadging; import android.net.NetworkCapabilities; -import android.net.NetworkKey; -import android.net.NetworkScoreManager; -import android.net.ScoredNetwork; import android.net.wifi.WifiManager; -import android.net.wifi.WifiNetworkScoreCache; -import android.net.wifi.WifiNetworkScoreCache.CacheListener; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Messenger; -import android.provider.Settings; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.AsyncChannel; -import com.android.settingslib.Utils; import com.android.settingslib.wifi.WifiStatusTracker; +import com.android.systemui.R; import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; -import com.android.systemui.R; - import java.util.Objects; -import java.util.List; public class WifiSignalController extends SignalController<WifiSignalController.WifiState, SignalController.IconGroup> { - private final WifiManager mWifiManager; private final AsyncChannel mWifiChannel; private final boolean mHasMobileData; - private final NetworkScoreManager mNetworkScoreManager; - private final WifiNetworkScoreCache mScoreCache; private final WifiStatusTracker mWifiTracker; - private boolean mScoringUiEnabled = false; - public WifiSignalController(Context context, boolean hasMobileData, - CallbackHandler callbackHandler, NetworkControllerImpl networkController, - NetworkScoreManager networkScoreManager) { + CallbackHandler callbackHandler, NetworkControllerImpl networkController) { super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI, callbackHandler, networkController); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); @@ -85,44 +68,6 @@ public class WifiSignalController extends AccessibilityContentDescriptions.WIFI_NO_CONNECTION ); - mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(handler) { - @Override - public void networkCacheUpdated(List<ScoredNetwork> networks) { - mCurrentState.badgeEnum = getWifiBadgeEnum(); - notifyListenersIfNecessary(); - } - }); - - // Setup scoring - mNetworkScoreManager = networkScoreManager; - configureScoringGating(); - registerScoreCache(); - } - - private void configureScoringGating() { - ContentObserver observer = new ContentObserver(new Handler(Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange) { - mScoringUiEnabled = - Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1; - } - }; - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.NETWORK_SCORING_UI_ENABLED), - false /* notifyForDescendants */, - observer); - - observer.onChange(false /* selfChange */); // Set the initial values - } - - private void registerScoreCache() { - Log.d(mTag, "Registered score cache"); - mNetworkScoreManager.registerNetworkScoreCache( - NetworkKey.TYPE_WIFI, - mScoreCache, - NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK); } @Override @@ -143,77 +88,27 @@ public class WifiSignalController extends ("," + mContext.getString(R.string.accessibility_quick_settings_no_internet)); } - IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), - Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription); - IconState qsIcon = new IconState( - mCurrentState.connected, getQsCurrentIconId(), - Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription); + IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription); + IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(), + contentDescription); callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon, ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut, wifiDesc, mCurrentState.isTransient); } - @Override - public int getCurrentIconId() { - if (mCurrentState.badgeEnum != NetworkBadging.BADGING_NONE) { - return Utils.WIFI_PIE_FOR_BADGING[mCurrentState.level]; - } - return super.getCurrentIconId(); - } - /** * Extract wifi state directly from broadcasts about changes in wifi state. */ public void handleBroadcast(Intent intent) { - // Update the WifiStatusTracker with the new information and update the score cache. - NetworkKey previousNetworkKey = mWifiTracker.networkKey; mWifiTracker.handleBroadcast(intent); - updateScoreCacheIfNecessary(previousNetworkKey); - - mCurrentState.isTransient = mWifiTracker.state == WifiManager.WIFI_STATE_ENABLING - || mWifiTracker.state == WifiManager.WIFI_AP_STATE_DISABLING - || mWifiTracker.connecting; mCurrentState.enabled = mWifiTracker.enabled; mCurrentState.connected = mWifiTracker.connected; mCurrentState.ssid = mWifiTracker.ssid; mCurrentState.rssi = mWifiTracker.rssi; mCurrentState.level = mWifiTracker.level; - mCurrentState.badgeEnum = getWifiBadgeEnum(); notifyListenersIfNecessary(); } - /** - * Clears old scores out of the cache and requests new scores if the network key has changed. - * - * <p>New scores are requested asynchronously. - */ - private void updateScoreCacheIfNecessary(NetworkKey previousNetworkKey) { - if (mWifiTracker.networkKey == null) { - return; - } - if ((previousNetworkKey == null) || !mWifiTracker.networkKey.equals(previousNetworkKey)) { - mScoreCache.clearScores(); - mNetworkScoreManager.requestScores(new NetworkKey[]{mWifiTracker.networkKey}); - } - } - - /** - * Returns the wifi badge enum for the current {@link #mWifiTracker} state. - * - * <p>{@link #updateScoreCacheIfNecessary} should be called prior to this method. - */ - private int getWifiBadgeEnum() { - if (!mScoringUiEnabled || mWifiTracker.networkKey == null) { - return NetworkBadging.BADGING_NONE; - } - ScoredNetwork score = mScoreCache.getScoredNetwork(mWifiTracker.networkKey); - - if (score != null) { - return score.calculateBadge(mWifiTracker.rssi); - } - return NetworkBadging.BADGING_NONE; - } - @VisibleForTesting void setActivity(int wifiActivity) { mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT @@ -254,7 +149,6 @@ public class WifiSignalController extends static class WifiState extends SignalController.State { String ssid; - int badgeEnum; boolean isTransient; @Override @@ -262,7 +156,6 @@ public class WifiSignalController extends super.copyFrom(s); WifiState state = (WifiState) s; ssid = state.ssid; - badgeEnum = state.badgeEnum; isTransient = state.isTransient; } @@ -270,7 +163,6 @@ public class WifiSignalController extends protected void toString(StringBuilder builder) { super.toString(builder); builder.append(',').append("ssid=").append(ssid); - builder.append(',').append("badgeEnum=").append(badgeEnum); builder.append(',').append("isTransient=").append(isTransient); } @@ -278,7 +170,6 @@ public class WifiSignalController extends public boolean equals(Object o) { return super.equals(o) && Objects.equals(((WifiState) o).ssid, ssid) - && (((WifiState) o).badgeEnum == badgeEnum) && (((WifiState) o).isTransient == isTransient); } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 1d3b533b6d8f..e11b23ea0efd 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -18,7 +18,6 @@ package com.android.systemui.volume; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import android.accessibilityservice.AccessibilityServiceInfo; import android.animation.ObjectAnimator; @@ -62,7 +61,6 @@ import android.view.View.OnAttachStateChangeListener; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.view.Window; import android.view.WindowManager; @@ -77,7 +75,6 @@ import android.widget.TextView; import com.android.settingslib.Utils; import com.android.systemui.Dependency; -import com.android.systemui.HardwareUiLayout; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.plugins.VolumeDialogController; @@ -103,8 +100,7 @@ import java.util.List; * * Methods ending in "H" must be called on the (ui) handler. */ -public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, - ColorExtractor.OnColorsChangedListener { +public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable { private static final String TAG = Util.logTag(VolumeDialogImpl.class); public static final String SHOW_FULL_ZEN = "sysui_show_full_zen"; @@ -114,8 +110,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, private final Context mContext; private final H mHandler = new H(); - private final GradientDrawable mGradientDrawable; - private final ColorExtractor mColorExtractor; private VolumeDialogController mController; private Window mWindow; @@ -170,9 +164,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext)); mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive); - mGradientDrawable = new GradientDrawable(mContext); - mGradientDrawable.setAlpha((int) (ScrimController.GRADIENT_SCRIM_ALPHA * 255)); - mColorExtractor = Dependency.get(ColorExtractor.class); } public void init(int windowType, Callback callback) { @@ -194,7 +185,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, @Override public void destroy() { mController.removeCallback(mControllerCallbackH); - mColorExtractor.removeOnColorsChangedListener(this); } private void initDialog() { @@ -205,7 +195,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, mShowing = false; mWindow = mDialog.getWindow(); mWindow.requestFeature(Window.FEATURE_NO_TITLE); - mWindow.setBackgroundDrawable(mGradientDrawable); + mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN @@ -213,49 +203,55 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); - Point displaySize = new Point(); - mContext.getDisplay().getRealSize(displaySize); mDialog.setCanceledOnTouchOutside(true); final Resources res = mContext.getResources(); + final WindowManager.LayoutParams lp = mWindow.getAttributes(); + lp.type = mWindowType; + lp.format = PixelFormat.TRANSLUCENT; + lp.setTitle(VolumeDialogImpl.class.getSimpleName()); + lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top); + lp.gravity = Gravity.TOP; + lp.windowAnimations = -1; + mWindow.setAttributes(lp); mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); - mDialog.setContentView(R.layout.volume_dialog_wrapped); - mDialogView = mDialog.findViewById(R.id.volume_dialog); - mDialogView.setOnHoverListener((v, event) -> { - int action = event.getActionMasked(); - mHovering = (action == MotionEvent.ACTION_HOVER_ENTER) - || (action == MotionEvent.ACTION_HOVER_MOVE); - rescheduleTimeoutH(); - return true; + mDialog.setContentView(R.layout.volume_dialog); + mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog); + mDialogView.setOnHoverListener(new View.OnHoverListener() { + @Override + public boolean onHover(View v, MotionEvent event) { + int action = event.getActionMasked(); + mHovering = (action == MotionEvent.ACTION_HOVER_ENTER) + || (action == MotionEvent.ACTION_HOVER_MOVE); + rescheduleTimeoutH(); + return true; + } }); - - mColorExtractor.addOnColorsChangedListener(this); - mGradientDrawable.setScreenSize(displaySize.x, displaySize.y); - ColorExtractor.GradientColors colors = mColorExtractor.getColors( - mKeyguard.isKeyguardLocked() ? WallpaperManager.FLAG_LOCK - : WallpaperManager.FLAG_SYSTEM); - mGradientDrawable.setColors(colors, false); - - mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content); - mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows); + mDialogContentView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog_content); + mDialogRowsView = (ViewGroup) mDialogContentView.findViewById(R.id.volume_dialog_rows); mExpanded = false; - mExpandButton = mDialogView.findViewById(R.id.volume_expand_button); + mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button); mExpandButton.setOnClickListener(mClickExpand); mExpandButton.setVisibility( AudioSystem.isSingleVolume(mContext) ? View.GONE : View.VISIBLE); + updateWindowWidthH(); updateExpandButtonH(); - mMotion = new VolumeDialogMotion(mDialog, (View) mDialogView.getParent(), - mDialogContentView, mExpandButton, mGradientDrawable, animating -> { - if (animating) return; - if (mPendingStateChanged) { - mHandler.sendEmptyMessage(H.STATE_CHANGED); - mPendingStateChanged = false; - } - if (mPendingRecheckAll) { - mHandler.sendEmptyMessage(H.RECHECK_ALL); - mPendingRecheckAll = false; + mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton, + new VolumeDialogMotion.Callback() { + @Override + public void onAnimatingChanged(boolean animating) { + if (animating) return; + if (mPendingStateChanged) { + mHandler.sendEmptyMessage(H.STATE_CHANGED); + mPendingStateChanged = false; + } + if (mPendingRecheckAll) { + mHandler.sendEmptyMessage(H.RECHECK_ALL); + mPendingRecheckAll = false; + } } }); @@ -280,20 +276,11 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, addExistingRows(); } mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration); - mZenFooter = mDialog.findViewById(R.id.volume_zen_footer); + mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer); mZenFooter.init(mZenModeController); - mZenPanel = mDialog.findViewById(R.id.tuner_zen_mode_panel); + mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel); mZenPanel.init(mZenModeController); mZenPanel.setCallback(mZenPanelCallback); - - final WindowManager.LayoutParams lp = mWindow.getAttributes(); - lp.width = MATCH_PARENT; - lp.height = MATCH_PARENT; - lp.type = mWindowType; - lp.format = PixelFormat.TRANSLUCENT; - lp.setTitle(VolumeDialogImpl.class.getSimpleName()); - lp.windowAnimations = -1; - mWindow.setAttributes(lp); } @Override @@ -307,6 +294,20 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, return ColorStateList.valueOf(mContext.getColor(colorResId)); } + private void updateWindowWidthH() { + final ViewGroup.LayoutParams lp = mDialogView.getLayoutParams(); + final DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); + if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels); + int w = dm.widthPixels; + final int max = mContext.getResources() + .getDimensionPixelSize(R.dimen.volume_dialog_panel_width); + if (w > max) { + w = max; + } + lp.width = w; + mDialogView.setLayoutParams(lp); + } + public void setStreamImportant(int stream, boolean important) { mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget(); } @@ -547,8 +548,10 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, } private void updateDialogBottomMarginH() { + final long diff = System.currentTimeMillis() - mCollapseTime; + final boolean collapsing = mCollapseTime != 0 && diff < getConservativeCollapseDuration(); final ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams(); - final int bottomMargin = + final int bottomMargin = collapsing ? mDialogContentView.getHeight() : mContext.getResources().getDimensionPixelSize(R.dimen.volume_dialog_margin_bottom); if (bottomMargin != mlp.bottomMargin) { if (D.BUG) Log.d(TAG, "bottomMargin " + mlp.bottomMargin + " -> " + bottomMargin); @@ -578,7 +581,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, TransitionManager.endTransitions(mDialogView); final VolumeRow activeRow = getActiveRow(); if (!dismissing) { - mWindow.setLayout(mWindow.getAttributes().width, MATCH_PARENT); + mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT); TransitionManager.beginDelayedTransition(mDialogView, getTransistion()); } updateRowsH(activeRow); @@ -640,7 +643,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, final boolean isActive = row == activeRow; final boolean shouldBeVisible = shouldBeVisibleH(row, isActive); Util.setVisOrGone(row.view, shouldBeVisible); - Util.setVisOrGone(row.header, shouldBeVisible && mExpanded); + Util.setVisOrGone(row.header, shouldBeVisible); if (row.view.isShown()) { updateVolumeRowSliderTintH(row, isActive); } @@ -697,18 +700,12 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF && (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded) && !mZenPanel.isEditing(); - - if (wasVisible != visible) { - mZenFooter.update(); - if (visible) { - HardwareUiLayout.get(mZenFooter).setDivisionView(mZenFooter); - } else { - mHandler.postDelayed(() -> - HardwareUiLayout.get(mZenFooter).setDivisionView(mZenFooter), - mExpandButtonAnimationDuration); - } - Util.setVisOrGone(mZenFooter, visible); + TransitionManager.beginDelayedTransition(mDialogView, getTransistion()); + if (wasVisible != visible && !visible) { + prepareForCollapse(); } + Util.setVisOrGone(mZenFooter, visible); + mZenFooter.update(); final boolean fullWasVisible = mZenPanel.getVisibility() == View.VISIBLE; final boolean fullVisible = mShowFullZen && !visible; @@ -968,7 +965,8 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, @Override public void onTransitionEnd(Transition transition) { - mWindow.setLayout(MATCH_PARENT, MATCH_PARENT); + mWindow.setLayout( + mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT); } @Override @@ -977,7 +975,8 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, @Override public void onTransitionPause(Transition transition) { - mWindow.setLayout(MATCH_PARENT, MATCH_PARENT); + mWindow.setLayout( + mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT); } @Override @@ -1029,6 +1028,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, initDialog(); mDensity = density; } + updateWindowWidthH(); mConfigurableTexts.update(); mZenFooter.onConfigurationChanged(); } @@ -1084,26 +1084,10 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, if (mExpandButtonAnimationRunning) return; final boolean newExpand = !mExpanded; Events.writeEvent(mContext, Events.EVENT_EXPAND, newExpand); - if (!newExpand) { - HardwareUiLayout.get(mDialogContentView).setCollapse(); - } updateExpandedH(newExpand, false /* dismissing */); } }; - @Override - public void onColorsChanged(ColorExtractor extractor, int which) { - if (mKeyguard.isKeyguardLocked()) { - if ((WallpaperManager.FLAG_LOCK & which) != 0) { - mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_LOCK)); - } - } else { - if ((WallpaperManager.FLAG_SYSTEM & which) != 0) { - mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM)); - } - } - } - private final class H extends Handler { private static final int SHOW = 1; private static final int DISMISS = 2; @@ -1175,8 +1159,8 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable, event.setPackageName(mContext.getPackageName()); ViewGroup.LayoutParams params = getWindow().getAttributes(); - boolean isFullScreen = (params.width == MATCH_PARENT) && - (params.height == MATCH_PARENT); + boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) && + (params.height == ViewGroup.LayoutParams.MATCH_PARENT); event.setFullScreen(isFullScreen); if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java index 2df222764dcc..01d31e2a9852 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.systemui.volume; import android.animation.Animator; @@ -41,10 +42,8 @@ public class VolumeDialogMotion { private final View mDialogView; private final ViewGroup mContents; // volume rows + zen footer private final View mChevron; - private final Drawable mBackground; private final Handler mHandler = new Handler(); private final Callback mCallback; - private final int mBackgroundTargetAlpha; private boolean mAnimating; // show or dismiss animation is running private boolean mShowing; // show animation is running @@ -53,14 +52,12 @@ public class VolumeDialogMotion { private ValueAnimator mContentsPositionAnimator; public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron, - Drawable background, Callback callback) { + Callback callback) { mDialog = dialog; mDialogView = dialogView; mContents = contents; mChevron = chevron; mCallback = callback; - mBackground = background; - mBackgroundTargetAlpha = mBackground.getAlpha(); mDialog.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { @@ -71,9 +68,8 @@ public class VolumeDialogMotion { @Override public void onShow(DialogInterface dialog) { if (D.BUG) Log.d(TAG, "mDialog.onShow"); - final int w = mDialogView.getWidth() / 4; - mDialogView.setTranslationX(w); - mBackground.setAlpha(0); + final int h = mDialogView.getHeight(); + mDialogView.setTranslationY(-h); startShowAnimation(); } }); @@ -122,7 +118,7 @@ public class VolumeDialogMotion { } private int chevronDistance() { - return 0; + return mChevron.getHeight() / 6; } private int chevronPosY() { @@ -133,29 +129,26 @@ public class VolumeDialogMotion { private void startShowAnimation() { if (D.BUG) Log.d(TAG, "startShowAnimation"); mDialogView.animate() - .translationX(0) .translationY(0) - .alpha(1) .setDuration(scaledDuration(300)) .setInterpolator(new LogDecelerateInterpolator()) .setListener(null) .setUpdateListener(animation -> { - mBackground.setAlpha( - (int) (animation.getAnimatedFraction() * mBackgroundTargetAlpha)); if (mChevronPositionAnimator != null) { final float v = (Float) mChevronPositionAnimator.getAnimatedValue(); if (mChevronPositionAnimator == null) return; // reposition chevron final int posY = chevronPosY(); + mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY()); } }) .withEndAction(new Runnable() { @Override public void run() { - mBackground.setAlpha(mBackgroundTargetAlpha); if (mChevronPositionAnimator == null) return; // reposition chevron final int posY = chevronPosY(); + mChevron.setTranslationY(posY + -mDialogView.getTranslationY()); } }) .start(); @@ -171,13 +164,19 @@ public class VolumeDialogMotion { if (D.BUG) Log.d(TAG, "show.onAnimationEnd"); setShowing(false); } - @Override public void onAnimationCancel(Animator animation) { if (D.BUG) Log.d(TAG, "show.onAnimationCancel"); mCancelled = true; } }); + mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float v = (Float) animation.getAnimatedValue(); + mContents.setTranslationY(v + -mDialogView.getTranslationY()); + } + }); mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator()); mContentsPositionAnimator.start(); @@ -219,30 +218,34 @@ public class VolumeDialogMotion { setShowing(false); } mDialogView.animate() - .translationX(mDialogView.getWidth() / 4) - .alpha(0) + .translationY(-mDialogView.getHeight()) .setDuration(scaledDuration(250)) .setInterpolator(new LogAccelerateInterpolator()) - .setUpdateListener(animation -> { - final float v = 1 - mChevronPositionAnimator.getAnimatedFraction(); - mBackground.setAlpha((int) (v * mBackgroundTargetAlpha)); + .setUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mContents.setTranslationY(-mDialogView.getTranslationY()); + final int posY = chevronPosY(); + mChevron.setTranslationY(posY + -mDialogView.getTranslationY()); + } }) .setListener(new AnimatorListenerAdapter() { private boolean mCancelled; - @Override public void onAnimationEnd(Animator animation) { if (mCancelled) return; if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd"); - mHandler.postDelayed(() -> { - if (D.BUG) Log.d(TAG, "mDialog.dismiss()"); - mDialog.dismiss(); - onComplete.run(); - setDismissing(false); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (D.BUG) Log.d(TAG, "mDialog.dismiss()"); + mDialog.dismiss(); + onComplete.run(); + setDismissing(false); + } }, PRE_DISMISS_DELAY); } - @Override public void onAnimationCancel(Animator animation) { if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel"); diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index 612a54a8d70d..b12fd1c9ba89 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -37,6 +37,7 @@ <uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" /> <uses-permission android:name="android.permission.CONTROL_VPN" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.GET_APP_OPS_STATS" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java new file mode 100644 index 000000000000..1ed5f565117f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.colorextraction; + +import static org.junit.Assert.assertEquals; + +import android.app.WallpaperColors; +import android.app.WallpaperManager; +import android.graphics.Color; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Pair; + +import com.android.systemui.SysuiTestCase; + +import com.google.android.colorextraction.ColorExtractor; +import com.google.android.colorextraction.types.Tonal; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests color extraction generation. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SysuiColorExtractorTests extends SysuiTestCase { + + private static int[] sWhich = new int[]{ + WallpaperManager.FLAG_SYSTEM, + WallpaperManager.FLAG_LOCK}; + private static int[] sTypes = new int[]{ + ColorExtractor.TYPE_NORMAL, + ColorExtractor.TYPE_DARK, + ColorExtractor.TYPE_EXTRA_DARK}; + + @Test + public void getColors_usesGreyIfWallpaperNotVisible() { + ColorExtractor.GradientColors fallbackColors = new ColorExtractor.GradientColors(); + fallbackColors.setMainColor(ColorExtractor.FALLBACK_COLOR); + fallbackColors.setSecondaryColor(ColorExtractor.FALLBACK_COLOR); + + SysuiColorExtractor extractor = new SysuiColorExtractor(getContext(), new Tonal(), false); + simulateEvent(extractor); + extractor.setWallpaperVisible(false); + + for (int which : sWhich) { + for (int type : sTypes) { + assertEquals("Not using fallback!", extractor.getColors(which, type), + fallbackColors); + } + } + } + + @Test + public void getColors_doesntUseFallbackIfVisible() { + ColorExtractor.GradientColors colors = new ColorExtractor.GradientColors(); + colors.setMainColor(Color.RED); + colors.setSecondaryColor(Color.RED); + + SysuiColorExtractor extractor = new SysuiColorExtractor(getContext(), + (inWallpaperColors, outGradientColorsNormal, outGradientColorsDark, + outGradientColorsExtraDark) -> { + outGradientColorsNormal.set(colors); + outGradientColorsDark.set(colors); + outGradientColorsExtraDark.set(colors); + return true; + }, false); + simulateEvent(extractor); + extractor.setWallpaperVisible(true); + + for (int which : sWhich) { + for (int type : sTypes) { + assertEquals("Not using extracted colors!", + extractor.getColors(which, type), colors); + } + } + } + + private void simulateEvent(SysuiColorExtractor extractor) { + // Let's fake a color event + extractor.onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), null, null, 0), + WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java index 56c07f905446..23451106a20c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java @@ -28,6 +28,8 @@ class DozeHostFake implements DozeHost { boolean pulseExtended; boolean animateWakeup; boolean dozing; + float doubleTapX; + float doubleTapY; @Override public void addCallback(@NonNull Callback callback) { @@ -88,4 +90,10 @@ class DozeHostFake implements DozeHost { public void setAnimateWakeup(boolean animateWakeup) { this.animateWakeup = animateWakeup; } + + @Override + public void onDoubleTap(float x, float y) { + doubleTapX = y; + doubleTapY = y; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java index 59eca504d7a4..6a85511daca2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java @@ -23,6 +23,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import org.junit.After; +import org.junit.Ignore; import android.support.test.filters.SmallTest; import android.support.test.filters.FlakyTest; import android.testing.AndroidTestingRunner; @@ -38,8 +40,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.DetailAdapter; -import org.junit.After; -import org.junit.Ignore; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -67,7 +67,7 @@ public class QSDetailTest extends SysuiTestCase { mQsDetail = (QSDetail) LayoutInflater.from(mContext).inflate(R.layout.qs_detail, null); mQsPanel = mock(QSPanel.class); mQuickHeader = mock(QuickStatusBarHeader.class); - mQsDetail.setQsPanel(mQsPanel, mQuickHeader); + mQsDetail.setQsPanel(mQsPanel, mQuickHeader, mock(View.class)); mMockDetailAdapter = mock(DetailAdapter.class); when(mMockDetailAdapter.createDetailView(any(), any(), any())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/SlashImageViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/SlashImageViewTest.java new file mode 100644 index 000000000000..aef584f8d986 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/SlashImageViewTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.qs.QSTile.SlashState; +import com.android.systemui.qs.tileimpl.SlashImageView; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class SlashImageViewTest extends SysuiTestCase { + private TestableSlashImageView mSlashView; + + @Test + public void testSetSlashStateCreatesSlashDrawable() { + SlashState mockState = mock(SlashState.class); + Drawable mockDrawable = mock(Drawable.class); + mSlashView = new TestableSlashImageView(mContext); + assertTrue(mSlashView.getSlashDrawable() == null); + + mSlashView.setImageDrawable(mockDrawable); + mSlashView.setState(mockState); + + assertTrue(mSlashView.getSlashDrawable() != null); + } + + @Test + public void testSetNullDrawableRemovesSlashDrawable() { + SlashState mockState = mock(SlashState.class); + Drawable mockDrawable = mock(Drawable.class); + + mSlashView = new TestableSlashImageView(mContext); + mSlashView.setImageDrawable(mockDrawable); + mSlashView.setState(mockState); + mSlashView.setImageDrawable(null); + + assertTrue(mSlashView.getSlashDrawable() == null); + } + + // Expose getSlashDrawable + private static class TestableSlashImageView extends SlashImageView { + TestableSlashImageView(Context c) { + super(c); + } + + private SlashDrawable getSlashDrawable() { + return mSlash; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java index 68f9cb05ecaf..8e7ffdfd4b47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java @@ -16,8 +16,10 @@ package com.android.systemui.statusbar; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -33,12 +35,14 @@ import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.drawable.Icon; import android.os.UserHandle; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.util.NotificationColorUtil; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; @@ -100,4 +104,17 @@ public class StatusBarIconViewTest extends SysuiTestCase { assertFalse(mIconView.set(mStatusBarIcon)); } + + @Test + public void testGetContrastedStaticDrawableColor() { + mIconView.setStaticDrawableColor(Color.DKGRAY); + int color = mIconView.getContrastedStaticDrawableColor(Color.WHITE); + assertEquals("Color should not change when we have enough contrast", + Color.DKGRAY, color); + + mIconView.setStaticDrawableColor(Color.WHITE); + color = mIconView.getContrastedStaticDrawableColor(Color.WHITE); + assertTrue("Similar colors should be shifted to satisfy contrast", + NotificationColorUtil.satisfiesTextContrast(Color.WHITE, color)); + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java index c7af0d9c8979..1b42d1b5dffc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java @@ -24,12 +24,17 @@ import static org.mockito.Mockito.verify; import android.app.Notification; import android.content.Context; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.Looper; import android.service.notification.StatusBarNotification; import android.support.test.InstrumentationRegistry; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.FlakyTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.view.View; +import android.view.ViewGroup; import android.widget.RemoteViews; import com.android.systemui.R; @@ -45,7 +50,9 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.HashMap; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; @SmallTest @RunWith(AndroidJUnit4.class) @@ -142,6 +149,41 @@ public class NotificationInflaterTest extends SysuiTestCase { Assert.assertNull(mRow.getEntry().getRunningTask()); } + @Test + public void testInflationIsRetriedIfAsyncFails() throws Exception { + NotificationInflater.InflationProgress result = + new NotificationInflater.InflationProgress(); + result.packageContext = mContext; + CountDownLatch countDownLatch = new CountDownLatch(1); + NotificationInflater.applyRemoteView(result, + NotificationInflater.FLAG_REINFLATE_EXPANDED_VIEW, 0, mRow, + false /* redactAmbient */, true /* isNewView */, new RemoteViews.OnClickHandler(), + new NotificationInflater.InflationCallback() { + @Override + public void handleInflationException(StatusBarNotification notification, + Exception e) { + countDownLatch.countDown(); + throw new RuntimeException("No Exception expected"); + } + + @Override + public void onAsyncInflationFinished(NotificationData.Entry entry) { + countDownLatch.countDown(); + } + }, mRow.getEntry(), mRow.getPrivateLayout(), null, null, new HashMap<>(), + new NotificationInflater.ApplyCallback() { + @Override + public void setResultView(View v) { + } + + @Override + public RemoteViews getRemoteView() { + return new AsyncFailRemoteView(mContext.getPackageName(), + R.layout.custom_view_dark); + } + }); + countDownLatch.await(); + } @Test public void testSupersedesExistingTask() throws Exception { @@ -200,4 +242,30 @@ public class NotificationInflaterTest extends SysuiTestCase { mException = exception; } } + + private class AsyncFailRemoteView extends RemoteViews { + Handler mHandler = new Handler(Looper.getMainLooper()); + + public AsyncFailRemoteView(String packageName, int layoutId) { + super(packageName, layoutId); + } + + @Override + public View apply(Context context, ViewGroup parent) { + return super.apply(context, parent); + } + + @Override + public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, + OnViewAppliedListener listener, OnClickHandler handler) { + mHandler.post(() -> listener.onError(new RuntimeException("Failed to inflate async"))); + return new CancellationSignal(); + } + + @Override + public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, + OnViewAppliedListener listener) { + return applyAsync(context, parent, executor, listener, null); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java index a120cecb55e8..4cc83f632426 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java @@ -14,7 +14,6 @@ package com.android.systemui.statusbar.phone; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -33,17 +32,13 @@ import com.android.systemui.recents.Recents; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; -import com.android.systemui.utils.leaks.BaseLeakChecker; import android.testing.TestableLooper.RunWithLooper; -import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) @@ -54,6 +49,10 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { super(NavigationBarFragment.class); } + protected void createRootView() { + mView = new NavigationBarFrame(mContext); + } + @Before public void setup() { mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index db6647c10aca..17ca92458bcf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -20,37 +20,57 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static junit.framework.TestCase.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; import android.app.Notification; +import android.app.trust.TrustManager; +import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.os.Handler; import android.os.HandlerThread; import android.os.IPowerManager; -import android.os.Looper; +import android.os.Message; import android.os.PowerManager; +import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.support.test.filters.SmallTest; import android.support.test.metricshelper.MetricsAsserts; import android.support.test.runner.AndroidJUnit4; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.MessageHandler; +import android.testing.TestableLooper.RunWithLooper; import android.util.DisplayMetrics; +import android.view.ViewGroup.LayoutParams; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.testing.FakeMetricsLogger; +import com.android.internal.statusbar.IStatusBarService; import com.android.keyguard.KeyguardHostView.OnDismissAction; import com.android.systemui.SysuiTestCase; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.statusbar.ActivatableNotificationView; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.NotificationData.Entry; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; @@ -58,25 +78,34 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; + @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(AndroidTestingRunner.class) +@RunWithLooper public class StatusBarTest extends SysuiTestCase { StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; UnlockMethodCache mUnlockMethodCache; KeyguardIndicationController mKeyguardIndicationController; NotificationStackScrollLayout mStackScroller; - StatusBar mStatusBar; + TestableStatusBar mStatusBar; FakeMetricsLogger mMetricsLogger; HeadsUpManager mHeadsUpManager; NotificationData mNotificationData; PowerManager mPowerManager; SystemServicesProxy mSystemServicesProxy; NotificationPanelView mNotificationPanelView; + IStatusBarService mBarService; + ArrayList<Entry> mNotificationList; private DisplayMetrics mDisplayMetrics = new DisplayMetrics(); @Before public void setup() throws Exception { + mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); + mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager.class); mUnlockMethodCache = mock(UnlockMethodCache.class); mKeyguardIndicationController = mock(KeyguardIndicationController.class); @@ -86,18 +115,22 @@ public class StatusBarTest extends SysuiTestCase { mNotificationData = mock(NotificationData.class); mSystemServicesProxy = mock(SystemServicesProxy.class); mNotificationPanelView = mock(NotificationPanelView.class); + when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0)); + mNotificationList = mock(ArrayList.class); IPowerManager powerManagerService = mock(IPowerManager.class); HandlerThread handlerThread = new HandlerThread("TestThread"); handlerThread.start(); mPowerManager = new PowerManager(mContext, powerManagerService, new Handler(handlerThread.getLooper())); when(powerManagerService.isInteractive()).thenReturn(true); + mBarService = mock(IStatusBarService.class); mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache, mKeyguardIndicationController, mStackScroller, mHeadsUpManager, - mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView); - + mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView, + mBarService); + mStatusBar.mContext = mContext; doAnswer(invocation -> { OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0]; onDismissAction.onDismiss(); @@ -111,6 +144,15 @@ public class StatusBarTest extends SysuiTestCase { }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any()); when(mStackScroller.getActivatedChild()).thenReturn(null); + TestableLooper.get(this).setMessageHandler(new MessageHandler() { + @Override + public boolean onMessageHandled(Message m) { + if (m.getCallback() == mStatusBar.mVisibilityReporter) { + return false; + } + return true; + } + }); } @Test @@ -284,11 +326,85 @@ public class StatusBarTest extends SysuiTestCase { assertFalse(mStatusBar.shouldPeek(entry, sbn)); } + @Test + public void testLogHidden() { + try { + mStatusBar.handleVisibleToUserChanged(false); + verify(mBarService, times(1)).onPanelHidden(); + verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt()); + } catch (RemoteException e) { + fail(); + } + } + + @Test + public void testPanelOpenForPeek() { + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); + when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList); + when(mNotificationList.size()).thenReturn(5); + when(mNotificationPanelView.isFullyCollapsed()).thenReturn(true); + mStatusBar.setBarStateForTest(StatusBarState.SHADE); + + try { + mStatusBar.handleVisibleToUserChanged(true); + + verify(mBarService, never()).onPanelHidden(); + verify(mBarService, times(1)).onPanelRevealed(false, 1); + } catch (RemoteException e) { + fail(); + } + TestableLooper.get(this).processAllMessages(); + } + + @Test + public void testPanelOpenAndClear() { + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); + when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList); + when(mNotificationList.size()).thenReturn(5); + when(mNotificationPanelView.isFullyCollapsed()).thenReturn(false); + mStatusBar.setBarStateForTest(StatusBarState.SHADE); + + try { + mStatusBar.handleVisibleToUserChanged(true); + + verify(mBarService, never()).onPanelHidden(); + verify(mBarService, times(1)).onPanelRevealed(true, 5); + } catch (RemoteException e) { + fail(); + } + TestableLooper.get(this).processAllMessages(); + } + + @Test + public void testPanelOpenAndNoClear() { + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); + when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList); + when(mNotificationList.size()).thenReturn(5); + when(mNotificationPanelView.isFullyCollapsed()).thenReturn(false); + mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD); + + try { + mStatusBar.handleVisibleToUserChanged(true); + + verify(mBarService, never()).onPanelHidden(); + verify(mBarService, times(1)).onPanelRevealed(false, 5); + } catch (RemoteException e) { + fail(); + } + TestableLooper.get(this).processAllMessages(); + } + + @Test + public void testDump_DoesNotCrash() { + mStatusBar.dump(null, new PrintWriter(new ByteArrayOutputStream()), null); + } + static class TestableStatusBar extends StatusBar { public TestableStatusBar(StatusBarKeyguardViewManager man, UnlockMethodCache unlock, KeyguardIndicationController key, NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd, - PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView) { + PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView, + IStatusBarService barService) { mStatusBarKeyguardViewManager = man; mUnlockMethodCache = unlock; mKeyguardIndicationController = key; @@ -299,11 +415,11 @@ public class StatusBarTest extends SysuiTestCase { mPowerManager = pm; mSystemServicesProxy = ssp; mNotificationPanel = panelView; + mBarService = barService; } - @Override - protected H createHandler() { - return null; + public void setBarStateForTest(int state) { + mState = state; } } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index 2eb95603cef0..4cc8bcae541a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -14,16 +14,21 @@ package com.android.systemui.statusbar.policy; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; +import android.os.Looper; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import android.util.Log; import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -80,4 +85,56 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothAdapter.STATE_DISCONNECTED); assertTrue(mBluetoothControllerImpl.isBluetoothConnected()); } + + @Test + public void testDefaultConnectionState() { + CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); + assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device)); + assertEquals(BluetoothProfile.STATE_DISCONNECTED, + mBluetoothControllerImpl.getMaxConnectionState(device)); + } + + @Test + public void testAsyncBondState() throws Exception { + CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); + when(device.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + BluetoothController.Callback callback = mock(BluetoothController.Callback.class); + mBluetoothControllerImpl.addCallback(callback); + + // Grab the main looper, we'll need it later. + TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper()); + + // Trigger the state getting. + assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device)); + + mTestableLooper.processMessages(1); + mainLooper.processAllMessages(); + + assertEquals(BluetoothDevice.BOND_BONDED, mBluetoothControllerImpl.getBondState(device)); + verify(callback).onBluetoothDevicesChanged(); + mainLooper.destroy(); + } + + @Test + public void testAsyncConnectionState() throws Exception { + CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); + when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED); + BluetoothController.Callback callback = mock(BluetoothController.Callback.class); + mBluetoothControllerImpl.addCallback(callback); + + // Grab the main looper, we'll need it later. + TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper()); + + // Trigger the state getting. + assertEquals(BluetoothProfile.STATE_DISCONNECTED, + mBluetoothControllerImpl.getMaxConnectionState(device)); + + mTestableLooper.processMessages(1); + mainLooper.processAllMessages(); + + assertEquals(BluetoothProfile.STATE_CONNECTED, + mBluetoothControllerImpl.getMaxConnectionState(device)); + verify(callback).onBluetoothDevicesChanged(); + mainLooper.destroy(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java index cb2063967639..51bd7bccbe71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.policy; import android.os.HandlerThread; import android.support.test.runner.AndroidJUnit4; import android.telephony.SubscriptionInfo; +import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java new file mode 100644 index 000000000000..a10bebfd4f2d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Intent; +import android.location.LocationManager; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +@SmallTest +public class LocationControllerImplTest extends SysuiTestCase { + + private LocationControllerImpl mLocationController; + + @Before + public void setup() { + mLocationController = spy(new LocationControllerImpl(mContext, + TestableLooper.get(this).getLooper())); + } + + @Test + public void testRemoveSelfActive_DoesNotCrash() { + LocationController.LocationChangeCallback callback = new LocationChangeCallback() { + @Override + public void onLocationActiveChanged(boolean active) { + mLocationController.removeCallback(this); + } + }; + mLocationController.addCallback(callback); + mLocationController.addCallback(mock(LocationChangeCallback.class)); + + when(mLocationController.areActiveHighPowerLocationRequests()).thenReturn(false); + mLocationController.onReceive(mContext, new Intent( + LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION)); + when(mLocationController.areActiveHighPowerLocationRequests()).thenReturn(true); + mLocationController.onReceive(mContext, new Intent( + LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION)); + + TestableLooper.get(this).processAllMessages(); + } + + @Test + public void testRemoveSelfSettings_DoesNotCrash() { + LocationController.LocationChangeCallback callback = new LocationChangeCallback() { + @Override + public void onLocationSettingsChanged(boolean isEnabled) { + mLocationController.removeCallback(this); + } + }; + mLocationController.addCallback(callback); + mLocationController.addCallback(mock(LocationChangeCallback.class)); + + TestableLooper.get(this).processAllMessages(); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 505e1d8346fd..a8319a8ee538 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.policy; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; -import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.Looper; import android.telephony.PhoneStateListener; @@ -84,7 +83,6 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected Config mConfig; protected CallbackHandler mCallbackHandler; protected SubscriptionDefaults mMockSubDefaults; - protected NetworkScoreManager mMockNetworkScoreManager; protected DeviceProvisionedController mMockProvisionController; protected DeviceProvisionedListener mUserCallback; @@ -113,8 +111,6 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mMockCm = mock(ConnectivityManager.class); mMockSubDefaults = mock(SubscriptionDefaults.class); mNetCapabilities = new NetworkCapabilities(); - mMockNetworkScoreManager = mock(NetworkScoreManager.class); - when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true); when(mMockCm.getDefaultNetworkCapabilitiesForUser(0)).thenReturn( new NetworkCapabilities[] { mNetCapabilities }); @@ -135,8 +131,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { return null; }).when(mMockProvisionController).addCallback(any()); - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, - mMockTm, mMockWm, mMockSm, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mMockProvisionController); @@ -177,8 +172,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected NetworkControllerImpl setUpNoMobileData() { when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false); NetworkControllerImpl networkControllerNoMobile - = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, mMockTm, - mMockWm, mMockSm, mConfig, mContext.getMainLooper(), mCallbackHandler, + = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, + mConfig, mContext.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java index dfe00f95fe4d..8d106b4b9616 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java @@ -91,8 +91,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { public void test4gDataIcon() { // Switch to showing 4g icon and re-initialize the NetworkController. mConfig.show4gForLte = true; - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, - mMockTm, mMockWm, mMockSm, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java index 1627925ae1bc..be3802bd68ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java @@ -54,8 +54,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { // Turn off mobile network support. Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, - mMockTm, mMockWm, mMockSm, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class)); @@ -117,8 +116,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { // Turn off mobile network support. Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, - mMockTm, mMockWm, mMockSm, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java index dbaa2c5fea0a..ffd0165195e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java @@ -1,47 +1,24 @@ package com.android.systemui.statusbar.policy; import android.content.Intent; -import android.net.NetworkBadging; import android.net.NetworkCapabilities; import android.net.NetworkInfo; -import android.net.NetworkKey; -import android.net.RssiCurve; -import android.net.ScoredNetwork; -import android.net.WifiKey; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiNetworkScoreCache; -import android.os.Bundle; -import android.provider.Settings; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import com.android.settingslib.Utils; import com.android.systemui.statusbar.policy.NetworkController.IconState; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Matchers; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import static junit.framework.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidJUnit4.class) @@ -50,13 +27,6 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { private static final int MIN_RSSI = -100; private static final int MAX_RSSI = -55; - private static final int LATCH_TIMEOUT = 2000; - private static final String TEST_SSID = "\"Test SSID\""; - private static final String TEST_BSSID = "00:00:00:00:00:00"; - - private final List<NetworkKey> mRequestedKeys = new ArrayList<>(); - private CountDownLatch mRequestScoresLatch; - @Test public void testWifiIcon() { String testSsid = "Test SSID"; @@ -77,79 +47,6 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } @Test - public void testBadgedWifiIcon() throws Exception { - // TODO(sghuman): Refactor this setup code when creating a test for the badged QsIcon. - int testLevel = 1; - RssiCurve mockBadgeCurve = mock(RssiCurve.class); - Bundle attr = new Bundle(); - attr.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve); - ScoredNetwork score = - new ScoredNetwork( - new NetworkKey(new WifiKey(TEST_SSID, TEST_BSSID)), - null, - false /* meteredHint */, - attr); - - // Must set the Settings value before instantiating the NetworkControllerImpl due to bugs in - // TestableSettingsProvider. - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORING_UI_ENABLED, - "1"); - super.setUp(); // re-instantiate NetworkControllImpl now that setting has been updated - setupNetworkScoreManager(); - - // Test Requesting Scores - mRequestScoresLatch = new CountDownLatch(1); - setWifiEnabled(true); - setWifiState(true, TEST_SSID, TEST_BSSID); - mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS); - - when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) NetworkBadging.BADGING_SD); - - ArgumentCaptor<WifiNetworkScoreCache> scoreCacheCaptor = - ArgumentCaptor.forClass(WifiNetworkScoreCache.class); - verify(mMockNetworkScoreManager).registerNetworkScoreCache( - anyInt(), - scoreCacheCaptor.capture(), - Matchers.anyInt()); - scoreCacheCaptor.getValue().updateScores(Arrays.asList(score)); - - // Test badge is set - setWifiLevel(testLevel); - - ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class); - Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators( - anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(), - any(), anyBoolean()); - IconState iconState = iconArg.getValue(); - - assertEquals("Badged Wifi Resource is set", - Utils.WIFI_PIE_FOR_BADGING[testLevel], - iconState.icon); - assertEquals("SD Badge is set", - Utils.getWifiBadgeResource(NetworkBadging.BADGING_SD), - iconState.iconOverlay); - } - - private void setupNetworkScoreManager() { - // Capture requested keys and count down latch if present - doAnswer( - new Answer<Boolean>() { - @Override - public Boolean answer(InvocationOnMock input) { - if (mRequestScoresLatch != null) { - mRequestScoresLatch.countDown(); - } - NetworkKey[] keys = (NetworkKey[]) input.getArguments()[0]; - for (NetworkKey key : keys) { - mRequestedKeys.add(key); - } - return true; - } - }).when(mMockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any()); - } - - @Test public void testQsWifiIcon() { String testSsid = "Test SSID"; @@ -200,7 +97,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { @Test public void testRoamingIconDuringWifi() { // Setup normal connection - String testSsid = "\"Test SSID\""; + String testSsid = "Test SSID"; int testLevel = 2; setWifiEnabled(true); setWifiState(true, testSsid); @@ -241,19 +138,12 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } protected void setWifiState(boolean connected, String ssid) { - setWifiState(connected, ssid, null); - } - - protected void setWifiState(boolean connected, String ssid, String bssid) { Intent i = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); NetworkInfo networkInfo = Mockito.mock(NetworkInfo.class); Mockito.when(networkInfo.isConnected()).thenReturn(connected); WifiInfo wifiInfo = Mockito.mock(WifiInfo.class); Mockito.when(wifiInfo.getSSID()).thenReturn(ssid); - if (bssid != null) { - Mockito.when(wifiInfo.getBSSID()).thenReturn(bssid); - } i.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo); i.putExtra(WifiManager.EXTRA_WIFI_INFO, wifiInfo); @@ -278,7 +168,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators( enabledArg.capture(), any(), iconArg.capture(), anyBoolean(), - anyBoolean(), descArg.capture(), anyBoolean()); + anyBoolean(), descArg.capture(), anyBoolean()); IconState iconState = iconArg.getValue(); assertEquals("WiFi enabled, in quick settings", enabled, (boolean) enabledArg.getValue()); assertEquals("WiFi connected, in quick settings", connected, iconState.visible); diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java index 0ba031908d90..9ec096ad49e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java @@ -83,4 +83,14 @@ public class FakeBluetoothController extends BaseLeakChecker<Callback> implement public boolean canConfigBluetooth() { return false; } + + @Override + public int getMaxConnectionState(CachedBluetoothDevice device) { + return 0; + } + + @Override + public int getBondState(CachedBluetoothDevice device) { + return 0; + } } diff --git a/packages/SysuiDarkThemeOverlay/Android.mk b/packages/overlays/SysuiDarkThemeOverlay/Android.mk index 4b83058ab481..4b83058ab481 100644 --- a/packages/SysuiDarkThemeOverlay/Android.mk +++ b/packages/overlays/SysuiDarkThemeOverlay/Android.mk diff --git a/packages/SysuiDarkThemeOverlay/AndroidManifest.xml b/packages/overlays/SysuiDarkThemeOverlay/AndroidManifest.xml index ba1c91cbdebe..ba1c91cbdebe 100644 --- a/packages/SysuiDarkThemeOverlay/AndroidManifest.xml +++ b/packages/overlays/SysuiDarkThemeOverlay/AndroidManifest.xml diff --git a/packages/SysuiDarkThemeOverlay/res/values/strings.xml b/packages/overlays/SysuiDarkThemeOverlay/res/values/strings.xml index 71f48d6279f1..71f48d6279f1 100644 --- a/packages/SysuiDarkThemeOverlay/res/values/strings.xml +++ b/packages/overlays/SysuiDarkThemeOverlay/res/values/strings.xml diff --git a/packages/SysuiDarkThemeOverlay/res/values/themes_device_defaults.xml b/packages/overlays/SysuiDarkThemeOverlay/res/values/themes_device_defaults.xml index 28ecfa0b37fa..7e2b955a8871 100644 --- a/packages/SysuiDarkThemeOverlay/res/values/themes_device_defaults.xml +++ b/packages/overlays/SysuiDarkThemeOverlay/res/values/themes_device_defaults.xml @@ -1,12 +1,13 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <!-- Dark theme for a window that should look like the Settings app. --> <style name="Theme.DeviceDefault.QuickSettings" parent="android:Theme.DeviceDefault"> - <!-- Color palette --> <item name="android:colorPrimary">@*android:color/primary_device_default_settings</item> <item name="android:colorPrimaryDark">@*android:color/primary_dark_device_default_settings</item> - <item name="android:textColorPrimaryInverse">@*android:color/primary_text_material_light</item> - <item name="android:textColorSecondaryInverse">@*android:color/secondary_text_material_light</item> + <!-- textColorPrimaryInverse is used on the lock screen and this means that we can't just + invert text colors otherwise we won't have contrast on the keyguard --> + <item name="android:textColorPrimaryInverse">@*android:color/primary_text_material_dark</item> + <!-- same for textColorSecondaryInverse --> + <item name="android:textColorSecondaryInverse">@*android:color/secondary_text_material_dark</item> <item name="android:colorSecondary">@*android:color/secondary_device_default_settings</item> <item name="android:colorAccent">@*android:color/accent_device_default_dark</item> <item name="android:colorControlNormal">?android:attr/textColorPrimary</item> diff --git a/packages/overlays/SysuiLightWallpaperThemeOverlay/Android.mk b/packages/overlays/SysuiLightWallpaperThemeOverlay/Android.mk new file mode 100644 index 000000000000..4782a166146b --- /dev/null +++ b/packages/overlays/SysuiLightWallpaperThemeOverlay/Android.mk @@ -0,0 +1,13 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_RRO_THEME := SysuiLightWallpaperTheme +LOCAL_CERTIFICATE := platform + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := SysuiLightWallpaperThemeOverlay + +include $(BUILD_RRO_PACKAGE) diff --git a/packages/overlays/SysuiLightWallpaperThemeOverlay/AndroidManifest.xml b/packages/overlays/SysuiLightWallpaperThemeOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..1745b4cda03d --- /dev/null +++ b/packages/overlays/SysuiLightWallpaperThemeOverlay/AndroidManifest.xml @@ -0,0 +1,8 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.theme.lightwallpaper" + android:versionCode="1" + android:versionName="1.0"> + <overlay android:targetPackage="android" android:priority="2"/> + + <application android:label="@string/sysui_overlay_light" android:hasCode="false"/> +</manifest> diff --git a/core/java/android/service/euicc/DownloadResult.aidl b/packages/overlays/SysuiLightWallpaperThemeOverlay/res/values/strings.xml index 66ec999b6ce8..acc3d16610ec 100644 --- a/core/java/android/service/euicc/DownloadResult.aidl +++ b/packages/overlays/SysuiLightWallpaperThemeOverlay/res/values/strings.xml @@ -1,11 +1,13 @@ -/* - * Copyright (C) 2017 The Android Open Source Project +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2017, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -13,7 +15,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> -package android.service.euicc; + <string name="sysui_overlay_light">Light</string> + +</resources> -parcelable DownloadResult; diff --git a/packages/overlays/SysuiLightWallpaperThemeOverlay/res/values/themes_device_defaults.xml b/packages/overlays/SysuiLightWallpaperThemeOverlay/res/values/themes_device_defaults.xml new file mode 100644 index 000000000000..877ebf8c4e9d --- /dev/null +++ b/packages/overlays/SysuiLightWallpaperThemeOverlay/res/values/themes_device_defaults.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <style name="Theme.DeviceDefault.QuickSettings" parent="android:Theme.DeviceDefault.Light"> + <item name="android:textColorPrimaryInverse">@*android:color/primary_text_material_light</item> + <item name="android:textColorSecondaryInverse">@*android:color/secondary_text_material_light</item> + </style> +</resources>
\ No newline at end of file diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index fa2b1ee83419..b3a4007ff399 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -4095,6 +4095,45 @@ message MetricsEvent { // OS: O DR CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR = 1013; + // OPEN: Settings > Network > Tether > Wi-Fi hotspot + WIFI_TETHER_SETTINGS = 1014; + + // OPEN: Settings->Connected Devices->Bluetooth->(click on details link for a paired device) + // -> Edit name button. + // CATEGORY: SETTINGS + // OS: O DR + DIALOG_BLUETOOTH_PAIRED_DEVICE_RENAME = 1015; + + // ACTION: Settings > Notification Settings > Open application notification + // CATEGORY: SETTINGS + // OS: O DR + ACTION_OPEN_APP_NOTIFICATION_SETTING = 1016; + + // ACTION: Settings > App Info > Open app settings + // CATEGORY: SETTINGS + // OS: O DR + ACTION_OPEN_APP_SETTING = 1017; + + // OPEN: Settings > Connected devices > Bluetooth > Pair new device + // CATEGORY: SETTINGS + // OS: O DR + BLUETOOTH_PAIRING = 1018; + + // ACTION: Collect PSD Signals + // CATEGORY: SETTINGS + // OS: O DR + ACTION_PSD_LOADER = 1019; + + // ACTION: Background check action on an app + // CATEGORY: SETTINGS + // OS: O DR + ACTION_APP_BACKGROUND_CHECK = 1020; + + // ACTION: Location check action on an app + // CATEGORY: SETTINGS + // OS: O DR + ACTION_APP_LOCATION_CHECK = 1021; + // Add new aosp constants above this line. // END OF AOSP CONSTANTS } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 8cecb4b421d2..93fae58b7c1d 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -219,6 +219,10 @@ message SystemMessage { // Package: com.android.systemui NOTE_TV_PIP = 1100; + // Notify the user that open Wi-Fi networks are available. + // Package: android + NOTE_NETWORK_AVAILABLE = 17303299; + // Communicate to the user about remote bugreports. // Package: android NOTE_REMOTE_BUGREPORT = 678432343; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 9e2f52a875b5..a58ba09e3937 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -243,6 +243,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private WindowsForAccessibilityCallback mWindowsForAccessibilityCallback; + private boolean mIsAccessibilityButtonShown; + private UserState getCurrentUserStateLocked() { return getUserStateLocked(mCurrentUserId); } @@ -872,21 +874,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } /** - * Invoked remotely over AIDL by SysUi when the availability of the accessibility + * Invoked remotely over AIDL by SysUi when the visibility of the accessibility * button within the system's navigation area has changed. * - * @param available {@code true} if the accessibility button is available to the + * @param shown {@code true} if the accessibility button is shown to the * user, {@code false} otherwise */ @Override - public void notifyAccessibilityButtonAvailabilityChanged(boolean available) { + public void notifyAccessibilityButtonVisibilityChanged(boolean shown) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Caller does not hold permission " + android.Manifest.permission.STATUS_BAR_SERVICE); } synchronized (mLock) { - notifyAccessibilityButtonAvailabilityChangedLocked(available); + notifyAccessibilityButtonVisibilityChangedLocked(shown); } } @@ -1191,13 +1193,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } - private void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) { + private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) { final UserState state = getCurrentUserStateLocked(); - state.mIsAccessibilityButtonAvailable = available; + mIsAccessibilityButtonShown = available; for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { final Service service = state.mBoundServices.get(i); if (service.mRequestAccessibilityButton) { - service.notifyAccessibilityButtonAvailabilityChangedLocked(available); + service.notifyAccessibilityButtonAvailabilityChangedLocked( + service.isAccessibilityButtonAvailableLocked(state)); } } } @@ -1724,7 +1727,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { scheduleUpdateInputFilter(userState); scheduleUpdateClientsIfNeededLocked(userState); updateRelevantEventsLocked(userState); - updateAccessibilityButtonTargets(userState); + updateAccessibilityButtonTargetsLocked(userState); } private void updateAccessibilityFocusBehaviorLocked(UserState userState) { @@ -2174,18 +2177,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } - private void updateAccessibilityButtonTargets(UserState userState) { - final List<Service> services; - synchronized (mLock) { - services = userState.mBoundServices; - int numServices = services.size(); - for (int i = 0; i < numServices; i++) { - final Service service = services.get(i); - if (service.mRequestAccessibilityButton) { - boolean available = service.mComponentName.equals( - userState.mServiceAssignedToAccessibilityButton); - service.notifyAccessibilityButtonAvailabilityChangedLocked(available); - } + private void updateAccessibilityButtonTargetsLocked(UserState userState) { + for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { + final Service service = userState.mBoundServices.get(i); + if (service.mRequestAccessibilityButton) { + service.notifyAccessibilityButtonAvailabilityChangedLocked( + service.isAccessibilityButtonAvailableLocked(userState)); } } } @@ -2492,7 +2489,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { case MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER: { showAccessibilityButtonTargetSelection(); - } + } break; } } @@ -2647,6 +2644,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { boolean mRequestAccessibilityButton; + boolean mReceivedAccessibilityButtonCallbackSinceBind; + + boolean mLastAccessibilityButtonCallbackState; + int mFetchFlags; long mNotificationTimeout; @@ -3587,9 +3588,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return false; } userState = getCurrentUserStateLocked(); + return isAccessibilityButtonAvailableLocked(userState); } - - return mRequestAccessibilityButton && userState.mIsAccessibilityButtonAvailable; } @Override @@ -3647,6 +3647,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mService = null; } mServiceInterface = null; + mReceivedAccessibilityButtonCallbackSinceBind = false; } public boolean isConnectedLocked() { @@ -3719,6 +3720,48 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + private boolean isAccessibilityButtonAvailableLocked(UserState userState) { + // If the service does not request the accessibility button, it isn't available + if (!mRequestAccessibilityButton) { + return false; + } + + // If the accessibility button isn't currently shown, it cannot be available to services + if (!mIsAccessibilityButtonShown) { + return false; + } + + // If magnification is on and assigned to the accessibility button, services cannot be + if (userState.mIsNavBarMagnificationEnabled + && userState.mIsNavBarMagnificationAssignedToAccessibilityButton) { + return false; + } + + int requestingServices = 0; + for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { + final Service service = userState.mBoundServices.get(i); + if (service.mRequestAccessibilityButton) { + requestingServices++; + } + } + + if (requestingServices == 1) { + // If only a single service is requesting, it must be this service, and the + // accessibility button is available to it + return true; + } else { + // With more than one active service, we derive the target from the user's settings + if (userState.mServiceAssignedToAccessibilityButton == null) { + // If the user has not made an assignment, we treat the button as available to + // all services until the user interacts with the button to make an assignment + return true; + } else { + // If an assignment was made, it defines availability + return mComponentName.equals(userState.mServiceAssignedToAccessibilityButton); + } + } + } + /** * Notifies an accessibility service client for a scheduled event given the event type. * @@ -3866,6 +3909,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) { + // Only notify the service if it's not been notified or the state has changed + if (mReceivedAccessibilityButtonCallbackSinceBind + && (mLastAccessibilityButtonCallbackState == available)) { + return; + } + mReceivedAccessibilityButtonCallbackSinceBind = true; + mLastAccessibilityButtonCallbackState = available; final IAccessibilityServiceClient listener; synchronized (mLock) { listener = mServiceInterface; @@ -4865,7 +4915,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public int mSoftKeyboardShowMode = 0; - public boolean mIsAccessibilityButtonAvailable; public boolean mIsNavBarMagnificationAssignedToAccessibilityButton; public ComponentName mServiceAssignedToAccessibilityButton; @@ -4945,9 +4994,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mIsNavBarMagnificationAssignedToAccessibilityButton = false; mIsAutoclickEnabled = false; mSoftKeyboardShowMode = 0; - - // Clear state tracked from system UI - mIsAccessibilityButtonAvailable = false; } public void destroyUiAutomationService() { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index e85f96be3160..cb91f9308b03 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -270,6 +270,12 @@ public final class AutofillManagerService extends SystemService { } @Override + public void onSwitchUser(int userHandle) { + if (sDebug) Slog.d(TAG, "Hiding UI when user switched"); + mUi.hideAll(null); + } + + @Override public void onCleanupUser(int userId) { synchronized (mLock) { removeCachedServiceLocked(userId); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 38b796b316de..751c0547afd6 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -360,8 +360,7 @@ final class AutofillManagerServiceImpl { } void disableOwnedAutofillServicesLocked(int uid) { - if (mInfo == null || mInfo.getServiceInfo().applicationInfo.uid - != UserHandle.getAppId(uid)) { + if (mInfo == null || mInfo.getServiceInfo().applicationInfo.uid != uid) { return; } final long identity = Binder.clearCallingIdentity(); @@ -489,46 +488,78 @@ final class AutofillManagerServiceImpl { * Initializes the last fill selection after an autofill service returned a new * {@link FillResponse}. */ - void setLastResponse(int serviceUid, @NonNull FillResponse response) { + void setLastResponse(int serviceUid, int sessionId, @NonNull FillResponse response) { synchronized (mLock) { - mEventHistory = new FillEventHistory(serviceUid, response.getClientState()); + mEventHistory = new FillEventHistory(serviceUid, sessionId, response.getClientState()); } } /** + * Resets the last fill selection. + */ + void resetLastResponse() { + synchronized (mLock) { + mEventHistory = null; + } + } + + private boolean isValidEventLocked(String method, int sessionId) { + if (mEventHistory == null) { + Slog.w(TAG, method + ": not logging event because history is null"); + return false; + } + if (sessionId != mEventHistory.getSessionId()) { + if (sDebug) { + Slog.d(TAG, method + ": not logging event for session " + sessionId + + " because tracked session is " + mEventHistory.getSessionId()); + } + return false; + } + return true; + } + + /** * Updates the last fill selection when an authentication was selected. */ - void setAuthenticationSelected() { + void setAuthenticationSelected(int sessionId) { synchronized (mLock) { - mEventHistory.addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null)); + if (isValidEventLocked("setAuthenticationSelected()", sessionId)) { + mEventHistory.addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null)); + } } } /** * Updates the last fill selection when an dataset authentication was selected. */ - void setDatasetAuthenticationSelected(@Nullable String selectedDataset) { + void setDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId) { synchronized (mLock) { - mEventHistory.addEvent( - new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset)); + if (isValidEventLocked("setDatasetAuthenticationSelected()", sessionId)) { + mEventHistory.addEvent( + new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset)); + } } } /** * Updates the last fill selection when an save Ui is shown. */ - void setSaveShown() { + void setSaveShown(int sessionId) { synchronized (mLock) { - mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null)); + if (isValidEventLocked("setSaveShown()", sessionId)) { + mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null)); + } } } /** * Updates the last fill response when a dataset was selected. */ - void setDatasetSelected(@Nullable String selectedDataset) { + void setDatasetSelected(@Nullable String selectedDataset, int sessionId) { synchronized (mLock) { - mEventHistory.addEvent(new Event(Event.TYPE_DATASET_SELECTED, selectedDataset)); + if (isValidEventLocked("setDatasetSelected()", sessionId)) { + mEventHistory.addEvent(new Event(Event.TYPE_DATASET_SELECTED, selectedDataset)); + } } } diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 9aebf6d2bacb..aebe92e1687d 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -427,6 +427,7 @@ final class RemoteFillService implements DeathRecipient { mCompleted = true; } + Slog.w(LOG_TAG, getClass().getSimpleName() + " timed out"); final RemoteFillService remoteService = mWeakService.get(); if (remoteService != null) { fail(remoteService); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 073d7b20ed64..72ad752caf19 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -255,7 +255,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ViewNode node = nodes[i]; if (node == null) { - Slog.w(TAG, "fillStructureWithAllowedValues(): no node for " + viewState.id); + if (sVerbose) { + Slog.v(TAG, "fillStructureWithAllowedValues(): no node for " + viewState.id); + } continue; } @@ -405,13 +407,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if ((requestFlags & FLAG_MANUAL_REQUEST) != 0) { getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this); } + mService.resetLastResponse(); // Nothing to be done, but need to notify client. notifyUnavailableToClient(); removeSelf(); return; } - mService.setLastResponse(serviceUid, response); + mService.setLastResponse(serviceUid, id, response); if ((response.getDatasets() == null || response.getDatasets().isEmpty()) && response.getAuthentication() == null) { @@ -442,6 +445,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + id + " destroyed"); return; } + mService.resetLastResponse(); } LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST)) .setType(MetricsEvent.TYPE_FAILURE) @@ -540,7 +544,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState getFillContextByRequestIdLocked(requestId).getStructure(), extras); } - mService.setAuthenticationSelected(); + mService.setAuthenticationSelected(id); final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex); mHandlerCaller.getHandler().post(() -> startAuthentication(authenticationId, @@ -829,7 +833,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (atLeastOneChanged) { if (sDebug) Slog.d(TAG, "at least one field changed - showing save UI"); - mService.setSaveShown(); + mService.setSaveShown(id); getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, mPackageName, this); @@ -862,11 +866,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int numContexts = mContexts.size(); for (int i = 0; i < numContexts; i++) { final FillContext context = mContexts.get(i); - // TODO: create a function that gets just one node so it doesn't create an array - // unnecessarily - final ViewNode[] nodes = context.findViewNodesByAutofillIds(id); - if (nodes != null) { - AutofillValue candidate = nodes[0].getAutofillValue(); + final ViewNode node = context.findViewNodeByAutofillId(id); + if (node != null) { + final AutofillValue candidate = node.getAutofillValue(); if (sDebug) { Slog.d(TAG, "getValueFromContexts(" + id + ") at " + i + ": " + candidate); } @@ -1362,14 +1364,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // Autofill it directly... if (dataset.getAuthentication() == null) { - mService.setDatasetSelected(dataset.getId()); + mService.setDatasetSelected(dataset.getId(), id); autoFillApp(dataset); return; } // ...or handle authentication. - mService.setDatasetAuthenticationSelected(dataset.getId()); + mService.setDatasetAuthenticationSelected(dataset.getId(), id); setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false); final Intent fillInIntent = createAuthFillInIntent( getFillContextByRequestIdLocked(requestId).getStructure(), mClientState); diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index d1fbbf9ceda1..c9e2a928dee0 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -191,6 +191,7 @@ final class SaveUi { | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); + window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS); window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); window.setGravity(Gravity.BOTTOM | Gravity.CENTER); window.setCloseOnTouchOutside(true); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 73f1705e35df..4810f4fe8c82 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -47,6 +47,7 @@ import android.os.IBinder; import android.os.IDeviceIdleController; import android.os.IInterface; import android.os.Parcel; +import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -345,7 +346,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } private static boolean isCallerSystem() { - return getCallingUserId() == UserHandle.USER_SYSTEM; + return Binder.getCallingUid() == Process.SYSTEM_UID; } private ServiceConnection createServiceConnection( diff --git a/services/core/Android.mk b/services/core/Android.mk index 4d080e9d6103..0d01c2026176 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -33,6 +33,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ android.hardware.oemlock-V1.0-java-static \ android.hardware.tetheroffload.control-V1.0-java-static \ android.hardware.vibrator-V1.0-java-constants \ + android.hardware.configstore-V1.0-java-static ifneq ($(INCREMENTAL_BUILDS),) LOCAL_PROGUARD_ENABLED := disabled diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index b408da85425d..4c9495a786c3 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -17,7 +17,6 @@ package com.android.server; import android.app.ActivityManager; -import android.app.KeyguardManager; import android.app.StatusBarManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -41,10 +40,12 @@ import android.provider.Settings; import android.util.MutableBoolean; import android.util.Slog; import android.view.KeyEvent; +import android.view.WindowManagerInternal; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; /** @@ -56,6 +57,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; */ public class GestureLauncherService extends SystemService { private static final boolean DBG = false; + private static final boolean DBG_CAMERA_LIFT = true; // false once b/62623620 is fixed private static final String TAG = "GestureLauncherService"; /** @@ -82,7 +84,7 @@ public class GestureLauncherService extends SystemService { private Context mContext; private final MetricsLogger mMetricsLogger; private PowerManager mPowerManager; - private KeyguardManager mKeyguardManager; + private WindowManagerInternal mWindowManagerInternal; /** The wake lock held when a gesture is detected. */ private WakeLock mWakeLock; @@ -150,8 +152,7 @@ public class GestureLauncherService extends SystemService { return; } - mKeyguardManager = (KeyguardManager) mContext.getSystemService( - Context.KEYGUARD_SERVICE); + mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mPowerManager = (PowerManager) mContext.getSystemService( Context.POWER_SERVICE); mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, @@ -517,33 +518,40 @@ public class GestureLauncherService extends SystemService { private final class CameraLiftTriggerEventListener extends TriggerEventListener { @Override public void onTrigger(TriggerEvent event) { + if (DBG_CAMERA_LIFT) Slog.d(TAG, String.format("onTrigger event - time: %d, name: %s", + event.timestamp, event.sensor.getName())); if (!mCameraLiftRegistered) { - if (DBG) Slog.d(TAG, "Ignoring camera lift event because it's unregistered."); + if (DBG_CAMERA_LIFT) Slog.d(TAG, "Ignoring camera lift event because it's " + + "unregistered."); return; } if (event.sensor == mCameraLiftTriggerSensor) { Resources resources = mContext.getResources(); SensorManager sensorManager = (SensorManager) mContext.getSystemService( Context.SENSOR_SERVICE); - - if (DBG) { + boolean keyguardShowingAndNotOccluded = + mWindowManagerInternal.isKeyguardShowingAndNotOccluded(); + boolean interactive = mPowerManager.isInteractive(); + if (DBG_CAMERA_LIFT) { float[] values = event.values; - Slog.d(TAG, String.format("Received a camera lift trigger event: " + - "values=[%.4f].", values[0])); + Slog.d(TAG, String.format("Received a camera lift trigger " + + "event: values=[%.4f], keyguard showing: %b, interactive: %b", values[0], + keyguardShowingAndNotOccluded, interactive)); } - if (mKeyguardManager.isKeyguardLocked() || !mPowerManager.isInteractive()) { + if (keyguardShowingAndNotOccluded || !interactive) { if (handleCameraGesture(true /* useWakelock */, StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)) { MetricsLogger.action(mContext, MetricsEvent.ACTION_CAMERA_LIFT_TRIGGER); } - } else if (DBG) { - Slog.d(TAG, "Ignoring lift event because device is awake"); + } else { + if (DBG_CAMERA_LIFT) Slog.d(TAG, "Ignoring lift event"); } - mCameraLiftRegistered = sensorManager.requestTriggerSensor(mCameraLiftTriggerListener, - mCameraLiftTriggerSensor); + mCameraLiftRegistered = sensorManager.requestTriggerSensor( + mCameraLiftTriggerListener, mCameraLiftTriggerSensor); - if (DBG) Slog.d(TAG, "Camera lift trigger sensor re-registered: " + mCameraLiftRegistered); + if (DBG_CAMERA_LIFT) Slog.d(TAG, "Camera lift trigger sensor re-registered: " + + mCameraLiftRegistered); return; } } diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java index 7080c41fea26..516f8f67a790 100644 --- a/services/core/java/com/android/server/MasterClearReceiver.java +++ b/services/core/java/com/android/server/MasterClearReceiver.java @@ -16,6 +16,7 @@ package com.android.server; +import android.app.PendingIntent; import android.app.ProgressDialog; import android.content.BroadcastReceiver; import android.content.Context; @@ -23,6 +24,7 @@ import android.content.Intent; import android.os.AsyncTask; import android.os.RecoverySystem; import android.os.storage.StorageManager; +import android.provider.Settings; import android.telephony.euicc.EuiccManager; import android.util.Log; import android.util.Slog; @@ -31,14 +33,29 @@ import android.view.WindowManager; import com.android.internal.R; import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; public class MasterClearReceiver extends BroadcastReceiver { private static final String TAG = "MasterClear"; + private static final String ACTION_WIPE_EUICC_DATA = + "com.android.internal.action.wipe_euicc_data"; + private static final long DEFAULT_EUICC_WIPING_TIMEOUT_MILLIS = 30000L; // 30 s private boolean mWipeExternalStorage; - private boolean mWipeEims; + private boolean mWipeEsims; + private static CountDownLatch mEuiccFactoryResetLatch; @Override public void onReceive(final Context context, final Intent intent) { + if (ACTION_WIPE_EUICC_DATA.equals(intent.getAction())) { + if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) { + int detailedCode = intent.getIntExtra( + EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0); + Slog.e(TAG, "Error wiping euicc data, Detailed code = " + detailedCode); + } + mEuiccFactoryResetLatch.countDown(); + return; + } if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) { if (!"google.com".equals(intent.getStringExtra("from"))) { Slog.w(TAG, "Ignoring master clear request -- not from trusted server."); @@ -57,7 +74,7 @@ public class MasterClearReceiver extends BroadcastReceiver { final boolean shutdown = intent.getBooleanExtra("shutdown", false); final String reason = intent.getStringExtra(Intent.EXTRA_REASON); mWipeExternalStorage = intent.getBooleanExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false); - mWipeEims = intent.getBooleanExtra(Intent.EXTRA_WIPE_ESIMS, false); + mWipeEsims = intent.getBooleanExtra(Intent.EXTRA_WIPE_ESIMS, false); final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false) || intent.getBooleanExtra(Intent.EXTRA_FORCE_FACTORY_RESET, false); @@ -77,7 +94,7 @@ public class MasterClearReceiver extends BroadcastReceiver { } }; - if (mWipeExternalStorage || mWipeEims) { + if (mWipeExternalStorage || mWipeEsims) { // thr will be started at the end of this task. new WipeDataTask(context, thr).execute(); } else { @@ -112,10 +129,31 @@ public class MasterClearReceiver extends BroadcastReceiver { Context.STORAGE_SERVICE); sm.wipeAdoptableDisks(); } - if (mWipeEims) { + if (mWipeEsims) { EuiccManager euiccManager = (EuiccManager) mContext.getSystemService( Context.EUICC_SERVICE); - // STOPSHIP: add EuiccManager API to factory reset eUICC + Intent intent = new Intent(mContext, MasterClearReceiver.class); + intent.setAction(ACTION_WIPE_EUICC_DATA); + PendingIntent callbackIntent = PendingIntent.getBroadcast( + mContext, + 0 /* requestCode */, + intent, + PendingIntent.FLAG_UPDATE_CURRENT); + mEuiccFactoryResetLatch = new CountDownLatch(1); + euiccManager.eraseSubscriptions(callbackIntent); + try { + long waitingTime = Settings.Global.getLong( + mContext.getContentResolver(), + Settings.Global.EUICC_WIPING_TIMEOUT_MILLIS, + DEFAULT_EUICC_WIPING_TIMEOUT_MILLIS); + + if (!mEuiccFactoryResetLatch.await(waitingTime, TimeUnit.MILLISECONDS)) { + Slog.e(TAG, "Timeout wiping eUICC data."); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Slog.e(TAG, "Wiping eUICC data interrupted", e); + } } return null; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index c41748424b10..756e2748b8cb 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -81,6 +81,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.EventLog; +import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -175,6 +176,9 @@ public final class ActiveServices { long mStartVisibleTime; long mEndTime; int mNumActive; + + // Temp output of foregroundAppShownEnoughLocked + long mHideTime; } /** @@ -622,19 +626,19 @@ public final class ActiveServices { != ActivityManager.APP_START_MODE_NORMAL) { if (stopping == null) { stopping = new ArrayList<>(); - String compName = service.name.flattenToShortString(); - EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName); - StringBuilder sb = new StringBuilder(64); - sb.append("Stopping service due to app idle: "); - UserHandle.formatUid(sb, service.appInfo.uid); - sb.append(" "); - TimeUtils.formatDuration(service.createTime - - SystemClock.elapsedRealtime(), sb); - sb.append(" "); - sb.append(compName); - Slog.w(TAG, sb.toString()); - stopping.add(service); } + String compName = service.name.flattenToShortString(); + EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName); + StringBuilder sb = new StringBuilder(64); + sb.append("Stopping service due to app idle: "); + UserHandle.formatUid(sb, service.appInfo.uid); + sb.append(" "); + TimeUtils.formatDuration(service.createTime + - SystemClock.elapsedRealtime(), sb); + sb.append(" "); + sb.append(compName); + Slog.w(TAG, sb.toString()); + stopping.add(service); } } } @@ -736,50 +740,90 @@ public final class ActiveServices { } } + boolean foregroundAppShownEnoughLocked(ActiveForegroundApp aa, long nowElapsed) { + if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Shown enough: pkg=" + aa.mPackageName + ", uid=" + + aa.mUid); + boolean canRemove = false; + aa.mHideTime = Long.MAX_VALUE; + if (aa.mShownWhileTop) { + // If the app was ever at the top of the screen while the foreground + // service was running, then we can always just immediately remove it. + canRemove = true; + if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - shown while on top"); + } else if (mScreenOn || aa.mShownWhileScreenOn) { + final long minTime = aa.mStartVisibleTime + + (aa.mStartTime != aa.mStartVisibleTime + ? mAm.mConstants.FGSERVICE_SCREEN_ON_AFTER_TIME + : mAm.mConstants.FGSERVICE_MIN_SHOWN_TIME); + if (nowElapsed >= minTime) { + // If shown while the screen is on, and it has been shown for + // at least the minimum show time, then we can now remove it. + if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - shown long enough with screen on"); + canRemove = true; + } else { + // This is when we will be okay to stop telling the user. + long reportTime = nowElapsed + mAm.mConstants.FGSERVICE_MIN_REPORT_TIME; + aa.mHideTime = reportTime > minTime ? reportTime : minTime; + if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "NO -- wait " + (aa.mHideTime-nowElapsed) + + " with screen on"); + } + } else { + final long minTime = aa.mEndTime + + mAm.mConstants.FGSERVICE_SCREEN_ON_BEFORE_TIME; + if (nowElapsed >= minTime) { + // If the foreground service has only run while the screen is + // off, but it has been gone now for long enough that we won't + // care to tell the user about it when the screen comes back on, + // then we can remove it now. + if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - gone long enough with screen off"); + canRemove = true; + } else { + // This is when we won't care about this old fg service. + aa.mHideTime = minTime; + if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "NO -- wait " + (aa.mHideTime-nowElapsed) + + " with screen off"); + } + } + return canRemove; + } + void updateForegroundApps(ServiceMap smap) { // This is called from the handler without the lock held. ArrayList<ActiveForegroundApp> active = null; synchronized (mAm) { final long now = SystemClock.elapsedRealtime(); - final long nowPlusMin = now + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME; long nextUpdateTime = Long.MAX_VALUE; if (smap != null) { + if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Updating foreground apps for user " + + smap.mUserId); for (int i = smap.mActiveForegroundApps.size()-1; i >= 0; i--) { ActiveForegroundApp aa = smap.mActiveForegroundApps.valueAt(i); - if (aa.mEndTime != 0 && (mScreenOn || aa.mShownWhileScreenOn)) { - if (!aa.mShownWhileTop && aa.mEndTime < (aa.mStartVisibleTime - + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME)) { - // Check to see if this should still be displayed... we continue - // until it has been shown for at least the timeout duration. - if (nowPlusMin >= aa.mStartVisibleTime) { - // All over! - smap.mActiveForegroundApps.removeAt(i); - smap.mActiveForegroundAppsChanged = true; - continue; - } else { - long hideTime = aa.mStartVisibleTime - + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME; - if (hideTime < nextUpdateTime) { - nextUpdateTime = hideTime; - } - } - } else { + if (aa.mEndTime != 0) { + boolean canRemove = foregroundAppShownEnoughLocked(aa, now); + if (canRemove) { // This was up for longer than the timeout, so just remove immediately. smap.mActiveForegroundApps.removeAt(i); smap.mActiveForegroundAppsChanged = true; continue; } + if (aa.mHideTime < nextUpdateTime) { + nextUpdateTime = aa.mHideTime; + } } if (!aa.mAppOnTop) { if (active == null) { active = new ArrayList<>(); } + if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Adding active: pkg=" + + aa.mPackageName + ", uid=" + aa.mUid); active.add(aa); } } smap.removeMessages(ServiceMap.MSG_UPDATE_FOREGROUND_APPS); if (nextUpdateTime < Long.MAX_VALUE) { - Message msg = smap.obtainMessage(); + if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Next update time in: " + + (nextUpdateTime-now)); + Message msg = smap.obtainMessage(ServiceMap.MSG_UPDATE_FOREGROUND_APPS); smap.sendMessageAtTime(msg, nextUpdateTime + SystemClock.uptimeMillis() - SystemClock.elapsedRealtime()); } @@ -882,15 +926,14 @@ public final class ActiveServices { active.mNumActive--; if (active.mNumActive <= 0) { active.mEndTime = SystemClock.elapsedRealtime(); - if (active.mShownWhileTop || active.mEndTime >= (active.mStartVisibleTime - + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME)) { + if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Ended running of service"); + if (foregroundAppShownEnoughLocked(active, active.mEndTime)) { // Have been active for long enough that we will remove it immediately. smap.mActiveForegroundApps.remove(r.packageName); smap.mActiveForegroundAppsChanged = true; requestUpdateActiveForegroundAppsLocked(smap, 0); - } else { - requestUpdateActiveForegroundAppsLocked(smap, active.mStartVisibleTime - + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME); + } else if (active.mHideTime < Long.MAX_VALUE){ + requestUpdateActiveForegroundAppsLocked(smap, active.mHideTime); } } } @@ -904,26 +947,44 @@ public final class ActiveServices { // services that were started while the screen was off. if (screenOn) { final long nowElapsed = SystemClock.elapsedRealtime(); + if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Screen turned on"); for (int i = mServiceMap.size()-1; i >= 0; i--) { ServiceMap smap = mServiceMap.valueAt(i); + long nextUpdateTime = Long.MAX_VALUE; boolean changed = false; for (int j = smap.mActiveForegroundApps.size()-1; j >= 0; j--) { ActiveForegroundApp active = smap.mActiveForegroundApps.valueAt(j); - if (!active.mShownWhileScreenOn) { - changed = true; - active.mShownWhileScreenOn = mScreenOn; - active.mStartVisibleTime = nowElapsed; - if (active.mEndTime != 0) { - active.mEndTime = nowElapsed; + if (active.mEndTime == 0) { + if (!active.mShownWhileScreenOn) { + active.mShownWhileScreenOn = true; + active.mStartVisibleTime = nowElapsed; + } + } else { + if (!active.mShownWhileScreenOn + && active.mStartVisibleTime == active.mStartTime) { + // If this was never shown while the screen was on, then we will + // count the time it started being visible as now, to tell the user + // about it now that they have a screen to look at. + active.mEndTime = active.mStartVisibleTime = nowElapsed; + } + if (foregroundAppShownEnoughLocked(active, nowElapsed)) { + // Have been active for long enough that we will remove it + // immediately. + smap.mActiveForegroundApps.remove(active.mPackageName); + smap.mActiveForegroundAppsChanged = true; + changed = true; + } else { + if (active.mHideTime < nextUpdateTime) { + nextUpdateTime = active.mHideTime; + } } } } if (changed) { - requestUpdateActiveForegroundAppsLocked(smap, - nowElapsed + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME); - } else if (smap.mActiveForegroundApps.size() > 0) { - // Just being paranoid. + // Need to immediately update. requestUpdateActiveForegroundAppsLocked(smap, 0); + } else if (nextUpdateTime < Long.MAX_VALUE) { + requestUpdateActiveForegroundAppsLocked(smap, nextUpdateTime); } } } @@ -2318,7 +2379,7 @@ public final class ActiveServices { return true; } - // Is someone still bound to us keepign us running? + // Is someone still bound to us keeping us running? if (!knowConn) { hasConn = r.hasAutoCreateConnections(); } @@ -3741,6 +3802,17 @@ public final class ActiveServices { pw.println(); } } + if (smap.hasMessagesOrCallbacks()) { + if (needSep) { + pw.println(); + } + printedAnything = true; + needSep = true; + pw.print(" Handler - user "); + pw.print(user); + pw.println(":"); + smap.dumpMine(new PrintWriterPrinter(pw), " "); + } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 5749f31ddb2e..6c3fe915f386 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -35,8 +35,14 @@ final class ActivityManagerConstants extends ContentObserver { // Key names stored in the settings value. private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes"; private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time"; - private static final String KEY_FOREGROUND_SERVICE_UI_MIN_TIME - = "foreground_service_ui_min_time"; + private static final String KEY_FGSERVICE_MIN_SHOWN_TIME + = "fgservice_min_shown_time"; + private static final String KEY_FGSERVICE_MIN_REPORT_TIME + = "fgservice_min_report_time"; + private static final String KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME + = "fgservice_screen_on_before_time"; + private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME + = "fgservice_screen_on_after_time"; private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time"; private static final String KEY_GC_TIMEOUT = "gc_timeout"; private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval"; @@ -58,7 +64,10 @@ final class ActivityManagerConstants extends ContentObserver { private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000; - private static final long DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME = 30*1000; + private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000; + private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000; + private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000; + private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000; private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000; private static final long DEFAULT_GC_TIMEOUT = 5*1000; private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000; @@ -85,8 +94,26 @@ final class ActivityManagerConstants extends ContentObserver { // before we start restricting what it can do. public long BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME; - // The minimum time a foreground service will be shown as running in the notification UI. - public long FOREGROUND_SERVICE_UI_MIN_TIME = DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME; + // The minimum time we allow a foreground service to run with a notification and the + // screen on without otherwise telling the user about it. (If it runs for less than this, + // it will still be reported to the user as a running app for at least this amount of time.) + public long FGSERVICE_MIN_SHOWN_TIME = DEFAULT_FGSERVICE_MIN_SHOWN_TIME; + + // If a foreground service is shown for less than FGSERVICE_MIN_SHOWN_TIME, we will display + // the background app running notification about it for at least this amount of time (if it + // is larger than the remaining shown time). + public long FGSERVICE_MIN_REPORT_TIME = DEFAULT_FGSERVICE_MIN_REPORT_TIME; + + // The minimum amount of time the foreground service needs to have remain being shown + // before the screen goes on for us to consider it not worth showing to the user. That is + // if an app has a foreground service that stops itself this amount of time or more before + // the user turns on the screen, we will just let it go without the user being told about it. + public long FGSERVICE_SCREEN_ON_BEFORE_TIME = DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME; + + // The minimum amount of time a foreground service should remain reported to the user if + // it is stopped when the screen turns on. This is the time from when the screen turns + // on until we will stop reporting it. + public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME; // How long we will retain processes hosting content providers in the "last activity" // state before allowing them to drop down to the regular cached LRU list. This is @@ -225,8 +252,14 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_MAX_CACHED_PROCESSES); BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME, DEFAULT_BACKGROUND_SETTLE_TIME); - FOREGROUND_SERVICE_UI_MIN_TIME = mParser.getLong(KEY_FOREGROUND_SERVICE_UI_MIN_TIME, - DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME); + FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME, + DEFAULT_FGSERVICE_MIN_SHOWN_TIME); + FGSERVICE_MIN_REPORT_TIME = mParser.getLong(KEY_FGSERVICE_MIN_REPORT_TIME, + DEFAULT_FGSERVICE_MIN_REPORT_TIME); + FGSERVICE_SCREEN_ON_BEFORE_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME, + DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME); + FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME, + DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME); CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME, DEFAULT_CONTENT_PROVIDER_RETAIN_TIME); GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT, @@ -284,8 +317,14 @@ final class ActivityManagerConstants extends ContentObserver { pw.println(MAX_CACHED_PROCESSES); pw.print(" "); pw.print(KEY_BACKGROUND_SETTLE_TIME); pw.print("="); pw.println(BACKGROUND_SETTLE_TIME); - pw.print(" "); pw.print(KEY_FOREGROUND_SERVICE_UI_MIN_TIME); pw.print("="); - pw.println(FOREGROUND_SERVICE_UI_MIN_TIME); + pw.print(" "); pw.print(KEY_FGSERVICE_MIN_SHOWN_TIME); pw.print("="); + pw.println(FGSERVICE_MIN_SHOWN_TIME); + pw.print(" "); pw.print(KEY_FGSERVICE_MIN_REPORT_TIME); pw.print("="); + pw.println(FGSERVICE_MIN_REPORT_TIME); + pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME); pw.print("="); + pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME); + pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("="); + pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print(" "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("="); pw.println(CONTENT_PROVIDER_RETAIN_TIME); pw.print(" "); pw.print(KEY_GC_TIMEOUT); pw.print("="); diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index ff5efde27f93..f440100b7621 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -79,6 +79,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_SAVED_STATE = DEBUG_ALL_ACTIVITIES || false; static final boolean DEBUG_SCREENSHOTS = DEBUG_ALL_ACTIVITIES || false; static final boolean DEBUG_SERVICE = DEBUG_ALL || false; + static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false; static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false; static final boolean DEBUG_STACK = DEBUG_ALL || false; static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || false; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 76456f3edccc..5b2e77937199 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1804,7 +1804,7 @@ public class ActivityManagerService extends IActivityManager.Stub } AppErrorResult res = (AppErrorResult) data.get("result"); if (mShowDialogs && !mSleeping && !mShuttingDown) { - Dialog d = new StrictModeViolationDialog(mContext, + Dialog d = new StrictModeViolationDialog(mUiContext, ActivityManagerService.this, res, proc); d.show(); proc.crashDialog = d; @@ -4118,7 +4118,8 @@ public class ActivityManagerService extends IActivityManager.Stub ri.activityInfo.packageName, ri.activityInfo.name)); mActivityStarter.startActivityLocked(null, intent, null /*ephemeralIntent*/, null, ri.activityInfo, null /*rInfo*/, null, null, null, null, 0, 0, 0, - null, 0, 0, 0, null, false, false, null, null, null); + null, 0, 0, 0, null, false, false, null, null, null, + "startSetupActivity"); } } } @@ -4457,8 +4458,9 @@ public class ActivityManagerService extends IActivityManager.Stub container.checkEmbeddedAllowedInner(userId, intent, mimeType); intent.addFlags(FORCE_NEW_TASK_FLAGS); - return mActivityStarter.startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, - null, 0, 0, null, null, null, null, false, userId, container, null); + return mActivityStarter.startActivityMayWait(null, -1, null, intent, mimeType, null, null, + null, null, 0, 0, null, null, null, null, false, userId, container, null, + "startActivity"); } @Override @@ -4471,7 +4473,8 @@ public class ActivityManagerService extends IActivityManager.Stub // TODO: Switch to user app stacks here. return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, - profilerInfo, null, null, bOptions, false, userId, null, null); + profilerInfo, null, null, bOptions, false, userId, null, null, + "startActivityAsUser"); } @Override @@ -4534,7 +4537,8 @@ public class ActivityManagerService extends IActivityManager.Stub try { int ret = mActivityStarter.startActivityMayWait(null, targetUid, targetPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null, - null, null, bOptions, ignoreTargetSecurity, userId, null, null); + null, null, bOptions, ignoreTargetSecurity, userId, null, null, + "startActivityAsCaller"); return ret; } catch (SecurityException e) { // XXX need to figure out how to propagate to original app. @@ -4563,7 +4567,7 @@ public class ActivityManagerService extends IActivityManager.Stub // TODO: Switch to user app stacks here. mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, res, null, - bOptions, false, userId, null, null); + bOptions, false, userId, null, null, "startActivityAndWait"); return res; } @@ -4577,7 +4581,7 @@ public class ActivityManagerService extends IActivityManager.Stub // TODO: Switch to user app stacks here. int ret = mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, - null, null, config, bOptions, false, userId, null, null); + null, null, config, bOptions, false, userId, null, null, "startActivityWithConfig"); return ret; } @@ -4634,7 +4638,7 @@ public class ActivityManagerService extends IActivityManager.Stub // TODO: Switch to user app stacks here. return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent, resolvedType, session, interactor, null, null, 0, startFlags, profilerInfo, null, - null, bOptions, false, userId, null, null); + null, bOptions, false, userId, null, null, "startVoiceActivity"); } @Override @@ -4653,7 +4657,7 @@ public class ActivityManagerService extends IActivityManager.Stub ALLOW_FULL_ONLY, "startAssistantActivity", null); return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent, resolvedType, null, null, null, null, 0, 0, null, null, null, bOptions, false, - userId, null, null); + userId, null, null, "startAssistantActivity"); } @Override @@ -4826,7 +4830,7 @@ public class ActivityManagerService extends IActivityManager.Stub null /*ephemeralIntent*/, r.resolvedType, aInfo, null /*rInfo*/, null, null, resultTo != null ? resultTo.appToken : null, resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, -1, r.launchedFromUid, 0, options, - false, false, null, null, null); + false, false, null, null, null, "startNextMatchingActivity"); Binder.restoreCallingIdentity(origId); r.finishing = wasFinishing; @@ -4858,7 +4862,7 @@ public class ActivityManagerService extends IActivityManager.Stub final int startActivityInPackage(int uid, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, Bundle bOptions, int userId, - IActivityContainer container, TaskRecord inTask) { + IActivityContainer container, TaskRecord inTask, String reason) { userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null); @@ -4866,7 +4870,7 @@ public class ActivityManagerService extends IActivityManager.Stub // TODO: Switch to user app stacks here. int ret = mActivityStarter.startActivityMayWait(null, uid, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, - null, null, null, bOptions, false, userId, container, inTask); + null, null, null, bOptions, false, userId, container, inTask, reason); return ret; } @@ -4874,12 +4878,13 @@ public class ActivityManagerService extends IActivityManager.Stub public final int startActivities(IApplicationThread caller, String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle bOptions, int userId) { - enforceNotIsolatedCaller("startActivities"); + final String reason = "startActivities"; + enforceNotIsolatedCaller(reason); userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), - userId, false, ALLOW_FULL_ONLY, "startActivity", null); + userId, false, ALLOW_FULL_ONLY, reason, null); // TODO: Switch to user app stacks here. int ret = mActivityStarter.startActivities(caller, -1, callingPackage, intents, - resolvedTypes, resultTo, bOptions, userId); + resolvedTypes, resultTo, bOptions, userId, reason); return ret; } @@ -4887,11 +4892,12 @@ public class ActivityManagerService extends IActivityManager.Stub Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle bOptions, int userId) { + final String reason = "startActivityInPackage"; userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), - userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null); + userId, false, ALLOW_FULL_ONLY, reason, null); // TODO: Switch to user app stacks here. int ret = mActivityStarter.startActivities(null, uid, callingPackage, intents, resolvedTypes, - resultTo, bOptions, userId); + resultTo, bOptions, userId, reason); return ret; } @@ -10323,11 +10329,11 @@ public class ActivityManagerService extends IActivityManager.Stub if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToFront: moving taskId=" + taskId); synchronized(this) { - moveTaskToFrontLocked(taskId, flags, bOptions); + moveTaskToFrontLocked(taskId, flags, bOptions, false /* fromRecents */); } } - void moveTaskToFrontLocked(int taskId, int flags, Bundle bOptions) { + void moveTaskToFrontLocked(int taskId, int flags, Bundle bOptions, boolean fromRecents) { ActivityOptions options = ActivityOptions.fromBundle(bOptions); if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(), @@ -10360,7 +10366,7 @@ public class ActivityManagerService extends IActivityManager.Stub // We are reshowing a task, use a starting window to hide the initial draw delay // so the transition can start earlier. topActivity.showStartingWindow(null /* prev */, false /* newTask */, - true /* taskSwitch */); + true /* taskSwitch */, fromRecents); } } finally { Binder.restoreCallingIdentity(origId); @@ -12563,6 +12569,7 @@ public class ActivityManagerService extends IActivityManager.Stub Binder.restoreCallingIdentity(ident); } } + closeSystemDialogs("setLockScreenShown"); } @Override @@ -15004,6 +15011,10 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { dumpLastANRLocked(pw); } + } else if ("starter".equals(cmd)) { + synchronized (this) { + dumpActivityStarterLocked(pw); + } } else if ("recents".equals(cmd) || "r".equals(cmd)) { synchronized (this) { dumpRecentsLocked(fd, pw, args, opti, true, dumpPackage); @@ -15237,6 +15248,11 @@ public class ActivityManagerService extends IActivityManager.Stub if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } + dumpActivityStarterLocked(pw); + pw.println(); + if (dumpAll) { + pw.println("-------------------------------------------------------------------------------"); + } dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage); if (mAssociations.size() > 0) { pw.println(); @@ -15302,6 +15318,11 @@ public class ActivityManagerService extends IActivityManager.Stub if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } + dumpActivityStarterLocked(pw); + pw.println(); + if (dumpAll) { + pw.println("-------------------------------------------------------------------------------"); + } dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage); if (mAssociations.size() > 0) { pw.println(); @@ -15321,14 +15342,19 @@ public class ActivityManagerService extends IActivityManager.Stub } private void dumpLastANRLocked(PrintWriter pw) { + pw.println("ACTIVITY MANAGER ACTIVITIES (dumpsys activity lastanr)"); if (mLastANRState == null) { - pw.println("ACTIVITY MANAGER ACTIVITIES (dumpsys activity lastanr)"); pw.println(" <no ANR has occurred since boot>"); } else { pw.println(mLastANRState); } } + private void dumpActivityStarterLocked(PrintWriter pw) { + pw.println("ACTIVITY MANAGER ACTIVITIES (dumpsys activity starter)"); + mActivityStarter.dump(pw, ""); + } + void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) { dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage, @@ -15355,7 +15381,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (needSep) { pw.println(); } - needSep = true; printedAnything = true; mStackSupervisor.dump(pw, " "); } @@ -23815,9 +23840,10 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void notifyAppTransitionStarting(SparseIntArray reasons) { + public void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp) { synchronized (ActivityManagerService.this) { - mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(reasons); + mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting( + reasons, timestamp); } } @@ -24078,9 +24104,12 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(" Reason: " + reason); } pw.println(); + mActivityStarter.dump(pw, " "); + pw.println(); + pw.println("-------------------------------------------------------------------------------"); dumpActivitiesLocked(null /* fd */, pw, null /* args */, 0 /* opti */, true /* dumpAll */, false /* dumpClient */, null /* dumpPackage */, - "ACTIVITY MANAGER ACTIVITIES (dumpsys activity lastanr)"); + "" /* header */); pw.println(); pw.close(); @@ -24322,7 +24351,7 @@ public class ActivityManagerService extends IActivityManager.Stub } return mActivityStarter.startActivityMayWait(appThread, -1, callingPackage, intent, resolvedType, null, null, null, null, 0, 0, null, null, - null, bOptions, false, callingUser, null, tr); + null, bOptions, false, callingUser, null, tr, "AppTaskImpl"); } @Override diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java index bf7b663454b8..98815d7e18c7 100644 --- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java @@ -230,12 +230,12 @@ class ActivityMetricsLogger { /** * Notifies the tracker that all windows of the app have been drawn. */ - void notifyWindowsDrawn(int stackId) { + void notifyWindowsDrawn(int stackId, long timestamp) { final StackTransitionInfo info = mStackTransitionInfo.get(stackId); if (info == null || info.loggedWindowsDrawn) { return; } - info.windowsDrawnDelayMs = calculateCurrentDelay(); + info.windowsDrawnDelayMs = calculateDelay(timestamp); info.loggedWindowsDrawn = true; if (allStacksWindowsDrawn() && mLoggedTransitionStarting) { reset(false /* abort */); @@ -245,13 +245,13 @@ class ActivityMetricsLogger { /** * Notifies the tracker that the starting window was drawn. */ - void notifyStartingWindowDrawn(int stackId) { + void notifyStartingWindowDrawn(int stackId, long timestamp) { final StackTransitionInfo info = mStackTransitionInfo.get(stackId); if (info == null || info.loggedStartingWindowDrawn) { return; } info.loggedStartingWindowDrawn = true; - info.startingWindowDelayMs = calculateCurrentDelay(); + info.startingWindowDelayMs = calculateDelay(timestamp); } /** @@ -260,11 +260,11 @@ class ActivityMetricsLogger { * @param stackIdReasons A map from stack id to a reason integer, which must be on of * ActivityManagerInternal.APP_TRANSITION_* reasons. */ - void notifyTransitionStarting(SparseIntArray stackIdReasons) { + void notifyTransitionStarting(SparseIntArray stackIdReasons, long timestamp) { if (!isAnyTransitionActive() || mLoggedTransitionStarting) { return; } - mCurrentTransitionDelayMs = calculateCurrentDelay(); + mCurrentTransitionDelayMs = calculateDelay(timestamp); mLoggedTransitionStarting = true; for (int index = stackIdReasons.size() - 1; index >= 0; index--) { final int stackId = stackIdReasons.keyAt(index); @@ -344,6 +344,11 @@ class ActivityMetricsLogger { return (int) (SystemClock.uptimeMillis() - mCurrentTransitionStartTime); } + private int calculateDelay(long timestamp) { + // Shouldn't take more than 25 days to launch an app, so int is fine here. + return (int) (timestamp - mCurrentTransitionStartTime); + } + private void logAppTransitionMultiEvents() { for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) { final StackTransitionInfo info = mStackTransitionInfo.valueAt(index); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index ec6a4f6b7f7c..68f4d0d99f93 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1928,18 +1928,19 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } @Override - public void onStartingWindowDrawn() { + public void onStartingWindowDrawn(long timestamp) { synchronized (service) { - mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn(getStackId()); + mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn( + getStackId(), timestamp); } } @Override - public void onWindowsDrawn() { + public void onWindowsDrawn(long timestamp) { synchronized (service) { - mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(getStackId()); + mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(getStackId(), timestamp); if (displayStartTime != 0) { - reportLaunchTimeLocked(SystemClock.uptimeMillis()); + reportLaunchTimeLocked(timestamp); } mStackSupervisor.sendWaitingVisibleReportLocked(this); startTime = 0; @@ -2153,6 +2154,11 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) { + showStartingWindow(prev, newTask, taskSwitch, false /* fromRecents */); + } + + void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch, + boolean fromRecents) { if (mWindowContainerController == null) { return; } @@ -2167,7 +2173,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(), allowTaskSnapshot(), - state.ordinal() >= RESUMED.ordinal() && state.ordinal() <= STOPPED.ordinal()); + state.ordinal() >= RESUMED.ordinal() && state.ordinal() <= STOPPED.ordinal(), + fromRecents); if (shown) { mStartingWindowState = STARTING_WINDOW_SHOWN; } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index a45becdd1dda..9cde98598987 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -3920,7 +3920,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai destIntent, null /*ephemeralIntent*/, null, aInfo, null /*rInfo*/, null, null, parent.appToken, null, 0, -1, parent.launchedFromUid, parent.launchedFromPackage, -1, parent.launchedFromUid, 0, null, - false, true, null, null, null); + false, true, null, null, null, "navigateUpTo"); foundParentInTask = res == ActivityManager.START_SUCCESS; } catch (RemoteException e) { foundParentInTask = false; diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 25dc354ca3a7..1ccac1b8dc8a 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2273,6 +2273,31 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return null; } + /** + * Get next valid stack for launching provided activity in the system. This will search across + * displays and stacks in last-focused order for a focusable and visible stack, except those + * that are on a currently focused display. + * + * @param r The activity that is being launched. + * @param currentFocus The display that previously had focus and thus needs to be ignored when + * searching for the next candidate. + * @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) { + continue; + } + final ActivityStack stack = getValidLaunchStackOnDisplay(displayId, r); + if (stack != null) { + return stack; + } + } + return null; + } + ActivityRecord getHomeActivity() { return getHomeActivityForUser(mCurrentUser); } @@ -5135,7 +5160,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D && task.getRootActivity() != null) { mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */); mActivityMetricsLogger.notifyActivityLaunching(); - mService.moveTaskToFrontLocked(task.taskId, 0, bOptions); + mService.moveTaskToFrontLocked(task.taskId, 0, bOptions, true /* fromRecents */); mActivityMetricsLogger.notifyActivityLaunched(ActivityManager.START_TASK_TO_FRONT, task.getTopActivity()); @@ -5159,7 +5184,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); userId = task.userId; int result = mService.startActivityInPackage(callingUid, callingPackage, intent, null, - null, null, 0, 0, bOptions, userId, null, task); + null, null, 0, 0, bOptions, userId, null, task, "startActivityFromRecents"); if (launchStackId == DOCKED_STACK_ID) { setResizingDuringAnimation(task); } diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 1ed2ac19ea9d..be30d5aaeab9 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -115,6 +115,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.service.voice.IVoiceInteractionSession; +import android.text.TextUtils; import android.util.EventLog; import android.util.Slog; @@ -124,7 +125,10 @@ import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch; import com.android.server.pm.InstantAppResolver; import com.android.server.wm.WindowManagerService; +import java.io.PrintWriter; +import java.text.DateFormat; import java.util.ArrayList; +import java.util.Date; /** * Controller for interpreting how and then launching activities. @@ -188,6 +192,19 @@ class ActivityStarter { private boolean mUsingVr2dDisplay; + // Last home activity record we attempted to start + private final ActivityRecord[] mLastHomeActivityStartRecord = new ActivityRecord[1]; + // The result of the last home activity we attempted to start. + private int mLastHomeActivityStartResult; + // Last activity record we attempted to start + private final ActivityRecord[] mLastStartActivityRecord = new ActivityRecord[1]; + // The result of the last activity we attempted to start. + private int mLastStartActivityResult; + // Time in milli seconds we attempted to start the last activity. + private long mLastStartActivityTimeMs; + // The reason we were trying to start the last activity + private String mLastStartReason; + private void reset() { mStartActivity = null; mIntent = null; @@ -236,7 +253,37 @@ class ActivityStarter { mUsingVr2dDisplay = false; } - final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent, + int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent, + String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, + String callingPackage, int realCallingPid, int realCallingUid, int startFlags, + ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, + ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container, + TaskRecord inTask, String reason) { + + if (TextUtils.isEmpty(reason)) { + throw new IllegalArgumentException("Need to specify a reason."); + } + mLastStartReason = reason; + mLastStartActivityTimeMs = System.currentTimeMillis(); + mLastStartActivityRecord[0] = null; + + mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType, + aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode, + callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, + options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord, + container, inTask); + + if (outActivity != null) { + // mLastStartActivityRecord[0] is set in the call to startActivity above. + outActivity[0] = mLastStartActivityRecord[0]; + } + return mLastStartActivityResult; + } + + /** DO NOT call this method directly. Use {@link #startActivityLocked} instead. */ + private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent, String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, @@ -592,13 +639,14 @@ class ActivityStarter { void startHomeActivityLocked(Intent intent, ActivityInfo aInfo, String reason) { mSupervisor.moveHomeStackTaskToTop(reason); - startActivityLocked(null /*caller*/, intent, null /*ephemeralIntent*/, - null /*resolvedType*/, aInfo, null /*rInfo*/, null /*voiceSession*/, - null /*voiceInteractor*/, null /*resultTo*/, null /*resultWho*/, - 0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/, null /*callingPackage*/, - 0 /*realCallingPid*/, 0 /*realCallingUid*/, 0 /*startFlags*/, null /*options*/, - false /*ignoreTargetSecurity*/, false /*componentSpecified*/, null /*outActivity*/, - null /*container*/, null /*inTask*/); + mLastHomeActivityStartResult = startActivityLocked(null /*caller*/, intent, + null /*ephemeralIntent*/, null /*resolvedType*/, aInfo, null /*rInfo*/, + null /*voiceSession*/, null /*voiceInteractor*/, null /*resultTo*/, + null /*resultWho*/, 0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/, + null /*callingPackage*/, 0 /*realCallingPid*/, 0 /*realCallingUid*/, + 0 /*startFlags*/, null /*options*/, false /*ignoreTargetSecurity*/, + false /*componentSpecified*/, mLastHomeActivityStartRecord /*outActivity*/, + null /*container*/, null /*inTask*/, "startHomeActivity: " + reason); if (mSupervisor.inResumeTopActivity) { // If we are in resume section already, home activity will be initialized, but not // resumed (to avoid recursive resume) and will stay that way until something pokes it @@ -623,7 +671,7 @@ class ActivityStarter { IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, WaitResult outResult, Configuration globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, int userId, - IActivityContainer iContainer, TaskRecord inTask) { + IActivityContainer iContainer, TaskRecord inTask, String reason) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -778,7 +826,7 @@ class ActivityStarter { resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity, componentSpecified, outRecord, container, - inTask); + inTask, reason); Binder.restoreCallingIdentity(origId); @@ -841,7 +889,7 @@ class ActivityStarter { final int startActivities(IApplicationThread caller, int callingUid, String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo, - Bundle bOptions, int userId) { + Bundle bOptions, int userId, String reason) { if (intents == null) { throw new NullPointerException("intents is null"); } @@ -903,7 +951,7 @@ class ActivityStarter { resolvedTypes[i], aInfo, null /*rInfo*/, null, null, resultTo, null, -1, callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, 0, - options, false, componentSpecified, outActivity, null, null); + options, false, componentSpecified, outActivity, null, null, reason); if (res < 0) { return res; } @@ -2021,7 +2069,18 @@ class ActivityStarter { return mSupervisor.mFocusedStack; } - if (mSourceDisplayId == DEFAULT_DISPLAY) { + if (mSourceDisplayId != DEFAULT_DISPLAY) { + // Try to put the activity in a stack on a secondary display. + stack = mSupervisor.getValidLaunchStackOnDisplay(mSourceDisplayId, r); + if (stack == null) { + // If source display is not suitable - look for topmost valid stack in the system. + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Can't launch on mSourceDisplayId=" + mSourceDisplayId + + ", looking on all displays."); + stack = mSupervisor.getNextValidLaunchStackLocked(r, mSourceDisplayId); + } + } + if (stack == null) { // We first try to put the task in the first dynamic stack on home display. final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks; for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { @@ -2037,8 +2096,6 @@ class ActivityStarter { bounds != null ? FREEFORM_WORKSPACE_STACK_ID : FULLSCREEN_WORKSPACE_STACK_ID; stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP); - } else { - stack = mSupervisor.getValidLaunchStackOnDisplay(mSourceDisplayId, r); } if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r=" + r + " stackId=" + stack.mStackId); @@ -2246,4 +2303,40 @@ class ActivityStarter { } return didSomething; } + + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "ActivityStarter:"); + prefix = prefix + " "; + + pw.println(prefix + "mLastStartReason=" + mLastStartReason); + pw.println(prefix + "mLastStartActivityTimeMs=" + + DateFormat.getDateTimeInstance().format(new Date(mLastStartActivityTimeMs))); + pw.println(prefix + "mLastStartActivityResult=" + mLastStartActivityResult); + ActivityRecord r = mLastStartActivityRecord[0]; + if (r != null) { + pw.println(prefix + "mLastStartActivityRecord:"); + r.dump(pw, prefix + " "); + } + pw.println(prefix + "mLastHomeActivityStartResult=" + mLastHomeActivityStartResult); + r = mLastHomeActivityStartRecord[0]; + if (r != null) { + pw.println(prefix + "mLastHomeActivityStartRecord:"); + r.dump(pw, prefix + " "); + } + if (mStartActivity != null) { + pw.println(prefix + "mStartActivity:"); + mStartActivity.dump(pw, prefix + " "); + } + if (mIntent != null) { + pw.println(prefix + "mIntent=" + mIntent); + } + if (mOptions != null) { + pw.println(prefix + "mOptions=" + mOptions); + } + pw.println(prefix + "mLaunchSingleTop=" + mLaunchSingleTop + + " mLaunchSingleInstance=" + mLaunchSingleInstance + + " mLaunchSingleTask=" + mLaunchSingleTask + + " mLaunchFlags=0x" + Integer.toHexString(mLaunchFlags) + + " mDoResume=" + mDoResume + " mAddingToTask=" + mAddingToTask); + } } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index a83f989fbc5d..0d1c579f2b81 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -412,7 +412,7 @@ class AppErrors { task.mCallingPackage, task.intent, null, null, null, 0, 0, ActivityOptions.makeBasic().toBundle(), - task.userId, null, null); + task.userId, null, null, "AppErrors"); } } } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 1705b5846cc3..064ca58dee96 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -614,9 +614,10 @@ public final class BroadcastQueue { skip = true; } - if (!skip && (filter.receiverList.app == null || filter.receiverList.app.crashing)) { + if (!skip && (filter.receiverList.app == null || filter.receiverList.app.killed + || filter.receiverList.app.crashing)) { Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r - + " to " + filter.receiverList + ": process crashing"); + + " to " + filter.receiverList + ": process gone or crashing"); skip = true; } @@ -1317,7 +1318,7 @@ public final class BroadcastQueue { } // Is this receiver's application already running? - if (app != null && app.thread != null) { + if (app != null && app.thread != null && !app.killed) { try { app.addPackage(info.activityInfo.packageName, info.activityInfo.applicationInfo.versionCode, mService.mProcessStats); diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 6eca3fa7350a..cad5dcf6b565 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -346,7 +346,7 @@ final class PendingIntentRecord extends IIntentSender.Stub { } else { owner.startActivityInPackage(uid, key.packageName, finalIntent, resolvedType, resultTo, resultWho, requestCode, 0, - options, userId, container, null); + options, userId, container, null, "PendingIntentRecord"); } } catch (RuntimeException e) { Slog.w(TAG, "Unable to send startActivity intent", e); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index ba8fa31dc46c..f720cd51cd86 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -600,6 +600,7 @@ final class UserController { void finishUserStopping(final int userId, final UserState uss) { // On to the next. final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN); + shutdownIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); // This is the result receiver for the final shutdown broadcast. final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() { @Override diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java index 9970c82e2c75..b89586dc59dc 100644 --- a/services/core/java/com/android/server/am/UserState.java +++ b/services/core/java/com/android/server/am/UserState.java @@ -69,7 +69,6 @@ public final class UserState { public boolean setState(int oldState, int newState) { if (state == oldState) { setState(newState); - EventLogTags.writeAmUserStateChanged(mHandle.getIdentifier(), newState); return true; } else { Slog.w(TAG, "Expected user " + mHandle.getIdentifier() + " in state " @@ -84,6 +83,7 @@ public final class UserState { } Slog.i(TAG, "User " + mHandle.getIdentifier() + " state changed from " + stateToString(state) + " to " + stateToString(newState)); + EventLogTags.writeAmUserStateChanged(mHandle.getIdentifier(), newState); lastState = state; state = newState; } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 5a7f6e3e50b0..e5ab784df882 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -166,7 +166,6 @@ public class AudioService extends IAudioService.Stub /** debug calls to devices APIs */ protected static final boolean DEBUG_DEVICES = Log.isLoggable(TAG + ".DEVICES", Log.DEBUG); - /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; @@ -692,6 +691,7 @@ public class AudioService extends IAudioService.Stub // the mcc is read by onConfigureSafeVolume() mSafeMediaVolumeIndex = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_index) * 10; + mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex(); mUseFixedVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); @@ -1347,7 +1347,7 @@ public class AudioService extends IAudioService.Stub // This is simulated by stepping by the full allowed volume range if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && (device & mSafeMediaVolumeDevices) != 0) { - step = mSafeMediaVolumeIndex; + step = safeMediaVolumeIndex(device); } else { step = streamState.getMaxIndex(); } @@ -1693,7 +1693,7 @@ public class AudioService extends IAudioService.Stub if (index != 0) { if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && (device & mSafeMediaVolumeDevices) != 0) { - index = mSafeMediaVolumeIndex; + index = safeMediaVolumeIndex(device); } else { index = streamState.getMaxIndex(); } @@ -3436,7 +3436,7 @@ public class AudioService extends IAudioService.Stub MUSIC_ACTIVE_POLL_PERIOD_MS); int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device); if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && - (index > mSafeMediaVolumeIndex)) { + (index > safeMediaVolumeIndex(device))) { // Approximate cumulative active music time mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS; if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) { @@ -3454,12 +3454,40 @@ public class AudioService extends IAudioService.Stub mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget(); } + private int getSafeUsbMediaVolumeIndex() + { + // determine UI volume index corresponding to the wanted safe gain in dBFS + int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; + int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; + + while (Math.abs(max-min) > 1) { + int index = (max + min) / 2; + float gainDB = AudioSystem.getStreamVolumeDB( + AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET); + if (gainDB == Float.NaN) { + //keep last min in case of read error + break; + } else if (gainDB == SAVE_VOLUME_GAIN_DBFS) { + min = index; + break; + } else if (gainDB < SAVE_VOLUME_GAIN_DBFS) { + min = index; + } else { + max = index; + } + } + return min * 10; + } + private void onConfigureSafeVolume(boolean force, String caller) { synchronized (mSafeMediaVolumeState) { int mcc = mContext.getResources().getConfiguration().mcc; if ((mMcc != mcc) || ((mMcc == 0) && force)) { mSafeMediaVolumeIndex = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_index) * 10; + + mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex(); + boolean safeMediaVolumeEnabled = SystemProperties.getBoolean("audio.safemedia.force", false) || mContext.getResources().getBoolean( @@ -5302,38 +5330,20 @@ public class AudioService extends IAudioService.Stub return delay; } - private void sendDeviceConnectionIntent(int device, int state, String address, - String deviceName) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) + - " state:0x" + Integer.toHexString(state) + " address:" + address + - " name:" + deviceName + ");"); - } - Intent intent = new Intent(); - - intent.putExtra(CONNECT_INTENT_KEY_STATE, state); - intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); - intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); - - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - + private void updateAudioRoutes(int device, int state) + { int connType = 0; if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) { connType = AudioRoutesInfo.MAIN_HEADSET; - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", 1); } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE || device == AudioSystem.DEVICE_OUT_LINE) { - /*do apps care about line-out vs headphones?*/ connType = AudioRoutesInfo.MAIN_HEADPHONES; - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", 0); } else if (device == AudioSystem.DEVICE_OUT_HDMI || device == AudioSystem.DEVICE_OUT_HDMI_ARC) { connType = AudioRoutesInfo.MAIN_HDMI; - configureHdmiPlugIntent(intent, state); - } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE) { + } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE|| + device == AudioSystem.DEVICE_OUT_USB_HEADSET) { connType = AudioRoutesInfo.MAIN_USB; } @@ -5352,6 +5362,34 @@ public class AudioService extends IAudioService.Stub } } } + } + + private void sendDeviceConnectionIntent(int device, int state, String address, + String deviceName) { + if (DEBUG_DEVICES) { + Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) + + " state:0x" + Integer.toHexString(state) + " address:" + address + + " name:" + deviceName + ");"); + } + Intent intent = new Intent(); + + if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) { + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 1); + } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE || + device == AudioSystem.DEVICE_OUT_LINE) { + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 0); + } else if (device == AudioSystem.DEVICE_OUT_HDMI || + device == AudioSystem.DEVICE_OUT_HDMI_ARC) { + configureHdmiPlugIntent(intent, state); + } + + intent.putExtra(CONNECT_INTENT_KEY_STATE, state); + intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); + intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); + + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); final long ident = Binder.clearCallingIdentity(); try { @@ -5425,6 +5463,7 @@ public class AudioService extends IAudioService.Stub if (!isUsb && device != AudioSystem.DEVICE_IN_WIRED_HEADSET) { sendDeviceConnectionIntent(device, state, address, deviceName); } + updateAudioRoutes(device, state); } } @@ -5950,9 +5989,15 @@ public class AudioService extends IAudioService.Stub private int mMcc = 0; // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property private int mSafeMediaVolumeIndex; + // mSafeUsbMediaVolumeIndex is used for USB Headsets and is to the music volume + // UI index corresponding to a gain of -15 dBFS. This corresponds to a loudness of 85 dB SPL + // if the headset is compliant to EN 60950 with a max loudness of 100dB SPL. + private int mSafeUsbMediaVolumeIndex; + private static final float SAVE_VOLUME_GAIN_DBFS = -15; // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced, private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | - AudioSystem.DEVICE_OUT_WIRED_HEADPHONE; + AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | + AudioSystem.DEVICE_OUT_USB_HEADSET; // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled. // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS. @@ -5961,6 +6006,17 @@ public class AudioService extends IAudioService.Stub private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; // 30s after boot completed + private int safeMediaVolumeIndex(int device) { + if ((device & mSafeMediaVolumeDevices) == 0) { + return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; + } + if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) { + return mSafeUsbMediaVolumeIndex; + } else { + return mSafeMediaVolumeIndex; + } + } + private void setSafeMediaVolumeEnabled(boolean on, String caller) { synchronized (mSafeMediaVolumeState) { if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) && @@ -5995,8 +6051,8 @@ public class AudioService extends IAudioService.Stub continue; } int index = streamState.getIndex(device); - if (index > mSafeMediaVolumeIndex) { - streamState.setIndex(mSafeMediaVolumeIndex, device, caller); + if (index > safeMediaVolumeIndex(device)) { + streamState.setIndex(safeMediaVolumeIndex(device), device, caller); sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, @@ -6014,7 +6070,7 @@ public class AudioService extends IAudioService.Stub if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && ((device & mSafeMediaVolumeDevices) != 0) && - (index > mSafeMediaVolumeIndex)) { + (index > safeMediaVolumeIndex(device))) { return false; } return true; @@ -6240,6 +6296,7 @@ public class AudioService extends IAudioService.Stub pw.print(" mSafeMediaVolumeState="); pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState)); pw.print(" mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex); + pw.print(" mSafeUsbMediaVolumeIndex="); pw.println(mSafeUsbMediaVolumeIndex); pw.print(" sIndependentA11yVolume="); pw.println(sIndependentA11yVolume); pw.print(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand); pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs); diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index e6923d52664d..e5fc4b1d1775 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -88,7 +88,6 @@ import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.server.connectivity.tethering.IControlsTethering; import com.android.server.connectivity.tethering.IPv6TetheringCoordinator; -import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices; import com.android.server.connectivity.tethering.OffloadController; import com.android.server.connectivity.tethering.SimChangeListener; import com.android.server.connectivity.tethering.TetherInterfaceStateMachine; @@ -832,30 +831,41 @@ public class Tethering extends BaseNetworkObserver { case WifiManager.WIFI_AP_STATE_DISABLING: case WifiManager.WIFI_AP_STATE_FAILED: default: - disableWifiIpServingLocked(curState); + disableWifiIpServingLocked(ifname, curState); break; } } } } - // TODO: Pass in the interface name and, if non-empty, only turn down IP - // serving on that one interface. - private void disableWifiIpServingLocked(int apState) { - if (DBG) Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" + apState); + private void disableWifiIpServingLocked(String ifname, int apState) { + mLog.log("Canceling WiFi tethering request - AP_STATE=" + apState); + + // Regardless of whether we requested this transition, the AP has gone + // down. Don't try to tether again unless we're requested to do so. + // TODO: Remove this altogether, once Wi-Fi reliably gives us an + // interface name with every broadcast. + mWifiTetherRequested = false; + + if (!TextUtils.isEmpty(ifname)) { + final TetherState ts = mTetherStates.get(ifname); + if (ts != null) { + ts.stateMachine.unwanted(); + return; + } + } - // Tell appropriate interface state machines that they should tear - // themselves down. for (int i = 0; i < mTetherStates.size(); i++) { TetherInterfaceStateMachine tism = mTetherStates.valueAt(i).stateMachine; if (tism.interfaceType() == ConnectivityManager.TETHERING_WIFI) { - tism.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); - break; // There should be at most one of these. + tism.unwanted(); + return; } } - // Regardless of whether we requested this transition, the AP has gone - // down. Don't try to tether again unless we're requested to do so. - mWifiTetherRequested = false; + + mLog.log("Error disabling Wi-Fi IP serving; " + + (TextUtils.isEmpty(ifname) ? "no interface name specified" + : "specified interface: " + ifname)); } private void enableWifiIpServingLocked(String ifname, int wifiIpMode) { @@ -875,6 +885,7 @@ public class Tethering extends BaseNetworkObserver { } if (!TextUtils.isEmpty(ifname)) { + maybeTrackNewInterfaceLocked(ifname, ConnectivityManager.TETHERING_WIFI); changeInterfaceState(ifname, ipServingMode); } else { mLog.e(String.format( @@ -1248,9 +1259,9 @@ public class Tethering extends BaseNetworkObserver { protected void chooseUpstreamType(boolean tryCell) { updateConfiguration(); // TODO - remove? - final int upstreamType = findPreferredUpstreamType( - getConnectivityManager(), mConfig); - if (upstreamType == ConnectivityManager.TYPE_NONE) { + final NetworkState ns = mUpstreamNetworkMonitor.selectPreferredUpstreamType( + mConfig.preferredUpstreamIfaceTypes); + if (ns == null) { if (tryCell) { mUpstreamNetworkMonitor.registerMobileNetworkRequest(); // We think mobile should be coming up; don't set a retry. @@ -1258,92 +1269,30 @@ public class Tethering extends BaseNetworkObserver { sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); } } - setUpstreamByType(upstreamType); - } - - // TODO: Move this function into UpstreamNetworkMonitor. - protected int findPreferredUpstreamType(ConnectivityManager cm, - TetheringConfiguration cfg) { - int upType = ConnectivityManager.TYPE_NONE; - - if (VDBG) { - Log.d(TAG, "chooseUpstreamType has upstream iface types:"); - for (Integer netType : cfg.preferredUpstreamIfaceTypes) { - Log.d(TAG, " " + netType); - } - } - - for (Integer netType : cfg.preferredUpstreamIfaceTypes) { - NetworkInfo info = cm.getNetworkInfo(netType.intValue()); - // TODO: if the network is suspended we should consider - // that to be the same as connected here. - if ((info != null) && info.isConnected()) { - upType = netType.intValue(); - break; - } - } - - final int preferredUpstreamMobileApn = cfg.isDunRequired - ? ConnectivityManager.TYPE_MOBILE_DUN - : ConnectivityManager.TYPE_MOBILE_HIPRI; - mLog.log(String.format( - "findPreferredUpstreamType(), preferredApn=%s, got type=%s", - getNetworkTypeName(preferredUpstreamMobileApn), - getNetworkTypeName(upType))); - - switch (upType) { - case ConnectivityManager.TYPE_MOBILE_DUN: - case ConnectivityManager.TYPE_MOBILE_HIPRI: - // If we're on DUN, put our own grab on it. - mUpstreamNetworkMonitor.registerMobileNetworkRequest(); - break; - case ConnectivityManager.TYPE_NONE: - break; - default: - /* If we've found an active upstream connection that's not DUN/HIPRI - * we should stop any outstanding DUN/HIPRI start requests. - * - * If we found NONE we don't want to do this as we want any previous - * requests to keep trying to bring up something we can use. - */ - mUpstreamNetworkMonitor.releaseMobileNetworkRequest(); - break; - } - - return upType; + setUpstreamNetwork(ns); } - protected void setUpstreamByType(int upType) { - final ConnectivityManager cm = getConnectivityManager(); - Network network = null; + protected void setUpstreamNetwork(NetworkState ns) { String iface = null; - if (upType != ConnectivityManager.TYPE_NONE) { - LinkProperties linkProperties = cm.getLinkProperties(upType); - if (linkProperties != null) { - // Find the interface with the default IPv4 route. It may be the - // interface described by linkProperties, or one of the interfaces - // stacked on top of it. - Log.i(TAG, "Finding IPv4 upstream interface on: " + linkProperties); - RouteInfo ipv4Default = RouteInfo.selectBestRoute( - linkProperties.getAllRoutes(), Inet4Address.ANY); - if (ipv4Default != null) { - iface = ipv4Default.getInterface(); - Log.i(TAG, "Found interface " + ipv4Default.getInterface()); - } else { - Log.i(TAG, "No IPv4 upstream interface, giving up."); - } + if (ns != null && ns.linkProperties != null) { + // Find the interface with the default IPv4 route. It may be the + // interface described by linkProperties, or one of the interfaces + // stacked on top of it. + Log.i(TAG, "Finding IPv4 upstream interface on: " + ns.linkProperties); + RouteInfo ipv4Default = RouteInfo.selectBestRoute( + ns.linkProperties.getAllRoutes(), Inet4Address.ANY); + if (ipv4Default != null) { + iface = ipv4Default.getInterface(); + Log.i(TAG, "Found interface " + ipv4Default.getInterface()); + } else { + Log.i(TAG, "No IPv4 upstream interface, giving up."); } + } - if (iface != null) { - network = cm.getNetworkForType(upType); - if (network == null) { - Log.e(TAG, "No Network for upstream type " + upType + "!"); - } - setDnsForwarders(network, linkProperties); - } + if (iface != null) { + setDnsForwarders(ns.network, ns.linkProperties); } notifyTetheredOfNewUpstreamIface(iface); - NetworkState ns = mUpstreamNetworkMonitor.lookup(network); if (ns != null && pertainsToCurrentUpstream(ns)) { // If we already have NetworkState for this network examine // it immediately, because there likely will be no second @@ -1842,7 +1791,9 @@ public class Tethering extends BaseNetworkObserver { } } - mLog.log(String.format("OBSERVED LinkProperties update iface=%s state=%s", iface, state)); + mLog.log(String.format( + "OBSERVED LinkProperties update iface=%s state=%s lp=%s", + iface, IControlsTethering.getStateString(state), newLp)); final int which = TetherMasterSM.EVENT_IFACE_UPDATE_LINKPROPERTIES; mTetherMasterSM.sendMessage(which, state, 0, newLp); } @@ -1854,7 +1805,10 @@ public class Tethering extends BaseNetworkObserver { mLog.log(iface + " is not a tetherable iface, ignoring"); return; } + maybeTrackNewInterfaceLocked(iface, interfaceType); + } + private void maybeTrackNewInterfaceLocked(final String iface, int interfaceType) { // If we have already started a TISM for this interface, skip. if (mTetherStates.containsKey(iface)) { mLog.log("active iface (" + iface + ") reported as added, ignoring"); @@ -1865,8 +1819,7 @@ public class Tethering extends BaseNetworkObserver { final TetherState tetherState = new TetherState( new TetherInterfaceStateMachine( iface, mLooper, interfaceType, mLog, mNMService, mStatsService, - makeControlCallback(iface), - new IPv6TetheringInterfaceServices(iface, mNMService, mLog))); + makeControlCallback(iface))); mTetherStates.put(iface, tetherState); tetherState.stateMachine.start(); } diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java index aaa63b1613df..2b813475222f 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java +++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java @@ -33,6 +33,16 @@ public class IControlsTethering { public static final int STATE_TETHERED = 2; public static final int STATE_LOCAL_ONLY = 3; + public static String getStateString(int state) { + switch (state) { + case STATE_UNAVAILABLE: return "UNAVAILABLE"; + case STATE_AVAILABLE: return "AVAILABLE"; + case STATE_TETHERED: return "TETHERED"; + case STATE_LOCAL_ONLY: return "LOCAL_ONLY"; + } + return "UNKNOWN: " + state; + } + /** * Notify that |who| has changed its tethering state. * diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java deleted file mode 100644 index adf4af846e8e..000000000000 --- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (C) 2016 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.connectivity.tethering; - -import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; - -import android.net.INetd; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.NetworkCapabilities; -import android.net.NetworkState; -import android.net.RouteInfo; -import android.net.ip.RouterAdvertisementDaemon; -import android.net.ip.RouterAdvertisementDaemon.RaParams; -import android.net.util.NetdService; -import android.net.util.SharedLog; -import android.os.INetworkManagementService; -import android.os.ServiceSpecificException; -import android.os.RemoteException; -import android.util.Slog; - -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Objects; -import java.util.Random; - - -/** - * @hide - */ -public class IPv6TetheringInterfaceServices { - private static final String TAG = IPv6TetheringInterfaceServices.class.getSimpleName(); - private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64"); - - private final String mIfName; - private final INetworkManagementService mNMService; - private final SharedLog mLog; - - private NetworkInterface mNetworkInterface; - private byte[] mHwAddr; - private LinkProperties mLastIPv6LinkProperties; - private RouterAdvertisementDaemon mRaDaemon; - private RaParams mLastRaParams; - - public IPv6TetheringInterfaceServices( - String ifname, INetworkManagementService nms, SharedLog log) { - mIfName = ifname; - mNMService = nms; - mLog = log.forSubComponent(mIfName); - } - - public boolean start() { - // TODO: Refactor for testability (perhaps passing an android.system.Os - // instance and calling getifaddrs() directly). - try { - mNetworkInterface = NetworkInterface.getByName(mIfName); - } catch (SocketException e) { - mLog.e("Error looking up NetworkInterfaces: " + e); - stop(); - return false; - } - if (mNetworkInterface == null) { - mLog.e("Failed to find NetworkInterface"); - stop(); - return false; - } - - try { - mHwAddr = mNetworkInterface.getHardwareAddress(); - } catch (SocketException e) { - mLog.e("Failed to find hardware address: " + e); - stop(); - return false; - } - - final int ifindex = mNetworkInterface.getIndex(); - mRaDaemon = new RouterAdvertisementDaemon(mIfName, ifindex, mHwAddr); - if (!mRaDaemon.start()) { - stop(); - return false; - } - - return true; - } - - public void stop() { - mNetworkInterface = null; - mHwAddr = null; - setRaParams(null); - - if (mRaDaemon != null) { - mRaDaemon.stop(); - mRaDaemon = null; - } - } - - // IPv6TetheringCoordinator sends updates with carefully curated IPv6-only - // LinkProperties. These have extraneous data filtered out and only the - // necessary prefixes included (per its prefix distribution policy). - // - // TODO: Evaluate using a data structure than is more directly suited to - // communicating only the relevant information. - public void updateUpstreamIPv6LinkProperties(LinkProperties v6only) { - if (mRaDaemon == null) return; - - // Avoid unnecessary work on spurious updates. - if (Objects.equals(mLastIPv6LinkProperties, v6only)) { - return; - } - - RaParams params = null; - - if (v6only != null) { - params = new RaParams(); - params.mtu = v6only.getMtu(); - params.hasDefaultRoute = v6only.hasIPv6DefaultRoute(); - - for (LinkAddress linkAddr : v6only.getLinkAddresses()) { - if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue; - - final IpPrefix prefix = new IpPrefix( - linkAddr.getAddress(), linkAddr.getPrefixLength()); - params.prefixes.add(prefix); - - final Inet6Address dnsServer = getLocalDnsIpFor(prefix); - if (dnsServer != null) { - params.dnses.add(dnsServer); - } - } - } - // If v6only is null, we pass in null to setRaParams(), which handles - // deprecation of any existing RA data. - - setRaParams(params); - mLastIPv6LinkProperties = v6only; - } - - - private void configureLocalRoutes( - HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) { - // [1] Remove the routes that are deprecated. - if (!deprecatedPrefixes.isEmpty()) { - final ArrayList<RouteInfo> toBeRemoved = getLocalRoutesFor(deprecatedPrefixes); - try { - final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved); - if (removalFailures > 0) { - mLog.e(String.format("Failed to remove %d IPv6 routes from local table.", - removalFailures)); - } - } catch (RemoteException e) { - mLog.e("Failed to remove IPv6 routes from local table: " + e); - } - } - - // [2] Add only the routes that have not previously been added. - if (newPrefixes != null && !newPrefixes.isEmpty()) { - HashSet<IpPrefix> addedPrefixes = (HashSet) newPrefixes.clone(); - if (mLastRaParams != null) { - addedPrefixes.removeAll(mLastRaParams.prefixes); - } - - if (mLastRaParams == null || mLastRaParams.prefixes.isEmpty()) { - // We need to be able to send unicast RAs, and clients might - // like to ping the default router's link-local address. Note - // that we never remove the link-local route from the network - // until Tethering disables tethering on the interface. We - // only need to add the link-local prefix once, but in the - // event we add it more than once netd silently ignores EEXIST. - addedPrefixes.add(LINK_LOCAL_PREFIX); - } - - if (!addedPrefixes.isEmpty()) { - final ArrayList<RouteInfo> toBeAdded = getLocalRoutesFor(addedPrefixes); - try { - // It's safe to call addInterfaceToLocalNetwork() even if - // the interface is already in the local_network. Note also - // that adding routes that already exist does not cause an - // error (EEXIST is silently ignored). - mNMService.addInterfaceToLocalNetwork(mIfName, toBeAdded); - } catch (RemoteException e) { - mLog.e("Failed to add IPv6 routes to local table: " + e); - } - } - } - } - - private void configureLocalDns( - HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) { - final INetd netd = NetdService.getInstance(); - if (netd == null) { - if (newDnses != null) newDnses.clear(); - mLog.e("No netd service instance available; not setting local IPv6 addresses"); - return; - } - - // [1] Remove deprecated local DNS IP addresses. - if (!deprecatedDnses.isEmpty()) { - for (Inet6Address dns : deprecatedDnses) { - final String dnsString = dns.getHostAddress(); - try { - netd.interfaceDelAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH); - } catch (ServiceSpecificException | RemoteException e) { - mLog.e("Failed to remove local dns IP " + dnsString + ": " + e); - } - } - } - - // [2] Add only the local DNS IP addresses that have not previously been added. - if (newDnses != null && !newDnses.isEmpty()) { - final HashSet<Inet6Address> addedDnses = (HashSet) newDnses.clone(); - if (mLastRaParams != null) { - addedDnses.removeAll(mLastRaParams.dnses); - } - - for (Inet6Address dns : addedDnses) { - final String dnsString = dns.getHostAddress(); - try { - netd.interfaceAddAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH); - } catch (ServiceSpecificException | RemoteException e) { - mLog.e("Failed to add local dns IP " + dnsString + ": " + e); - newDnses.remove(dns); - } - } - } - - try { - netd.tetherApplyDnsInterfaces(); - } catch (ServiceSpecificException | RemoteException e) { - mLog.e("Failed to update local DNS caching server"); - if (newDnses != null) newDnses.clear(); - } - } - - private void setRaParams(RaParams newParams) { - if (mRaDaemon != null) { - final RaParams deprecatedParams = - RaParams.getDeprecatedRaParams(mLastRaParams, newParams); - - configureLocalRoutes(deprecatedParams.prefixes, - (newParams != null) ? newParams.prefixes : null); - - configureLocalDns(deprecatedParams.dnses, - (newParams != null) ? newParams.dnses : null); - - mRaDaemon.buildNewRa(deprecatedParams, newParams); - } - - mLastRaParams = newParams; - } - - // Accumulate routes representing "prefixes to be assigned to the local - // interface", for subsequent modification of local_network routing. - private ArrayList<RouteInfo> getLocalRoutesFor(HashSet<IpPrefix> prefixes) { - final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>(); - for (IpPrefix ipp : prefixes) { - localRoutes.add(new RouteInfo(ipp, null, mIfName)); - } - return localRoutes; - } - - // Given a prefix like 2001:db8::/64 return an address like 2001:db8::1. - private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) { - final byte[] dnsBytes = localPrefix.getRawAddress(); - dnsBytes[dnsBytes.length - 1] = getRandomNonZeroByte(); - try { - return Inet6Address.getByAddress(null, dnsBytes, 0); - } catch (UnknownHostException e) { - Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix); - return null; - } - } - - private static byte getRandomNonZeroByte() { - final byte random = (byte) (new Random()).nextInt(); - // Don't pick the subnet-router anycast address, since that might be - // in use on the upstream already. - return (random != 0) ? random : 0x1; - } -} diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java index 3aca45f8bd9a..08deef84f3ae 100644 --- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java @@ -85,13 +85,16 @@ public class OffloadController { mLog.i("tethering offload control not supported"); stop(); } + mLog.log("tethering offload started"); } public void stop() { + final boolean wasStarted = started(); mUpstreamLinkProperties = null; mHwInterface.stopOffloadControl(); mControlInitialized = false; mConfigInitialized = false; + if (wasStarted) mLog.log("tethering offload stopped"); } public void setUpstreamLinkProperties(LinkProperties lp) { @@ -161,6 +164,7 @@ public class OffloadController { } } - return mHwInterface.setUpstreamParameters(iface, v4addr, v4gateway, v6gateways); + return mHwInterface.setUpstreamParameters( + iface, v4addr, v4gateway, (v6gateways.isEmpty() ? null : v6gateways)); } } diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java index 3ecf0d1d0c71..1fc16841e355 100644 --- a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java @@ -16,6 +16,8 @@ package com.android.server.connectivity.tethering; +import static com.android.internal.util.BitUtils.uint16; + import android.hardware.tetheroffload.control.V1_0.IOffloadControl; import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; @@ -33,6 +35,9 @@ import java.util.ArrayList; */ public class OffloadHardwareInterface { private static final String TAG = OffloadHardwareInterface.class.getSimpleName(); + private static final String NO_INTERFACE_NAME = ""; + private static final String NO_IPV4_ADDRESS = ""; + private static final String NO_IPV4_GATEWAY = ""; private static native boolean configOffload(); @@ -107,6 +112,11 @@ public class OffloadHardwareInterface { public boolean setUpstreamParameters( String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) { + iface = iface != null ? iface : NO_INTERFACE_NAME; + v4addr = v4addr != null ? v4addr : NO_IPV4_ADDRESS; + v4gateway = v4gateway != null ? v4gateway : NO_IPV4_GATEWAY; + v6gws = v6gws != null ? v6gws : new ArrayList<>(); + final CbResults results = new CbResults(); try { mOffloadControl.setUpstreamParameters( @@ -143,8 +153,8 @@ public class OffloadHardwareInterface { handler.post(() -> { controlCb.onNatTimeoutUpdate( params.proto, - params.src.addr, params.src.port, - params.dst.addr, params.dst.port); + params.src.addr, uint16(params.src.port), + params.dst.addr, uint16(params.dst.port)); }); } } diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java index 82b9ca07bbc0..86b255128d6d 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java @@ -16,17 +16,28 @@ package com.android.server.connectivity.tethering; +import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; + import android.net.ConnectivityManager; +import android.net.INetd; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; +import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkUtils; +import android.net.RouteInfo; +import android.net.ip.RouterAdvertisementDaemon; +import android.net.ip.RouterAdvertisementDaemon.RaParams; +import android.net.util.NetdService; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.util.Log; +import android.util.Slog; import android.util.SparseArray; import com.android.internal.util.MessageUtils; @@ -34,14 +45,25 @@ import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import java.net.Inet6Address; import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Objects; +import java.util.Random; /** - * @hide + * Provides the interface to IP-layer serving functionality for a given network + * interface, e.g. for tethering or "local-only hotspot" mode. * - * Tracks the eligibility of a given network interface for tethering. + * @hide */ public class TetherInterfaceStateMachine extends StateMachine { + private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64"); + private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129"; private static final int USB_PREFIX_LENGTH = 24; private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1"; @@ -91,15 +113,25 @@ public class TetherInterfaceStateMachine extends StateMachine { private final String mIfaceName; private final int mInterfaceType; private final LinkProperties mLinkProperties; - private final IPv6TetheringInterfaceServices mIPv6TetherSvc; private int mLastError; private String mMyUpstreamIfaceName; // may change over time + private NetworkInterface mNetworkInterface; + private byte[] mHwAddr; + // TODO: De-duplicate this with mLinkProperties above. Currently, these link + // properties are those selected by the IPv6TetheringCoordinator and relayed + // to us. By comparison, mLinkProperties contains the addresses and directly + // connected routes that have been formed from these properties iff. we have + // succeeded in configuring them and are able to announce them within Router + // Advertisements (otherwise, we do not add them to mLinkProperties at all). + private LinkProperties mLastIPv6LinkProperties; + private RouterAdvertisementDaemon mRaDaemon; + private RaParams mLastRaParams; public TetherInterfaceStateMachine( String ifaceName, Looper looper, int interfaceType, SharedLog log, INetworkManagementService nMService, INetworkStatsService statsService, - IControlsTethering tetherController, IPv6TetheringInterfaceServices ipv6Svc) { + IControlsTethering tetherController) { super(ifaceName, looper); mLog = log.forSubComponent(ifaceName); mNMService = nMService; @@ -108,8 +140,7 @@ public class TetherInterfaceStateMachine extends StateMachine { mIfaceName = ifaceName; mInterfaceType = interfaceType; mLinkProperties = new LinkProperties(); - mLinkProperties.setInterfaceName(mIfaceName); - mIPv6TetherSvc = ipv6Svc; + resetLinkProperties(); mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR; mInitialState = new InitialState(); @@ -132,10 +163,21 @@ public class TetherInterfaceStateMachine extends StateMachine { public void stop() { sendMessage(CMD_INTERFACE_DOWN); } - // configured when we start tethering and unconfig'd on error or conclusion - private boolean configureIfaceIp(boolean enabled) { - if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")"); + public void unwanted() { sendMessage(CMD_TETHER_UNREQUESTED); } + + /** + * Internals. + */ + + private boolean startIPv4() { return configureIPv4(true); } + + private void stopIPv4() { configureIPv4(false); } + + private boolean configureIPv4(boolean enabled) { + if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")"); + // TODO: Replace this hard-coded information with dynamically selected + // config passed down to us by a higher layer IP-coordinating element. String ipAsString = null; int prefixLen = 0; if (mInterfaceType == ConnectivityManager.TETHERING_USB) { @@ -149,35 +191,256 @@ public class TetherInterfaceStateMachine extends StateMachine { return true; } - InterfaceConfiguration ifcg = null; + final LinkAddress linkAddr; try { - ifcg = mNMService.getInterfaceConfig(mIfaceName); - if (ifcg != null) { - InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString); - ifcg.setLinkAddress(new LinkAddress(addr, prefixLen)); - if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) { - // The WiFi stack has ownership of the interface up/down state. - // It is unclear whether the bluetooth or USB stacks will manage their own - // state. - ifcg.ignoreInterfaceUpDownStatus(); + final InterfaceConfiguration ifcg = mNMService.getInterfaceConfig(mIfaceName); + if (ifcg == null) { + mLog.e("Received null interface config"); + return false; + } + + InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString); + linkAddr = new LinkAddress(addr, prefixLen); + ifcg.setLinkAddress(linkAddr); + if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) { + // The WiFi stack has ownership of the interface up/down state. + // It is unclear whether the Bluetooth or USB stacks will manage their own + // state. + ifcg.ignoreInterfaceUpDownStatus(); + } else { + if (enabled) { + ifcg.setInterfaceUp(); } else { - if (enabled) { - ifcg.setInterfaceUp(); - } else { - ifcg.setInterfaceDown(); - } + ifcg.setInterfaceDown(); } - ifcg.clearFlag("running"); - mNMService.setInterfaceConfig(mIfaceName, ifcg); } + ifcg.clearFlag("running"); + mNMService.setInterfaceConfig(mIfaceName, ifcg); } catch (Exception e) { mLog.e("Error configuring interface " + e); return false; } + // Directly-connected route. + final RouteInfo route = new RouteInfo(linkAddr); + if (enabled) { + mLinkProperties.addLinkAddress(linkAddr); + mLinkProperties.addRoute(route); + } else { + mLinkProperties.removeLinkAddress(linkAddr); + mLinkProperties.removeRoute(route); + } + return true; + } + + private boolean startIPv6() { + // TODO: Refactor for testability (perhaps passing an android.system.Os + // instance and calling getifaddrs() directly). + try { + mNetworkInterface = NetworkInterface.getByName(mIfaceName); + } catch (SocketException e) { + mLog.e("Error looking up NetworkInterfaces: " + e); + stopIPv6(); + return false; + } + if (mNetworkInterface == null) { + mLog.e("Failed to find NetworkInterface"); + stopIPv6(); + return false; + } + + try { + mHwAddr = mNetworkInterface.getHardwareAddress(); + } catch (SocketException e) { + mLog.e("Failed to find hardware address: " + e); + stopIPv6(); + return false; + } + + final int ifindex = mNetworkInterface.getIndex(); + mRaDaemon = new RouterAdvertisementDaemon(mIfaceName, ifindex, mHwAddr); + if (!mRaDaemon.start()) { + stopIPv6(); + return false; + } + return true; } + private void stopIPv6() { + mNetworkInterface = null; + mHwAddr = null; + setRaParams(null); + + if (mRaDaemon != null) { + mRaDaemon.stop(); + mRaDaemon = null; + } + } + + // IPv6TetheringCoordinator sends updates with carefully curated IPv6-only + // LinkProperties. These have extraneous data filtered out and only the + // necessary prefixes included (per its prefix distribution policy). + // + // TODO: Evaluate using a data structure than is more directly suited to + // communicating only the relevant information. + private void updateUpstreamIPv6LinkProperties(LinkProperties v6only) { + if (mRaDaemon == null) return; + + // Avoid unnecessary work on spurious updates. + if (Objects.equals(mLastIPv6LinkProperties, v6only)) { + return; + } + + RaParams params = null; + + if (v6only != null) { + params = new RaParams(); + params.mtu = v6only.getMtu(); + params.hasDefaultRoute = v6only.hasIPv6DefaultRoute(); + + for (LinkAddress linkAddr : v6only.getLinkAddresses()) { + if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue; + + final IpPrefix prefix = new IpPrefix( + linkAddr.getAddress(), linkAddr.getPrefixLength()); + params.prefixes.add(prefix); + + final Inet6Address dnsServer = getLocalDnsIpFor(prefix); + if (dnsServer != null) { + params.dnses.add(dnsServer); + } + } + } + // If v6only is null, we pass in null to setRaParams(), which handles + // deprecation of any existing RA data. + + setRaParams(params); + mLastIPv6LinkProperties = v6only; + } + + private void configureLocalIPv6Routes( + HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) { + // [1] Remove the routes that are deprecated. + if (!deprecatedPrefixes.isEmpty()) { + final ArrayList<RouteInfo> toBeRemoved = + getLocalRoutesFor(mIfaceName, deprecatedPrefixes); + try { + final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved); + if (removalFailures > 0) { + mLog.e(String.format("Failed to remove %d IPv6 routes from local table.", + removalFailures)); + } + } catch (RemoteException e) { + mLog.e("Failed to remove IPv6 routes from local table: " + e); + } + + for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route); + } + + // [2] Add only the routes that have not previously been added. + if (newPrefixes != null && !newPrefixes.isEmpty()) { + HashSet<IpPrefix> addedPrefixes = (HashSet) newPrefixes.clone(); + if (mLastRaParams != null) { + addedPrefixes.removeAll(mLastRaParams.prefixes); + } + + if (mLastRaParams == null || mLastRaParams.prefixes.isEmpty()) { + // We need to be able to send unicast RAs, and clients might + // like to ping the default router's link-local address. Note + // that we never remove the link-local route from the network + // until Tethering disables tethering on the interface. We + // only need to add the link-local prefix once, but in the + // event we add it more than once netd silently ignores EEXIST. + addedPrefixes.add(LINK_LOCAL_PREFIX); + } + + if (!addedPrefixes.isEmpty()) { + final ArrayList<RouteInfo> toBeAdded = + getLocalRoutesFor(mIfaceName, addedPrefixes); + try { + // It's safe to call addInterfaceToLocalNetwork() even if + // the interface is already in the local_network. Note also + // that adding routes that already exist does not cause an + // error (EEXIST is silently ignored). + mNMService.addInterfaceToLocalNetwork(mIfaceName, toBeAdded); + } catch (RemoteException e) { + mLog.e("Failed to add IPv6 routes to local table: " + e); + } + + for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route); + } + } + } + + private void configureLocalIPv6Dns( + HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) { + final INetd netd = NetdService.getInstance(); + if (netd == null) { + if (newDnses != null) newDnses.clear(); + mLog.e("No netd service instance available; not setting local IPv6 addresses"); + return; + } + + // [1] Remove deprecated local DNS IP addresses. + if (!deprecatedDnses.isEmpty()) { + for (Inet6Address dns : deprecatedDnses) { + final String dnsString = dns.getHostAddress(); + try { + netd.interfaceDelAddress(mIfaceName, dnsString, RFC7421_PREFIX_LENGTH); + } catch (ServiceSpecificException | RemoteException e) { + mLog.e("Failed to remove local dns IP " + dnsString + ": " + e); + } + + mLinkProperties.removeLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH)); + } + } + + // [2] Add only the local DNS IP addresses that have not previously been added. + if (newDnses != null && !newDnses.isEmpty()) { + final HashSet<Inet6Address> addedDnses = (HashSet) newDnses.clone(); + if (mLastRaParams != null) { + addedDnses.removeAll(mLastRaParams.dnses); + } + + for (Inet6Address dns : addedDnses) { + final String dnsString = dns.getHostAddress(); + try { + netd.interfaceAddAddress(mIfaceName, dnsString, RFC7421_PREFIX_LENGTH); + } catch (ServiceSpecificException | RemoteException e) { + mLog.e("Failed to add local dns IP " + dnsString + ": " + e); + newDnses.remove(dns); + } + + mLinkProperties.addLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH)); + } + } + + try { + netd.tetherApplyDnsInterfaces(); + } catch (ServiceSpecificException | RemoteException e) { + mLog.e("Failed to update local DNS caching server"); + if (newDnses != null) newDnses.clear(); + } + } + + private void setRaParams(RaParams newParams) { + if (mRaDaemon != null) { + final RaParams deprecatedParams = + RaParams.getDeprecatedRaParams(mLastRaParams, newParams); + + configureLocalIPv6Routes(deprecatedParams.prefixes, + (newParams != null) ? newParams.prefixes : null); + + configureLocalIPv6Dns(deprecatedParams.dnses, + (newParams != null) ? newParams.dnses : null); + + mRaDaemon.buildNewRa(deprecatedParams, newParams); + } + + mLastRaParams = newParams; + } + private void maybeLogMessage(State state, int what) { if (DBG) { Log.d(TAG, state.getName() + " got " + @@ -189,12 +452,19 @@ public class TetherInterfaceStateMachine extends StateMachine { private void sendInterfaceState(int newInterfaceState) { mTetherController.updateInterfaceState( TetherInterfaceStateMachine.this, newInterfaceState, mLastError); - // TODO: Populate mLinkProperties correctly, and send more sensible - // updates more frequently (not just here). + sendLinkProperties(); + } + + private void sendLinkProperties() { mTetherController.updateLinkProperties( TetherInterfaceStateMachine.this, new LinkProperties(mLinkProperties)); } + private void resetLinkProperties() { + mLinkProperties.clear(); + mLinkProperties.setInterfaceName(mIfaceName); + } + class InitialState extends State { @Override public void enter() { @@ -222,8 +492,7 @@ public class TetherInterfaceStateMachine extends StateMachine { transitionTo(mUnavailableState); break; case CMD_IPV6_TETHER_UPDATE: - mIPv6TetherSvc.updateUpstreamIPv6LinkProperties( - (LinkProperties) message.obj); + updateUpstreamIPv6LinkProperties((LinkProperties) message.obj); break; default: return NOT_HANDLED; @@ -235,7 +504,7 @@ public class TetherInterfaceStateMachine extends StateMachine { class BaseServingState extends State { @Override public void enter() { - if (!configureIfaceIp(true)) { + if (!startIPv4()) { mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR; return; } @@ -248,8 +517,8 @@ public class TetherInterfaceStateMachine extends StateMachine { return; } - if (!mIPv6TetherSvc.start()) { - mLog.e("Failed to start IPv6TetheringInterfaceServices"); + if (!startIPv6()) { + mLog.e("Failed to startIPv6"); // TODO: Make this a fatal error once Bluetooth IPv6 is sorted. return; } @@ -260,7 +529,7 @@ public class TetherInterfaceStateMachine extends StateMachine { // Note that at this point, we're leaving the tethered state. We can fail any // of these operations, but it doesn't really change that we have to try them // all in sequence. - mIPv6TetherSvc.stop(); + stopIPv6(); try { mNMService.untetherInterface(mIfaceName); @@ -269,7 +538,9 @@ public class TetherInterfaceStateMachine extends StateMachine { mLog.e("Failed to untether interface: " + e); } - configureIfaceIp(false); + stopIPv4(); + + resetLinkProperties(); } @Override @@ -285,8 +556,8 @@ public class TetherInterfaceStateMachine extends StateMachine { if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName); break; case CMD_IPV6_TETHER_UPDATE: - mIPv6TetherSvc.updateUpstreamIPv6LinkProperties( - (LinkProperties) message.obj); + updateUpstreamIPv6LinkProperties((LinkProperties) message.obj); + sendLinkProperties(); break; case CMD_IP_FORWARDING_ENABLE_ERROR: case CMD_IP_FORWARDING_DISABLE_ERROR: @@ -397,7 +668,6 @@ public class TetherInterfaceStateMachine extends StateMachine { if (super.processMessage(message)) return true; maybeLogMessage(this, message.what); - boolean retValue = true; switch (message.what) { case CMD_TETHER_REQUESTED: mLog.e("CMD_TETHER_REQUESTED while already tethering."); @@ -427,10 +697,9 @@ public class TetherInterfaceStateMachine extends StateMachine { mMyUpstreamIfaceName = newUpstreamIfaceName; break; default: - retValue = false; - break; + return false; } - return retValue; + return true; } } @@ -448,4 +717,34 @@ public class TetherInterfaceStateMachine extends StateMachine { sendInterfaceState(IControlsTethering.STATE_UNAVAILABLE); } } + + // Accumulate routes representing "prefixes to be assigned to the local + // interface", for subsequent modification of local_network routing. + private static ArrayList<RouteInfo> getLocalRoutesFor( + String ifname, HashSet<IpPrefix> prefixes) { + final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>(); + for (IpPrefix ipp : prefixes) { + localRoutes.add(new RouteInfo(ipp, null, ifname)); + } + return localRoutes; + } + + // Given a prefix like 2001:db8::/64 return an address like 2001:db8::1. + private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) { + final byte[] dnsBytes = localPrefix.getRawAddress(); + dnsBytes[dnsBytes.length - 1] = getRandomNonZeroByte(); + try { + return Inet6Address.getByAddress(null, dnsBytes, 0); + } catch (UnknownHostException e) { + Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix); + return null; + } + } + + private static byte getRandomNonZeroByte() { + final byte random = (byte) (new Random()).nextInt(); + // Don't pick the subnet-router anycast address, since that might be + // in use on the upstream already. + return (random != 0) ? random : 0x1; + } } 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 b7fbfb756b1d..7efa1664788c 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -17,6 +17,7 @@ package com.android.server.connectivity.tethering; import static android.content.Context.TELEPHONY_SERVICE; +import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; @@ -211,29 +212,26 @@ public class TetheringConfiguration { // *always* an upstream, regardless of the upstream interface types // specified by configuration resources. if (dunCheck == DUN_REQUIRED) { - if (!upstreamIfaceTypes.contains(TYPE_MOBILE_DUN)) { - upstreamIfaceTypes.add(TYPE_MOBILE_DUN); - } + appendIfNotPresent(upstreamIfaceTypes, TYPE_MOBILE_DUN); } else if (dunCheck == DUN_NOT_REQUIRED) { - if (!upstreamIfaceTypes.contains(TYPE_MOBILE)) { - upstreamIfaceTypes.add(TYPE_MOBILE); - } - if (!upstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI)) { - upstreamIfaceTypes.add(TYPE_MOBILE_HIPRI); - } + appendIfNotPresent(upstreamIfaceTypes, TYPE_MOBILE); + appendIfNotPresent(upstreamIfaceTypes, TYPE_MOBILE_HIPRI); } else { // Fix upstream interface types for case DUN_UNSPECIFIED. // Do not modify if a cellular interface type is already present in the // upstream interface types. Add TYPE_MOBILE and TYPE_MOBILE_HIPRI if no // cellular interface types are found in the upstream interface types. - if (!(upstreamIfaceTypes.contains(TYPE_MOBILE_DUN) - || upstreamIfaceTypes.contains(TYPE_MOBILE) - || upstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI))) { + if (!(containsOneOf(upstreamIfaceTypes, + TYPE_MOBILE_DUN, TYPE_MOBILE, TYPE_MOBILE_HIPRI))) { upstreamIfaceTypes.add(TYPE_MOBILE); upstreamIfaceTypes.add(TYPE_MOBILE_HIPRI); } } + // Always make sure our good friend Ethernet is present. + // TODO: consider unilaterally forcing this at the front. + prependIfNotPresent(upstreamIfaceTypes, TYPE_ETHERNET); + return upstreamIfaceTypes; } @@ -256,4 +254,21 @@ public class TetheringConfiguration { private static String[] copy(String[] strarray) { return Arrays.copyOf(strarray, strarray.length); } + + private static void prependIfNotPresent(ArrayList<Integer> list, int value) { + if (list.contains(value)) return; + list.add(0, value); + } + + private static void appendIfNotPresent(ArrayList<Integer> list, int value) { + if (list.contains(value)) return; + list.add(value); + } + + private static boolean containsOneOf(ArrayList<Integer> list, Integer... values) { + for (Integer value : values) { + if (list.contains(value)) return true; + } + return false; + } } diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index cd6038ffa0dd..9ebfaf7b3893 100644 --- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -16,6 +16,8 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.getNetworkTypeName; +import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; @@ -172,8 +174,39 @@ public class UpstreamNetworkMonitor { mMobileNetworkCallback = null; } - public NetworkState lookup(Network network) { - return (network != null) ? mNetworkMap.get(network) : null; + // So many TODOs here, but chief among them is: make this functionality an + // integral part of this class such that whenever a higher priority network + // becomes available and useful we (a) file a request to keep it up as + // necessary and (b) change all upstream tracking state accordingly (by + // passing LinkProperties up to Tethering). + // + // Next TODO: return NetworkState instead of just the type. + public NetworkState selectPreferredUpstreamType(Iterable<Integer> preferredTypes) { + final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType( + mNetworkMap.values(), preferredTypes); + + mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type)); + + switch (typeStatePair.type) { + case TYPE_MOBILE_DUN: + case TYPE_MOBILE_HIPRI: + // If we're on DUN, put our own grab on it. + registerMobileNetworkRequest(); + break; + case TYPE_NONE: + break; + default: + /* If we've found an active upstream connection that's not DUN/HIPRI + * we should stop any outstanding DUN/HIPRI start requests. + * + * If we found NONE we don't want to do this as we want any previous + * requests to keep trying to bring up something we can use. + */ + releaseMobileNetworkRequest(); + break; + } + + return typeStatePair.ns; } private void handleAvailable(int callbackType, Network network) { @@ -365,4 +398,37 @@ public class UpstreamNetworkMonitor { private void notifyTarget(int which, NetworkState netstate) { mTarget.sendMessage(mWhat, which, 0, netstate); } + + static private class TypeStatePair { + public int type = TYPE_NONE; + public NetworkState ns = null; + } + + static private TypeStatePair findFirstAvailableUpstreamByType( + Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) { + final TypeStatePair result = new TypeStatePair(); + + for (int type : preferredTypes) { + NetworkCapabilities nc; + try { + nc = ConnectivityManager.networkCapabilitiesForType(type); + } catch (IllegalArgumentException iae) { + Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " + + ConnectivityManager.getNetworkTypeName(type)); + continue; + } + + for (NetworkState value : netStates) { + if (!nc.satisfiedByNetworkCapabilities(value.networkCapabilities)) { + continue; + } + + result.type = type; + result.ns = value; + return result; + } + } + + return result; + } } diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 3ca65cd0b826..e3e26583b686 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -74,7 +74,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.provider.Settings; -import android.text.format.DateUtils; import android.text.format.Time; import android.util.EventLog; import android.util.Log; @@ -113,6 +112,7 @@ import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; +import java.util.function.Predicate; /** * Implementation details: @@ -1761,10 +1761,7 @@ public class SyncManager { protected void dump(FileDescriptor fd, PrintWriter pw) { final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); - dumpPendingSyncs(pw); - dumpPeriodicSyncs(pw); dumpSyncState(ipw); - dumpSyncHistory(ipw); dumpSyncAdapters(ipw); } @@ -1774,9 +1771,58 @@ public class SyncManager { return tobj.format("%Y-%m-%d %H:%M:%S"); } + private final static Comparator<SyncOperation> sOpDumpComparator = (op1, op2) -> { + int res = Integer.compare(op1.target.userId, op2.target.userId); + if (res != 0) return res; + + final Comparator<String> stringComparator = String.CASE_INSENSITIVE_ORDER; + + res = stringComparator.compare(op1.target.account.type, op2.target.account.type); + if (res != 0) return res; + + res = stringComparator.compare(op1.target.account.name, op2.target.account.name); + if (res != 0) return res; + + res = stringComparator.compare(op1.target.provider, op2.target.provider); + if (res != 0) return res; + + res = Integer.compare(op1.reason, op2.reason); + if (res != 0) return res; + + res = Long.compare(op1.periodMillis, op2.periodMillis); + if (res != 0) return res; + + res = Long.compare(op1.expectedRuntime, op2.expectedRuntime); + if (res != 0) return res; + + res = Long.compare(op1.jobId, op2.jobId); + if (res != 0) return res; + + return 0; + }; + + private final static Comparator<SyncOperation> sOpRuntimeComparator = (op1, op2) -> { + int res = Long.compare(op1.expectedRuntime, op2.expectedRuntime); + if (res != 0) return res; + + return sOpDumpComparator.compare(op1, op2); + }; + + private static <T> int countIf(Collection<T> col, Predicate<T> p) { + int ret = 0; + for (T item : col) { + if (p.test(item)) ret++; + } + return ret; + } + protected void dumpPendingSyncs(PrintWriter pw) { - pw.println("Pending Syncs:"); List<SyncOperation> pendingSyncs = getAllPendingSyncs(); + + pw.print("Pending Syncs: "); + pw.println(countIf(pendingSyncs, op -> !op.isPeriodic)); + + Collections.sort(pendingSyncs, sOpRuntimeComparator); int count = 0; for (SyncOperation op: pendingSyncs) { if (!op.isPeriodic) { @@ -1784,13 +1830,16 @@ public class SyncManager { count++; } } - pw.println("Total: " + count); pw.println(); } protected void dumpPeriodicSyncs(PrintWriter pw) { - pw.println("Periodic Syncs:"); List<SyncOperation> pendingSyncs = getAllPendingSyncs(); + + pw.print("Periodic Syncs: "); + pw.println(countIf(pendingSyncs, op -> op.isPeriodic)); + + Collections.sort(pendingSyncs, sOpDumpComparator); int count = 0; for (SyncOperation op: pendingSyncs) { if (op.isPeriodic) { @@ -1798,11 +1847,62 @@ public class SyncManager { count++; } } - pw.println("Total: " + count); pw.println(); } + /** + * Similar to {@link android.util.TimeUtils#formatDuration}, but it's more suitable and concise + * for the sync manager dumpsys. (Don't add the leading + sign, don't show milliseconds.) + */ + public static StringBuilder formatDurationHMS(StringBuilder sb, long duration) { + duration /= 1000; + if (duration < 0) { + sb.append('-'); + duration = -duration; + } + final long seconds = duration % 60; + duration /= 60; + + final long minutes = duration % 60; + duration /= 60; + + final long hours = duration % 24; + duration /= 24; + + final long days = duration; + + boolean print = false; + if (days > 0) { + sb.append(days); + sb.append('d'); + print = true; + } + print = printTwoDigitNumber(sb, hours, 'h', print); + print = printTwoDigitNumber(sb, minutes, 'm', print); + print = printTwoDigitNumber(sb, seconds, 's', print); + if (!print) { + sb.append("0s"); + } + + return sb; + } + + private static boolean printTwoDigitNumber(StringBuilder sb, long value, char unit, + boolean always) { + if (!always && (value == 0)) { + return false; + } + if (always && (value < 10)) { + sb.append('0'); + } + sb.append(value); + sb.append(unit); + return true; + } + protected void dumpSyncState(PrintWriter pw) { + final StringBuilder sb = new StringBuilder(); + pw.print("data connected: "); pw.println(mDataConnectionIsConnected); pw.print("auto sync: "); List<UserInfo> users = getAllUsers(); @@ -1828,13 +1928,16 @@ public class SyncManager { final long now = SystemClock.elapsedRealtime(); pw.print("now: "); pw.print(now); pw.println(" (" + formatTime(System.currentTimeMillis()) + ")"); - pw.println(" (HH:MM:SS)"); - pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now / 1000)); - pw.println(" (HH:MM:SS)"); + + sb.setLength(0); + pw.print("uptime: "); pw.print(formatDurationHMS(sb, now)); + pw.println(); pw.print("time spent syncing: "); - pw.print(DateUtils.formatElapsedTime( - mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000)); - pw.print(" (HH:MM:SS), sync "); + + sb.setLength(0); + pw.print(formatDurationHMS(sb, + mSyncHandler.mSyncTimeTracker.timeSpentSyncing())); + pw.print(", sync "); pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not "); pw.println("in progress"); @@ -1842,17 +1945,24 @@ public class SyncManager { pw.println("Active Syncs: " + mActiveSyncContexts.size()); final PackageManager pm = mContext.getPackageManager(); for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) { - final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000; + final long durationInSeconds = (now - activeSyncContext.mStartTime); pw.print(" "); - pw.print(DateUtils.formatElapsedTime(durationInSeconds)); + sb.setLength(0); + pw.print(formatDurationHMS(sb, durationInSeconds)); pw.print(" - "); pw.print(activeSyncContext.mSyncOperation.dump(pm, false)); pw.println(); } + pw.println(); + + dumpPendingSyncs(pw); + dumpPeriodicSyncs(pw); // Join the installed sync adapter with the accounts list and emit for everything. - pw.println(); pw.println("Sync Status"); + + final ArrayList<Pair<EndPoint, SyncStatusInfo>> statuses = new ArrayList<>(); + for (AccountAndUser account : accounts) { pw.printf("Account %s u%d %s\n", account.account.name, account.userId, account.account.type); @@ -1872,7 +1982,7 @@ public class SyncManager { "Tot", // 9 "Time", // 10 "Last Sync", // 11 - "Etc" // 12 + "Backoff" // 12 ); final List<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> sorted = @@ -1899,11 +2009,14 @@ public class SyncManager { account.userId)); SyncStorageEngine.AuthorityInfo settings = syncAuthoritySyncStatus.first; SyncStatusInfo status = syncAuthoritySyncStatus.second; + statuses.add(Pair.create(settings.target, status)); String authority = settings.target.provider; if (authority.length() > 50) { authority = authority.substring(authority.length() - 50); } table.set(row, 0, authority, settings.syncable, settings.enabled); + + sb.setLength(0); table.set(row, 4, status.numSourceLocal, status.numSourcePoll, @@ -1911,7 +2024,7 @@ public class SyncManager { status.numSourceServer, status.numSourceUser, status.numSyncs, - DateUtils.formatElapsedTime(status.totalElapsedTime / 1000)); + formatDurationHMS(sb, status.totalElapsedTime)); int row1 = row; if (settings.delayUntil > now) { @@ -1938,6 +2051,34 @@ public class SyncManager { } table.writeTo(pw); } + + dumpSyncHistory(pw); + + pw.println(); + pw.println("Per Adapter History"); + + for (int i = 0; i < statuses.size(); i++) { + final Pair<EndPoint, SyncStatusInfo> event = statuses.get(i); + + pw.print(" "); + pw.print(event.first.account.name); + pw.print('/'); + pw.print(event.first.account.type); + pw.print(" u"); + pw.print(event.first.userId); + pw.print(" ["); + pw.print(event.first.provider); + pw.print("]"); + pw.println(); + + for (int j = 0; j < event.second.getEventCount(); j++) { + pw.print(" "); + pw.print(formatTime(event.second.getEventTime(j))); + pw.print(' '); + pw.print(event.second.getEvent(j)); + pw.println(); + } + } } private void dumpTimeSec(PrintWriter pw, long time) { @@ -3403,7 +3544,7 @@ public class SyncManager { } static class PrintTable { - private ArrayList<Object[]> mTable = Lists.newArrayList(); + private ArrayList<String[]> mTable = Lists.newArrayList(); private final int mCols; PrintTable(int cols) { @@ -3416,13 +3557,17 @@ public class SyncManager { " columns. can't set " + values.length + " at column " + col); } for (int i = mTable.size(); i <= row; i++) { - final Object[] list = new Object[mCols]; + final String[] list = new String[mCols]; mTable.add(list); for (int j = 0; j < mCols; j++) { list[j] = ""; } } - System.arraycopy(values, 0, mTable.get(row), col, values.length); + final String[] rowArray = mTable.get(row); + for (int i = 0; i < values.length; i++) { + final Object value = values[i]; + rowArray[col + i] = (value == null) ? "" : value.toString(); + } } void writeTo(PrintWriter out) { diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java index c371f9705285..b46426cc4a05 100644 --- a/services/core/java/com/android/server/content/SyncOperation.java +++ b/services/core/java/com/android/server/content/SyncOperation.java @@ -18,10 +18,11 @@ package com.android.server.content; import android.accounts.Account; import android.app.job.JobInfo; -import android.content.pm.PackageManager; import android.content.ContentResolver; +import android.content.pm.PackageManager; import android.os.Bundle; import android.os.PersistableBundle; +import android.os.SystemClock; import android.os.UserHandle; import android.util.Slog; @@ -350,37 +351,46 @@ public class SyncOperation { return dump(null, true); } - String dump(PackageManager pm, boolean useOneLine) { + String dump(PackageManager pm, boolean shorter) { StringBuilder sb = new StringBuilder(); - sb.append("JobId: ").append(jobId) - .append(", ") + sb.append("JobId=").append(jobId) + .append(" ") .append(target.account.name) - .append(" u") - .append(target.userId).append(" (") + .append("/") .append(target.account.type) - .append(")") - .append(", ") + .append(" u") + .append(target.userId) + .append(" [") .append(target.provider) - .append(", "); + .append("] "); sb.append(SyncStorageEngine.SOURCES[syncSource]); + if (expectedRuntime != 0) { + sb.append(" ExpectedIn="); + SyncManager.formatDurationHMS(sb, + (expectedRuntime - SystemClock.elapsedRealtime())); + } if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { - sb.append(", EXPEDITED"); + sb.append(" EXPEDITED"); } - sb.append(", reason: "); + sb.append(" Reason="); sb.append(reasonToString(pm, reason)); if (isPeriodic) { - sb.append(", period: " + periodMillis).append(", flexMillis: " + flexMillis); + sb.append(" (period="); + SyncManager.formatDurationHMS(sb, periodMillis); + sb.append(" flex="); + SyncManager.formatDurationHMS(sb, flexMillis); + sb.append(")"); } - if (!useOneLine) { - sb.append("\n "); - sb.append("owningUid="); + if (!shorter) { + sb.append(" Owner={"); UserHandle.formatUid(sb, owningUid); - sb.append(" owningPackage="); + sb.append(" "); sb.append(owningPackage); - } - if (!useOneLine && !extras.keySet().isEmpty()) { - sb.append("\n "); - extrasToStringBuilder(extras, sb); + sb.append("}"); + if (!extras.keySet().isEmpty()) { + sb.append(" "); + extrasToStringBuilder(extras, sb); + } } return sb.toString(); } @@ -434,7 +444,7 @@ public class SyncOperation { return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false); } - private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { + static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { sb.append("["); for (String key : bundle.keySet()) { sb.append(key).append("=").append(bundle.get(key)).append(" "); diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index f804fa1cff3e..7b277c06328e 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -1183,6 +1183,16 @@ public class SyncStorageEngine extends Handler { ds.failureCount++; ds.failureTime += elapsedTime; } + final StringBuilder event = new StringBuilder(); + event.append("" + resultMessage + " Source=" + SyncStorageEngine.SOURCES[item.source] + + " Elapsed="); + SyncManager.formatDurationHMS(event, elapsedTime); + event.append(" Reason="); + event.append(SyncOperation.reasonToString(null, item.reason)); + event.append(" Extras="); + SyncOperation.extrasToStringBuilder(item.extras, event); + + status.addEvent(event.toString()); if (writeStatusNow) { writeStatusLocked(); diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java index f23caf2e7a76..0fdf2daf1c8d 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java +++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java @@ -30,6 +30,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.TextureView; +import android.view.ThreadedRenderer; import android.view.View; import android.view.WindowManager; import android.view.TextureView.SurfaceTextureListener; @@ -95,6 +96,8 @@ final class OverlayDisplayWindow implements DumpUtils.Dump { public OverlayDisplayWindow(Context context, String name, int width, int height, int densityDpi, int gravity, boolean secure, Listener listener) { + // Workaround device freeze (b/38372997) + ThreadedRenderer.disableVsync(); mContext = context; mName = name; mGravity = gravity; diff --git a/services/core/java/com/android/server/job/GrantedUriPermissions.java b/services/core/java/com/android/server/job/GrantedUriPermissions.java index e413d8d1d8c0..c23b109bb9de 100644 --- a/services/core/java/com/android/server/job/GrantedUriPermissions.java +++ b/services/core/java/com/android/server/job/GrantedUriPermissions.java @@ -29,7 +29,7 @@ import android.util.Slog; import java.io.PrintWriter; import java.util.ArrayList; -public class GrantedUriPermissions { +public final class GrantedUriPermissions { private final int mGrantFlags; private final int mSourceUserId; private final String mTag; diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java index 8ad1beace643..ba922959213c 100644 --- a/services/core/java/com/android/server/job/JobPackageTracker.java +++ b/services/core/java/com/android/server/job/JobPackageTracker.java @@ -39,19 +39,23 @@ public final class JobPackageTracker { public static final int EVENT_NULL = 0; public static final int EVENT_START_JOB = 1; public static final int EVENT_STOP_JOB = 2; + public static final int EVENT_START_PERIODIC_JOB = 3; + public static final int EVENT_STOP_PERIODIC_JOB = 4; private final RingBufferIndices mEventIndices = new RingBufferIndices(EVENT_BUFFER_SIZE); private final int[] mEventCmds = new int[EVENT_BUFFER_SIZE]; private final long[] mEventTimes = new long[EVENT_BUFFER_SIZE]; private final int[] mEventUids = new int[EVENT_BUFFER_SIZE]; private final String[] mEventTags = new String[EVENT_BUFFER_SIZE]; + private final int[] mEventJobIds = new int[EVENT_BUFFER_SIZE]; - public void addEvent(int cmd, int uid, String tag) { + public void addEvent(int cmd, int uid, String tag, int jobId) { int index = mEventIndices.add(); mEventCmds[index] = cmd; mEventTimes[index] = SystemClock.elapsedRealtime(); mEventUids[index] = uid; mEventTags[index] = tag; + mEventJobIds[index] = jobId; } DataSet mCurDataSet = new DataSet(); @@ -365,7 +369,8 @@ public final class JobPackageTracker { } else { mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now); } - addEvent(EVENT_START_JOB, job.getSourceUid(), job.getBatteryName()); + addEvent(job.getJob().isPeriodic() ? EVENT_START_PERIODIC_JOB : EVENT_START_JOB, + job.getSourceUid(), job.getBatteryName(), job.getJobId()); } public void noteInactive(JobStatus job) { @@ -376,7 +381,8 @@ public final class JobPackageTracker { mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now); } rebatchIfNeeded(now); - addEvent(EVENT_STOP_JOB, job.getSourceUid(), job.getBatteryName()); + addEvent(job.getJob().isPeriodic() ? EVENT_STOP_JOB : EVENT_STOP_PERIODIC_JOB, + job.getSourceUid(), job.getBatteryName(), job.getJobId()); } public void noteConcurrency(int totalActive, int fgActive) { @@ -448,16 +454,20 @@ public final class JobPackageTracker { } final String label; switch (mEventCmds[index]) { - case EVENT_START_JOB: label = "START"; break; - case EVENT_STOP_JOB: label = " STOP"; break; - default: label = " ??"; break; + case EVENT_START_JOB: label = " START"; break; + case EVENT_STOP_JOB: label = " STOP"; break; + case EVENT_START_PERIODIC_JOB: label = "START-P"; break; + case EVENT_STOP_PERIODIC_JOB: label = " STOP-P"; break; + default: label = " ??"; break; } pw.print(prefix); TimeUtils.formatDuration(mEventTimes[index]-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); pw.print(" "); pw.print(label); - pw.print(": "); + pw.print(": #"); UserHandle.formatUid(pw, uid); + pw.print("/"); + pw.print(mEventJobIds[index]); pw.print(" "); pw.println(mEventTags[index]); } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index b8fe88439c2d..98c65fd487ca 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -1122,7 +1122,8 @@ public final class JobSchedulerService extends com.android.server.SystemService delayMillis = Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS); JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, - JobStatus.NO_LATEST_RUNTIME, backoffAttempts); + JobStatus.NO_LATEST_RUNTIME, backoffAttempts, + failureToReschedule.getLastSuccessfulRunTime(), System.currentTimeMillis()); for (int ic=0; ic<mControllers.size(); ic++) { StateController controller = mControllers.get(ic); controller.rescheduleForFailureLocked(newJob, failureToReschedule); @@ -1160,7 +1161,9 @@ public final class JobSchedulerService extends com.android.server.SystemService newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s"); } return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed, - newLatestRuntimeElapsed, 0 /* backoffAttempt */); + newLatestRuntimeElapsed, 0 /* backoffAttempt */, + System.currentTimeMillis() /* lastSuccessfulRunTime */, + periodicToReschedule.getLastFailedRunTime()); } // JobCompletedListener implementations. diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java index 2d2f61f0288d..a53c0885f334 100644 --- a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java @@ -26,7 +26,7 @@ import android.os.UserHandle; import java.io.PrintWriter; -public class JobSchedulerShellCommand extends ShellCommand { +public final class JobSchedulerShellCommand extends ShellCommand { public static final int CMD_ERR_NO_PACKAGE = -1000; public static final int CMD_ERR_NO_JOB = -1001; public static final int CMD_ERR_CONSTRAINTS = -1002; diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java index 637db11af6d8..107475f36c73 100644 --- a/services/core/java/com/android/server/job/JobServiceContext.java +++ b/services/core/java/com/android/server/job/JobServiceContext.java @@ -55,13 +55,13 @@ import com.android.server.job.controllers.JobStatus; * job lands, and again when it is complete. * - Cancelling is trickier, because there are also interactions from the client. It's possible * the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a - * {@link #doCancelLocked(int)} after the client has already finished. This is handled by having + * {@link #doCancelLocked} after the client has already finished. This is handled by having * {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelLocked} check whether * the context is still valid. * To mitigate this, we avoid sending duplicate onStopJob() * calls to the client after they've specified jobFinished(). */ -public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection { +public final class JobServiceContext implements ServiceConnection { private static final boolean DEBUG = JobSchedulerService.DEBUG; private static final String TAG = "JobServiceContext"; /** Amount of time a job is allowed to execute for before being considered timed-out. */ @@ -112,6 +112,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}. */ private JobStatus mRunningJob; + private JobCallback mRunningCallback; /** Used to store next job to run when current job is to be preempted. */ private int mPreferredUid; IJobService service; @@ -133,6 +134,36 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne // Debugging: time this job was last stopped. public long mStoppedTime; + final class JobCallback extends IJobCallback.Stub { + public String mStoppedReason; + public long mStoppedTime; + + @Override + public void acknowledgeStartMessage(int jobId, boolean ongoing) { + doAcknowledgeStartMessage(this, jobId, ongoing); + } + + @Override + public void acknowledgeStopMessage(int jobId, boolean reschedule) { + doAcknowledgeStopMessage(this, jobId, reschedule); + } + + @Override + public JobWorkItem dequeueWork(int jobId) { + return doDequeueWork(this, jobId); + } + + @Override + public boolean completeWork(int jobId, int workId) { + return doCompleteWork(this, jobId, workId); + } + + @Override + public void jobFinished(int jobId, boolean reschedule) { + doJobFinished(this, jobId, reschedule); + } + } + JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) { this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper); @@ -168,6 +199,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne mPreferredUid = NO_PREFERRED_UID; mRunningJob = job; + mRunningCallback = new JobCallback(); final boolean isDeadlineExpired = job.hasDeadlineConstraint() && (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime()); @@ -182,7 +214,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne job.changedAuthorities.toArray(triggeredAuthorities); } final JobInfo ji = job.getJob(); - mParams = new JobParameters(this, job.getJobId(), ji.getExtras(), + mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(), ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(), isDeadlineExpired, triggeredUris, triggeredAuthorities); mExecutionStartTimeElapsed = SystemClock.elapsedRealtime(); @@ -198,6 +230,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable."); } mRunningJob = null; + mRunningCallback = null; mParams = null; mExecutionStartTimeElapsed = 0L; mVerb = VERB_FINISHED; @@ -263,28 +296,29 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne return false; } - @Override - public void jobFinished(int jobId, boolean reschedule) { - doCallback(reschedule, "app called jobFinished"); + void doJobFinished(JobCallback cb, int jobId, boolean reschedule) { + doCallback(cb, reschedule, "app called jobFinished"); } - @Override - public void acknowledgeStopMessage(int jobId, boolean reschedule) { - doCallback(reschedule, null); + void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) { + doCallback(cb, reschedule, null); } - @Override - public void acknowledgeStartMessage(int jobId, boolean ongoing) { - doCallback(ongoing, "finished start"); + void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) { + doCallback(cb, ongoing, "finished start"); } - @Override - public JobWorkItem dequeueWork(int jobId) { - final int callingUid = Binder.getCallingUid(); + JobWorkItem doDequeueWork(JobCallback cb, int jobId) { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - assertCallingUidLocked(callingUid); + assertCallerLocked(cb); + if (mVerb == VERB_STOPPING || mVerb == VERB_FINISHED) { + // This job is either all done, or on its way out. Either way, it + // should not dispatch any more work. We will pick up any remaining + // work the next time we start the job again. + return null; + } final JobWorkItem work = mRunningJob.dequeueWorkLocked(); if (work == null && !mRunningJob.hasExecutingWorkLocked()) { // This will finish the job. @@ -297,13 +331,11 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne } } - @Override - public boolean completeWork(int jobId, int workId) { - final int callingUid = Binder.getCallingUid(); + boolean doCompleteWork(JobCallback cb, int jobId, int workId) { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - assertCallingUidLocked(callingUid); + assertCallerLocked(cb); return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId); } } finally { @@ -369,8 +401,8 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne * whether the client exercising the callback is the client we expect. * @return True if the binder calling is coming from the client we expect. */ - private boolean verifyCallingUidLocked(final int callingUid) { - if (mRunningJob == null || callingUid != mRunningJob.getUid()) { + private boolean verifyCallerLocked(JobCallback cb) { + if (mRunningCallback != cb) { if (DEBUG) { Slog.d(TAG, "Stale callback received, ignoring."); } @@ -379,16 +411,15 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne return true; } - private void assertCallingUidLocked(final int callingUid) { - if (!verifyCallingUidLocked(callingUid)) { + private void assertCallerLocked(JobCallback cb) { + if (!verifyCallerLocked(cb)) { StringBuilder sb = new StringBuilder(128); - sb.append("Bad calling uid "); - sb.append(callingUid); - if (mStoppedReason != null) { + sb.append("Caller no longer running"); + if (cb.mStoppedReason != null) { sb.append(", last stopped "); - TimeUtils.formatDuration(SystemClock.elapsedRealtime() - mStoppedTime, sb); + TimeUtils.formatDuration(SystemClock.elapsedRealtime() - cb.mStoppedTime, sb); sb.append(" because: "); - sb.append(mStoppedReason); + sb.append(cb.mStoppedReason); } throw new SecurityException(sb.toString()); } @@ -407,7 +438,21 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne switch (message.what) { case MSG_TIMEOUT: synchronized (mLock) { - handleOpTimeoutLocked(); + if (message.obj == mRunningCallback) { + handleOpTimeoutLocked(); + } else { + JobCallback jc = (JobCallback)message.obj; + StringBuilder sb = new StringBuilder(128); + sb.append("Ignoring timeout of no longer active job"); + if (jc.mStoppedReason != null) { + sb.append(", stopped "); + TimeUtils.formatDuration(SystemClock.elapsedRealtime() + - jc.mStoppedTime, sb); + sb.append(" because: "); + sb.append(jc.mStoppedReason); + } + Slog.w(TAG, sb.toString()); + } } break; default: @@ -421,12 +466,11 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne handleServiceBoundLocked(); } - void doCallback(boolean reschedule, String reason) { - final int callingUid = Binder.getCallingUid(); + void doCallback(JobCallback cb, boolean reschedule, String reason) { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - if (!verifyCallingUidLocked(callingUid)) { + if (!verifyCallerLocked(cb)) { return; } doCallbackLocked(reschedule, reason); @@ -559,7 +603,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne * VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for * {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)} * _STARTING -> Mark as cancelled and wait for - * {@link JobServiceContext#acknowledgeStartMessage(int, boolean)} + * {@link JobServiceContext#doAcknowledgeStartMessage} * _EXECUTING -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks * in the message queue. * _ENDING -> No point in doing anything here, so we ignore. @@ -591,7 +635,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne private void handleOpTimeoutLocked() { switch (mVerb) { case VERB_BINDING: - Slog.e(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() + + Slog.w(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() + ", dropping."); closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding"); break; @@ -599,26 +643,28 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne // Client unresponsive - wedged or failed to respond in time. We don't really // know what happened so let's log it and notify the JobScheduler // FINISHED/NO-RETRY. - Slog.e(TAG, "No response from client for onStartJob '" + - mRunningJob.toShortString()); + Slog.w(TAG, "No response from client for onStartJob " + + mRunningJob != null ? mRunningJob.toShortString() : "<null>"); closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting"); break; case VERB_STOPPING: // At least we got somewhere, so fail but ask the JobScheduler to reschedule. - Slog.e(TAG, "No response from client for onStopJob, '" + - mRunningJob.toShortString()); + Slog.w(TAG, "No response from client for onStopJob " + + mRunningJob != null ? mRunningJob.toShortString() : "<null>"); closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping"); break; case VERB_EXECUTING: // Not an error - client ran out of time. - Slog.i(TAG, "Client timed out while executing (no jobFinished received)." + - " sending onStop. " + mRunningJob.toShortString()); + Slog.i(TAG, "Client timed out while executing (no jobFinished received), " + + "sending onStop: " + + mRunningJob != null ? mRunningJob.toShortString() : "<null>"); mParams.setStopReason(JobParameters.REASON_TIMEOUT); sendStopMessageLocked("timeout while executing"); break; default: Slog.e(TAG, "Handling timeout for an invalid job state: " + - mRunningJob.toShortString() + ", dropping."); + mRunningJob != null ? mRunningJob.toShortString() : "<null>" + + ", dropping."); closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout"); } } @@ -671,6 +717,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne mContext.unbindService(JobServiceContext.this); mWakeLock = null; mRunningJob = null; + mRunningCallback = null; mParams = null; mVerb = VERB_FINISHED; mCancelled = false; @@ -684,6 +731,10 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne if (reason != null && mStoppedReason == null) { mStoppedReason = reason; mStoppedTime = SystemClock.elapsedRealtime(); + if (mRunningCallback != null) { + mRunningCallback.mStoppedReason = mStoppedReason; + mRunningCallback.mStoppedTime = mStoppedTime; + } } } @@ -714,7 +765,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne mRunningJob.getServiceComponent().getShortClassName() + "' jId: " + mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s"); } - Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT); + Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback); mCallbackHandler.sendMessageDelayed(m, timeoutMillis); mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis; } diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java index 22eed3b04250..84810bebf1f1 100644 --- a/services/core/java/com/android/server/job/JobStore.java +++ b/services/core/java/com/android/server/job/JobStore.java @@ -66,7 +66,7 @@ import org.xmlpull.v1.XmlSerializer; * and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that * object. */ -public class JobStore { +public final class JobStore { private static final String TAG = "JobStore"; private static final boolean DEBUG = JobSchedulerService.DEBUG; @@ -263,7 +263,7 @@ public class JobStore { * Runnable that writes {@link #mJobSet} out to xml. * NOTE: This Runnable locks on mLock */ - private class WriteJobsMapToDiskRunnable implements Runnable { + private final class WriteJobsMapToDiskRunnable implements Runnable { @Override public void run() { final long startElapsed = SystemClock.elapsedRealtime(); @@ -345,6 +345,11 @@ public class JobStore { out.attribute(null, "uid", Integer.toString(jobStatus.getUid())); out.attribute(null, "priority", String.valueOf(jobStatus.getPriority())); out.attribute(null, "flags", String.valueOf(jobStatus.getFlags())); + + out.attribute(null, "lastSuccessfulRunTime", + String.valueOf(jobStatus.getLastSuccessfulRunTime())); + out.attribute(null, "lastFailedRunTime", + String.valueOf(jobStatus.getLastFailedRunTime())); } private void writeBundleToXml(PersistableBundle extras, XmlSerializer out) @@ -444,7 +449,7 @@ public class JobStore { * Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}. */ - private class ReadJobMapFromDiskRunnable implements Runnable { + private final class ReadJobMapFromDiskRunnable implements Runnable { private final JobSet jobSet; /** @@ -555,6 +560,8 @@ public class JobStore { IOException { JobInfo.Builder jobBuilder; int uid, sourceUserId; + long lastSuccessfulRunTime; + long lastFailedRunTime; // Read out job identifier attributes and priority. try { @@ -572,6 +579,12 @@ public class JobStore { } val = parser.getAttributeValue(null, "sourceUserId"); sourceUserId = val == null ? -1 : Integer.parseInt(val); + + val = parser.getAttributeValue(null, "lastSuccessfulRunTime"); + lastSuccessfulRunTime = val == null ? 0 : Long.parseLong(val); + + val = parser.getAttributeValue(null, "lastFailedRunTime"); + lastFailedRunTime = val == null ? 0 : Long.parseLong(val); } catch (NumberFormatException e) { Slog.e(TAG, "Error parsing job's required fields, skipping"); return null; @@ -708,7 +721,8 @@ public class JobStore { // And now we're done JobStatus js = new JobStatus( jobBuilder.build(), uid, sourcePackageName, sourceUserId, sourceTag, - elapsedRuntimes.first, elapsedRuntimes.second); + elapsedRuntimes.first, elapsedRuntimes.second, + lastSuccessfulRunTime, lastFailedRunTime); return js; } @@ -796,7 +810,7 @@ public class JobStore { } } - static class JobSet { + static final class JobSet { // Key is the getUid() originator of the jobs in each sheaf private SparseArray<ArraySet<JobStatus>> mJobs; diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java index 68dd00ff00c1..39f2a96b30e3 100644 --- a/services/core/java/com/android/server/job/controllers/AppIdleController.java +++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java @@ -33,7 +33,7 @@ import java.io.PrintWriter; * for a certain amount of time (maybe hours or days) are considered idle. When the app comes * out of idle state, it will be allowed to run scheduled jobs. */ -public class AppIdleController extends StateController { +public final class AppIdleController extends StateController { private static final String LOG_TAG = "AppIdleController"; private static final boolean DEBUG = false; @@ -171,7 +171,7 @@ public class AppIdleController extends StateController { } } - private class AppIdleStateChangeListener + private final class AppIdleStateChangeListener extends UsageStatsManagerInternal.AppIdleStateChangeListener { @Override public void onAppIdleStateChanged(String packageName, int userId, boolean idle) { diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java index d275bd94a6f4..91119690617a 100644 --- a/services/core/java/com/android/server/job/controllers/BatteryController.java +++ b/services/core/java/com/android/server/job/controllers/BatteryController.java @@ -39,7 +39,7 @@ import java.io.PrintWriter; * be charging when it's been plugged in for more than two minutes, and the system has broadcast * ACTION_BATTERY_OK. */ -public class BatteryController extends StateController { +public final class BatteryController extends StateController { private static final String TAG = "JobScheduler.Batt"; private static final Object sCreationLock = new Object(); @@ -121,7 +121,7 @@ public class BatteryController extends StateController { } } - public class ChargingTracker extends BroadcastReceiver { + public final class ChargingTracker extends BroadcastReceiver { /** * Track whether we're "charging", where charging means that we're ready to commit to * doing work. diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index f4268185aa40..17c89282f280 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -43,7 +43,7 @@ import java.io.PrintWriter; * status due to user-requested network policies, so we need to check * constraints on a per-UID basis. */ -public class ConnectivityController extends StateController implements +public final class ConnectivityController extends StateController implements ConnectivityManager.OnNetworkActiveListener { private static final String TAG = "JobScheduler.Conn"; private static final boolean DEBUG = false; diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java index cfafc38428f3..ff807eccee7b 100644 --- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java +++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java @@ -39,7 +39,7 @@ import java.util.ArrayList; /** * Controller for monitoring changes to content URIs through a ContentObserver. */ -public class ContentObserverController extends StateController { +public final class ContentObserverController extends StateController { private static final String TAG = "JobScheduler.Content"; private static final boolean DEBUG = false; diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java index 5ccf81288255..85993b900dc9 100644 --- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -37,7 +37,7 @@ import java.util.Arrays; * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied. * When device is not dozing, set constraint for all jobs as satisfied. */ -public class DeviceIdleJobsController extends StateController { +public final class DeviceIdleJobsController extends StateController { private static final String LOG_TAG = "DeviceIdleJobsController"; private static final boolean LOG_DEBUG = false; diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java index 7e922930e6e8..9eda046fa1e4 100644 --- a/services/core/java/com/android/server/job/controllers/IdleController.java +++ b/services/core/java/com/android/server/job/controllers/IdleController.java @@ -33,7 +33,7 @@ import com.android.server.am.ActivityManagerService; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateChangedListener; -public class IdleController extends StateController { +public final class IdleController extends StateController { private static final String TAG = "IdleController"; // Policy: we decide that we're "idle" if the device has been unused / @@ -107,7 +107,7 @@ public class IdleController extends StateController { mIdleTracker.startTracking(); } - class IdlenessTracker extends BroadcastReceiver { + final class IdlenessTracker extends BroadcastReceiver { private AlarmManager mAlarm; private PendingIntent mIdleTriggerIntent; boolean mIdle; diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index 53bf40245819..9658da7a5de0 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -26,6 +26,7 @@ import android.net.Uri; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; +import android.text.format.Time; import android.util.ArraySet; import android.util.Slog; import android.util.TimeUtils; @@ -184,6 +185,17 @@ public final class JobStatus { public long madeActive; /** + * Last time a job finished successfully for a periodic job, in the currentTimeMillis time, + * for dumpsys. + */ + private long mLastSuccessfulRunTime; + + /** + * Last time a job finished unsuccessfully, in the currentTimeMillis time, for dumpsys. + */ + private long mLastFailedRunTime; + + /** * For use only by ContentObserverController: state it is maintaining about content URIs * being observed. */ @@ -196,7 +208,7 @@ public final class JobStatus { private JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis, - long latestRunTimeElapsedMillis) { + long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime) { this.job = job; this.callingUid = callingUid; @@ -263,6 +275,9 @@ public final class JobStatus { requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER; } this.requiredConstraints = requiredConstraints; + + mLastSuccessfulRunTime = lastSuccessfulRunTime; + mLastFailedRunTime = lastFailedRunTime; } /** Copy constructor. */ @@ -270,7 +285,8 @@ public final class JobStatus { this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(), jobStatus.getSourceTag(), jobStatus.getNumFailures(), - jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed()); + jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(), + jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime()); } /** @@ -281,18 +297,22 @@ public final class JobStatus { * We consider a freshly loaded job to no longer be in back-off. */ public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, - String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { + String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, + long lastSuccessfulRunTime, long lastFailedRunTime) { this(job, callingUid, sourcePackageName, sourceUserId, sourceTag, 0, - earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); + earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, + lastSuccessfulRunTime, lastFailedRunTime); } /** Create a new job to be rescheduled with the provided parameters. */ public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, - long newLatestRuntimeElapsedMillis, int backoffAttempt) { + long newLatestRuntimeElapsedMillis, int backoffAttempt, + long lastSuccessfulRunTime, long lastFailedRunTime) { this(rescheduling.job, rescheduling.getUid(), rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(), rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis, - newLatestRuntimeElapsedMillis); + newLatestRuntimeElapsedMillis, + lastSuccessfulRunTime, lastFailedRunTime); } /** @@ -316,7 +336,8 @@ public final class JobStatus { elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME; } return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0, - earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); + earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, + 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */); } public void enqueueWorkLocked(IActivityManager am, JobWorkItem work) { @@ -669,6 +690,14 @@ public final class JobStatus { trackingControllers |= which; } + public long getLastSuccessfulRunTime() { + return mLastSuccessfulRunTime; + } + + public long getLastFailedRunTime() { + return mLastFailedRunTime; + } + public boolean shouldDump(int filterUid) { return filterUid == -1 || UserHandle.getAppId(getUid()) == filterUid || UserHandle.getAppId(getSourceUid()) == filterUid; @@ -698,7 +727,8 @@ public final class JobStatus { static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | - CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | CONSTRAINT_NOT_ROAMING | + CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | + CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER; // Soft override covers all non-"functional" constraints @@ -865,6 +895,9 @@ public final class JobStatus { if ((constraints&CONSTRAINT_NOT_ROAMING) != 0) { pw.print(" NOT_ROAMING"); } + if ((constraints&CONSTRAINT_METERED) != 0) { + pw.print(" METERED"); + } if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) { pw.print(" APP_NOT_IDLE"); } @@ -1037,5 +1070,17 @@ public final class JobStatus { if (numFailures != 0) { pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures); } + final Time t = new Time(); + final String format = "%Y-%m-%d %H:%M:%S"; + if (mLastSuccessfulRunTime != 0) { + pw.print(prefix); pw.print("Last successful run: "); + t.set(mLastSuccessfulRunTime); + pw.println(t.format(format)); + } + if (mLastFailedRunTime != 0) { + pw.print(prefix); pw.print("Last failed run: "); + t.set(mLastFailedRunTime); + pw.println(t.format(format)); + } } } diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java index 4fe8eca54a6e..c24e563948d4 100644 --- a/services/core/java/com/android/server/job/controllers/StorageController.java +++ b/services/core/java/com/android/server/job/controllers/StorageController.java @@ -35,7 +35,7 @@ import java.io.PrintWriter; /** * Simple controller that tracks the status of the device's storage. */ -public class StorageController extends StateController { +public final class StorageController extends StateController { private static final String TAG = "JobScheduler.Stor"; private static final Object sCreationLock = new Object(); @@ -112,7 +112,7 @@ public class StorageController extends StateController { } } - public class StorageTracker extends BroadcastReceiver { + public final class StorageTracker extends BroadcastReceiver { /** * Track whether storage is low. */ diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java index 01c841e2083c..d90699a61928 100644 --- a/services/core/java/com/android/server/job/controllers/TimeController.java +++ b/services/core/java/com/android/server/job/controllers/TimeController.java @@ -38,7 +38,7 @@ import java.util.ListIterator; * This class sets an alarm for the next expiring job, and determines whether a job's minimum * delay has been satisfied. */ -public class TimeController extends StateController { +public final class TimeController extends StateController { private static final String TAG = "JobScheduler.Time"; /** Deadline alarm tag for logging purposes */ diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f038f9ae4e4b..5ee7ac4d16d1 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -307,11 +307,15 @@ public class NotificationManagerService extends SystemService { // used as a mutex for access to all active notifications & listeners final Object mNotificationLock = new Object(); + @GuardedBy("mNotificationLock") final ArrayList<NotificationRecord> mNotificationList = new ArrayList<NotificationRecord>(); + @GuardedBy("mNotificationLock") final ArrayMap<String, NotificationRecord> mNotificationsByKey = new ArrayMap<String, NotificationRecord>(); + @GuardedBy("mNotificationLock") final ArrayList<NotificationRecord> mEnqueuedNotifications = new ArrayList<>(); + @GuardedBy("mNotificationLock") final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>(); final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>(); final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>(); @@ -607,7 +611,7 @@ public class NotificationManagerService extends SystemService { @Override public void onPanelRevealed(boolean clearEffects, int items) { MetricsLogger.visible(getContext(), MetricsEvent.NOTIFICATION_PANEL); - MetricsLogger.histogram(getContext(), "notification_load", items); + MetricsLogger.histogram(getContext(), "note_load", items); EventLogTags.writeNotificationPanelRevealed(items); if (clearEffects) { clearEffects(); @@ -2806,7 +2810,8 @@ public class NotificationManagerService extends SystemService { // Clear summary. final NotificationRecord removed = findNotificationByKeyLocked(summaries.remove(pkg)); if (removed != null) { - cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED); + boolean wasPosted = removeFromNotificationListsLocked(removed); + cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED, wasPosted); } } } @@ -3420,7 +3425,8 @@ public class NotificationManagerService extends SystemService { .setType(MetricsEvent.TYPE_CLOSE) .addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA, mSnoozeCriterionId == null ? 0 : 1)); - cancelNotificationLocked(r, false, REASON_SNOOZED); + boolean wasPosted = removeFromNotificationListsLocked(r); + cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted); updateLightsLocked(); if (mSnoozeCriterionId != null) { mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn, mSnoozeCriterionId); @@ -4206,15 +4212,18 @@ public class NotificationManagerService extends SystemService { manager.sendAccessibilityEvent(event); } + /** + * Removes all NotificationsRecords with the same key as the given notification record + * from both lists. Do not call this method while iterating over either list. + */ @GuardedBy("mNotificationLock") - private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason) { - final String canceledKey = r.getKey(); - - // Remove from both lists, either list could have a separate Record for what is effectively - // the same notification. + private boolean removeFromNotificationListsLocked(NotificationRecord r) { + // Remove from both lists, either list could have a separate Record for what is + // effectively the same notification. boolean wasPosted = false; NotificationRecord recordInList = null; - if ((recordInList = findNotificationByListLocked(mNotificationList, r.getKey())) != null) { + if ((recordInList = findNotificationByListLocked(mNotificationList, r.getKey())) + != null) { mNotificationList.remove(recordInList); mNotificationsByKey.remove(recordInList.sbn.getKey()); wasPosted = true; @@ -4223,6 +4232,13 @@ public class NotificationManagerService extends SystemService { != null) { mEnqueuedNotifications.remove(recordInList); } + return wasPosted; + } + + @GuardedBy("mNotificationLock") + private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason, + boolean wasPosted) { + final String canceledKey = r.getKey(); // Record caller. recordCallerLocked(r); @@ -4363,7 +4379,8 @@ public class NotificationManagerService extends SystemService { } // Cancel the notification. - cancelNotificationLocked(r, sendDelete, reason); + boolean wasPosted = removeFromNotificationListsLocked(r); + cancelNotificationLocked(r, sendDelete, reason, wasPosted); cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName, sendDelete); updateLightsLocked(); @@ -4440,11 +4457,11 @@ public class NotificationManagerService extends SystemService { cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker, false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason, - listenerName); + listenerName, true /* wasPosted */); cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid, callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker, false /*includeCurrentProfiles*/, userId, - false /*sendDelete*/, reason, listenerName); + false /*sendDelete*/, reason, listenerName, false /* wasPosted */); mSnoozeHelper.cancel(userId, pkg); } } @@ -4460,7 +4477,7 @@ public class NotificationManagerService extends SystemService { private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList, int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch, String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId, - boolean sendDelete, int reason, String listenerName) { + boolean sendDelete, int reason, String listenerName, boolean wasPosted) { ArrayList<NotificationRecord> canceledNotifications = null; for (int i = notificationList.size() - 1; i >= 0; --i) { NotificationRecord r = notificationList.get(i); @@ -4488,8 +4505,9 @@ public class NotificationManagerService extends SystemService { if (canceledNotifications == null) { canceledNotifications = new ArrayList<>(); } + notificationList.remove(i); canceledNotifications.add(r); - cancelNotificationLocked(r, sendDelete, reason); + cancelNotificationLocked(r, sendDelete, reason, wasPosted); } if (canceledNotifications != null) { final int M = canceledNotifications.size(); @@ -4548,11 +4566,11 @@ public class NotificationManagerService extends SystemService { cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/, reason, - listenerName); + listenerName, true); cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid, callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/, - reason, listenerName); + reason, listenerName, false); mSnoozeHelper.cancel(userId, includeCurrentProfiles); } } @@ -4569,7 +4587,6 @@ public class NotificationManagerService extends SystemService { } String pkg = r.sbn.getPackageName(); - int userId = r.getUserId(); if (pkg == null) { if (DBG) Log.e(TAG, "No package for group summary: " + r.getKey()); @@ -4577,15 +4594,15 @@ public class NotificationManagerService extends SystemService { } cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName, - sendDelete); + sendDelete, true); cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid, - listenerName, sendDelete); + listenerName, sendDelete, false); } @GuardedBy("mNotificationLock") private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList, NotificationRecord parentNotification, int callingUid, int callingPid, - String listenerName, boolean sendDelete) { + String listenerName, boolean sendDelete, boolean wasPosted) { final String pkg = parentNotification.sbn.getPackageName(); final int userId = parentNotification.getUserId(); final int reason = REASON_GROUP_SUMMARY_CANCELED; @@ -4597,7 +4614,8 @@ public class NotificationManagerService extends SystemService { && (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0) { EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(), childSbn.getTag(), userId, 0, 0, reason, listenerName); - cancelNotificationLocked(childR, sendDelete, reason); + notificationList.remove(i); + cancelNotificationLocked(childR, sendDelete, reason, wasPosted); } } } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 8952870b8585..6953ffddaf33 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -25,6 +25,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.Bitmap; @@ -170,6 +171,11 @@ public final class NotificationRecord { private Uri calculateSound() { final Notification n = sbn.getNotification(); + // No notification sounds on tv + if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { + return null; + } + Uri sound = mChannel.getSound(); if (mPreChannelsNotification && (getChannel().getUserLockedFields() & NotificationChannel.USER_LOCKED_SOUND) == 0) { diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 2c0cc9585e9f..d7b36aaa8008 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -53,6 +53,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -524,12 +525,11 @@ public class RankingHelper implements RankingConfig { if (r == null) { throw new IllegalArgumentException("Invalid package"); } - LogMaker lm = new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP) - .setType(MetricsProto.MetricsEvent.TYPE_UPDATE) - .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID, - group.getId()) - .setPackageName(pkg); - MetricsLogger.action(lm); + final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); + if (!group.equals(oldGroup)) { + // will log for new entries as well as name changes + MetricsLogger.action(getChannelGroupLog(group.getId(), pkg)); + } r.groups.put(group.getId(), group); updateConfig(); } @@ -557,13 +557,16 @@ public class RankingHelper implements RankingConfig { if (existing != null && fromTargetApp) { if (existing.isDeleted()) { existing.setDeleted(false); + + // log a resurrected channel as if it's new again + MetricsLogger.action(getChannelLog(channel, pkg).setType( + MetricsProto.MetricsEvent.TYPE_OPEN)); } existing.setName(channel.getName().toString()); existing.setDescription(channel.getDescription()); existing.setBlockableSystem(channel.isBlockableSystem()); - MetricsLogger.action(getChannelLog(channel, pkg)); updateConfig(); return; } @@ -621,7 +624,10 @@ public class RankingHelper implements RankingConfig { r.showBadge = updatedChannel.canShowBadge(); } - MetricsLogger.action(getChannelLog(updatedChannel, pkg)); + if (!channel.equals(updatedChannel)) { + // only log if there are real changes + MetricsLogger.action(getChannelLog(updatedChannel, pkg)); + } updateConfig(); } @@ -1140,6 +1146,14 @@ public class RankingHelper implements RankingConfig { channel.getImportance()); } + private LogMaker getChannelGroupLog(String groupId, String pkg) { + return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP) + .setType(MetricsProto.MetricsEvent.TYPE_UPDATE) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID, + groupId) + .setPackageName(pkg); + } + public void updateBadgingEnabled() { if (mBadgingEnabled == null) { mBadgingEnabled = new SparseBooleanArray(); @@ -1186,6 +1200,6 @@ public class RankingHelper implements RankingConfig { boolean showBadge = DEFAULT_SHOW_BADGE; ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); - ArrayMap<String, NotificationChannelGroup> groups = new ArrayMap<>(); + Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>(); } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 75190f3e3e89..15e32ff6469e 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -762,7 +762,9 @@ public class ZenModeHelper { for (int usage : AudioAttributes.SDK_USAGES) { final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage); - if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NOTIFICATION) { + if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NEVER) { + applyRestrictions(false /*mute*/, usage); + } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NOTIFICATION) { applyRestrictions(muteNotifications || muteEverything, usage); } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL) { applyRestrictions(muteCalls || muteEverything, usage); diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index ef3e7bcefc17..2940a6e3fc8d 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -714,11 +714,17 @@ public final class OverlayManagerService extends SystemService { final Map<String, List<String>> pendingChanges = new ArrayMap<>(targetPackageNames.size()); synchronized (mLock) { + final List<String> frameworkOverlays = + mImpl.getEnabledOverlayPackageNames("android", userId); final int N = targetPackageNames.size(); for (int i = 0; i < N; i++) { final String targetPackageName = targetPackageNames.get(i); - pendingChanges.put(targetPackageName, - mImpl.getEnabledOverlayPackageNames(targetPackageName, userId)); + List<String> list = new ArrayList<>(); + if (!"android".equals(targetPackageName)) { + list.addAll(frameworkOverlays); + } + list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId)); + pendingChanges.put(targetPackageName, list); } } diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 261bcc5838f8..db6e9749535b 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -170,6 +170,7 @@ final class OverlayManagerServiceImpl { final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId); updateAllOverlaysForTarget(packageName, userId, targetPackage); + mListener.onOverlaysChanged(packageName, userId); } void onTargetPackageChanged(@NonNull final String packageName, final int userId) { @@ -178,7 +179,9 @@ final class OverlayManagerServiceImpl { } final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId); - updateAllOverlaysForTarget(packageName, userId, targetPackage); + if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) { + mListener.onOverlaysChanged(packageName, userId); + } } void onTargetPackageUpgrading(@NonNull final String packageName, final int userId) { @@ -186,7 +189,9 @@ final class OverlayManagerServiceImpl { Slog.d(TAG, "onTargetPackageUpgrading packageName=" + packageName + " userId=" + userId); } - updateAllOverlaysForTarget(packageName, userId, null); + if (updateAllOverlaysForTarget(packageName, userId, null)) { + mListener.onOverlaysChanged(packageName, userId); + } } void onTargetPackageUpgraded(@NonNull final String packageName, final int userId) { @@ -195,7 +200,9 @@ final class OverlayManagerServiceImpl { } final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId); - updateAllOverlaysForTarget(packageName, userId, targetPackage); + if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) { + mListener.onOverlaysChanged(packageName, userId); + } } void onTargetPackageRemoved(@NonNull final String packageName, final int userId) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index f5808affdb9e..5c54ba803a05 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -862,8 +862,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mResolvedInstructionSets.add(archSubDir.getName()); List<File> oatFiles = Arrays.asList(archSubDir.listFiles()); - if (!oatFiles.isEmpty()) { - mResolvedInheritedFiles.addAll(oatFiles); + + // Only add compiled files associated with the base. + // Once b/62269291 is resolved, we can add all compiled files again. + for (File oatFile : oatFiles) { + if (oatFile.getName().equals("base.art") + || oatFile.getName().equals("base.odex") + || oatFile.getName().equals("base.vdex")) { + mResolvedInheritedFiles.add(oatFile); + } } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 9c04801c056b..47b45276236c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -49,6 +49,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_INSTALL_L import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; import static android.content.pm.PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE; +import static android.content.pm.PackageManager.INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE; import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; @@ -57,6 +58,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE import static android.content.pm.PackageManager.INSTALL_FORWARD_LOCK; import static android.content.pm.PackageManager.INSTALL_INTERNAL; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; @@ -677,13 +679,6 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mPackages") final SparseIntArray mIsolatedOwners = new SparseIntArray(); - // List of APK paths to load for each user and package. This data is never - // persisted by the package manager. Instead, the overlay manager will - // ensure the data is up-to-date in runtime. - @GuardedBy("mPackages") - final SparseArray<ArrayMap<String, ArrayList<String>>> mEnabledOverlayPaths = - new SparseArray<ArrayMap<String, ArrayList<String>>>(); - /** * Tracks new system packages [received in an OTA] that we expect to * find updated user-installed versions. Keys are package name, values @@ -3584,8 +3579,6 @@ public class PackageManagerService extends IPackageManager.Stub return null; } - rebaseEnabledOverlays(packageInfo.applicationInfo, userId); - packageInfo.packageName = packageInfo.applicationInfo.packageName = resolveExternalPackageNameLPr(p); @@ -4097,7 +4090,6 @@ public class PackageManagerService extends IPackageManager.Stub ApplicationInfo ai = PackageParser.generateApplicationInfo(ps.pkg, flags, ps.readUserState(userId), userId); if (ai != null) { - rebaseEnabledOverlays(ai, userId); ai.packageName = resolveExternalPackageNameLPr(ps.pkg); } return ai; @@ -4146,7 +4138,6 @@ public class PackageManagerService extends IPackageManager.Stub ApplicationInfo ai = PackageParser.generateApplicationInfo( p, flags, ps.readUserState(userId), userId); if (ai != null) { - rebaseEnabledOverlays(ai, userId); ai.packageName = resolveExternalPackageNameLPr(p); } return ai; @@ -4163,26 +4154,6 @@ public class PackageManagerService extends IPackageManager.Stub return null; } - private void rebaseEnabledOverlays(@NonNull ApplicationInfo ai, int userId) { - List<String> paths = new ArrayList<>(); - ArrayMap<String, ArrayList<String>> userSpecificOverlays = - mEnabledOverlayPaths.get(userId); - if (userSpecificOverlays != null) { - if (!"android".equals(ai.packageName)) { - ArrayList<String> frameworkOverlays = userSpecificOverlays.get("android"); - if (frameworkOverlays != null) { - paths.addAll(frameworkOverlays); - } - } - - ArrayList<String> appOverlays = userSpecificOverlays.get(ai.packageName); - if (appOverlays != null) { - paths.addAll(appOverlays); - } - } - ai.resourceDirs = paths.size() > 0 ? paths.toArray(new String[paths.size()]) : null; - } - private String normalizePackageNameLPr(String packageName) { String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName); return normalizedPackageName != null ? normalizedPackageName : packageName; @@ -4564,24 +4535,6 @@ public class PackageManagerService extends IPackageManager.Stub return updateFlagsForComponent(flags, userId, intent /*cookie*/); } - private ActivityInfo generateActivityInfo(ActivityInfo ai, int flags, PackageUserState state, - int userId) { - ActivityInfo ret = PackageParser.generateActivityInfo(ai, flags, state, userId); - if (ret != null) { - rebaseEnabledOverlays(ret.applicationInfo, userId); - } - return ret; - } - - private ActivityInfo generateActivityInfo(PackageParser.Activity a, int flags, - PackageUserState state, int userId) { - ActivityInfo ai = PackageParser.generateActivityInfo(a, flags, state, userId); - if (ai != null) { - rebaseEnabledOverlays(ai.applicationInfo, userId); - } - return ai; - } - @Override public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId); @@ -4609,11 +4562,12 @@ public class PackageManagerService extends IPackageManager.Stub if (filterAppAccessLPr(ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) { return null; } - return generateActivityInfo(a, flags, ps.readUserState(userId), userId); + return PackageParser.generateActivityInfo( + a, flags, ps.readUserState(userId), userId); } if (mResolveComponentName.equals(component)) { - return generateActivityInfo(mResolveActivity, flags, new PackageUserState(), - userId); + return PackageParser.generateActivityInfo( + mResolveActivity, flags, new PackageUserState(), userId); } } return null; @@ -4667,7 +4621,8 @@ public class PackageManagerService extends IPackageManager.Stub if (filterAppAccessLPr(ps, callingUid, component, TYPE_RECEIVER, userId)) { return null; } - return generateActivityInfo(a, flags, ps.readUserState(userId), userId); + return PackageParser.generateActivityInfo( + a, flags, ps.readUserState(userId), userId); } } return null; @@ -4803,12 +4758,8 @@ public class PackageManagerService extends IPackageManager.Stub if (filterAppAccessLPr(ps, callingUid, component, TYPE_SERVICE, userId)) { return null; } - ServiceInfo si = PackageParser.generateServiceInfo(s, flags, - ps.readUserState(userId), userId); - if (si != null) { - rebaseEnabledOverlays(si.applicationInfo, userId); - } - return si; + return PackageParser.generateServiceInfo( + s, flags, ps.readUserState(userId), userId); } } return null; @@ -4831,12 +4782,8 @@ public class PackageManagerService extends IPackageManager.Stub if (filterAppAccessLPr(ps, callingUid, component, TYPE_PROVIDER, userId)) { return null; } - ProviderInfo pi = PackageParser.generateProviderInfo(p, flags, - ps.readUserState(userId), userId); - if (pi != null) { - rebaseEnabledOverlays(pi.applicationInfo, userId); - } - return pi; + return PackageParser.generateProviderInfo( + p, flags, ps.readUserState(userId), userId); } } return null; @@ -5114,9 +5061,6 @@ public class PackageManagerService extends IPackageManager.Stub @Override public String getPermissionControllerPackageName() { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - throw new SecurityException("Instant applications don't have access to this method"); - } synchronized (mPackages) { return mRequiredInstallerPackage; } @@ -8262,7 +8206,6 @@ public class PackageManagerService extends IPackageManager.Stub ai = PackageParser.generateApplicationInfo(ps.pkg, effectiveFlags, ps.readUserState(userId), userId); if (ai != null) { - rebaseEnabledOverlays(ai, userId); ai.packageName = resolveExternalPackageNameLPr(ps.pkg); } } else { @@ -8289,7 +8232,6 @@ public class PackageManagerService extends IPackageManager.Stub ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags, ps.readUserState(userId), userId); if (ai != null) { - rebaseEnabledOverlays(ai, userId); ai.packageName = resolveExternalPackageNameLPr(p); list.add(ai); } @@ -8443,7 +8385,6 @@ public class PackageManagerService extends IPackageManager.Stub ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags, ps.readUserState(userId), userId); if (ai != null) { - rebaseEnabledOverlays(ai, userId); finalList.add(ai); } } @@ -10548,8 +10489,9 @@ public class PackageManagerService extends IPackageManager.Stub if ((scanFlags & SCAN_NEW_INSTALL) == 0) { if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi"); - derivePackageAbi( - pkg, scanFile, cpuAbiOverride, true /*extractLibs*/, mAppLib32InstallDir); + final boolean extractNativeLibs = !pkg.isLibrary(); + derivePackageAbi(pkg, scanFile, cpuAbiOverride, extractNativeLibs, + mAppLib32InstallDir); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); // Some system apps still use directory structure for native libraries @@ -11577,6 +11519,12 @@ public class PackageManagerService extends IPackageManager.Stub Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + // Shared library native code should be in the APK zip aligned + if (abi32 >= 0 && pkg.isLibrary() && extractLibs) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Shared library native lib extraction not supported"); + } + maybeThrowExceptionForMultiArchCopy( "Error unpackaging 32 bit native libs for multiarch app.", abi32); @@ -11597,6 +11545,11 @@ public class PackageManagerService extends IPackageManager.Stub "Error unpackaging 64 bit native libs for multiarch app.", abi64); if (abi64 >= 0) { + // Shared library native libs should be in the APK zip aligned + if (extractLibs && pkg.isLibrary()) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Shared library native lib extraction not supported"); + } pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64]; } @@ -11613,7 +11566,6 @@ public class PackageManagerService extends IPackageManager.Stub pkg.applicationInfo.primaryCpuAbi = abi; } } - } else { String[] abiList = (cpuAbiOverride != null) ? new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS; @@ -11646,6 +11598,11 @@ public class PackageManagerService extends IPackageManager.Stub } if (copyRet >= 0) { + // Shared libraries that have native libs must be multi-architecture + if (pkg.isLibrary()) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Shared library with native libs must be multiarch"); + } pkg.applicationInfo.primaryCpuAbi = abiList[copyRet]; } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) { pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride; @@ -13367,7 +13324,8 @@ public class PackageManagerService extends IPackageManager.Stub return null; } final PackageUserState userState = ps.readUserState(userId); - ActivityInfo ai = generateActivityInfo(activity, mFlags, userState, userId); + ActivityInfo ai = + PackageParser.generateActivityInfo(activity, mFlags, userState, userId); if (ai == null) { return null; } @@ -17896,16 +17854,17 @@ public class PackageManagerService extends IPackageManager.Stub // Instant apps must have target SDK >= O and have targetSanboxVersion >= 2 if (instantApp && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) { - Slog.w(TAG, "Instant app package " + pkg.packageName - + " does not target O, this will be a fatal error."); - // STOPSHIP: Make this a fatal error - pkg.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O; + Slog.w(TAG, "Instant app package " + pkg.packageName + " does not target O"); + res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE, + "Instant app package must target O"); + return; } if (instantApp && pkg.applicationInfo.targetSandboxVersion != 2) { Slog.w(TAG, "Instant app package " + pkg.packageName - + " does not target targetSandboxVersion 2, this will be a fatal error."); - // STOPSHIP: Make this a fatal error - pkg.applicationInfo.targetSandboxVersion = 2; + + " does not target targetSandboxVersion 2"); + res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE, + "Instant app package must use targetSanboxVersion 2"); + return; } if (pkg.applicationInfo.isStaticSharedLibrary()) { @@ -18198,8 +18157,9 @@ public class PackageManagerService extends IPackageManager.Stub try { String abiOverride = (TextUtils.isEmpty(pkg.cpuAbiOverride) ? args.abiOverride : pkg.cpuAbiOverride); + final boolean extractNativeLibs = !pkg.isLibrary(); derivePackageAbi(pkg, new File(pkg.codePath), abiOverride, - true /*extractLibs*/, mAppLib32InstallDir); + extractNativeLibs, mAppLib32InstallDir); } catch (PackageManagerException pme) { Slog.e(TAG, "Error deriving application ABI", pme); res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI"); @@ -18219,8 +18179,10 @@ public class PackageManagerService extends IPackageManager.Stub // step during installation. Instead, we'll take extra time the first time the // instant app starts. It's preferred to do it this way to provide continuous // progress to the user instead of mysteriously blocking somewhere in the - // middle of running an instant app. - if (!instantApp) { + // middle of running an instant app. The default behaviour can be overridden + // via gservices. + if (!instantApp || Global.getInt( + mContext.getContentResolver(), Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); // Do not run PackageDexOptimizer through the local performDexOpt // method because `pkg` may not be in `mPackages` yet. @@ -19433,7 +19395,7 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mPackages) { final PackageSetting ps = mSettings.mPackages.get(packageName); if (ps == null || filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) { - return true; + return false; } return mSettings.getBlockUninstallLPr(userId, packageName); } @@ -21709,8 +21671,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); public static final int DUMP_FROZEN = 1 << 19; public static final int DUMP_DEXOPT = 1 << 20; public static final int DUMP_COMPILER_STATS = 1 << 21; - public static final int DUMP_ENABLED_OVERLAYS = 1 << 22; - public static final int DUMP_CHANGES = 1 << 23; + public static final int DUMP_CHANGES = 1 << 22; public static final int OPTION_SHOW_FILTERS = 1 << 0; @@ -21954,8 +21915,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); dumpState.setDump(DumpState.DUMP_DEXOPT); } else if ("compiler-stats".equals(cmd)) { dumpState.setDump(DumpState.DUMP_COMPILER_STATS); - } else if ("enabled-overlays".equals(cmd)) { - dumpState.setDump(DumpState.DUMP_ENABLED_OVERLAYS); } else if ("changes".equals(cmd)) { dumpState.setDump(DumpState.DUMP_CHANGES); } else if ("write".equals(cmd)) { @@ -22346,11 +22305,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); dumpCompilerStatsLPr(pw, packageName); } - if (!checkin && dumpState.isDumping(DumpState.DUMP_ENABLED_OVERLAYS)) { - if (dumpState.onTitlePrinted()) pw.println(); - dumpEnabledOverlaysLPr(pw); - } - if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) { if (dumpState.onTitlePrinted()) pw.println(); mSettings.dumpReadMessagesLPr(pw, dumpState); @@ -22547,23 +22501,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } } - private void dumpEnabledOverlaysLPr(PrintWriter pw) { - pw.println("Enabled overlay paths:"); - final int N = mEnabledOverlayPaths.size(); - for (int i = 0; i < N; i++) { - final int userId = mEnabledOverlayPaths.keyAt(i); - pw.println(String.format(" User %d:", userId)); - final ArrayMap<String, ArrayList<String>> userSpecificOverlays = - mEnabledOverlayPaths.valueAt(i); - final int M = userSpecificOverlays.size(); - for (int j = 0; j < M; j++) { - final String targetPackageName = userSpecificOverlays.keyAt(j); - final ArrayList<String> overlayPackagePaths = userSpecificOverlays.valueAt(j); - pw.println(String.format(" %s: %s", targetPackageName, overlayPackagePaths)); - } - } - } - private String dumpDomainString(String packageName) { List<IntentFilterVerificationInfo> iviList = getIntentFilterVerifications(packageName) .getList(); @@ -24741,11 +24678,10 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); Slog.e(TAG, "failed to find package " + targetPackageName); return false; } - - ArrayList<String> paths = null; - if (overlayPackageNames != null) { + ArrayList<String> overlayPaths = null; + if (overlayPackageNames != null && overlayPackageNames.size() > 0) { final int N = overlayPackageNames.size(); - paths = new ArrayList<>(N); + overlayPaths = new ArrayList<>(N); for (int i = 0; i < N; i++) { final String packageName = overlayPackageNames.get(i); final PackageParser.Package pkg = mPackages.get(packageName); @@ -24753,22 +24689,12 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); Slog.e(TAG, "failed to find package " + packageName); return false; } - paths.add(pkg.baseCodePath); + overlayPaths.add(pkg.baseCodePath); } } - ArrayMap<String, ArrayList<String>> userSpecificOverlays = - mEnabledOverlayPaths.get(userId); - if (userSpecificOverlays == null) { - userSpecificOverlays = new ArrayMap<>(); - mEnabledOverlayPaths.put(userId, userSpecificOverlays); - } - - if (paths != null && paths.size() > 0) { - userSpecificOverlays.put(targetPackageName, paths); - } else { - userSpecificOverlays.remove(targetPackageName); - } + final PackageSetting ps = mSettings.mPackages.get(targetPackageName); + ps.setOverlayPaths(overlayPaths, userId); return true; } } diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 14f65eb485f4..f68512758456 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -31,6 +31,7 @@ import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; +import com.google.android.collect.Lists; import java.io.File; import java.util.ArrayList; @@ -329,6 +330,15 @@ abstract class PackageSettingBase extends SettingBase { modifyUserState(userId).installReason = installReason; } + void setOverlayPaths(List<String> overlayPaths, int userId) { + modifyUserState(userId).overlayPaths = overlayPaths == null ? null : + overlayPaths.toArray(new String[overlayPaths.size()]); + } + + String[] getOverlayPaths(int userId) { + return readUserState(userId).overlayPaths; + } + /** Only use for testing. Do NOT use in production code. */ @VisibleForTesting SparseArray<PackageUserState> getUserState() { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 24cbdbff9153..45d0c585627b 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4858,6 +4858,15 @@ final class Settings { pw.print(ps.getEnabled(user.id)); pw.print(" instant="); pw.println(ps.getInstantApp(user.id)); + + String[] overlayPaths = ps.getOverlayPaths(user.id); + if (overlayPaths != null && overlayPaths.length > 0) { + pw.print(prefix); pw.println(" overlay paths:"); + for (String path : overlayPaths) { + pw.print(prefix); pw.print(" "); pw.println(path); + } + } + String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id); if (lastDisabledAppCaller != null) { pw.print(prefix); pw.print(" lastDisabledCaller: "); diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java index 7a2808146f62..b1792358be53 100644 --- a/services/core/java/com/android/server/policy/BarController.java +++ b/services/core/java/com/android/server/policy/BarController.java @@ -166,8 +166,14 @@ public class BarController { return change || stateChanged; } - void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener) { + void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener, + boolean invokeWithState) { mVisibilityChangeListener = listener; + if (invokeWithState) { + // Optionally report the initial window state for initialization purposes + mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED, + (mState == StatusBarManager.WINDOW_STATE_SHOWING) ? 1 : 0, 0).sendToTarget(); + } } protected boolean skipAnimation() { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 126e3ec30289..89dbc2a1c1c4 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -23,6 +23,7 @@ import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.AppOpsManager.OP_TOAST_WINDOW; +import static android.content.Context.CONTEXT_RESTRICTED; import static android.content.Context.DISPLAY_SERVICE; import static android.content.Context.WINDOW_SERVICE; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; @@ -1033,7 +1034,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { new BarController.OnBarVisibilityChangedListener() { @Override public void onBarVisibilityChanged(boolean visible) { - mAccessibilityManager.notifyAccessibilityButtonAvailabilityChanged(visible); + mAccessibilityManager.notifyAccessibilityButtonVisibilityChanged(visible); } }; @@ -2823,7 +2824,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (theme != context.getThemeResId() || labelRes != 0) { try { - context = context.createPackageContext(packageName, 0); + context = context.createPackageContext(packageName, CONTEXT_RESTRICTED); context.setTheme(theme); } catch (PackageManager.NameNotFoundException e) { // Ignore @@ -3017,7 +3018,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mNavigationBar = win; mNavigationBarController.setWindow(win); mNavigationBarController.setOnBarVisibilityChangedListener( - mNavBarVisibilityListener); + mNavBarVisibilityListener, true); if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar); break; case TYPE_NAVIGATION_BAR_PANEL: @@ -7713,6 +7714,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { return VibrationEffect.get(VibrationEffect.EFFECT_TICK); case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE: return VibrationEffect.get(VibrationEffect.EFFECT_TICK); + case HapticFeedbackConstants.TEXT_HANDLE_MOVE: + return VibrationEffect.get(VibrationEffect.EFFECT_TICK); default: return null; } diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index 28ffc9491160..02f2afcb0be8 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -391,7 +391,8 @@ public final class ShutdownThread extends Thread { // First send the high-level shut down broadcast. mActionDone = false; Intent intent = new Intent(Intent.ACTION_SHUTDOWN); - intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, null, br, mHandler, 0, null, null); diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index b937f9d4c6d8..55d471919099 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -24,9 +24,12 @@ import android.app.AppOpsManager; import android.app.Vr2dDisplayProperties; import android.app.NotificationManager; import android.annotation.NonNull; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -39,6 +42,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.service.notification.NotificationListenerService; @@ -140,7 +144,13 @@ public class VrManagerService extends SystemService implements EnabledComponentC private final NotificationAccessManager mNotifAccessManager = new NotificationAccessManager(); /** Tracks the state of the screen and keyguard UI.*/ private int mSystemSleepFlags = FLAG_AWAKE; + /** + * Set when ACTION_USER_UNLOCKED is fired. We shouldn't try to bind to the + * vr service before then. + */ + private boolean mUserUnlocked; private Vr2dDisplay mVr2dDisplay; + private boolean mBootsToVr; private static final int MSG_VR_STATE_CHANGE = 0; private static final int MSG_PENDING_VR_STATE_CHANGE = 1; @@ -152,15 +162,20 @@ public class VrManagerService extends SystemService implements EnabledComponentC * If VR mode is not allowed to be enabled, calls to set VR mode will be cached. When VR mode * is again allowed to be enabled, the most recent cached state will be applied. * - * @param allowed {@code true} if calling any of the setVrMode methods may cause the device to - * enter VR mode. */ - private void setVrModeAllowedLocked(boolean allowed) { + private void updateVrModeAllowedLocked() { + boolean allowed = mSystemSleepFlags == FLAG_ALL && mUserUnlocked; if (mVrModeAllowed != allowed) { mVrModeAllowed = allowed; if (DBG) Slog.d(TAG, "VR mode is " + ((allowed) ? "allowed" : "disallowed")); if (mVrModeAllowed) { + if (mBootsToVr) { + setPersistentVrModeEnabled(true); + } consumeAndApplyPendingStateLocked(); + if (mBootsToVr && !mVrModeEnabled) { + setVrMode(true, mDefaultVrService, 0, null); + } } else { // Disable persistent mode when VR mode isn't allowed, allows an escape hatch to // exit persistent VR mode when screen is turned off. @@ -187,7 +202,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC mSystemSleepFlags &= ~FLAG_AWAKE; } - setVrModeAllowedLocked(mSystemSleepFlags == FLAG_ALL); + updateVrModeAllowedLocked(); } } @@ -198,7 +213,14 @@ public class VrManagerService extends SystemService implements EnabledComponentC } else { mSystemSleepFlags &= ~FLAG_SCREEN_ON; } - setVrModeAllowedLocked(mSystemSleepFlags == FLAG_ALL); + updateVrModeAllowedLocked(); + } + } + + private void setUserUnlocked() { + synchronized(mLock) { + mUserUnlocked = true; + updateVrModeAllowedLocked(); } } @@ -563,6 +585,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC mContext = getContext(); } + mBootsToVr = SystemProperties.getBoolean("ro.boot.vr", false); publishLocalService(VrManagerInternal.class, new LocalService()); publishBinderService(Context.VR_SERVICE, mVrManager.asBinder()); } @@ -597,10 +620,17 @@ public class VrManagerService extends SystemService implements EnabledComponentC ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class); mVr2dDisplay = new Vr2dDisplay(dm, ami, mVrManager); mVr2dDisplay.init(getContext()); - } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { - synchronized (mLock) { - mVrModeAllowed = true; - } + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); + getContext().registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { + VrManagerService.this.setUserUnlocked(); + } + } + }, intentFilter); } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 71c92b88947d..929f28da66f3 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -52,7 +52,6 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; -import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; @@ -92,7 +91,6 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.content.PackageMonitor; -import com.android.internal.graphics.palette.Palette; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; @@ -168,7 +166,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { static final String WALLPAPER_LOCK_ORIG = "wallpaper_lock_orig"; static final String WALLPAPER_LOCK_CROP = "wallpaper_lock"; static final String WALLPAPER_INFO = "wallpaper_info.xml"; - static final int MAX_WALLPAPER_EXTRACTION_AREA = 112 * 112; // All the various per-user state files we need to be aware of static final String[] sPerUserFiles = new String[] { @@ -397,8 +394,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { // It prevents color extraction on big bitmaps. int wallpaperId = -1; + boolean imageWallpaper = false; synchronized (mLock) { - final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent) + imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent) || wallpaper.wallpaperComponent == null; if (imageWallpaper) { if (wallpaper.cropFile != null && wallpaper.cropFile.exists()) { @@ -422,45 +420,33 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { wallpaperId = wallpaper.wallpaperId; } - Bitmap bitmap = null; + WallpaperColors colors = null; if (cropFile != null) { - bitmap = BitmapFactory.decodeFile(cropFile); + Bitmap bitmap = BitmapFactory.decodeFile(cropFile); + colors = WallpaperColors.fromBitmap(bitmap); + bitmap.recycle(); } else if (thumbnail != null) { - // Calculate how big the bitmap needs to be. - // This avoids unnecessary processing and allocation inside Palette. - final int requestedArea = thumbnail.getIntrinsicWidth() * - thumbnail.getIntrinsicHeight(); - double scale = 1; - if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) { - scale = Math.sqrt(MAX_WALLPAPER_EXTRACTION_AREA / (double) requestedArea); - } - bitmap = Bitmap.createBitmap((int) (thumbnail.getIntrinsicWidth() * scale), - (int) (thumbnail.getIntrinsicHeight() * scale), Bitmap.Config.ARGB_8888); - final Canvas bmpCanvas = new Canvas(bitmap); - thumbnail.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); - thumbnail.draw(bmpCanvas); - } - - if (bitmap == null) { + colors = WallpaperColors.fromDrawable(thumbnail); + } + + if (colors == null) { Slog.w(TAG, "Cannot extract colors because wallpaper could not be read."); return; } - Palette palette = Palette - .from(bitmap) - .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA) - .generate(); - bitmap.recycle(); - - final List<Pair<Color, Integer>> colors = new ArrayList<>(); - for (Palette.Swatch swatch : palette.getSwatches()) { - colors.add(new Pair<>(Color.valueOf(swatch.getRgb()), - swatch.getPopulation())); + // Even though we can extract colors from live wallpaper thumbnails, + // it's risky to assume that it might support dark text on top of it: + // • Thumbnail might not be accurate. + // • Colors might change over time. + if (!imageWallpaper) { + int colorHints = colors.getColorHints(); + colorHints &= ~WallpaperColors.HINT_SUPPORTS_DARK_TEXT; + colors.setColorHints(colorHints); } synchronized (mLock) { if (wallpaper.wallpaperId == wallpaperId) { - wallpaper.primaryColors = new WallpaperColors(colors); + wallpaper.primaryColors = colors; } else { Slog.w(TAG, "Not setting primary colors since wallpaper changed"); } @@ -2224,17 +2210,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } if (wallpaper.primaryColors != null) { - int colorsCount = wallpaper.primaryColors.getColors().size(); + int colorsCount = wallpaper.primaryColors.getMainColors().size(); out.attribute(null, "colorsCount", Integer.toString(colorsCount)); if (colorsCount > 0) { for (int i = 0; i < colorsCount; i++) { - Pair<Color, Integer> wc = wallpaper.primaryColors.getColors().get(i); - out.attribute(null, "colorValue"+i, Integer.toString(wc.first.toArgb())); - out.attribute(null, "colorWeight"+i, Integer.toString(wc.second)); + final Color wc = wallpaper.primaryColors.getMainColors().get(i); + out.attribute(null, "colorValue"+i, Integer.toString(wc.toArgb())); } } out.attribute(null, "supportsDarkText", - Integer.toString(wallpaper.primaryColors.supportsDarkText() ? 1 : 0)); + Integer.toString(wallpaper.primaryColors.getColorHints())); } out.attribute(null, "name", wallpaper.name); @@ -2469,15 +2454,22 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { wallpaper.padding.bottom = getAttributeInt(parser, "paddingBottom", 0); int colorsCount = getAttributeInt(parser, "colorsCount", 0); if (colorsCount > 0) { - List<Pair<Color, Integer>> colors = new ArrayList<>(); + Color primary = null, secondary = null, tertiary = null; + final List<Color> colors = new ArrayList<>(); for (int i = 0; i < colorsCount; i++) { - colors.add(new Pair<>( - Color.valueOf(getAttributeInt(parser, "colorValue"+i, 0)), - getAttributeInt(parser, "colorWeight"+i, 0) - )); + Color color = Color.valueOf(getAttributeInt(parser, "colorValue" + i, 0)); + if (i == 0) { + primary = color; + } else if (i == 1) { + secondary = color; + } else if (i == 2) { + tertiary = color; + } else { + break; + } } - boolean dark = getAttributeInt(parser, "supportsDarkText", 0) == 1; - wallpaper.primaryColors = new WallpaperColors(colors, dark); + int colorHints = getAttributeInt(parser, "colorHints", 0); + wallpaper.primaryColors = new WallpaperColors(primary, secondary, tertiary, colorHints); } wallpaper.name = parser.getAttributeValue(null, "name"); wallpaper.allowBackup = "true".equals(parser.getAttributeValue(null, "backup")); diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index f138add7ba09..78f21956f548 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT; + import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -418,13 +421,7 @@ final class AccessibilityController { public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) { MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked(); if (spec != null && !spec.isNop()) { - WindowManagerPolicy policy = mWindowManagerService.mPolicy; - final int windowType = windowState.mAttrs.type; - if (!policy.isTopLevelWindow(windowType) && windowState.isChildWindow() - && !policy.canMagnifyWindow(windowType)) { - return null; - } - if (!policy.canMagnifyWindow(windowState.mAttrs.type)) { + if (!mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) { return null; } } @@ -540,8 +537,9 @@ final class AccessibilityController { final int visibleWindowCount = visibleWindows.size(); for (int i = visibleWindowCount - 1; i >= 0; i--) { WindowState windowState = visibleWindows.valueAt(i); - if (windowState.mAttrs.type == WindowManager - .LayoutParams.TYPE_MAGNIFICATION_OVERLAY) { + if ((windowState.mAttrs.type == TYPE_MAGNIFICATION_OVERLAY) + || ((windowState.mAttrs.privateFlags + & PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT) != 0)) { continue; } @@ -715,7 +713,7 @@ final class AccessibilityController { mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay() .getLayerStack()); mSurfaceControl.setLayer(mWindowManagerService.mPolicy.getWindowLayerFromTypeLw( - WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) + TYPE_MAGNIFICATION_OVERLAY) * WindowManagerService.TYPE_LAYER_MULTIPLIER); mSurfaceControl.setPosition(0, 0); mSurface.copyFrom(mSurfaceControl); @@ -1313,7 +1311,7 @@ final class AccessibilityController { && windowType != WindowManager.LayoutParams.TYPE_DRAG && windowType != WindowManager.LayoutParams.TYPE_INPUT_CONSUMER && windowType != WindowManager.LayoutParams.TYPE_POINTER - && windowType != WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY + && windowType != TYPE_MAGNIFICATION_OVERLAY && windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY && windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index c1c72cad0c6b..b1ed35867dcf 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -2044,7 +2044,10 @@ public class AppTransition implements Dump { if (forceOverride || isKeyguardTransit(transit) || !isTransitionSet() || mNextAppTransition == TRANSIT_NONE) { setAppTransition(transit, flags); - } else if (!alwaysKeepCurrent) { + } + // We never want to change from a Keyguard transit to a non-Keyguard transit, as our logic + // relies on the fact that we always execute a Keyguard transition after preparing one. + else if (!alwaysKeepCurrent && !isKeyguardTransit(transit)) { if (transit == TRANSIT_TASK_OPEN && isTransitionEqual(TRANSIT_TASK_CLOSE)) { // Opening a new task always supersedes a close for the anim. setAppTransition(transit, flags); diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java index 8cfbf68e3b5d..fe7494728ac3 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerController.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java @@ -38,8 +38,11 @@ import android.graphics.Rect; import android.os.Debug; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; +import android.os.Message; import android.os.Trace; import android.util.Slog; +import android.view.DisplayInfo; import android.view.IApplicationToken; import android.view.WindowManagerPolicy.StartingSurface; @@ -61,23 +64,38 @@ public class AppWindowContainerController private final IApplicationToken mToken; private final Handler mHandler; - private final Runnable mOnStartingWindowDrawn = () -> { - if (mListener == null) { - return; + private final class H extends Handler { + public static final int NOTIFY_WINDOWS_DRAWN = 1; + public static final int NOTIFY_STARTING_WINDOW_DRAWN = 2; + + public H(Looper looper) { + super(looper); } - if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in " - + AppWindowContainerController.this.mToken); - mListener.onStartingWindowDrawn(); - }; - private final Runnable mOnWindowsDrawn = () -> { - if (mListener == null) { - return; + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case NOTIFY_WINDOWS_DRAWN: + if (mListener == null) { + return; + } + if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in " + + AppWindowContainerController.this.mToken); + mListener.onWindowsDrawn(msg.getWhen()); + break; + case NOTIFY_STARTING_WINDOW_DRAWN: + if (mListener == null) { + return; + } + if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in " + + AppWindowContainerController.this.mToken); + mListener.onStartingWindowDrawn(msg.getWhen()); + break; + default: + break; + } } - if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in " - + AppWindowContainerController.this.mToken); - mListener.onWindowsDrawn(); - }; + } private final Runnable mOnWindowsVisible = () -> { if (mListener == null) { @@ -212,7 +230,7 @@ public class AppWindowContainerController int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos, WindowManagerService service, Configuration overrideConfig, Rect bounds) { super(listener, service); - mHandler = new Handler(service.mH.getLooper()); + mHandler = new H(service.mH.getLooper()); mToken = token; synchronized(mWindowMap) { AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder()); @@ -481,7 +499,7 @@ public class AppWindowContainerController public boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags, IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning, - boolean allowTaskSnapshot, boolean activityCreated) { + boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) { synchronized(mWindowMap) { if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "setAppStartingWindow: token=" + mToken + " pkg=" + pkg + " transferFrom=" + transferFrom + " newTask=" + newTask @@ -510,11 +528,14 @@ public class AppWindowContainerController return false; } + final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot( + mContainer.getTask().mTaskId, mContainer.getTask().mUserId, + false /* restoreFromDisk */, false /* reducedResolution */); final int type = getStartingWindowType(newTask, taskSwitch, processRunning, - allowTaskSnapshot, activityCreated); + allowTaskSnapshot, activityCreated, fromRecents, snapshot); if (type == STARTING_WINDOW_TYPE_SNAPSHOT) { - return createSnapshot(); + return createSnapshot(snapshot); } // If this is a translucent window, then don't show a starting window -- the current @@ -582,7 +603,8 @@ public class AppWindowContainerController } private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning, - boolean allowTaskSnapshot, boolean activityCreated) { + boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents, + TaskSnapshot snapshot) { if (mService.mAppTransition.getAppTransition() == TRANSIT_DOCK_TASK_FROM_RECENTS) { // TODO(b/34099271): Remove this statement to add back the starting window and figure // out why it causes flickering, the starting window appears over the thumbnail while @@ -591,7 +613,9 @@ public class AppWindowContainerController } else if (newTask || !processRunning || (taskSwitch && !activityCreated)) { return STARTING_WINDOW_TYPE_SPLASH_SCREEN; } else if (taskSwitch && allowTaskSnapshot) { - return STARTING_WINDOW_TYPE_SNAPSHOT; + return snapshot == null ? STARTING_WINDOW_TYPE_NONE + : snapshotFillsWidth(snapshot) || fromRecents ? STARTING_WINDOW_TYPE_SNAPSHOT + : STARTING_WINDOW_TYPE_SPLASH_SCREEN; } else { return STARTING_WINDOW_TYPE_NONE; } @@ -605,11 +629,7 @@ public class AppWindowContainerController mService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow); } - private boolean createSnapshot() { - final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot( - mContainer.getTask().mTaskId, mContainer.getTask().mUserId, - false /* restoreFromDisk */, false /* reducedResolution */); - + private boolean createSnapshot(TaskSnapshot snapshot) { if (snapshot == null) { return false; } @@ -620,6 +640,24 @@ public class AppWindowContainerController return true; } + private boolean snapshotFillsWidth(TaskSnapshot snapshot) { + if (snapshot == null) { + return false; + } + final Rect rect = new Rect(0, 0, snapshot.getSnapshot().getWidth(), + snapshot.getSnapshot().getHeight()); + rect.inset(snapshot.getContentInsets()); + final Rect taskBoundsWithoutInsets = new Rect(); + mContainer.getTask().getBounds(taskBoundsWithoutInsets); + final DisplayInfo di = mContainer.getDisplayContent().getDisplayInfo(); + final Rect displayBounds = new Rect(0, 0, di.logicalWidth, di.logicalHeight); + final Rect stableInsets = new Rect(); + mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, + stableInsets); + displayBounds.inset(stableInsets); + return rect.width() >= displayBounds.width(); + } + public void removeStartingWindow() { synchronized (mWindowMap) { if (mHandler.hasCallbacks(mRemoveStartingWindow)) { @@ -740,11 +778,11 @@ public class AppWindowContainerController } void reportStartingWindowDrawn() { - mHandler.post(mOnStartingWindowDrawn); + mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_STARTING_WINDOW_DRAWN)); } void reportWindowsDrawn() { - mHandler.post(mOnWindowsDrawn); + mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_WINDOWS_DRAWN)); } void reportWindowsVisible() { diff --git a/services/core/java/com/android/server/wm/AppWindowContainerListener.java b/services/core/java/com/android/server/wm/AppWindowContainerListener.java index 26537f27bce8..8a39a7408058 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerListener.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerListener.java @@ -19,7 +19,7 @@ package com.android.server.wm; /** Interface used by the creator of the controller to listen to changes with the container. */ public interface AppWindowContainerListener extends WindowContainerListener { /** Called when the windows associated app window container are drawn. */ - void onWindowsDrawn(); + void onWindowsDrawn(long timestamp); /** Called when the windows associated app window container are visible. */ void onWindowsVisible(); /** Called when the windows associated app window container are no longer visible. */ @@ -28,7 +28,7 @@ public interface AppWindowContainerListener extends WindowContainerListener { /** * Called when the starting window for this container is drawn. */ - void onStartingWindowDrawn(); + void onStartingWindowDrawn(long timestamp); /** * Called when the key dispatching to a window associated with the app window container diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 71ecaf61da48..bd379344e18b 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -394,6 +394,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree startingWindow.mPolicyVisibility = false; startingWindow.mPolicyVisibilityAfterAnim = false; } + + // We are becoming visible, so better freeze the screen with the windows that are + // getting visible so we also wait for them. + forAllWindows(mService::makeWindowFreezingScreenIfNeededLocked, true); } if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "setVisibility: " + this @@ -891,8 +895,24 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return mPendingRelaunchCount > 0; } + boolean shouldFreezeBounds() { + final Task task = getTask(); + + // For freeform windows, we can't freeze the bounds at the moment because this would make + // the resizing unresponsive. + if (task == null || task.inFreeformWorkspace()) { + return false; + } + + // We freeze the bounds while drag resizing to deal with the time between + // the divider/drag handle being released, and the handling it's new + // configuration. If we are relaunched outside of the drag resizing state, + // we need to be careful not to do this. + return getTask().isDragResizing(); + } + void startRelaunching() { - if (canFreezeBounds()) { + if (shouldFreezeBounds()) { freezeBounds(); } @@ -909,9 +929,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } void finishRelaunching() { - if (canFreezeBounds()) { - unfreezeBounds(); - } + unfreezeBounds(); + if (mPendingRelaunchCount > 0) { mPendingRelaunchCount--; } else { @@ -926,9 +945,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (mPendingRelaunchCount == 0) { return; } - if (canFreezeBounds()) { - unfreezeBounds(); - } + unfreezeBounds(); mPendingRelaunchCount = 0; } @@ -1032,14 +1049,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } } - private boolean canFreezeBounds() { - final Task task = getTask(); - - // For freeform windows, we can't freeze the bounds at the moment because this would make - // the resizing unresponsive. - return task != null && !task.inFreeformWorkspace(); - } - /** * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even @@ -1064,9 +1073,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree * Unfreezes the previously frozen bounds. See {@link #freezeBounds}. */ private void unfreezeBounds() { - if (!mFrozenBounds.isEmpty()) { - mFrozenBounds.remove(); + if (mFrozenBounds.isEmpty()) { + return; } + mFrozenBounds.remove(); if (!mFrozenMergedConfig.isEmpty()) { mFrozenMergedConfig.remove(); } diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java index 7d13889eee35..cff2fadd7649 100644 --- a/services/core/java/com/android/server/wm/BoundsAnimationController.java +++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java @@ -206,6 +206,10 @@ public class BoundsAnimationController { mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth, mFrom.top + mFrozenTaskHeight); + // Boost the thread priority of the animation thread while the bounds animation is + // running + updateBooster(); + // Ensure that we have prepared the target for animation before // we trigger any size changes, so it can swap surfaces // in to appropriate modes, or do as it wishes otherwise. @@ -316,6 +320,9 @@ public class BoundsAnimationController { removeListener(this); removeUpdateListener(this); mRunningAnimations.remove(mTarget); + + // Reset the thread priority of the animation thread after the bounds animation is done + updateBooster(); } @Override @@ -446,4 +453,9 @@ public class BoundsAnimationController { b.resume(); } } + + private void updateBooster() { + WindowManagerService.sThreadPriorityBooster.setBoundsAnimationRunning( + !mRunningAnimations.isEmpty()); + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ccc8f63e4355..c98d60dcad93 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -210,6 +210,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics(); /** @see #computeCompatSmallestWidth(boolean, int, int, int, int) */ private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics(); + /** * Compat metrics computed based on {@link #mDisplayMetrics}. * @see #updateDisplayAndOrientation(int) @@ -226,6 +227,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @see #updateRotationUnchecked(boolean) */ private int mRotation = 0; + /** * Last applied orientation of the display. * Constants as per {@link android.content.pm.ActivityInfo.ScreenOrientation}. @@ -233,6 +235,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @see WindowManagerService#updateOrientationFromAppTokensLocked(boolean, int) */ private int mLastOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + /** * Flag indicating that the application is receiving an orientation that has different metrics * than it expected. E.g. Portrait instead of Landscape. @@ -240,6 +243,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @see #updateRotationUnchecked(boolean) */ private boolean mAltOrientation = false; + /** * Orientation forced by some window. If there is no visible window that specifies orientation * it is set to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}. @@ -247,6 +251,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @see NonAppWindowContainers#getOrientation() */ private int mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + /** * Last orientation forced by the keyguard. It is applied when keyguard is shown and is not * occluded. @@ -255,6 +260,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ private int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + /** + * Keep track of wallpaper visibility to notify changes. + */ + private boolean mLastWallpaperVisible = false; + private Rect mBaseDisplayRect = new Rect(); private Rect mContentRect = new Rect(); @@ -314,6 +324,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // the display's direct children should be allowed. private boolean mRemovingDisplay = false; + // {@code false} if this display is in the processing of being created. + private boolean mDisplayReady = false; + private final WindowLayersController mLayersController; WallpaperController mWallpaperController; int mInputMethodAnimLayerAdjustment; @@ -720,7 +733,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ DisplayContent(Display display, WindowManagerService service, WindowLayersController layersController, WallpaperController wallpaperController) { - if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) { throw new IllegalArgumentException("Display with ID=" + display.getDisplayId() + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId()) @@ -748,6 +760,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Add itself as a child to the root container. mService.mRoot.addChild(this, null); + + // TODO(b/62541591): evaluate whether this is the best spot to declare the + // {@link DisplayContent} ready for use. + mDisplayReady = true; + } + + boolean isReady() { + // The display is ready when the system and the individual display are both ready. + return mService.mDisplayReady && mDisplayReady; } int getDisplayId() { @@ -1196,7 +1217,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo (displayInfo.isHdr() ? Configuration.COLOR_MODE_HDR_YES : Configuration.COLOR_MODE_HDR_NO) - | (displayInfo.isWideColorGamut() + | (displayInfo.isWideColorGamut() && mService.hasWideColorGamutSupport() ? Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES : Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO); @@ -2754,6 +2775,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo stopDimmingIfNeeded(); + final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible(); + if (wallpaperVisible != mLastWallpaperVisible) { + mLastWallpaperVisible = wallpaperVisible; + mService.mWallpaperVisibilityListeners.notifyWallpaperVisibilityChanged(this); + } + while (!mTmpUpdateAllDrawn.isEmpty()) { final AppWindowToken atoken = mTmpUpdateAllDrawn.removeLast(); // See if any windows have been drawn, so they (and others associated with them) diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index 9a9e29a7eb88..6d33ce2941bc 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -155,6 +155,10 @@ class PinnedStackController { mSnapAlgorithm = new PipSnapAlgorithm(service.mContext); mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo()); reloadResources(); + // Initialize the aspect ratio to the default aspect ratio. Don't do this in reload + // resources as it would clobber mAspectRatio when entering PiP from fullscreen which + // triggers a configuration change and the resources to be reloaded. + mAspectRatio = mDefaultAspectRatio; } void onConfigurationChanged() { @@ -171,7 +175,6 @@ class PinnedStackController { mCurrentMinSize = mDefaultMinSize; mDefaultAspectRatio = res.getFloat( com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio); - mAspectRatio = mDefaultAspectRatio; final String screenEdgeInsetsDpString = res.getString( com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets); final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty() diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 091e1cba43a2..22b0f5bcdf07 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; @@ -37,6 +38,7 @@ import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.Trace; import android.os.UserHandle; import android.util.MergedConfiguration; import android.util.Slog; @@ -85,6 +87,7 @@ public class Session extends IWindowSession.Stub private boolean mClientDead = false; private float mLastReportedAnimatorScale; private String mPackageName; + private String mRelayoutTag; public Session(WindowManagerService service, IWindowSessionCallback callback, IInputMethodClient client, IInputContext inputContext) { @@ -224,10 +227,12 @@ public class Session extends IWindowSession.Stub MergedConfiguration mergedConfiguration, Surface outSurface) { if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag); int res = mService.relayoutWindow(this, window, seq, attrs, requestedWidth, requestedHeight, viewFlags, flags, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, outStableInsets, outsets, outBackdropFrame, mergedConfiguration, outSurface); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); return res; @@ -575,6 +580,7 @@ public class Session extends IWindowSession.Stub void windowAddedLocked(String packageName) { mPackageName = packageName; + mRelayoutTag = "relayoutWindow: " + mPackageName; if (mSurfaceSession == null) { if (WindowManagerService.localLOGV) Slog.v( TAG_WM, "First window added to " + this + ", creating SurfaceSession"); @@ -698,6 +704,7 @@ public class Session extends IWindowSession.Stub pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces); pw.print(" mClientDead="); pw.print(mClientDead); pw.print(" mSurfaceSession="); pw.println(mSurfaceSession); + pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName); } @Override diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index 551e3bf13339..a96d22412918 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -125,6 +125,7 @@ class TaskSnapshotSurface implements StartingSurface { private final Paint mBackgroundPaint = new Paint(); private final int mStatusBarColor; @VisibleForTesting final SystemBarBackgroundPainter mSystemBarBackgroundPainter; + private final int mOrientationOnCreation; static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token, TaskSnapshot snapshot) { @@ -146,6 +147,7 @@ class TaskSnapshotSurface implements StartingSurface { final int sysUiVis; final int windowFlags; final int windowPrivateFlags; + final int currentOrientation; synchronized (service.mWindowMap) { final WindowState mainWindow = token.findMainWindow(); if (mainWindow == null) { @@ -183,6 +185,7 @@ class TaskSnapshotSurface implements StartingSurface { } else { taskBounds = null; } + currentOrientation = mainWindow.getConfiguration().orientation; } try { final int res = session.addToDisplay(window, window.mSeq, layoutParams, @@ -197,7 +200,8 @@ class TaskSnapshotSurface implements StartingSurface { } final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window, surface, snapshot, layoutParams.getTitle(), backgroundColor, statusBarColor, - navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds); + navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds, + currentOrientation); window.setOuter(snapshotSurface); try { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame, @@ -215,7 +219,7 @@ class TaskSnapshotSurface implements StartingSurface { TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface, TaskSnapshot snapshot, CharSequence title, int backgroundColor, int statusBarColor, int navigationBarColor, int sysUiVis, int windowFlags, int windowPrivateFlags, - Rect taskBounds) { + Rect taskBounds, int currentOrientation) { mService = service; mHandler = new Handler(mService.mH.getLooper()); mSession = WindowManagerGlobal.getWindowSession(); @@ -228,6 +232,7 @@ class TaskSnapshotSurface implements StartingSurface { mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags, windowPrivateFlags, sysUiVis, statusBarColor, navigationBarColor); mStatusBarColor = statusBarColor; + mOrientationOnCreation = currentOrientation; } @Override @@ -394,6 +399,7 @@ class TaskSnapshotSurface implements StartingSurface { static class Window extends BaseIWindow { private TaskSnapshotSurface mOuter; + public void setOuter(TaskSnapshotSurface outer) { mOuter = outer; } @@ -403,6 +409,15 @@ class TaskSnapshotSurface implements StartingSurface { Rect stableInsets, Rect outsets, boolean reportDraw, MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId) { + if (mergedConfiguration != null && mOuter != null + && mOuter.mOrientationOnCreation + != mergedConfiguration.getMergedConfiguration().orientation) { + + // The orientation of the screen is changing. We better remove the snapshot ASAP as + // we are going to wait on the new window in any case to unfreeze the screen, and + // the starting window is not needed anymore. + sHandler.post(mOuter::remove); + } if (reportDraw) { sHandler.obtainMessage(MSG_REPORT_DRAW, mOuter).sendToTarget(); } diff --git a/services/core/java/com/android/server/wm/WallpaperVisibilityListeners.java b/services/core/java/com/android/server/wm/WallpaperVisibilityListeners.java new file mode 100644 index 000000000000..2c06851cacf4 --- /dev/null +++ b/services/core/java/com/android/server/wm/WallpaperVisibilityListeners.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.SparseArray; +import android.view.IWallpaperVisibilityListener; + +/** + * Manages and trigger wallpaper visibility listeners. + */ +class WallpaperVisibilityListeners { + + /** + * A map of displayIds and its listeners. + */ + private final SparseArray<RemoteCallbackList<IWallpaperVisibilityListener>> mDisplayListeners = + new SparseArray<>(); + + void registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener, + int displayId) { + RemoteCallbackList<IWallpaperVisibilityListener> listeners = + mDisplayListeners.get(displayId); + if (listeners == null) { + listeners = new RemoteCallbackList<>(); + mDisplayListeners.append(displayId, listeners); + } + listeners.register(listener); + } + + void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener, + int displayId) { + RemoteCallbackList<IWallpaperVisibilityListener> listeners = + mDisplayListeners.get(displayId); + if (listeners == null) { + return; + } + listeners.unregister(listener); + } + + void notifyWallpaperVisibilityChanged(DisplayContent displayContent) { + final int displayId = displayContent.getDisplayId(); + final boolean visible = displayContent.mWallpaperController.isWallpaperVisible(); + RemoteCallbackList<IWallpaperVisibilityListener> displayListeners = + mDisplayListeners.get(displayId); + + // No listeners for this display. + if (displayListeners == null) { + return; + } + + int i = displayListeners.beginBroadcast(); + while (i > 0) { + i--; + IWallpaperVisibilityListener listener = displayListeners.getBroadcastItem(i); + try { + listener.onWallpaperVisibilityChanged(visible, displayId); + } catch (RemoteException e) { + // Nothing to do in here, RemoteCallbackListener will clean it up. + } + } + displayListeners.finishBroadcast(); + } +} diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 03b5b827db74..fe5b7f23d5e0 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -108,7 +108,8 @@ public class WindowAnimator { } void addDisplayLocked(final int displayId) { - // Create the DisplayContentsAnimator object by retrieving it. + // Create the DisplayContentsAnimator object by retrieving it if the associated + // {@link DisplayContent} exists. getDisplayContentsAnimatorLocked(displayId); if (displayId == DEFAULT_DISPLAY) { mInitialized = true; @@ -227,7 +228,10 @@ public class WindowAnimator { Slog.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { if (transactionOpen) { - mService.closeSurfaceTransaction(); + + // Do not hold window manager lock while closing the transaction, as this might be + // blocking until the next frame, which can lead to total lock starvation. + mService.closeSurfaceTransaction(false /* withLockHeld */); if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate"); } } @@ -356,8 +360,16 @@ public class WindowAnimator { } private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) { + if (displayId < 0) { + return null; + } + DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId); - if (displayAnimator == null) { + + // It is possible that this underlying {@link DisplayContent} has been removed. In this + // case, we do not want to create an animator associated with it as {link #animate} will + // fail. + if (displayAnimator == null && mService.mRoot.getDisplayContent(displayId) != null) { displayAnimator = new DisplayContentsAnimator(); mDisplayContentsAnimators.put(displayId, displayAnimator); } @@ -365,8 +377,10 @@ public class WindowAnimator { } void setScreenRotationAnimationLocked(int displayId, ScreenRotationAnimation animation) { - if (displayId >= 0) { - getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation = animation; + final DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId); + + if (animator != null) { + animator.mScreenRotationAnimation = animation; } } @@ -374,7 +388,9 @@ public class WindowAnimator { if (displayId < 0) { return null; } - return getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation; + + DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId); + return animator != null? animator.mScreenRotationAnimation : null; } void requestRemovalOfReplacedWindows(WindowState win) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 3b7ec341b496..f9d7c3704341 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -30,6 +30,7 @@ import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Process.THREAD_PRIORITY_DISPLAY; import static android.os.Process.myPid; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.os.UserHandle.USER_NULL; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -131,6 +132,8 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; +import android.hardware.configstore.V1_0.ISurfaceFlingerConfigs; +import android.hardware.configstore.V1_0.OptionalBool; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.InputManager; @@ -181,6 +184,7 @@ import android.view.IInputFilter; import android.view.IOnKeyguardExitResult; import android.view.IPinnedStackListener; import android.view.IRotationWatcher; +import android.view.IWallpaperVisibilityListener; import android.view.IWindow; import android.view.IWindowId; import android.view.IWindowManager; @@ -549,9 +553,9 @@ public class WindowManagerService extends IWindowManager.Stub } class RotationWatcher { - IRotationWatcher mWatcher; - IBinder.DeathRecipient mDeathRecipient; - int mDisplayId; + final IRotationWatcher mWatcher; + final IBinder.DeathRecipient mDeathRecipient; + final int mDisplayId; RotationWatcher(IRotationWatcher watcher, IBinder.DeathRecipient deathRecipient, int displayId) { mWatcher = watcher; @@ -562,6 +566,8 @@ public class WindowManagerService extends IWindowManager.Stub ArrayList<RotationWatcher> mRotationWatchers = new ArrayList<>(); int mDeferredRotationPauseCount; + final WallpaperVisibilityListeners mWallpaperVisibilityListeners = + new WallpaperVisibilityListeners(); int mSystemDecorLayer = 0; final Rect mScreenRect = new Rect(); @@ -712,6 +718,9 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayManager mDisplayManager; private final Display[] mDisplays; + // Indicates whether this device supports wide color gamut rendering + private boolean mHasWideColorGamutSupport; + // Who is holding the screen on. private Session mHoldingScreenOn; private PowerManager.WakeLock mHoldingScreenWakeLock; @@ -884,20 +893,46 @@ public class WindowManagerService extends IWindowManager.Stub } void openSurfaceTransaction() { - synchronized (mWindowMap) { - if (mRoot.mSurfaceTraceEnabled) { - mRoot.mRemoteEventTrace.openSurfaceTransaction(); + try { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction"); + synchronized (mWindowMap) { + if (mRoot.mSurfaceTraceEnabled) { + mRoot.mRemoteEventTrace.openSurfaceTransaction(); + } + SurfaceControl.openTransaction(); } - SurfaceControl.openTransaction(); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } void closeSurfaceTransaction() { - synchronized (mWindowMap) { - if (mRoot.mSurfaceTraceEnabled) { - mRoot.mRemoteEventTrace.closeSurfaceTransaction(); + closeSurfaceTransaction(true /* withLockHeld */); + } + + /** + * Closes a surface transaction. + * + * @param withLockHeld Whether to acquire the window manager while doing so. In some cases + * holding the lock my lead to starvation in WM in case closeTransaction + * blocks and we call it repeatedly, like we do for animations. + */ + void closeSurfaceTransaction(boolean withLockHeld) { + try { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction"); + synchronized (mWindowMap) { + if (mRoot.mSurfaceTraceEnabled) { + mRoot.mRemoteEventTrace.closeSurfaceTransaction(); + } + if (withLockHeld) { + SurfaceControl.closeTransaction(); + } + } + if (!withLockHeld) { + SurfaceControl.closeTransaction(); } - SurfaceControl.closeTransaction(); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } @@ -1270,14 +1305,6 @@ public class WindowManagerService extends IWindowManager.Stub + token + ". Aborting."); return WindowManagerGlobal.ADD_APP_EXITING; } - if (rootType == TYPE_APPLICATION_STARTING - && (attrs.privateFlags & PRIVATE_FLAG_TASK_SNAPSHOT) == 0 - && atoken.firstWindowDrawn) { - // No need for this guy! - if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v( - TAG_WM, "**** NO NEED TO START: " + attrs.getTitle()); - return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED; - } } else if (rootType == TYPE_INPUT_METHOD) { if (token.windowType != TYPE_INPUT_METHOD) { Slog.w(TAG_WM, "Attempted to add input method window with bad token " @@ -1980,6 +2007,8 @@ public class WindowManagerService extends IWindowManager.Stub (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING || !win.mAppToken.isClientHidden())) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1"); + // We are about to create a surface, but we didn't run a layout yet. So better run // a layout now that we already know the right size, as a resize call will make the // surface transaction blocking until next vsync and slow us down. @@ -1991,6 +2020,7 @@ public class WindowManagerService extends IWindowManager.Stub } result = win.relayoutVisibleWindow(mergedConfiguration, result, attrChanges, oldVisibility); + try { result = createSurfaceControl(outSurface, result, win, winAnimator); } catch (Exception e) { @@ -2010,7 +2040,10 @@ public class WindowManagerService extends IWindowManager.Stub imMayMove = true; } win.adjustStartingWindowFlags(); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } else { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_2"); + winAnimator.mEnterAnimationPending = false; winAnimator.mEnteringAnimation = false; final boolean usingSavedSurfaceBeforeVisible = @@ -2045,18 +2078,22 @@ public class WindowManagerService extends IWindowManager.Stub // We already told the client to go invisible, but the message may not be // handled yet, or it might want to draw a last frame. If we already have a // surface, let the client use that, but don't create new surface at this point. + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface"); winAnimator.mSurfaceController.getSurface(outSurface); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } else { if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win); try { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_" + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_" + win.mAttrs.getTitle()); outSurface.release(); } finally { - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } + + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } if (focusMayChange) { @@ -2093,8 +2130,11 @@ public class WindowManagerService extends IWindowManager.Stub } win.setDisplayLayoutNeeded(); - win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0; + win.mGivenInsetsPending = (flags & WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0; + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, + "relayoutWindow: updateOrientationFromAppTokens"); configChanged = updateOrientationFromAppTokensLocked(false, displayId); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); // We may be deferring layout passes at the moment, but since the client is interested // in the new out values right now we need to force a layout. @@ -2147,7 +2187,9 @@ public class WindowManagerService extends IWindowManager.Stub } if (configChanged) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: sendNewConfiguration"); sendNewConfiguration(displayId); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } Binder.restoreCallingIdentity(origId); return result; @@ -2208,8 +2250,14 @@ public class WindowManagerService extends IWindowManager.Stub if (!win.mHasSurface) { result |= RELAYOUT_RES_SURFACE_CHANGED; } - WindowSurfaceController surfaceController = winAnimator.createSurfaceLocked( - win.mAttrs.type, win.mOwnerUid); + + WindowSurfaceController surfaceController; + try { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl"); + surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } if (surfaceController != null) { surfaceController.getSurface(outSurface); if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " OUT SURFACE " + outSurface + ": copied"); @@ -2219,6 +2267,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG_WM, "Failed to create surface control for " + win); outSurface.release(); } + return result; } @@ -2265,7 +2314,7 @@ public class WindowManagerService extends IWindowManager.Stub // frozen, there is no reason to animate and it can cause strange // artifacts when we unfreeze the display if some different animation // is running. - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked"); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked"); if (okToDisplay()) { final DisplayContent displayContent = atoken.getTask().getDisplayContent(); final DisplayInfo displayInfo = displayContent.getDisplayInfo(); @@ -2321,7 +2370,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { atoken.mAppAnimator.clearAnimation(); } - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return atoken.mAppAnimator.animation != null; } @@ -3029,6 +3078,10 @@ public class WindowManagerService extends IWindowManager.Stub return mPolicy.isKeyguardLocked(); } + public boolean isKeyguardShowingAndNotOccluded() { + return mPolicy.isKeyguardShowingAndNotOccluded(); + } + @Override public boolean isKeyguardSecure() { int userId = UserHandle.getCallingUserId(); @@ -3404,7 +3457,7 @@ public class WindowManagerService extends IWindowManager.Stub if (!mBootAnimationStopped) { // Do this one time. - Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0); + Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0); try { IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger"); if (surfaceFlinger != null) { @@ -3427,7 +3480,7 @@ public class WindowManagerService extends IWindowManager.Stub } EventLog.writeEvent(EventLogTags.WM_BOOT_ANIMATION_DONE, SystemClock.uptimeMillis()); - Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0); + Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0); mDisplayEnabled = true; if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM, "******************** ENABLING SCREEN!"); @@ -3658,12 +3711,12 @@ public class WindowManagerService extends IWindowManager.Stub throw new SecurityException("Requires READ_FRAME_BUFFER permission"); } try { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper"); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper"); return screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */, -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */, Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */); } finally { - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } @@ -3849,6 +3902,8 @@ public class WindowManagerService extends IWindowManager.Stub + " alwaysSendConfiguration=" + alwaysSendConfiguration + " forceRelayout=" + forceRelayout); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation"); + long origId = Binder.clearCallingIdentity(); try { @@ -3857,20 +3912,28 @@ public class WindowManagerService extends IWindowManager.Stub final int displayId; synchronized (mWindowMap) { final DisplayContent displayContent = getDefaultDisplayContentLocked(); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display"); rotationChanged = displayContent.updateRotationUnchecked( false /* inTransaction */); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (!rotationChanged || forceRelayout) { displayContent.setLayoutNeeded(); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, + "updateRotation: performSurfacePlacement"); mWindowPlacerLocked.performSurfacePlacement(); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } displayId = displayContent.getDisplayId(); } if (rotationChanged || alwaysSendConfiguration) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: sendNewConfiguration"); sendNewConfiguration(displayId); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } finally { Binder.restoreCallingIdentity(origId); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } @@ -3937,6 +4000,29 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public boolean registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener, + int displayId) { + synchronized (mWindowMap) { + final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId); + if (displayContent == null) { + throw new IllegalArgumentException("Trying to register visibility event " + + "for invalid display: " + displayId); + } + mWallpaperVisibilityListeners.registerWallpaperVisibilityListener(listener, displayId); + return displayContent.mWallpaperController.isWallpaperVisible(); + } + } + + @Override + public void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener, + int displayId) { + synchronized (mWindowMap) { + mWallpaperVisibilityListeners + .unregisterWallpaperVisibilityListener(listener, displayId); + } + } + /** * Apps that use the compact menu panel (as controlled by the panelMenuIsCompact * theme attribute) on devices that feature a physical options menu key attempt to position @@ -4688,6 +4774,20 @@ public class WindowManagerService extends IWindowManager.Stub public void systemReady() { mPolicy.systemReady(); mTaskSnapshotController.systemReady(); + mHasWideColorGamutSupport = queryWideColorGamutSupport(); + } + + private static boolean queryWideColorGamutSupport() { + try { + ISurfaceFlingerConfigs surfaceFlinger = ISurfaceFlingerConfigs.getService(); + OptionalBool hasWideColor = surfaceFlinger.hasWideColorDisplay(); + if (hasWideColor != null) { + return hasWideColor.value; + } + } catch (RemoteException e) { + // Ignore, we're in big trouble if we can't talk to SurfaceFlinger's config store + } + return false; } // ------------------------------------------------------------- @@ -5150,7 +5250,8 @@ public class WindowManagerService extends IWindowManager.Stub } break; case NOTIFY_APP_TRANSITION_STARTING: { - mAmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj); + mAmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj, + msg.getWhen()); } break; case NOTIFY_APP_TRANSITION_CANCELLED: { @@ -5797,7 +5898,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { WindowState newFocus = mRoot.computeFocusedWindow(); if (mCurrentFocus != newFocus) { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus"); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus"); // This check makes sure that we don't already have the focus // change message pending. mH.removeMessages(H.REPORT_FOCUS_CHANGE); @@ -5873,7 +5974,7 @@ public class WindowManagerService extends IWindowManager.Stub // other apps' UI. displayContent.scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus); - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return true; } return false; @@ -5890,8 +5991,8 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (!mDisplayReady || !mPolicy.isScreenOn()) { - // No need to freeze the screen before the system is ready or if + if (!displayContent.isReady() || !mPolicy.isScreenOn()) { + // No need to freeze the screen before the display is ready, system is ready, or if // the screen is off. return; } @@ -7256,6 +7357,11 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public boolean isKeyguardShowingAndNotOccluded() { + return WindowManagerService.this.isKeyguardShowingAndNotOccluded(); + } + + @Override public void showGlobalActions() { WindowManagerService.this.showGlobalActions(); } @@ -7477,4 +7583,8 @@ public class WindowManagerService extends IWindowManager.Stub } } } + + boolean hasWideColorGamutSupport() { + return mHasWideColorGamutSupport; + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java index 6a244a251537..1b2eb465399a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java +++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java @@ -23,6 +23,7 @@ import static android.os.Process.setThreadPriority; import static com.android.server.LockGuard.INDEX_WINDOW; import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST; +import com.android.internal.annotations.GuardedBy; import com.android.server.AnimationThread; import com.android.server.ThreadPriorityBooster; @@ -32,12 +33,18 @@ import com.android.server.ThreadPriorityBooster; */ class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster { - private final AnimationThread mAnimationThread; + private final Object mLock = new Object(); + + private final int mAnimationThreadId; + + @GuardedBy("mLock") private boolean mAppTransitionRunning; + @GuardedBy("mLock") + private boolean mBoundsAnimationRunning; WindowManagerThreadPriorityBooster() { super(THREAD_PRIORITY_DISPLAY, INDEX_WINDOW); - mAnimationThread = AnimationThread.get(); + mAnimationThreadId = AnimationThread.get().getThreadId(); } @Override @@ -45,7 +52,7 @@ class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster { // Do not boost the animation thread. As the animation thread is changing priorities, // boosting it might mess up the priority because we reset it the the previous priority. - if (myTid() == mAnimationThread.getThreadId()) { + if (myTid() == mAnimationThreadId) { return; } super.boost(); @@ -55,24 +62,35 @@ class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster { public void reset() { // See comment in boost(). - if (myTid() == mAnimationThread.getThreadId()) { + if (myTid() == mAnimationThreadId) { return; } super.reset(); } void setAppTransitionRunning(boolean running) { - if (mAppTransitionRunning == running) { - return; + synchronized (mLock) { + if (mAppTransitionRunning != running) { + mAppTransitionRunning = running; + updatePriorityLocked(); + } } + } - final int priority = calculatePriority(running); - setBoostToPriority(priority); - setThreadPriority(mAnimationThread.getThreadId(), priority); - mAppTransitionRunning = running; + void setBoundsAnimationRunning(boolean running) { + synchronized (mLock) { + if (mBoundsAnimationRunning != running) { + mBoundsAnimationRunning = running; + updatePriorityLocked(); + } + } } - private int calculatePriority(boolean appTransitionRunning) { - return appTransitionRunning ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY; + @GuardedBy("mLock") + private void updatePriorityLocked() { + int priority = (mAppTransitionRunning || mBoundsAnimationRunning) + ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY; + setBoostToPriority(priority); + setThreadPriority(mAnimationThreadId, priority); } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 73f8d27bc7a6..33cb9081325b 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1524,7 +1524,10 @@ class WindowStateAnimator { void prepareSurfaceLocked(final boolean recoveringMemory) { final WindowState w = mWin; if (!hasSurface()) { - if (w.mOrientationChanging) { + + // There is no need to wait for an animation change if our window is gone for layout + // already as we'll never be visible. + if (w.mOrientationChanging && w.isGoneForLayoutLw()) { if (DEBUG_ORIENTATION) { Slog.v(TAG, "Orientation change skips hidden " + w); } @@ -1557,13 +1560,11 @@ class WindowStateAnimator { hide("prepareSurfaceLocked"); mWallpaperControllerLocked.hideWallpapers(w); - // If we are waiting for this window to handle an - // orientation change, well, it is hidden, so - // doesn't really matter. Note that this does - // introduce a potential glitch if the window - // becomes unhidden before it has drawn for the - // new orientation. - if (w.mOrientationChanging) { + // If we are waiting for this window to handle an orientation change. If this window is + // really hidden (gone for layout), there is no point in still waiting for it. + // Note that this does introduce a potential glitch if the window becomes unhidden + // before it has drawn for the new orientation. + if (w.mOrientationChanging && w.isGoneForLayoutLw()) { w.mOrientationChanging = false; if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change skips hidden " + w); @@ -1630,18 +1631,19 @@ class WindowStateAnimator { displayed = true; } - if (displayed) { - if (w.mOrientationChanging) { - if (!w.isDrawnLw()) { - mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE; - mAnimator.mLastWindowFreezeSource = w; - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation continue waiting for draw in " + w); - } else { - w.mOrientationChanging = false; - if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w); - } + if (w.mOrientationChanging) { + if (!w.isDrawnLw()) { + mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE; + mAnimator.mLastWindowFreezeSource = w; + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation continue waiting for draw in " + w); + } else { + w.mOrientationChanging = false; + if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w); } + } + + if (displayed) { w.mToken.hasVisible = true; } } diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index edbdf8bc5ed9..27927e6c0693 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; @@ -33,6 +34,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.os.IBinder; import android.os.Debug; +import android.os.Trace; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -101,8 +103,10 @@ class WindowSurfaceController { mSurfaceControl = new SurfaceTrace( s, name, w, h, format, flags, windowType, ownerUid); } else { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl"); mSurfaceControl = new SurfaceControl( s, name, w, h, format, flags, windowType, ownerUid); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } if (mService.mRoot.mSurfaceTraceEnabled) { diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 4442bb87ec33..82c862f6a6a8 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -25,6 +25,7 @@ import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_CLOSE; import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE; import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN; import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_OPEN; +import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; @@ -239,7 +240,7 @@ class WindowSurfacePlacer { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO"); int transit = mService.mAppTransition.getAppTransition(); - if (mService.mSkipAppTransitionAnimation) { + if (mService.mSkipAppTransitionAnimation && !isKeyguardGoingAwayTransit(transit)) { transit = AppTransition.TRANSIT_UNSET; } mService.mSkipAppTransitionAnimation = false; @@ -598,42 +599,47 @@ class WindowSurfacePlacer { + ", openingApps=" + openingApps + ", closingApps=" + closingApps); mService.mAnimateWallpaperWithTarget = false; - if (closingAppHasWallpaper && openingAppHasWallpaper) { - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!"); - switch (transit) { - case TRANSIT_ACTIVITY_OPEN: - case TRANSIT_TASK_OPEN: - case TRANSIT_TASK_TO_FRONT: - transit = TRANSIT_WALLPAPER_INTRA_OPEN; - break; - case TRANSIT_ACTIVITY_CLOSE: - case TRANSIT_TASK_CLOSE: - case TRANSIT_TASK_TO_BACK: - transit = TRANSIT_WALLPAPER_INTRA_CLOSE; - break; - } - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "New transit: " + AppTransition.appTransitionToString(transit)); - } else if (openingCanBeWallpaperTarget && transit == TRANSIT_KEYGUARD_GOING_AWAY) { + if (openingCanBeWallpaperTarget && transit == TRANSIT_KEYGUARD_GOING_AWAY) { transit = TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit: " + AppTransition.appTransitionToString(transit)); - } else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty() - && !openingApps.contains(oldWallpaper.mAppToken) - && closingApps.contains(oldWallpaper.mAppToken)) { - // We are transitioning from an activity with a wallpaper to one without. - transit = TRANSIT_WALLPAPER_CLOSE; - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: " - + AppTransition.appTransitionToString(transit)); - } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw() && - openingApps.contains(wallpaperTarget.mAppToken)) { - // We are transitioning from an activity without - // a wallpaper to now showing the wallpaper - transit = TRANSIT_WALLPAPER_OPEN; - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit into wallpaper: " - + AppTransition.appTransitionToString(transit)); - } else { - mService.mAnimateWallpaperWithTarget = true; + } + // We never want to change from a Keyguard transit to a non-Keyguard transit, as our logic + // relies on the fact that we always execute a Keyguard transition after preparing one. + else if (!isKeyguardGoingAwayTransit(transit)) { + if (closingAppHasWallpaper && openingAppHasWallpaper) { + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!"); + switch (transit) { + case TRANSIT_ACTIVITY_OPEN: + case TRANSIT_TASK_OPEN: + case TRANSIT_TASK_TO_FRONT: + transit = TRANSIT_WALLPAPER_INTRA_OPEN; + break; + case TRANSIT_ACTIVITY_CLOSE: + case TRANSIT_TASK_CLOSE: + case TRANSIT_TASK_TO_BACK: + transit = TRANSIT_WALLPAPER_INTRA_CLOSE; + break; + } + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "New transit: " + AppTransition.appTransitionToString(transit)); + } else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty() + && !openingApps.contains(oldWallpaper.mAppToken) + && closingApps.contains(oldWallpaper.mAppToken)) { + // We are transitioning from an activity with a wallpaper to one without. + transit = TRANSIT_WALLPAPER_CLOSE; + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: " + + AppTransition.appTransitionToString(transit)); + } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw() && + openingApps.contains(wallpaperTarget.mAppToken)) { + // We are transitioning from an activity without + // a wallpaper to now showing the wallpaper + transit = TRANSIT_WALLPAPER_OPEN; + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit into wallpaper: " + + AppTransition.appTransitionToString(transit)); + } else { + mService.mAnimateWallpaperWithTarget = true; + } } return transit; } diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp index 96c2d7e38232..470cc572e3e5 100644 --- a/services/core/jni/com_android_server_SystemServer.cpp +++ b/services/core/jni/com_android_server_SystemServer.cpp @@ -39,7 +39,7 @@ static void android_server_SystemServer_startSensorService(JNIEnv* /* env */, jo } -static void android_server_SystemServer_startHidlServices(JNIEnv* /* env */, jobject /* clazz */) { +static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject /* clazz */) { using ::android::frameworks::schedulerservice::V1_0::ISchedulingPolicyService; using ::android::frameworks::schedulerservice::V1_0::implementation::SchedulingPolicyService; using ::android::frameworks::sensorservice::V1_0::ISensorManager; @@ -50,7 +50,10 @@ static void android_server_SystemServer_startHidlServices(JNIEnv* /* env */, job configureRpcThreadpool(5, false /* callerWillJoin */); - sp<ISensorManager> sensorService = new SensorManager(); + JavaVM *vm; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM"); + + sp<ISensorManager> sensorService = new SensorManager(vm); err = sensorService->registerAsService(); ALOGE_IF(err != OK, "Cannot register %s: %d", ISensorManager::descriptor, err); diff --git a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp index 241ccf6ed166..4e5c27fca784 100644 --- a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp +++ b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp @@ -71,7 +71,7 @@ int conntrackSocket(unsigned groups) { // auto-close it (otherwise there would be double-close problems). // // Rely upon the compiler to eliminate the constexprs used for clarity. -hidl_handle&& handleFromFileDescriptor(base::unique_fd fd) { +hidl_handle handleFromFileDescriptor(base::unique_fd fd) { hidl_handle h; NATIVE_HANDLE_DECLARE_STORAGE(storage, 0, 0); @@ -83,7 +83,7 @@ hidl_handle&& handleFromFileDescriptor(base::unique_fd fd) { static constexpr bool kTakeOwnership = true; h.setTo(nh, kTakeOwnership); - return std::move(h); + return h; } } // namespace @@ -116,13 +116,14 @@ static jboolean android_server_connectivity_tethering_OffloadHardwareInterface_c bool rval; hidl_string msg; - configInterface->setHandles(h1, h2, + const auto status = configInterface->setHandles(h1, h2, [&rval, &msg](bool success, const hidl_string& errMsg) { rval = success; msg = errMsg; }); - if (!rval) { - ALOGE("IOffloadConfig::setHandles() error: %s", msg.c_str()); + if (!status.isOk() || !rval) { + ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'", + status.description().c_str(), msg.c_str()); } return rval; diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index c722629a28f4..86c5e9927617 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -157,8 +157,10 @@ static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring static void nativeSetInteractive(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) { std::lock_guard<std::mutex> lock(gPowerHalMutex); if (getPowerHal()) { - String8 err("Excessive delay in setInteractive(%s) while turning screen %s"); - ALOGD_IF_SLOW(20, String8::format(err, enable ? "true" : "false", enable ? "on" : "off")); + String8 err = String8::format( + "Excessive delay in setInteractive(%s) while turning screen %s", + enable ? "true" : "false", enable ? "on" : "off"); + ALOGD_IF_SLOW(20, err); Return<void> ret = gPowerHal->setInteractive(enable); processReturn(ret, "setInteractive"); } diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java index 75df892b30b1..5770c5079d94 100644 --- a/services/print/java/com/android/server/print/UserState.java +++ b/services/print/java/com/android/server/print/UserState.java @@ -159,10 +159,12 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, readInstalledPrintServicesLocked(); upgradePersistentStateIfNeeded(); readDisabledPrintServicesLocked(); + } - // Some print services might have gotten installed before the User State came up - prunePrintServices(); + // Some print services might have gotten installed before the User State came up + prunePrintServices(); + synchronized (mLock) { onConfigurationChangedLocked(); } } diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java index ec0887489fba..ae9827428179 100644 --- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -20,21 +20,29 @@ import static android.app.Notification.GROUP_ALERT_CHILDREN; import static android.app.Notification.GROUP_ALERT_SUMMARY; import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; -import com.android.server.lights.Light; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.Notification; import android.app.Notification.Builder; -import android.app.NotificationManager; import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.Color; import android.media.AudioAttributes; import android.media.AudioManager; @@ -42,30 +50,23 @@ import android.net.Uri; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; -import android.os.Vibrator; import android.os.VibrationEffect; +import android.os.Vibrator; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import com.android.server.lights.Light; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @SmallTest @RunWith(AndroidJUnit4.class) public class BuzzBeepBlinkTest extends NotificationTestCase { @@ -163,6 +164,11 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { true /* noisy */, false /* buzzy*/, false /* lights */); } + private NotificationRecord getInsistentBeepyLeanbackNotification() { + return getLeanbackNotificationRecord(mId, true /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */); + } + private NotificationRecord getBuzzyNotification() { return getNotificationRecord(mId, false /* insistent */, false /* once */, false /* noisy */, true /* buzzy*/, false /* lights */); @@ -192,23 +198,30 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { return getNotificationRecord(mId, false /* insistent */, true /* once */, false /* noisy */, true /* buzzy*/, true /* lights */, true /* defaultVibration */, true /* defaultSound */, false /* defaultLights */, - null, Notification.GROUP_ALERT_ALL); + null, Notification.GROUP_ALERT_ALL, false); } private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once, boolean noisy, boolean buzzy, boolean lights) { return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true, - null, Notification.GROUP_ALERT_ALL); + null, Notification.GROUP_ALERT_ALL, false); + } + + private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent, boolean once, + boolean noisy, boolean buzzy, boolean lights) { + return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true, + null, Notification.GROUP_ALERT_ALL, true); } private NotificationRecord getBeepyNotificationRecord(String groupKey, int groupAlertBehavior) { return getNotificationRecord(mId, false, false, true, false, false, true, true, true, - groupKey, groupAlertBehavior); + groupKey, groupAlertBehavior, false); } private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once, boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration, - boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior) { + boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior, + boolean isLeanback) { NotificationChannel channel = new NotificationChannel("test", "test", IMPORTANCE_HIGH); final Builder builder = new Builder(getContext()) @@ -257,9 +270,15 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { n.flags |= Notification.FLAG_INSISTENT; } + Context context = spy(getContext()); + PackageManager packageManager = spy(context.getPackageManager()); + when(context.getPackageManager()).thenReturn(packageManager); + when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) + .thenReturn(isLeanback); + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, mPid, n, mUser, null, System.currentTimeMillis()); - NotificationRecord r = new NotificationRecord(getContext(), sbn, channel); + NotificationRecord r = new NotificationRecord(context, sbn, channel); mService.addNotification(r); return r; } @@ -367,6 +386,15 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { } @Test + public void testNoLeanbackBeep() throws Exception { + NotificationRecord r = getInsistentBeepyLeanbackNotification(); + + mService.buzzBeepBlinkLocked(r); + + verifyNeverBeep(); + } + + @Test public void testNoInterruptionForMin() throws Exception { NotificationRecord r = getBeepyNotification(); r.setImportance(NotificationManager.IMPORTANCE_MIN, "foo"); diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index 0a4cb1080b04..6090e35ac8c2 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -359,6 +359,43 @@ public class NotificationManagerServiceTest extends NotificationTestCase { } @Test + public void testCancelAllNotificationsMultipleEnqueuedDoesNotCrash() throws Exception { + final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + for (int i = 0; i < 10; i++) { + mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + } + mBinderService.cancelAllNotifications(PKG, sbn.getUserId()); + waitForIdle(); + } + + @Test + public void testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash() throws Exception { + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group1", true); + final NotificationRecord parentAsChild = generateNotificationRecord( + mTestNotificationChannel, 1, "group1", false); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group1", false); + + // fully post parent notification + mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", + parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId()); + waitForIdle(); + + // enqueue the child several times + for (int i = 0; i < 10; i++) { + mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", + child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId()); + } + // make the parent a child, which will cancel the child notification + mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", + parentAsChild.sbn.getId(), parentAsChild.sbn.getNotification(), + parentAsChild.sbn.getUserId()); + waitForIdle(); + } + + @Test public void testCancelAllNotifications_IgnoreForegroundService() throws Exception { final StatusBarNotification sbn = generateNotificationRecord(null).sbn; sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE; diff --git a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java index 07280bc881d8..62b0ca805c1b 100644 --- a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java +++ b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java @@ -21,16 +21,22 @@ import java.util.ArrayList; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; +import android.os.Looper; import android.os.UserHandle; import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; import com.android.server.content.ContentService.ObserverCall; import com.android.server.content.ContentService.ObserverNode; +/** + * bit FrameworksServicesTests:com.android.server.content.ObserverNodeTest + */ +@SmallTest public class ObserverNodeTest extends AndroidTestCase { static class TestObserver extends ContentObserver { public TestObserver() { - super(new Handler()); + super(new Handler(Looper.getMainLooper())); } } diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java index be6861c43699..d093e7961b22 100644 --- a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java @@ -1,9 +1,32 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.server.content; import android.os.Bundle; +import android.test.suitebuilder.annotation.SmallTest; import junit.framework.TestCase; +/** + * Tests for SyncManager. + * + * bit FrameworksServicesTests:com.android.server.content.SyncManagerTest + */ +@SmallTest public class SyncManagerTest extends TestCase { final String KEY_1 = "key_1"; @@ -61,4 +84,42 @@ public class SyncManagerTest extends TestCase { assertFalse("Extras considered equal when they are different.", SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */)); } + + public void testFormatDurationHMS() { + checkFormatDurationHMS("0s", 0, 0, 0, 0); + checkFormatDurationHMS("1s", 0, 0, 0, 1); + checkFormatDurationHMS("9s", 0, 0, 0, 9); + checkFormatDurationHMS("10s", 0, 0, 0, 10); + checkFormatDurationHMS("59s", 0, 0, 0, 59); + checkFormatDurationHMS("1m00s", 0, 0, 1, 0); + checkFormatDurationHMS("1m01s", 0, 0, 1, 1); + checkFormatDurationHMS("1m09s", 0, 0, 1, 9); + checkFormatDurationHMS("1m10s", 0, 0, 1, 10); + checkFormatDurationHMS("1m59s", 0, 0, 1, 59); + checkFormatDurationHMS("1h00m00s", 0, 1, 0, 0); + checkFormatDurationHMS("1h00m01s", 0, 1, 0, 1); + checkFormatDurationHMS("1h01m01s", 0, 1, 1, 1); + checkFormatDurationHMS("1h09m10s", 0, 1, 9, 10); + checkFormatDurationHMS("1h10m59s", 0, 1, 10, 59); + checkFormatDurationHMS("1h59m00s", 0, 1, 59, 0); + + checkFormatDurationHMS("1d00h00m00s", 1, 0, 0, 0); + checkFormatDurationHMS("1d00h00m00s", 1, 0, 0, 0); + checkFormatDurationHMS("1d01h00m00s", 1, 1, 0, 0); + checkFormatDurationHMS("1d09h00m00s", 1, 9, 0, 0); + checkFormatDurationHMS("1d10h00m00s", 1, 10, 0, 0); + checkFormatDurationHMS("1d23h00m00s", 1, 23, 0, 0); + checkFormatDurationHMS("123d01h00m00s", 123, 1, 0, 0); + + final StringBuilder sb = new StringBuilder(); + assertEquals("-1m01s", SyncManager.formatDurationHMS(sb, -61000L).toString()); + } + + private void checkFormatDurationHMS(String expected, + int d, int h, int m, int s) { + final long time = (d * 24 * 3600) + (h * 3600) + (m * 60) + s; + + final StringBuilder sb = new StringBuilder(); + assertEquals(expected, SyncManager.formatDurationHMS(sb, time * 1000).toString()); + } } diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java index e45b92a1b486..deaa34ca9f51 100644 --- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java +++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java @@ -17,24 +17,17 @@ package com.android.server.content; import android.accounts.Account; -import android.content.ContentResolver; -import android.content.Context; import android.os.Bundle; import android.os.PersistableBundle; -import android.os.SystemClock; -import android.provider.Settings; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; /** - * You can run those tests with: + * Test for SyncOperation. * - * adb shell am instrument - * -e debug false - * -w - * -e class android.content.SyncOperationTest com.android.frameworks.coretests/android.test.InstrumentationTestRunner + * bit FrameworksServicesTests:com.android.server.content.SyncOperationTest */ - +@SmallTest public class SyncOperationTest extends AndroidTestCase { Account mDummy; diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java index 91c0de64cb82..85de1f1c3e86 100644 --- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java +++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java @@ -22,7 +22,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; -import android.content.PeriodicSync; import android.content.res.Resources; import android.os.Bundle; import android.test.AndroidTestCase; @@ -33,14 +32,18 @@ import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; -import com.android.server.content.SyncStorageEngine.EndPoint; - import com.android.internal.os.AtomicFile; import java.io.File; import java.io.FileOutputStream; -import java.util.List; +/** + * Test for SyncStorageEngine. + * + * bit FrameworksServicesTests:com.android.server.content.SyncStorageEngineTest + * + * TODO Broken. Fix it. b/62485315 + */ public class SyncStorageEngineTest extends AndroidTestCase { protected Account account1; diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index 33e1a1652b22..689c8f7e6e01 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -206,7 +206,8 @@ public class JobStoreTest extends AndroidTestCase { invalidLateRuntimeElapsedMillis - TWO_HOURS; // Early is (late - period). final JobStatus js = new JobStatus(b.build(), SOME_UID, "somePackage", 0 /* sourceUserId */, "someTag", - invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis); + invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis, + 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */); mTaskStoreUnderTest.add(js); Thread.sleep(IO_WAIT); diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java index 65a56327bb24..606088128fc4 100644 --- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java @@ -107,7 +107,7 @@ public class AppWindowContainerControllerTests extends WindowTestsBase { createAppWindowController(); controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, - false); + false, false); waitUntilHandlersIdle(); final AppWindowToken atoken = controller.getAppWindowToken(mDisplayContent); assertHasStartingWindow(atoken); @@ -125,7 +125,7 @@ public class AppWindowContainerControllerTests extends WindowTestsBase { createAppWindowController(); controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, - false); + false, false); controller.removeStartingWindow(); waitUntilHandlersIdle(); assertNoStartingWindow(controller.getAppWindowToken(mDisplayContent)); @@ -140,11 +140,11 @@ public class AppWindowContainerControllerTests extends WindowTestsBase { createAppWindowController(); controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, - false); + false, false); waitUntilHandlersIdle(); controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(), - true, true, false, true, false); + true, true, false, true, false, false); waitUntilHandlersIdle(); assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent)); assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent)); @@ -161,11 +161,11 @@ public class AppWindowContainerControllerTests extends WindowTestsBase { // Surprise, ...! Transfer window in the middle of the creation flow. controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(), - true, true, false, true, false); + true, true, false, true, false, false); }); controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, - false); + false, false); waitUntilHandlersIdle(); assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent)); assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent)); diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java index e2868d7056b0..4288eac0faa9 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -62,7 +62,8 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { final TaskSnapshot snapshot = new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, contentInsets, false, 1.0f); mSurface = new TaskSnapshotSurface(sWm, new Window(), new Surface(), snapshot, "Test", - Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds); + Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds, + ORIENTATION_PORTRAIT); } private void setupSurface(int width, int height) { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 1d5fb55e254a..f53eb15d482b 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -558,6 +558,13 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } + boolean isRecognitionRequested(UUID modelId) { + synchronized (mLock) { + ModelData modelData = mModelDataMap.get(modelId); + return modelData != null && modelData.isRequested(); + } + } + //---- SoundTrigger.StatusListener methods @Override public void onRecognition(RecognitionEvent event) { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 9bca0128cc43..51c805da2cac 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -16,18 +16,25 @@ package com.android.server.soundtrigger; import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR; +import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.Manifest; import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.SoundTrigger; +import android.hardware.soundtrigger.SoundTrigger.SoundModel; import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; +import android.media.soundtrigger.SoundTriggerManager; +import android.os.Bundle; import android.os.Parcel; import android.os.ParcelUuid; +import android.os.PowerManager; import android.os.RemoteException; import android.util.Slog; @@ -36,6 +43,7 @@ import com.android.internal.app.ISoundTriggerService; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.TreeMap; import java.util.UUID; /** @@ -52,16 +60,23 @@ public class SoundTriggerService extends SystemService { private static final boolean DEBUG = true; final Context mContext; + private Object mLock; private final SoundTriggerServiceStub mServiceStub; private final LocalSoundTriggerService mLocalSoundTriggerService; private SoundTriggerDbHelper mDbHelper; private SoundTriggerHelper mSoundTriggerHelper; + private final TreeMap<UUID, SoundModel> mLoadedModels; + private final TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback> mIntentCallbacks; + private PowerManager.WakeLock mWakelock; public SoundTriggerService(Context context) { super(context); mContext = context; mServiceStub = new SoundTriggerServiceStub(); mLocalSoundTriggerService = new LocalSoundTriggerService(context); + mLoadedModels = new TreeMap<UUID, SoundModel>(); + mIntentCallbacks = new TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback>(); + mLock = new Object(); } @Override @@ -177,8 +192,357 @@ public class SoundTriggerService extends SystemService { mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid()); mDbHelper.deleteGenericSoundModel(soundModelId.getUuid()); } + + @Override + public int loadGenericSoundModel(GenericSoundModel soundModel) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (!isInitialized()) return STATUS_ERROR; + if (soundModel == null || soundModel.uuid == null) { + Slog.e(TAG, "Invalid sound model"); + return STATUS_ERROR; + } + if (DEBUG) { + Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid); + } + synchronized (mLock) { + SoundModel oldModel = mLoadedModels.get(soundModel.uuid); + // If the model we're loading is actually different than what we had loaded, we + // should unload that other model now. We don't care about return codes since we + // don't know if the other model is loaded. + if (oldModel != null && !oldModel.equals(soundModel)) { + mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid); + mIntentCallbacks.remove(soundModel.uuid); + } + mLoadedModels.put(soundModel.uuid, soundModel); + } + return STATUS_OK; + } + + @Override + public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (!isInitialized()) return STATUS_ERROR; + if (soundModel == null || soundModel.uuid == null) { + Slog.e(TAG, "Invalid sound model"); + return STATUS_ERROR; + } + if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) { + Slog.e(TAG, "Only one keyphrase per model is currently supported."); + return STATUS_ERROR; + } + if (DEBUG) { + Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid); + } + synchronized (mLock) { + SoundModel oldModel = mLoadedModels.get(soundModel.uuid); + // If the model we're loading is actually different than what we had loaded, we + // should unload that other model now. We don't care about return codes since we + // don't know if the other model is loaded. + if (oldModel != null && !oldModel.equals(soundModel)) { + mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id); + mIntentCallbacks.remove(soundModel.uuid); + } + mLoadedModels.put(soundModel.uuid, soundModel); + } + return STATUS_OK; + } + + @Override + public int startRecognitionForIntent(ParcelUuid soundModelId, PendingIntent callbackIntent, + SoundTrigger.RecognitionConfig config) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (!isInitialized()) return STATUS_ERROR; + if (DEBUG) { + Slog.i(TAG, "startRecognition(): id = " + soundModelId); + } + + synchronized (mLock) { + SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); + if (soundModel == null) { + Slog.e(TAG, soundModelId + " is not loaded"); + return STATUS_ERROR; + } + LocalSoundTriggerRecognitionStatusCallback callback = mIntentCallbacks.get( + soundModelId.getUuid()); + if (callback != null) { + Slog.e(TAG, soundModelId + " is already running"); + return STATUS_ERROR; + } + callback = new LocalSoundTriggerRecognitionStatusCallback(soundModelId.getUuid(), + callbackIntent, config); + int ret; + switch (soundModel.type) { + case SoundModel.TYPE_KEYPHRASE: { + KeyphraseSoundModel keyphraseSoundModel = (KeyphraseSoundModel) soundModel; + ret = mSoundTriggerHelper.startKeyphraseRecognition( + keyphraseSoundModel.keyphrases[0].id, keyphraseSoundModel, callback, + config); + } break; + case SoundModel.TYPE_GENERIC_SOUND: + ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid, + (GenericSoundModel) soundModel, callback, config); + break; + default: + Slog.e(TAG, "Unknown model type"); + return STATUS_ERROR; + } + + if (ret != STATUS_OK) { + Slog.e(TAG, "Failed to start model: " + ret); + return ret; + } + mIntentCallbacks.put(soundModelId.getUuid(), callback); + } + return STATUS_OK; + } + + @Override + public int stopRecognitionForIntent(ParcelUuid soundModelId) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (!isInitialized()) return STATUS_ERROR; + if (DEBUG) { + Slog.i(TAG, "stopRecognition(): id = " + soundModelId); + } + + synchronized (mLock) { + SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); + if (soundModel == null) { + Slog.e(TAG, soundModelId + " is not loaded"); + return STATUS_ERROR; + } + LocalSoundTriggerRecognitionStatusCallback callback = mIntentCallbacks.get( + soundModelId.getUuid()); + if (callback == null) { + Slog.e(TAG, soundModelId + " is not running"); + return STATUS_ERROR; + } + int ret; + switch (soundModel.type) { + case SoundModel.TYPE_KEYPHRASE: + ret = mSoundTriggerHelper.stopKeyphraseRecognition( + ((KeyphraseSoundModel)soundModel).keyphrases[0].id, callback); + break; + case SoundModel.TYPE_GENERIC_SOUND: + ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback); + break; + default: + Slog.e(TAG, "Unknown model type"); + return STATUS_ERROR; + } + + if (ret != STATUS_OK) { + Slog.e(TAG, "Failed to stop model: " + ret); + return ret; + } + mIntentCallbacks.remove(soundModelId.getUuid()); + } + return STATUS_OK; + } + + @Override + public int unloadSoundModel(ParcelUuid soundModelId) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (!isInitialized()) return STATUS_ERROR; + if (DEBUG) { + Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId); + } + + synchronized (mLock) { + SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); + if (soundModel == null) { + Slog.e(TAG, soundModelId + " is not loaded"); + return STATUS_ERROR; + } + int ret; + switch (soundModel.type) { + case SoundModel.TYPE_KEYPHRASE: + ret = mSoundTriggerHelper.unloadKeyphraseSoundModel( + ((KeyphraseSoundModel)soundModel).keyphrases[0].id); + break; + case SoundModel.TYPE_GENERIC_SOUND: + ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid); + break; + default: + Slog.e(TAG, "Unknown model type"); + return STATUS_ERROR; + } + if (ret != STATUS_OK) { + Slog.e(TAG, "Failed to unload model"); + return ret; + } + mLoadedModels.remove(soundModelId.getUuid()); + return STATUS_OK; + } + } + + @Override + public boolean isRecognitionActive(ParcelUuid parcelUuid) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (!isInitialized()) return false; + synchronized (mLock) { + LocalSoundTriggerRecognitionStatusCallback callback = + mIntentCallbacks.get(parcelUuid.getUuid()); + if (callback == null) { + return false; + } + return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid()); + } + } } + private final class LocalSoundTriggerRecognitionStatusCallback + extends IRecognitionStatusCallback.Stub { + private UUID mUuid; + private PendingIntent mCallbackIntent; + private RecognitionConfig mRecognitionConfig; + + public LocalSoundTriggerRecognitionStatusCallback(UUID modelUuid, + PendingIntent callbackIntent, + RecognitionConfig config) { + mUuid = modelUuid; + mCallbackIntent = callbackIntent; + mRecognitionConfig = config; + } + + @Override + public boolean pingBinder() { + return mCallbackIntent != null; + } + + @Override + public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) { + if (mCallbackIntent == null) { + return; + } + grabWakeLock(); + + Slog.w(TAG, "Keyphrase sound trigger event: " + event); + Intent extras = new Intent(); + extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE, + SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT); + extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event); + try { + mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null); + if (!mRecognitionConfig.allowMultipleTriggers) { + removeCallback(/*releaseWakeLock=*/false); + } + } catch (PendingIntent.CanceledException e) { + removeCallback(/*releaseWakeLock=*/true); + } + } + + @Override + public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) { + if (mCallbackIntent == null) { + return; + } + grabWakeLock(); + + Slog.w(TAG, "Generic sound trigger event: " + event); + Intent extras = new Intent(); + extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE, + SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT); + extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event); + try { + mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null); + if (!mRecognitionConfig.allowMultipleTriggers) { + removeCallback(/*releaseWakeLock=*/false); + } + } catch (PendingIntent.CanceledException e) { + removeCallback(/*releaseWakeLock=*/true); + } + } + + @Override + public void onError(int status) { + if (mCallbackIntent == null) { + return; + } + grabWakeLock(); + + Slog.i(TAG, "onError: " + status); + Intent extras = new Intent(); + extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE, + SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_ERROR); + extras.putExtra(SoundTriggerManager.EXTRA_STATUS, status); + try { + mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null); + // Remove the callback, but wait for the intent to finish before we let go of the + // wake lock + removeCallback(/*releaseWakeLock=*/false); + } catch (PendingIntent.CanceledException e) { + removeCallback(/*releaseWakeLock=*/true); + } + } + + @Override + public void onRecognitionPaused() { + if (mCallbackIntent == null) { + return; + } + grabWakeLock(); + + Slog.i(TAG, "onRecognitionPaused"); + Intent extras = new Intent(); + extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE, + SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED); + try { + mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null); + } catch (PendingIntent.CanceledException e) { + removeCallback(/*releaseWakeLock=*/true); + } + } + + @Override + public void onRecognitionResumed() { + if (mCallbackIntent == null) { + return; + } + grabWakeLock(); + + Slog.i(TAG, "onRecognitionResumed"); + Intent extras = new Intent(); + extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE, + SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED); + try { + mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null); + } catch (PendingIntent.CanceledException e) { + removeCallback(/*releaseWakeLock=*/true); + } + } + + private void removeCallback(boolean releaseWakeLock) { + mCallbackIntent = null; + synchronized (mLock) { + mIntentCallbacks.remove(mUuid); + if (releaseWakeLock) { + mWakelock.release(); + } + } + } + } + + private void grabWakeLock() { + synchronized (mLock) { + if (mWakelock == null) { + PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE)); + mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + } + mWakelock.acquire(); + } + } + + private PendingIntent.OnFinished mCallbackCompletedHandler = new PendingIntent.OnFinished() { + @Override + public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, + String resultData, Bundle resultExtras) { + // We're only ever invoked when the callback is done, so release the lock. + synchronized (mLock) { + mWakelock.release(); + } + } + }; + public final class LocalSoundTriggerService extends SoundTriggerInternal { private final Context mContext; private SoundTriggerHelper mSoundTriggerHelper; diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java index 640c9e12079b..de205380c3a7 100644 --- a/telecomm/java/android/telecom/Log.java +++ b/telecomm/java/android/telecom/Log.java @@ -325,7 +325,8 @@ public class Log { return sEventManager; } - private static SessionManager getSessionManager() { + @VisibleForTesting + public static SessionManager getSessionManager() { // Checking for null again outside of synchronization because we only need to synchronize // during the lazy loading of the session logger. We don't need to synchronize elsewhere. if (sSessionManager == null) { diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index 4390fae226e5..31bb0647c9d7 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -76,6 +76,13 @@ public final class PhoneAccount implements Parcelable { public static final String EXTRA_CALL_SUBJECT_CHARACTER_ENCODING = "android.telecom.extra.CALL_SUBJECT_CHARACTER_ENCODING"; + /** + * Indicating flag for phone account whether to use voip audio mode for voip calls + * @hide + */ + public static final String EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE = + "android.telecom.extra.ALWAYS_USE_VOIP_AUDIO_MODE"; + /** * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which * indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 331328d63841..b1eedf5cad4b 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -369,6 +369,15 @@ public class TelecomManager { public static final String EXTRA_IS_HANDOVER = "android.telecom.extra.IS_HANDOVER"; /** + * Parcelable extra used with {@link #EXTRA_IS_HANDOVER} to indicate the source + * {@link PhoneAccountHandle} when initiating a handover which {@link ConnectionService} + * the handover is from. + * @hide + */ + public static final String EXTRA_HANDOVER_FROM_PHONE_ACCOUNT = + "android.telecom.extra.HANDOVER_FROM_PHONE_ACCOUNT"; + + /** * Extra key specified in the {@link ConnectionRequest#getExtras()} when Telecom calls * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} * to inform the {@link ConnectionService} what the initial {@link CallAudioState} of the diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java index 0cb4cff0858e..f15fde8f0700 100644 --- a/telephony/java/android/telephony/NetworkScan.java +++ b/telephony/java/android/telephony/NetworkScan.java @@ -34,12 +34,26 @@ public class NetworkScan { public static final String TAG = "NetworkScan"; - public static final int SUCCESS = 0; - public static final int ERROR_INVALID_SCAN = 1; - public static final int ERROR_UNSUPPORTED = 2; - public static final int ERROR_INTERRUPTED = 3; - public static final int ERROR_CANCELLED = 4; + // Below errors are mapped from RadioError which is returned from RIL. We will consolidate + // RadioErrors during the mapping if those RadioErrors mean no difference to the users. + public static final int SUCCESS = 0; // RadioError:NONE + public static final int ERROR_MODEM_ERROR = 1; // RadioError:RADIO_NOT_AVAILABLE + // RadioError:NO_MEMORY + // RadioError:INTERNAL_ERR + // RadioError:MODEM_ERR + // RadioError:OPERATION_NOT_ALLOWED + public static final int ERROR_INVALID_SCAN = 2; // RadioError:INVALID_ARGUMENTS + public static final int ERROR_MODEM_BUSY = 3; // RadioError:DEVICE_IN_USE + public static final int ERROR_UNSUPPORTED = 4; // RadioError:REQUEST_NOT_SUPPORTED + // Below errors are generated at the Telephony. + public static final int ERROR_RIL_ERROR = 10000; // Nothing or only exception is + // returned from RIL. + public static final int ERROR_INVALID_SCANID = 10001; // The scanId is invalid. The user is + // either trying to stop a scan which + // does not exist or started by others. + public static final int ERROR_INTERRUPTED = 10002; // Scan was interrupted by another scan + // with higher priority. private final int mScanId; private final int mSubId; diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java index 0a542a7aacef..d2aef2007044 100644 --- a/telephony/java/android/telephony/NetworkScanRequest.java +++ b/telephony/java/android/telephony/NetworkScanRequest.java @@ -31,6 +31,14 @@ import java.util.Arrays; */ public final class NetworkScanRequest implements Parcelable { + // Below size limits for RAN/Band/Channel are for pre-treble modems and will be removed later. + /** @hide */ + public static final int MAX_RADIO_ACCESS_NETWORKS = 8; + /** @hide */ + public static final int MAX_BANDS = 8; + /** @hide */ + public static final int MAX_CHANNELS = 32; + /** Performs the scan only once */ public static final int SCAN_TYPE_ONE_SHOT = 0; /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 74327cef6bd3..4f78087c4c97 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -869,6 +869,20 @@ public class TelephonyManager { public static final String EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC = "android.telephony.event.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC"; + /** + * {@link android.telecom.Connection} event used to indicate that an outgoing call has been + * forwarded to another number. + * <p> + * Sent in response to an IMS supplementary service notification indicating the call has been + * forwarded. + * <p> + * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}. + * The {@link Bundle} parameter is expected to be null when this connection event is used. + * @hide + */ + public static final String EVENT_CALL_FORWARDED = + "android.telephony.event.EVENT_CALL_FORWARDED"; + /* Visual voicemail protocols */ /** diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index be32f72fe145..11770fb06c95 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -92,15 +92,12 @@ public class EuiccManager { public static final int EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR = 1; /** - * Result code for an operation indicating that a generic error occurred. + * Result code for an operation indicating that an unresolvable error occurred. * - * <p>Note that in the future, other result codes may be returned indicating more specific - * errors. Thus, the caller should check for {@link #EMBEDDED_SUBSCRIPTION_RESULT_OK} or - * {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} to determine if the operation - * succeeded or failed with a user-resolvable error, and assume the operation failed for any - * other result, rather than checking for this specific value. + * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} will be populated with a detailed error + * code for logging/debugging purposes only. */ - public static final int EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR = 2; + public static final int EMBEDDED_SUBSCRIPTION_RESULT_ERROR = 2; /** * Key for an extra set on {@link PendingIntent} result callbacks providing a detailed result @@ -156,6 +153,12 @@ public class EuiccManager { public static final String EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT"; + /** + * Optional meta-data attribute for a carrier app providing an icon to use to represent the + * carrier. If not provided, the app's launcher icon will be used as a fallback. + */ + public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon"; + private final Context mContext; private final IEuiccController mController; @@ -472,7 +475,7 @@ public class EuiccManager { private static void sendUnavailableError(PendingIntent callbackIntent) { try { - callbackIntent.send(EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR); + callbackIntent.send(EMBEDDED_SUBSCRIPTION_RESULT_ERROR); } catch (PendingIntent.CanceledException e) { // Caller canceled the callback; do nothing. } diff --git a/tests/Internal/Android.mk b/tests/Internal/Android.mk new file mode 100644 index 000000000000..f59a6240f897 --- /dev/null +++ b/tests/Internal/Android.mk @@ -0,0 +1,20 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_USE_AAPT2 := true +LOCAL_MODULE_TAGS := tests + +LOCAL_PROTOC_OPTIMIZE_TYPE := nano + +# Include some source files directly to be able to access package members +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test android-support-test + +LOCAL_CERTIFICATE := platform + +LOCAL_PACKAGE_NAME := InternalTests +LOCAL_COMPATIBILITY_SUITE := device-tests + +include $(BUILD_PACKAGE) diff --git a/tests/Internal/AndroidManifest.xml b/tests/Internal/AndroidManifest.xml new file mode 100644 index 000000000000..a2c95fbbfc0b --- /dev/null +++ b/tests/Internal/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.internal.tests"> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.internal.tests" + android:label="Internal Tests" /> +</manifest> diff --git a/tests/Internal/AndroidTest.xml b/tests/Internal/AndroidTest.xml new file mode 100644 index 000000000000..6531c9355e3d --- /dev/null +++ b/tests/Internal/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<configuration description="Runs tests for internal classes/utilities."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="InternalTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="InternalTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.internal.tests" /> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> + </test> +</configuration>
\ No newline at end of file diff --git a/tests/Internal/src/com/android/internal/ml/clustering/KMeansTest.java b/tests/Internal/src/com/android/internal/ml/clustering/KMeansTest.java new file mode 100644 index 000000000000..a64f8a60d485 --- /dev/null +++ b/tests/Internal/src/com/android/internal/ml/clustering/KMeansTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.ml.clustering; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.annotation.SuppressLint; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class KMeansTest { + + // Error tolerance (epsilon) + private static final double EPS = 0.01; + + private KMeans mKMeans; + + @Before + public void setUp() { + // Setup with a random seed to have predictable results + mKMeans = new KMeans(new Random(0), 30, 0); + } + + @Test + public void getCheckDataSanityTest() { + try { + mKMeans.checkDataSetSanity(new float[][] { + {0, 1, 2}, + {1, 2, 3} + }); + } catch (IllegalArgumentException e) { + Assert.fail("Valid data didn't pass sanity check"); + } + + try { + mKMeans.checkDataSetSanity(new float[][] { + null, + {1, 2, 3} + }); + Assert.fail("Data has null items and passed"); + } catch (IllegalArgumentException e) {} + + try { + mKMeans.checkDataSetSanity(new float[][] { + {0, 1, 2, 4}, + {1, 2, 3} + }); + Assert.fail("Data has invalid shape and passed"); + } catch (IllegalArgumentException e) {} + + try { + mKMeans.checkDataSetSanity(null); + Assert.fail("Null data should throw exception"); + } catch (IllegalArgumentException e) {} + } + + @Test + public void sqDistanceTest() { + float a[] = {4, 10}; + float b[] = {5, 2}; + float sqDist = (float) (Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2)); + + assertEquals("Squared distance not valid", mKMeans.sqDistance(a, b), sqDist, EPS); + } + + @Test + public void nearestMeanTest() { + KMeans.Mean meanA = new KMeans.Mean(0, 1); + KMeans.Mean meanB = new KMeans.Mean(1, 1); + List<KMeans.Mean> means = Arrays.asList(meanA, meanB); + + KMeans.Mean nearest = mKMeans.nearestMean(new float[] {1, 1}, means); + + assertEquals("Unexpected nearest mean for point {1, 1}", nearest, meanB); + } + + @SuppressLint("DefaultLocale") + @Test + public void scoreTest() { + List<KMeans.Mean> closeMeans = Arrays.asList(new KMeans.Mean(0, 0.1f, 0.1f), + new KMeans.Mean(0, 0.1f, 0.15f), + new KMeans.Mean(0.1f, 0.2f, 0.1f)); + List<KMeans.Mean> farMeans = Arrays.asList(new KMeans.Mean(0, 0, 0), + new KMeans.Mean(0, 0.5f, 0.5f), + new KMeans.Mean(1, 0.9f, 0.9f)); + + double closeScore = KMeans.score(closeMeans); + double farScore = KMeans.score(farMeans); + assertTrue(String.format("Score of well distributed means should be greater than " + + "close means but got: %f, %f", farScore, closeScore), farScore > closeScore); + } + + @Test + public void predictTest() { + float[] expectedCentroid1 = {1, 1, 1}; + float[] expectedCentroid2 = {0, 0, 0}; + float[][] X = new float[][] { + {1, 1, 1}, + {1, 1, 1}, + {1, 1, 1}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + }; + + final int numClusters = 2; + + // Here we assume that we won't get stuck into a local optima. + // It's fine because we're seeding a random, we won't ever have + // unstable results but in real life we need multiple initialization + // and score comparison + List<KMeans.Mean> means = mKMeans.predict(numClusters, X); + + assertEquals("Expected number of clusters is invalid", numClusters, means.size()); + + boolean exists1 = false, exists2 = false; + for (KMeans.Mean mean : means) { + if (Arrays.equals(mean.getCentroid(), expectedCentroid1)) { + exists1 = true; + } else if (Arrays.equals(mean.getCentroid(), expectedCentroid2)) { + exists2 = true; + } else { + throw new AssertionError("Unexpected mean: " + mean); + } + } + assertTrue("Expected means were not predicted, got: " + means, + exists1 && exists2); + } +} diff --git a/tests/JobSchedulerTestApp/res/layout/activity_main.xml b/tests/JobSchedulerTestApp/res/layout/activity_main.xml index 96e164103bce..41f977707097 100644 --- a/tests/JobSchedulerTestApp/res/layout/activity_main.xml +++ b/tests/JobSchedulerTestApp/res/layout/activity_main.xml @@ -73,10 +73,18 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> + <RadioButton android:id="@+id/checkbox_none" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/none"/> <RadioButton android:id="@+id/checkbox_any" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/any"/> + <RadioButton android:id="@+id/checkbox_metered" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/metered"/> <RadioButton android:id="@+id/checkbox_unmetered" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/tests/JobSchedulerTestApp/res/values/strings.xml b/tests/JobSchedulerTestApp/res/values/strings.xml index 90dd2b672fb2..866b61ecb308 100644 --- a/tests/JobSchedulerTestApp/res/values/strings.xml +++ b/tests/JobSchedulerTestApp/res/values/strings.xml @@ -30,7 +30,9 @@ limitations under the License. <string name="persisted_caption">Persisted:</string> <string name="constraints">Constraints</string> <string name="connectivity">Connectivity:</string> + <string name="none">None</string> <string name="any">Any</string> + <string name="metered">Metered</string> <string name="unmetered">WiFi</string> <string name="timing">Timing:</string> <string name="delay">Delay:</string> diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java index 51cdbb585659..3dfdba719ce5 100644 --- a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java +++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java @@ -63,6 +63,7 @@ public class MainActivity extends Activity { mDeadlineEditText = findViewById(R.id.deadline_time); mWiFiConnectivityRadioButton = findViewById(R.id.checkbox_unmetered); mAnyConnectivityRadioButton = findViewById(R.id.checkbox_any); + mCellConnectivityRadioButton = findViewById(R.id.checkbox_metered); mRequiresChargingCheckBox = findViewById(R.id.checkbox_charging); mRequiresIdleCheckbox = findViewById(R.id.checkbox_idle); mIsPersistedCheckbox = findViewById(R.id.checkbox_persisted); @@ -85,6 +86,7 @@ public class MainActivity extends Activity { EditText mDeadlineEditText; RadioButton mWiFiConnectivityRadioButton; RadioButton mAnyConnectivityRadioButton; + RadioButton mCellConnectivityRadioButton; CheckBox mRequiresChargingCheckBox; CheckBox mRequiresIdleCheckbox; CheckBox mIsPersistedCheckbox; @@ -141,9 +143,12 @@ public class MainActivity extends Activity { builder.setOverrideDeadline(Long.parseLong(deadline) * 1000); } boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked(); + boolean requiresMetered = mCellConnectivityRadioButton.isChecked(); boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked(); if (requiresUnmetered) { builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); + } else if (requiresMetered) { + builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED); } else if (requiresAnyConnectivity) { builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); } diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java index 9df11fe90553..b698a3a53ff1 100644 --- a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java +++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java @@ -81,7 +81,8 @@ public class TestJobService extends JobService { @Override public boolean onStartJob(JobParameters params) { - Log.i(TAG, "on start job: " + params.getJobId()); + Log.i(TAG, "on start job: " + params.getJobId() + + " deadline?=" + params.isOverrideDeadlineExpired()); currentId++; jobParamsMap.put(currentId, params); final int currId = this.currentId; diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index f9a30e938023..ab874ce2cc50 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -261,32 +261,51 @@ public class TetheringTest { mIntents.remove(bcast); } - @Test - public void failingLocalOnlyHotspotLegacyApBroadcast() throws Exception { + public void failingLocalOnlyHotspotLegacyApBroadcast( + boolean emulateInterfaceStatusChanged) throws Exception { when(mConnectivityManager.isTetheringSupported()).thenReturn(true); // Emulate externally-visible WifiManager effects, causing the // per-interface state machine to start up, and telling us that // hotspot mode is to be started. - mTethering.interfaceStatusChanged(mTestIfname, true); + if (emulateInterfaceStatusChanged) { + mTethering.interfaceStatusChanged(mTestIfname, true); + } sendWifiApStateChanged(WIFI_AP_STATE_ENABLED); mLooper.dispatchAll(); - verify(mConnectivityManager, atLeastOnce()).isTetheringSupported(); - verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER); + // If, and only if, Tethering received an interface status changed + // then it creates a TetherInterfaceStateMachine and sends out a + // broadcast indicating that the interface is "available". + if (emulateInterfaceStatusChanged) { + verify(mConnectivityManager, atLeastOnce()).isTetheringSupported(); + verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER); + } verifyNoMoreInteractions(mConnectivityManager); verifyNoMoreInteractions(mNMService); verifyNoMoreInteractions(mWifiManager); } @Test - public void workingLocalOnlyHotspotEnrichedApBroadcast() throws Exception { + public void failingLocalOnlyHotspotLegacyApBroadcastWithIfaceStatusChanged() throws Exception { + failingLocalOnlyHotspotLegacyApBroadcast(true); + } + + @Test + public void failingLocalOnlyHotspotLegacyApBroadcastSansIfaceStatusChanged() throws Exception { + failingLocalOnlyHotspotLegacyApBroadcast(false); + } + + public void workingLocalOnlyHotspotEnrichedApBroadcast( + boolean emulateInterfaceStatusChanged) throws Exception { when(mConnectivityManager.isTetheringSupported()).thenReturn(true); // Emulate externally-visible WifiManager effects, causing the // per-interface state machine to start up, and telling us that // hotspot mode is to be started. - mTethering.interfaceStatusChanged(mTestIfname, true); + if (emulateInterfaceStatusChanged) { + mTethering.interfaceStatusChanged(mTestIfname, true); + } sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_LOCAL_ONLY); mLooper.dispatchAll(); @@ -331,6 +350,17 @@ public class TetheringTest { } @Test + public void workingLocalOnlyHotspotEnrichedApBroadcastWithIfaceChanged() throws Exception { + workingLocalOnlyHotspotEnrichedApBroadcast(true); + } + + @Test + public void workingLocalOnlyHotspotEnrichedApBroadcastSansIfaceChanged() throws Exception { + workingLocalOnlyHotspotEnrichedApBroadcast(false); + } + + // TODO: Test with and without interfaceStatusChanged(). + @Test public void failingWifiTetheringLegacyApBroadcast() throws Exception { when(mConnectivityManager.isTetheringSupported()).thenReturn(true); when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true); @@ -357,6 +387,7 @@ public class TetheringTest { verifyNoMoreInteractions(mWifiManager); } + // TODO: Test with and without interfaceStatusChanged(). @Test public void workingWifiTetheringEnrichedApBroadcast() throws Exception { when(mConnectivityManager.isTetheringSupported()).thenReturn(true); @@ -394,7 +425,6 @@ public class TetheringTest { any(NetworkCallback.class), any(Handler.class)); // In tethering mode, in the default configuration, an explicit request // for a mobile network is also made. - verify(mConnectivityManager, atLeastOnce()).getNetworkInfo(anyInt()); verify(mConnectivityManager, times(1)).requestNetwork( any(NetworkRequest.class), any(NetworkCallback.class), eq(0), anyInt(), any(Handler.class)); @@ -438,6 +468,7 @@ public class TetheringTest { mTethering.getLastTetherError(mTestIfname)); } + // TODO: Test with and without interfaceStatusChanged(). @Test public void failureEnablingIpForwarding() throws Exception { when(mConnectivityManager.isTetheringSupported()).thenReturn(true); diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java index 4d340d1eff33..1ddaf66d2274 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java @@ -62,7 +62,8 @@ public class OffloadControllerTest { @Mock private OffloadHardwareInterface mHardware; @Mock private ApplicationInfo mApplicationInfo; @Mock private Context mContext; - final ArgumentCaptor<ArrayList> mStringArrayCaptor = ArgumentCaptor.forClass(ArrayList.class); + private final ArgumentCaptor<ArrayList> mStringArrayCaptor = + ArgumentCaptor.forClass(ArrayList.class); private MockContentResolver mContentResolver; @Before public void setUp() throws Exception { @@ -155,8 +156,7 @@ public class OffloadControllerTest { lp.setInterfaceName(testIfName); offload.setUpstreamLinkProperties(lp); inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(testIfName), eq(null), eq(null), mStringArrayCaptor.capture()); - assertTrue(mStringArrayCaptor.getValue().isEmpty()); + eq(testIfName), eq(null), eq(null), eq(null)); inOrder.verifyNoMoreInteractions(); final String ipv4Addr = "192.0.2.5"; @@ -164,16 +164,14 @@ public class OffloadControllerTest { lp.addLinkAddress(new LinkAddress(linkAddr)); offload.setUpstreamLinkProperties(lp); inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(testIfName), eq(ipv4Addr), eq(null), mStringArrayCaptor.capture()); - assertTrue(mStringArrayCaptor.getValue().isEmpty()); + eq(testIfName), eq(ipv4Addr), eq(null), eq(null)); inOrder.verifyNoMoreInteractions(); final String ipv4Gateway = "192.0.2.1"; lp.addRoute(new RouteInfo(InetAddress.getByName(ipv4Gateway))); offload.setUpstreamLinkProperties(lp); inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture()); - assertTrue(mStringArrayCaptor.getValue().isEmpty()); + eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), eq(null)); inOrder.verifyNoMoreInteractions(); final String ipv6Gw1 = "fe80::cafe"; diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java index 57c258f8f289..db5373ac34b2 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java @@ -16,6 +16,8 @@ package com.android.server.connectivity.tethering; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -40,17 +42,23 @@ import static com.android.server.connectivity.tethering.IControlsTethering.STATE import android.net.ConnectivityManager; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; +import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.RouteInfo; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.test.TestLooper; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.text.TextUtils; + +import java.net.Inet4Address; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -66,19 +74,20 @@ public class TetherInterfaceStateMachineTest { @Mock private INetworkStatsService mStatsService; @Mock private IControlsTethering mTetherHelper; @Mock private InterfaceConfiguration mInterfaceConfiguration; - @Mock private IPv6TetheringInterfaceServices mIPv6TetheringInterfaceServices; @Mock private SharedLog mSharedLog; private final TestLooper mLooper = new TestLooper(); + private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor = + ArgumentCaptor.forClass(LinkProperties.class); private TetherInterfaceStateMachine mTestedSm; private void initStateMachine(int interfaceType) throws Exception { mTestedSm = new TetherInterfaceStateMachine( IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, - mNMService, mStatsService, mTetherHelper, mIPv6TetheringInterfaceServices); + mNMService, mStatsService, mTetherHelper); mTestedSm.start(); // Starting the state machine always puts us in a consistent state and notifies - // the test of the world that we've changed from an unknown to available state. + // the rest of the world that we've changed from an unknown to available state. mLooper.dispatchAll(); reset(mNMService, mStatsService, mTetherHelper); when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); @@ -102,8 +111,7 @@ public class TetherInterfaceStateMachineTest { @Test public void startsOutAvailable() { mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), - TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper, - mIPv6TetheringInterfaceServices); + TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper); mTestedSm.start(); mLooper.dispatchAll(); verify(mTetherHelper).updateInterfaceState( @@ -183,7 +191,8 @@ public class TetherInterfaceStateMachineTest { inOrder.verify(mTetherHelper).updateInterfaceState( mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR); inOrder.verify(mTetherHelper).updateLinkProperties( - eq(mTestedSm), any(LinkProperties.class)); + eq(mTestedSm), mLinkPropertiesCaptor.capture()); + assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue()); verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); } @@ -283,7 +292,8 @@ public class TetherInterfaceStateMachineTest { usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( - eq(mTestedSm), any(LinkProperties.class)); + eq(mTestedSm), mLinkPropertiesCaptor.capture()); + assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); } } @@ -300,7 +310,8 @@ public class TetherInterfaceStateMachineTest { usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR); usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( - eq(mTestedSm), any(LinkProperties.class)); + eq(mTestedSm), mLinkPropertiesCaptor.capture()); + assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); } @Test @@ -315,7 +326,8 @@ public class TetherInterfaceStateMachineTest { usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR); usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( - eq(mTestedSm), any(LinkProperties.class)); + eq(mTestedSm), mLinkPropertiesCaptor.capture()); + assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); } @Test @@ -362,4 +374,28 @@ public class TetherInterfaceStateMachineTest { upstreamIface); mLooper.dispatchAll(); } + + private void assertIPv4AddressAndDirectlyConnectedRoute(LinkProperties lp) { + // Find the first IPv4 LinkAddress. + LinkAddress addr4 = null; + for (LinkAddress addr : lp.getLinkAddresses()) { + if (!(addr.getAddress() instanceof Inet4Address)) continue; + addr4 = addr; + break; + } + assertTrue("missing IPv4 address", addr4 != null); + + // Assert the presence of the associated directly connected route. + final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName()); + assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'", + lp.getRoutes().contains(directlyConnected)); + } + + private void assertNoAddressesNorRoutes(LinkProperties lp) { + assertTrue(lp.getLinkAddresses().isEmpty()); + assertTrue(lp.getRoutes().isEmpty()); + // We also check that interface name is non-empty, because we should + // never see an empty interface name in any LinkProperties update. + assertFalse(TextUtils.isEmpty(lp.getInterfaceName())); + } } diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java index 27be135cb796..b68f203d869f 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java @@ -16,6 +16,7 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; @@ -38,6 +39,8 @@ import android.telephony.TelephonyManager; import com.android.internal.util.test.BroadcastInterceptingContext; +import java.util.Iterator; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -134,4 +137,61 @@ public class TetheringConfigurationTest { assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE)); assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI)); } + + @Test + public void testNoDefinedUpstreamTypesAddsEthernet() { + when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types)) + .thenReturn(new int[]{}); + mHasTelephonyManager = false; + when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_UNSPECIFIED); + + final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); + final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator(); + assertTrue(upstreamIterator.hasNext()); + assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue()); + // The following is because the code always adds some kind of mobile + // upstream, be it DUN or, in this case where we use DUN_UNSPECIFIED, + // both vanilla and hipri mobile types. + assertTrue(upstreamIterator.hasNext()); + assertEquals(TYPE_MOBILE, upstreamIterator.next().intValue()); + assertTrue(upstreamIterator.hasNext()); + assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue()); + assertFalse(upstreamIterator.hasNext()); + } + + @Test + public void testDefinedUpstreamTypesSansEthernetAddsEthernet() { + when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types)) + .thenReturn(new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI}); + mHasTelephonyManager = false; + when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_UNSPECIFIED); + + final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); + final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator(); + assertTrue(upstreamIterator.hasNext()); + assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue()); + assertTrue(upstreamIterator.hasNext()); + assertEquals(TYPE_WIFI, upstreamIterator.next().intValue()); + assertTrue(upstreamIterator.hasNext()); + assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue()); + assertFalse(upstreamIterator.hasNext()); + } + + @Test + public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() { + when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types)) + .thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI}); + mHasTelephonyManager = false; + when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_UNSPECIFIED); + + final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); + final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator(); + assertTrue(upstreamIterator.hasNext()); + assertEquals(TYPE_WIFI, upstreamIterator.next().intValue()); + assertTrue(upstreamIterator.hasNext()); + assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue()); + assertTrue(upstreamIterator.hasNext()); + assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue()); + assertFalse(upstreamIterator.hasNext()); + } } diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java index 9bb392a23d56..fb5c5778d05b 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -18,7 +18,12 @@ package com.android.server.connectivity.tethering; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; +import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -42,6 +47,7 @@ import android.net.IConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.net.NetworkState; import android.net.util.SharedLog; import android.support.test.filters.SmallTest; @@ -59,6 +65,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -240,6 +247,84 @@ public class UpstreamNetworkMonitorTest { assertFalse(mUNM.mobileNetworkRequested()); } + @Test + public void testSelectPreferredUpstreamType() throws Exception { + final Collection<Integer> preferredTypes = new ArrayList<>(); + preferredTypes.add(TYPE_WIFI); + + mUNM.start(); + // There are no networks, so there is nothing to select. + assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + + final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); + wifiAgent.fakeConnect(); + // WiFi is up, we should prefer it. + assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); + wifiAgent.fakeDisconnect(); + // There are no networks, so there is nothing to select. + assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + + final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); + cellAgent.fakeConnect(); + assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + + preferredTypes.add(TYPE_MOBILE_DUN); + // This is coupled with preferred types in TetheringConfiguration. + mUNM.updateMobileRequiresDun(true); + // DUN is available, but only use regular cell: no upstream selected. + assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + preferredTypes.remove(TYPE_MOBILE_DUN); + // No WiFi, but our preferred flavour of cell is up. + preferredTypes.add(TYPE_MOBILE_HIPRI); + // This is coupled with preferred types in TetheringConfiguration. + mUNM.updateMobileRequiresDun(false); + assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, + mUNM.selectPreferredUpstreamType(preferredTypes)); + // Check to see we filed an explicit request. + assertEquals(1, mCM.requested.size()); + NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; + assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); + + wifiAgent.fakeConnect(); + // WiFi is up, and we should prefer it over cell. + assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); + assertEquals(0, mCM.requested.size()); + + preferredTypes.remove(TYPE_MOBILE_HIPRI); + preferredTypes.add(TYPE_MOBILE_DUN); + // This is coupled with preferred types in TetheringConfiguration. + mUNM.updateMobileRequiresDun(true); + assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); + + final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); + dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN); + dunAgent.fakeConnect(); + + // WiFi is still preferred. + assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); + + // WiFi goes down, cell and DUN are still up but only DUN is preferred. + wifiAgent.fakeDisconnect(); + assertSatisfiesLegacyType(TYPE_MOBILE_DUN, + mUNM.selectPreferredUpstreamType(preferredTypes)); + // Check to see we filed an explicit request. + assertEquals(1, mCM.requested.size()); + netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; + assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); + } + + private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) { + if (legacyType == TYPE_NONE) { + assertTrue(ns == null); + return; + } + + final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType); + assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities)); + } + private void assertUpstreamTypeRequested(int upstreamType) throws Exception { assertEquals(1, mCM.requested.size()); assertEquals(1, mCM.legacyTypeMap.size()); @@ -254,6 +339,8 @@ public class UpstreamNetworkMonitorTest { public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>(); public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>(); + private int mNetworkId = 100; + public TestConnectivityManager(Context ctx, IConnectivityManager svc) { super(ctx, svc); } @@ -287,6 +374,8 @@ public class UpstreamNetworkMonitorTest { return false; } + int getNetworkId() { return ++mNetworkId; } + @Override public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) { assertFalse(allCallbacks.containsKey(cb)); @@ -360,6 +449,35 @@ public class UpstreamNetworkMonitorTest { } } + public static class TestNetworkAgent { + public final TestConnectivityManager cm; + public final Network networkId; + public final int transportType; + public final NetworkCapabilities networkCapabilities; + + public TestNetworkAgent(TestConnectivityManager cm, int transportType) { + this.cm = cm; + this.networkId = new Network(cm.getNetworkId()); + this.transportType = transportType; + networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addTransportType(transportType); + networkCapabilities.addCapability(NET_CAPABILITY_INTERNET); + } + + public void fakeConnect() { + for (NetworkCallback cb : cm.listening.keySet()) { + cb.onAvailable(networkId); + cb.onCapabilitiesChanged(networkId, copy(networkCapabilities)); + } + } + + public void fakeDisconnect() { + for (NetworkCallback cb : cm.listening.keySet()) { + cb.onLost(networkId); + } + } + } + public static class TestStateMachine extends StateMachine { public final ArrayList<Message> messages = new ArrayList<>(); private final State mLoggingState = new LoggingState(); @@ -382,4 +500,8 @@ public class UpstreamNetworkMonitorTest { super.start(); } } + + static NetworkCapabilities copy(NetworkCapabilities nc) { + return new NetworkCapabilities(nc); + } } diff --git a/tests/testables/src/android/testing/BaseFragmentTest.java b/tests/testables/src/android/testing/BaseFragmentTest.java index 5cedbdffed35..5fa065a9135a 100644 --- a/tests/testables/src/android/testing/BaseFragmentTest.java +++ b/tests/testables/src/android/testing/BaseFragmentTest.java @@ -50,7 +50,7 @@ public abstract class BaseFragmentTest { private static final int VIEW_ID = 42; private final Class<? extends Fragment> mCls; private Handler mHandler; - private FrameLayout mView; + protected FrameLayout mView; protected FragmentController mFragments; protected Fragment mFragment; @@ -61,9 +61,13 @@ public abstract class BaseFragmentTest { mCls = cls; } + protected void createRootView() { + mView = new FrameLayout(mContext); + } + @Before public void setupFragment() throws Exception { - mView = new FrameLayout(mContext); + createRootView(); mView.setId(VIEW_ID); assertNotNull("BaseFragmentTest must be tagged with @RunWithLooper", diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 088cbc6d4a4c..7972d06e597a 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -19,6 +19,7 @@ package android.net.wifi; import android.content.pm.ParceledListSlice; +import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; import android.net.wifi.WifiConfiguration; @@ -61,6 +62,8 @@ interface IWifiManager WifiConfiguration getMatchingWifiConfig(in ScanResult scanResult); + List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult); + int addOrUpdateNetwork(in WifiConfiguration config); boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index c89a9a458393..613c529fa616 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -30,6 +30,7 @@ import android.net.DhcpInfo; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; import android.os.Binder; import android.os.Build; @@ -675,16 +676,28 @@ public class WifiManager { @SystemApi public static final int CHANGE_REASON_CONFIG_CHANGE = 2; /** - * An access point scan has completed, and results are available from the supplicant. - * Call {@link #getScanResults()} to obtain the results. {@link #EXTRA_RESULTS_UPDATED} - * indicates if the scan was completed successfully. + * An access point scan has completed, and results are available. + * Call {@link #getScanResults()} to obtain the results. + * The broadcast intent may contain an extra field with the key {@link #EXTRA_RESULTS_UPDATED} + * and a {@code boolean} value indicating if the scan was successful. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS"; /** - * Lookup key for a {@code boolean} representing the result of previous {@link #startScan} - * operation, reported with {@link #SCAN_RESULTS_AVAILABLE_ACTION}. + * Lookup key for a {@code boolean} extra in intent {@link #SCAN_RESULTS_AVAILABLE_ACTION} + * representing if the scan was successful or not. + * Scans may fail for multiple reasons, these may include: + * <ol> + * <li>A non-privileged app requested too many scans in a certain period of time. + * This may lead to additional scan request rejections via "scan throttling". + * See + * <a href="https://developer.android.com/preview/features/background-location-limits.html"> + * here</a> for details. + * </li> + * <li>The device is idle and scanning is disabled.</li> + * <li>Wifi hardware reported a scan failure.</li> + * </ol> * @return true scan was successful, results are updated * @return false scan was not successful, results haven't been updated since previous scan */ @@ -1000,11 +1013,9 @@ public class WifiManager { /** * Returns a WifiConfiguration matching this ScanResult * - * An {@link UnsupportedOperationException} will be thrown if Passpoint is not enabled - * on the device. - * * @param scanResult scanResult that represents the BSSID * @return {@link WifiConfiguration} that matches this BSSID or null + * @throws UnsupportedOperationException if Passpoint is not enabled on the device. * @hide */ public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) { @@ -1016,6 +1027,24 @@ public class WifiManager { } /** + * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP. + * + * An empty list will be returned if no match is found. + * + * @param scanResult scanResult that represents the BSSID + * @return list of {@link OsuProvider} + * @throws UnsupportedOperationException if Passpoint is not enabled on the device. + * @hide + */ + public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) { + try { + return mService.getMatchingOsuProviders(scanResult); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Add a new network description to the set of configured networks. * The {@code networkId} field of the supplied configuration object * is ignored. |