diff options
259 files changed, 6670 insertions, 4481 deletions
diff --git a/Android.mk b/Android.mk index 552103d4d1a5..7103f67862da 100644 --- a/Android.mk +++ b/Android.mk @@ -338,6 +338,7 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/backup/IObbBackupService.aidl \ core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl \ core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl \ + core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl \ core/java/com/android/internal/policy/IKeyguardExitCallback.aidl \ core/java/com/android/internal/policy/IKeyguardService.aidl \ core/java/com/android/internal/policy/IKeyguardStateCallback.aidl \ @@ -1129,7 +1130,9 @@ LOCAL_DROIDDOC_OPTIONS:=\ -proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \ -sdkvalues $(OUT_DOCS) \ -hdf android.whichdoc offline \ - -referenceonly + -referenceonly \ + -resourcesdir $(LOCAL_PATH)/docs/html/reference/images/ \ + -resourcesoutdir reference/android/images/ LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk diff --git a/api/current.txt b/api/current.txt index 8f7e305dfe19..6b140b3ecc12 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4824,6 +4824,7 @@ package android.app { public class KeyguardManager { method public android.content.Intent createConfirmDeviceCredentialIntent(java.lang.CharSequence, java.lang.CharSequence); + method public void dismissKeyguard(android.app.Activity, android.app.KeyguardManager.KeyguardDismissCallback, android.os.Handler); method public deprecated void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult); method public boolean inKeyguardRestrictedInputMode(); method public boolean isDeviceLocked(); @@ -4833,12 +4834,19 @@ package android.app { method public deprecated android.app.KeyguardManager.KeyguardLock newKeyguardLock(java.lang.String); } + public static abstract class KeyguardManager.KeyguardDismissCallback { + ctor public KeyguardManager.KeyguardDismissCallback(); + method public void onDismissCancelled(); + method public void onDismissError(); + method public void onDismissSucceeded(); + } + public deprecated class KeyguardManager.KeyguardLock { method public void disableKeyguard(); method public void reenableKeyguard(); } - public static abstract interface KeyguardManager.OnKeyguardExitResult { + public static abstract deprecated interface KeyguardManager.OnKeyguardExitResult { method public abstract void onKeyguardExitResult(boolean); } @@ -5970,6 +5978,8 @@ package android.app.admin { method public void onReceive(android.content.Context, android.content.Intent); method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent); method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long); + method public void onUserAdded(android.content.Context, android.content.Intent, android.os.UserHandle); + method public void onUserRemoved(android.content.Context, android.content.Intent, android.os.UserHandle); field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"; @@ -6003,6 +6013,7 @@ package android.app.admin { method public int enableSystemApp(android.content.ComponentName, android.content.Intent); method public java.lang.String[] getAccountTypesWithManagementDisabled(); method public java.util.List<android.content.ComponentName> getActiveAdmins(); + method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName); method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName); method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String); method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName); @@ -6078,6 +6089,7 @@ package android.app.admin { method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName); method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName); method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean); + method public void setAffiliationIds(android.content.ComponentName, java.util.List<java.lang.String>); method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException; method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean); method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle); @@ -6167,6 +6179,7 @@ package android.app.admin { field public static final java.lang.String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI"; field public static final java.lang.String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR"; field public static final java.lang.String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION"; + field public static final java.lang.String EXTRA_PROVISIONING_SKIP_USER_CONSENT = "android.app.extra.PROVISIONING_SKIP_USER_CONSENT"; field public static final java.lang.String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PAC_URL = "android.app.extra.PROVISIONING_WIFI_PAC_URL"; @@ -35094,6 +35107,8 @@ package android.service.notification { method public final void requestUnbind(); method public final void setNotificationsShown(java.lang.String[]); method public final void snoozeNotification(java.lang.String, long); + method public final void snoozeNotification(java.lang.String); + method public final void unsnoozeNotification(java.lang.String); field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4 field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2 diff --git a/api/system-current.txt b/api/system-current.txt index 6a57e4888f76..a71c0d3410e6 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4978,6 +4978,7 @@ package android.app { public class KeyguardManager { method public android.content.Intent createConfirmDeviceCredentialIntent(java.lang.CharSequence, java.lang.CharSequence); + method public void dismissKeyguard(android.app.Activity, android.app.KeyguardManager.KeyguardDismissCallback, android.os.Handler); method public deprecated void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult); method public boolean inKeyguardRestrictedInputMode(); method public boolean isDeviceLocked(); @@ -4987,12 +4988,19 @@ package android.app { method public deprecated android.app.KeyguardManager.KeyguardLock newKeyguardLock(java.lang.String); } + public static abstract class KeyguardManager.KeyguardDismissCallback { + ctor public KeyguardManager.KeyguardDismissCallback(); + method public void onDismissCancelled(); + method public void onDismissError(); + method public void onDismissSucceeded(); + } + public deprecated class KeyguardManager.KeyguardLock { method public void disableKeyguard(); method public void reenableKeyguard(); } - public static abstract interface KeyguardManager.OnKeyguardExitResult { + public static abstract deprecated interface KeyguardManager.OnKeyguardExitResult { method public abstract void onKeyguardExitResult(boolean); } @@ -6142,6 +6150,8 @@ package android.app.admin { method public void onReceive(android.content.Context, android.content.Intent); method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent); method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long); + method public void onUserAdded(android.content.Context, android.content.Intent, android.os.UserHandle); + method public void onUserRemoved(android.content.Context, android.content.Intent, android.os.UserHandle); field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"; @@ -6175,6 +6185,7 @@ package android.app.admin { method public int enableSystemApp(android.content.ComponentName, android.content.Intent); method public java.lang.String[] getAccountTypesWithManagementDisabled(); method public java.util.List<android.content.ComponentName> getActiveAdmins(); + method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName); method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName); method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String); method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName); @@ -6261,6 +6272,7 @@ package android.app.admin { method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName); method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean); method public deprecated boolean setActiveProfileOwner(android.content.ComponentName, java.lang.String) throws java.lang.IllegalArgumentException; + method public void setAffiliationIds(android.content.ComponentName, java.util.List<java.lang.String>); method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException; method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean); method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle); @@ -6354,6 +6366,7 @@ package android.app.admin { field public static final java.lang.String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI"; field public static final java.lang.String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR"; field public static final java.lang.String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION"; + field public static final java.lang.String EXTRA_PROVISIONING_SKIP_USER_CONSENT = "android.app.extra.PROVISIONING_SKIP_USER_CONSENT"; field public static final java.lang.String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PAC_URL = "android.app.extra.PROVISIONING_WIFI_PAC_URL"; @@ -37893,7 +37906,9 @@ package android.service.notification { method public final void setNotificationsShown(java.lang.String[]); method public final void setOnNotificationPostedTrim(int); method public final void snoozeNotification(java.lang.String, long); + method public final void snoozeNotification(java.lang.String); method public void unregisterAsSystemService() throws android.os.RemoteException; + method public final void unsnoozeNotification(java.lang.String); field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4 field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2 diff --git a/api/test-current.txt b/api/test-current.txt index 004350dd2eb2..b44715b6d694 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4834,6 +4834,7 @@ package android.app { public class KeyguardManager { method public android.content.Intent createConfirmDeviceCredentialIntent(java.lang.CharSequence, java.lang.CharSequence); + method public void dismissKeyguard(android.app.Activity, android.app.KeyguardManager.KeyguardDismissCallback, android.os.Handler); method public deprecated void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult); method public boolean inKeyguardRestrictedInputMode(); method public boolean isDeviceLocked(); @@ -4843,12 +4844,19 @@ package android.app { method public deprecated android.app.KeyguardManager.KeyguardLock newKeyguardLock(java.lang.String); } + public static abstract class KeyguardManager.KeyguardDismissCallback { + ctor public KeyguardManager.KeyguardDismissCallback(); + method public void onDismissCancelled(); + method public void onDismissError(); + method public void onDismissSucceeded(); + } + public deprecated class KeyguardManager.KeyguardLock { method public void disableKeyguard(); method public void reenableKeyguard(); } - public static abstract interface KeyguardManager.OnKeyguardExitResult { + public static abstract deprecated interface KeyguardManager.OnKeyguardExitResult { method public abstract void onKeyguardExitResult(boolean); } @@ -5395,6 +5403,7 @@ package android.app { method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String); method public java.util.Map<java.lang.String, android.app.AutomaticZenRule> getAutomaticZenRules(); method public final int getCurrentInterruptionFilter(); + method public android.content.ComponentName getEffectsSuppressor(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(java.lang.String); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); @@ -5986,6 +5995,8 @@ package android.app.admin { method public void onReceive(android.content.Context, android.content.Intent); method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent); method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long); + method public void onUserAdded(android.content.Context, android.content.Intent, android.os.UserHandle); + method public void onUserRemoved(android.content.Context, android.content.Intent, android.os.UserHandle); field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"; @@ -6019,6 +6030,7 @@ package android.app.admin { method public int enableSystemApp(android.content.ComponentName, android.content.Intent); method public java.lang.String[] getAccountTypesWithManagementDisabled(); method public java.util.List<android.content.ComponentName> getActiveAdmins(); + method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName); method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName); method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String); method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName); @@ -6097,6 +6109,7 @@ package android.app.admin { method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName); method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName); method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean); + method public void setAffiliationIds(android.content.ComponentName, java.util.List<java.lang.String>); method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException; method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean); method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle); @@ -6186,6 +6199,7 @@ package android.app.admin { field public static final java.lang.String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI"; field public static final java.lang.String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR"; field public static final java.lang.String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION"; + field public static final java.lang.String EXTRA_PROVISIONING_SKIP_USER_CONSENT = "android.app.extra.PROVISIONING_SKIP_USER_CONSENT"; field public static final java.lang.String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PAC_URL = "android.app.extra.PROVISIONING_WIFI_PAC_URL"; @@ -35187,6 +35201,8 @@ package android.service.notification { method public final void requestUnbind(); method public final void setNotificationsShown(java.lang.String[]); method public final void snoozeNotification(java.lang.String, long); + method public final void snoozeNotification(java.lang.String); + method public final void unsnoozeNotification(java.lang.String); field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4 field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2 diff --git a/cmds/wm/src/com/android/commands/wm/Wm.java b/cmds/wm/src/com/android/commands/wm/Wm.java index 84fb626cc4c6..8defb331e289 100644 --- a/cmds/wm/src/com/android/commands/wm/Wm.java +++ b/cmds/wm/src/com/android/commands/wm/Wm.java @@ -274,7 +274,7 @@ public class Wm extends BaseCommand { } private void runDismissKeyguard() throws Exception { - mWm.dismissKeyguard(); + mWm.dismissKeyguard(null /* callback */); } private int parseDimension(String s) throws NumberFormatException { diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index e222fee6e409..abb098f05184 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -534,6 +534,12 @@ final class BackStackRecord extends FragmentTransaction implements if (mSharedElementSourceNames == null) { mSharedElementSourceNames = new ArrayList<String>(); mSharedElementTargetNames = new ArrayList<String>(); + } else if (mSharedElementTargetNames.contains(name)) { + throw new IllegalArgumentException("A shared element with the target name '" + + name + "' has already been added to the transaction."); + } else if (mSharedElementSourceNames.contains(transitionName)) { + throw new IllegalArgumentException("A shared element with the source name '" + + transitionName + " has already been added to the transaction."); } mSharedElementSourceNames.add(transitionName); mSharedElementTargetNames.add(name); diff --git a/core/java/android/app/FragmentTransition.java b/core/java/android/app/FragmentTransition.java index 088fd08d97a8..33244481c289 100644 --- a/core/java/android/app/FragmentTransition.java +++ b/core/java/android/app/FragmentTransition.java @@ -367,9 +367,11 @@ class FragmentTransition { } if (exitingViews != null) { - ArrayList<View> tempExiting = new ArrayList<>(); - tempExiting.add(nonExistentView); - replaceTargets(exitTransition, exitingViews, tempExiting); + if (exitTransition != null) { + ArrayList<View> tempExiting = new ArrayList<>(); + tempExiting.add(nonExistentView); + replaceTargets(exitTransition, exitingViews, tempExiting); + } exitingViews.clear(); exitingViews.add(nonExistentView); } @@ -490,9 +492,17 @@ class FragmentTransition { if (nameOverrides.isEmpty()) { sharedElementTransition = null; + if (outSharedElements != null) { + outSharedElements.clear(); + } + if (inSharedElements != null) { + inSharedElements.clear(); + } } else { - sharedElementsOut.addAll(outSharedElements.values()); - sharedElementsIn.addAll(inSharedElements.values()); + addSharedElementsWithMatchingNames(sharedElementsOut, outSharedElements, + nameOverrides.keySet()); + addSharedElementsWithMatchingNames(sharedElementsIn, inSharedElements, + nameOverrides.values()); } if (enterTransition == null && exitTransition == null && sharedElementTransition == null) { @@ -538,6 +548,25 @@ class FragmentTransition { } /** + * Add Views from sharedElements into views that have the transitionName in the + * nameOverridesSet. + * + * @param views Views list to add shared elements to + * @param sharedElements List of shared elements + * @param nameOverridesSet The transition names for all views to be copied from + * sharedElements to views. + */ + private static void addSharedElementsWithMatchingNames(ArrayList<View> views, + ArrayMap<String, View> sharedElements, Collection<String> nameOverridesSet) { + for (int i = sharedElements.size() - 1; i >= 0; i--) { + View view = sharedElements.valueAt(i); + if (view != null && nameOverridesSet.contains(view.getTransitionName())) { + views.add(view); + } + } + } + + /** * Configures the shared elements of an unoptimized fragment transaction's transition. * This retrieves the shared elements of the incoming fragments, and schedules capturing * the incoming fragment's shared elements. It also maps the views, and sets up the epicenter diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 82be7abeb87d..1a36d1aa8449 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -31,6 +31,7 @@ import android.app.IStopUserCallback; import android.app.ITaskStackListener; import android.app.IUiAutomationConnection; import android.app.IUidObserver; + import android.app.IUserSwitchObserver; import android.app.Notification; import android.app.PendingIntent; @@ -65,6 +66,7 @@ import android.os.StrictMode; import android.service.voice.IVoiceInteractionSession; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; +import com.android.internal.policy.IKeyguardDismissCallback; import java.util.List; @@ -571,6 +573,7 @@ interface IActivityManager { void setPictureInPictureAspectRatio(in IBinder token, float aspectRatio); boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras, in IBinder activityToken); + void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback); // WARNING: when these transactions are updated, check if they are any callers on the native // side. If so, make sure they are using the correct transaction ids. diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 15b99c6e4f7f..2a7341ae6911 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -77,7 +77,10 @@ interface INotificationManager void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id); void cancelNotificationsFromListener(in INotificationListener token, in String[] keys); - void snoozeNotificationFromListener(in INotificationListener token, String key, long until); + + void snoozeNotificationUntilFromListener(in INotificationListener token, String key, long until); + void snoozeNotificationFromListener(in INotificationListener token, String key); + void unsnoozeNotificationFromListener(in INotificationListener token, String key); void requestBindListener(in ComponentName component); void requestUnbindListener(in INotificationListener token); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 725cc29bd323..036b47cafe64 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -17,24 +17,32 @@ package android.app; import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.app.trust.ITrustManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.UserInfo; import android.os.Binder; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; import android.os.RemoteException; import android.os.IBinder; import android.os.IUserManager; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.UserHandle; -import android.os.UserManager; +import android.util.Log; import android.view.IWindowManager; import android.view.IOnKeyguardExitResult; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; import android.view.WindowManagerGlobal; +import com.android.internal.policy.IKeyguardDismissCallback; + /** * Class that can be used to lock and unlock the keyboard. Get an instance of this * class by calling {@link android.content.Context#getSystemService(java.lang.String)} @@ -43,10 +51,13 @@ import android.view.WindowManagerGlobal; * {@link android.app.KeyguardManager.KeyguardLock}. */ public class KeyguardManager { - private IWindowManager mWM; - private ITrustManager mTrustManager; - private IUserManager mUserManager; - private Context mContext; + + private static final String TAG = "KeyguardManager"; + + private final Context mContext; + private final IWindowManager mWM; + private final IActivityManager mAm; + private final ITrustManager mTrustManager; /** * Intent used to prompt user for device credentials. @@ -125,8 +136,8 @@ public class KeyguardManager { } /** - * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD} - * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} + * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} + * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} * instead; this allows you to seamlessly hide the keyguard as your application * moves in and out of the foreground and does not require that any special * permissions be requested. @@ -190,9 +201,11 @@ public class KeyguardManager { } /** + * @deprecated Use {@link KeyguardDismissCallback} * Callback passed to {@link KeyguardManager#exitKeyguardSecurely} to notify * caller of result. */ + @Deprecated public interface OnKeyguardExitResult { /** @@ -202,19 +215,41 @@ public class KeyguardManager { void onKeyguardExitResult(boolean success); } + /** + * Callback passed to {@link KeyguardManager#dismissKeyguard} to notify caller of result. + */ + public static abstract class KeyguardDismissCallback { + + /** + * Called when dismissing Keyguard is currently not feasible, i.e. when Keyguard is not + * available, not showing or when the activity requesting the Keyguard dismissal isn't + * showing or isn't showing behind Keyguard. + */ + public void onDismissError() { } + + /** + * Called when dismissing Keyguard has succeeded and the device is now unlocked. + */ + public void onDismissSucceeded() { } + + /** + * Called when dismissing Keyguard has been cancelled, i.e. when the user cancelled the + * operation or the bouncer was hidden for some other reason. + */ + public void onDismissCancelled() { } + } KeyguardManager(Context context) throws ServiceNotFoundException { mContext = context; mWM = WindowManagerGlobal.getWindowManagerService(); + mAm = ActivityManager.getService(); mTrustManager = ITrustManager.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.TRUST_SERVICE)); - mUserManager = IUserManager.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.USER_SERVICE)); } /** - * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD} - * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} + * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} + * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} * instead; this allows you to seamlessly hide the keyguard as your application * moves in and out of the foreground and does not require that any special * permissions be requested. @@ -296,9 +331,8 @@ public class KeyguardManager { * @hide */ public boolean isDeviceLocked(int userId) { - ITrustManager trustManager = getTrustManager(); try { - return trustManager.isDeviceLocked(userId); + return mTrustManager.isDeviceLocked(userId); } catch (RemoteException e) { return false; } @@ -322,25 +356,63 @@ public class KeyguardManager { * @hide */ public boolean isDeviceSecure(int userId) { - ITrustManager trustManager = getTrustManager(); try { - return trustManager.isDeviceSecure(userId); + return mTrustManager.isDeviceSecure(userId); } catch (RemoteException e) { return false; } } - private synchronized ITrustManager getTrustManager() { - if (mTrustManager == null) { - mTrustManager = ITrustManager.Stub.asInterface( - ServiceManager.getService(Context.TRUST_SERVICE)); + /** + * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to + * be dismissed. + * <p> + * If the Keyguard is not secure or the device is currently in a trusted state, calling this + * method will immediately dismiss the Keyguard without any user interaction. + * <p> + * If the Keyguard is secure and the device is not in a trusted state, this will bring up the + * UI so the user can enter their credentials. + * + * @param activity The activity requesting the dismissal. The activity must be either visible + * by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in + * which it would be visible if Keyguard would not be hiding it. If that's not + * the case, the request will fail immediately and + * {@link KeyguardDismissCallback#onDismissError} will be invoked. + * @param callback The callback to be called if the request to dismiss Keyguard was successful + * or {@code null} if the caller isn't interested in knowing the result. + * @param handler The handler to invoke the callback on, or {@code null} to use the main + * handler. + */ + public void dismissKeyguard(@NonNull Activity activity, + @Nullable KeyguardDismissCallback callback, @Nullable Handler handler) { + try { + final Handler actualHandler = handler != null + ? handler + : new Handler(Looper.getMainLooper()); + mAm.dismissKeyguard(activity.getActivityToken(), new IKeyguardDismissCallback.Stub() { + @Override + public void onDismissError() throws RemoteException { + actualHandler.post(callback::onDismissError); + } + + @Override + public void onDismissSucceeded() throws RemoteException { + actualHandler.post(callback::onDismissSucceeded); + } + + @Override + public void onDismissCancelled() throws RemoteException { + actualHandler.post(callback::onDismissCancelled); + } + }); + } catch (RemoteException e) { + Log.i(TAG, "Failed to dismiss keyguard: " + e); } - return mTrustManager; } /** - * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD} - * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} + * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} + * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} * instead; this allows you to seamlessly hide the keyguard as your application * moves in and out of the foreground and does not require that any special * permissions be requested. diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 2234a3850005..047f349b1e54 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -19,6 +19,7 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SdkConstant; +import android.annotation.TestApi; import android.app.Notification.Builder; import android.content.ComponentName; import android.content.Context; @@ -428,6 +429,7 @@ public class NotificationManager /** * @hide */ + @TestApi public ComponentName getEffectsSuppressor() { INotificationManager service = getService(); try { diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java index e05feafcc93c..c1646bbbc180 100644 --- a/core/java/android/app/admin/ConnectEvent.java +++ b/core/java/android/app/admin/ConnectEvent.java @@ -75,6 +75,11 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable { }; @Override + public int describeContents() { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) { // write parcel token first out.writeInt(PARCEL_TOKEN_CONNECT_EVENT); diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index d89d2bb93f08..b1eca5c8ad4a 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.os.UserHandle; import android.security.KeyChain; import java.lang.annotation.Retention; @@ -306,6 +307,24 @@ public class DeviceAdminReceiver extends BroadcastReceiver { "android.app.extra.EXTRA_NETWORK_LOGS_COUNT"; /** + * Broadcast action: notify the device owner that a user or profile has been added. + * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of + * the new user. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_USER_ADDED = "android.app.action.USER_ADDED"; + + /** + * Broadcast action: notify the device owner that a user or profile has been removed. + * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of + * the new user. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_USER_REMOVED = "android.app.action.USER_REMOVED"; + + /** * A string containing the SHA-256 hash of the bugreport file. * * @see #ACTION_BUGREPORT_SHARE @@ -690,6 +709,30 @@ public class DeviceAdminReceiver extends BroadcastReceiver { int networkLogsCount) { } + /** + * Called when a user or profile is created. + * + * <p>This callback is only applicable to device owners. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param newUser The {@link UserHandle} of the user that has just been added. + */ + public void onUserAdded(Context context, Intent intent, UserHandle newUser) { + } + + /** + * Called when a user or profile is removed. + * + * <p>This callback is only applicable to device owners. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param removedUser The {@link UserHandle} of the user that has just been removed. + */ + public void onUserRemoved(Context context, Intent intent, UserHandle removedUser) { + } + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the @@ -748,6 +791,10 @@ public class DeviceAdminReceiver extends BroadcastReceiver { long batchToken = intent.getLongExtra(EXTRA_NETWORK_LOGS_TOKEN, -1); int networkLogsCount = intent.getIntExtra(EXTRA_NETWORK_LOGS_COUNT, 0); onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount); + } else if (ACTION_USER_ADDED.equals(action)) { + onUserAdded(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); + } else if (ACTION_USER_REMOVED.equals(action)) { + onUserRemoved(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index e0ec1b111f02..866a551a561c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -79,7 +79,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Set; /** * Public interface for managing policies enforced on a device. Most clients of this class must be @@ -151,6 +150,7 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li> * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li> * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_SKIP_USER_CONSENT}, optional</li> * </ul> * * <p>When managed provisioning has completed, broadcasts are sent to the application specified @@ -813,6 +813,16 @@ public class DevicePolicyManager { "android.app.extra.PROVISIONING_SKIP_USER_SETUP"; /** + * A boolean extra indicating if the user consent steps from the provisioning flow should be + * skipped. If unspecified, defaults to {@code false}. + * + * It can only be used by an existing device owner trying to create a managed profile via + * {@link #ACTION_PROVISION_MANAGED_PROFILE}. Otherwise it is ignored. + */ + public static final String EXTRA_PROVISIONING_SKIP_USER_CONSENT = + "android.app.extra.PROVISIONING_SKIP_USER_CONSENT"; + + /** * This MIME type is used for starting the device owner provisioning. * * <p>During device owner provisioning a device admin app is set as the owner of the device. @@ -6491,27 +6501,32 @@ public class DevicePolicyManager { } /** - * @hide - * Indicates the entity that controls the device or profile owner. A user/profile is considered - * affiliated if it is managed by the same entity as the device. - * - * <p> By definition, the user that the device owner runs on is always affiliated. Any other - * user/profile is considered affiliated if the following conditions are both met: - * <ul> - * <li>The device owner and the user's/profile's profile owner have called this method, - * specifying a set of opaque affiliation ids each. If the sets specified by the device owner - * and a profile owner intersect, they must have come from the same source, which means that - * the device owner and profile owner are controlled by the same entity.</li> - * <li>The device owner's and profile owner's package names are the same.</li> - * </ul> + * Indicates the entity that controls the device or profile owner. Two users/profiles are + * affiliated if the set of ids set by their device or profile owners intersect. * * @param admin Which profile or device owner this request is associated with. - * @param ids A set of opaque affiliation ids. + * @param ids A list of opaque non-empty affiliation ids. Duplicate elements will be ignored. + * + * @throws NullPointerException if {@code ids} is null or contains null elements. + * @throws IllegalArgumentException if {@code ids} contains an empty string. */ - public void setAffiliationIds(@NonNull ComponentName admin, Set<String> ids) { + public void setAffiliationIds(@NonNull ComponentName admin, @NonNull List<String> ids) { throwIfParentInstance("setAffiliationIds"); try { - mService.setAffiliationIds(admin, new ArrayList<String>(ids)); + mService.setAffiliationIds(admin, ids); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the list of affiliation ids previously set via {@link #setAffiliationIds}, or an + * empty list if none have been set. + */ + public @NonNull List<String> getAffiliationIds(@NonNull ComponentName admin) { + throwIfParentInstance("getAffiliationIds"); + try { + return mService.getAffiliationIds(admin); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -6519,15 +6534,17 @@ public class DevicePolicyManager { /** * @hide - * Returns whether this user/profile is affiliated with the device. See - * {@link #setAffiliationIds} for the definition of affiliation. + * Returns whether this user/profile is affiliated with the device. + * <p> + * By definition, the user that the device owner runs on is always affiliated with the device. + * Any other user/profile is considered affiliated with the device if the set specified by its + * profile owner via {@link #setAffiliationIds} intersects with the device owner's. * - * @return whether this user/profile is affiliated with the device. */ public boolean isAffiliatedUser() { throwIfParentInstance("isAffiliatedUser"); try { - return mService != null && mService.isAffiliatedUser(); + return mService.isAffiliatedUser(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java index 0ec134acdad5..63ea8db99f3b 100644 --- a/core/java/android/app/admin/DnsEvent.java +++ b/core/java/android/app/admin/DnsEvent.java @@ -95,6 +95,11 @@ public final class DnsEvent extends NetworkEvent implements Parcelable { }; @Override + public int describeContents() { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) { // write parcel token first out.writeInt(PARCEL_TOKEN_DNS_EVENT); diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index b7e0e9266e0e..3bc8cd015e70 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -299,6 +299,7 @@ interface IDevicePolicyManager { void setUserProvisioningState(int state, int userHandle); void setAffiliationIds(in ComponentName admin, in List<String> ids); + List<String> getAffiliationIds(in ComponentName admin); boolean isAffiliatedUser(); void setSecurityLoggingEnabled(in ComponentName admin, boolean enabled); diff --git a/core/java/android/app/admin/NetworkEvent.java b/core/java/android/app/admin/NetworkEvent.java index ec7ed00debb4..1dbff201e8d2 100644 --- a/core/java/android/app/admin/NetworkEvent.java +++ b/core/java/android/app/admin/NetworkEvent.java @@ -26,14 +26,14 @@ import android.os.ParcelFormatException; */ public abstract class NetworkEvent implements Parcelable { - protected static final int PARCEL_TOKEN_DNS_EVENT = 1; - protected static final int PARCEL_TOKEN_CONNECT_EVENT = 2; + static final int PARCEL_TOKEN_DNS_EVENT = 1; + static final int PARCEL_TOKEN_CONNECT_EVENT = 2; /** The package name of the UID that performed the query. */ - protected String packageName; + String packageName; /** The timestamp of the event being reported in milliseconds. */ - protected long timestamp; + long timestamp; protected NetworkEvent() { //empty constructor @@ -81,5 +81,8 @@ public abstract class NetworkEvent implements Parcelable { return new NetworkEvent[size]; } }; + + @Override + public abstract void writeToParcel(Parcel out, int flags); } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index cdf7013deb26..aa109de752e3 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -26,6 +26,7 @@ import android.os.CancellationSignal; import android.os.CancellationSignal.OnCancelListener; import android.os.Handler; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; @@ -757,20 +758,22 @@ public class FingerprintManager { new IFingerprintServiceLockoutResetCallback.Stub() { @Override - public void onLockoutReset(long deviceId) throws RemoteException { - final PowerManager.WakeLock wakeLock = powerManager.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, "lockoutResetCallback"); - wakeLock.acquire(); - mHandler.post(new Runnable() { - @Override - public void run() { + public void onLockoutReset(long deviceId, IRemoteCallback serverCallback) + throws RemoteException { + try { + final PowerManager.WakeLock wakeLock = powerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, "lockoutResetCallback"); + wakeLock.acquire(); + mHandler.post(() -> { try { callback.onLockoutReset(); } finally { wakeLock.release(); } - } - }); + }); + } finally { + serverCallback.sendResult(null /* data */); + } } }); } catch (RemoteException e) { diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl index e027a2b3d260..971e14c7251f 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl @@ -17,14 +17,17 @@ package android.hardware.fingerprint; import android.hardware.fingerprint.Fingerprint; import android.os.Bundle; +import android.os.IRemoteCallback; import android.os.UserHandle; /** * Callback when lockout period expired and clients are allowed to authenticate again. * @hide */ -interface IFingerprintServiceLockoutResetCallback { +oneway interface IFingerprintServiceLockoutResetCallback { - /** Method is synchronous so wakelock is held when this is called from a WAKEUP alarm. */ - void onLockoutReset(long deviceId); + /** + * A wakelock will be held until the reciever calls back into {@param callback} + */ + void onLockoutReset(long deviceId, IRemoteCallback callback); } diff --git a/core/java/android/net/RoughtimeClient.java b/core/java/android/net/RoughtimeClient.java deleted file mode 100644 index cf4d8a2d3f5a..000000000000 --- a/core/java/android/net/RoughtimeClient.java +++ /dev/null @@ -1,499 +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 android.net; - -import android.os.SystemClock; -import android.util.Log; - -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; - -/** - * {@hide} - * - * Simple Roughtime client class for retrieving network time. - */ -public class RoughtimeClient -{ - private static final String TAG = "RoughtimeClient"; - private static final boolean ENABLE_DEBUG = true; - - private static final int ROUGHTIME_PORT = 5333; - - private static final int MIN_REQUEST_SIZE = 1024; - - private static final int NONCE_SIZE = 64; - - private static final int MAX_DATAGRAM_SIZE = 65507; - - private final SecureRandom random = new SecureRandom(); - - /** - * Tag values. Exposed for use in tests only. - */ - protected static enum Tag { - /** - * Nonce used to initiate a transaction. - */ - NONC(0x434e4f4e), - - /** - * Signed portion of a response. - **/ - SREP(0x50455253), - - /** - * Pad data. Always the largest tag lexicographically. - */ - PAD(0xff444150), - - /** - * A signature for a neighboring SREP. - */ - SIG(0x474953), - - /** - * Server certificate. - */ - CERT(0x54524543), - - /** - * Position in the Merkle tree. - */ - INDX(0x58444e49), - - /** - * Upward path in the Merkle tree. - */ - PATH(0x48544150), - - /** - * Midpoint of the time interval in the response. - */ - MIDP(0x5044494d), - - /** - * Radius of the time interval in the response. - */ - RADI(0x49444152), - - /** - * Root of the Merkle tree. - */ - ROOT(0x544f4f52), - - /** - * Delegation from the long term key to an online key. - */ - DELE(0x454c4544), - - /** - * Online public key. - */ - PUBK(0x4b425550), - - /** - * Earliest midpoint time the given PUBK can authenticate. - */ - MINT(0x544e494d), - - /** - * Latest midpoint time the given PUBK can authenticate. - */ - MAXT(0x5458414d); - - private final int value; - - Tag(int value) { - this.value = value; - } - - private int value() { - return value; - } - } - - /** - * A result retrieved from a roughtime server. - */ - private static class Result { - public long midpoint; - public int radius; - public long collectionTime; - } - - /** - * A Roughtime protocol message. Functionally a serializable map from Tags - * to byte arrays. - */ - protected static class Message { - private HashMap<Integer,byte[]> items = new HashMap<Integer,byte[]>(); - public int padSize = 0; - - public Message() {} - - /** - * Set the given data for the given tag. - */ - public void put(Tag tag, byte[] data) { - put(tag.value(), data); - } - - private void put(int tag, byte[] data) { - items.put(tag, data); - } - - /** - * Get the data associated with the given tag. - */ - public byte[] get(Tag tag) { - return items.get(tag.value()); - } - - /** - * Get the data associated with the given tag and decode it as a 64-bit - * integer. - */ - public long getLong(Tag tag) { - ByteBuffer b = ByteBuffer.wrap(get(tag)); - return b.getLong(); - } - - /** - * Get the data associated with the given tag and decode it as a 32-bit - * integer. - */ - public int getInt(Tag tag) { - ByteBuffer b = ByteBuffer.wrap(get(tag)); - return b.getInt(); - } - - /** - * Encode the given long value as a 64-bit little-endian value and - * associate it with the given tag. - */ - public void putLong(Tag tag, long l) { - ByteBuffer b = ByteBuffer.allocate(8); - b.putLong(l); - put(tag, b.array()); - } - - /** - * Encode the given int value as a 32-bit little-endian value and - * associate it with the given tag. - */ - public void putInt(Tag tag, int l) { - ByteBuffer b = ByteBuffer.allocate(4); - b.putInt(l); - put(tag, b.array()); - } - - /** - * Get a packed representation of this message suitable for the wire. - */ - public byte[] serialize() { - if (items.size() == 0) { - if (padSize > 4) - return new byte[padSize]; - else - return new byte[4]; - } - - int size = 0; - - ArrayList<Integer> offsets = new ArrayList<Integer>(); - ArrayList<Integer> tagList = new ArrayList<Integer>(items.keySet()); - Collections.sort(tagList); - - boolean first = true; - for (int tag : tagList) { - if (! first) { - offsets.add(size); - } - - first = false; - size += items.get(tag).length; - } - - ByteBuffer dataBuf = ByteBuffer.allocate(size); - dataBuf.order(ByteOrder.LITTLE_ENDIAN); - - int valueDataSize = size; - size += 4 + offsets.size() * 4 + tagList.size() * 4; - - int tagCount = items.size(); - - if (size < padSize) { - offsets.add(valueDataSize); - tagList.add(Tag.PAD.value()); - - if (size + 8 > padSize) { - size = size + 8; - } else { - size = padSize; - } - - tagCount += 1; - } - - ByteBuffer buf = ByteBuffer.allocate(size); - buf.order(ByteOrder.LITTLE_ENDIAN); - buf.putInt(tagCount); - - for (int offset : offsets) { - buf.putInt(offset); - } - - for (int tag : tagList) { - buf.putInt(tag); - - if (tag != Tag.PAD.value()) { - dataBuf.put(items.get(tag)); - } - } - - buf.put(dataBuf.array()); - - return buf.array(); - } - - /** - * Given a byte stream from the wire, unpack it into a Message object. - */ - public static Message deserialize(byte[] data) { - ByteBuffer buf = ByteBuffer.wrap(data); - buf.order(ByteOrder.LITTLE_ENDIAN); - - Message msg = new Message(); - - int count = buf.getInt(); - - if (count == 0) { - return msg; - } - - ArrayList<Integer> offsets = new ArrayList<Integer>(); - offsets.add(0); - - for (int i = 1; i < count; i++) { - offsets.add(buf.getInt()); - } - - ArrayList<Integer> tags = new ArrayList<Integer>(); - for (int i = 0; i < count; i++) { - int tag = buf.getInt(); - tags.add(tag); - } - - offsets.add(buf.remaining()); - - for (int i = 0; i < count; i++) { - int tag = tags.get(i); - int start = offsets.get(i); - int end = offsets.get(i+1); - byte[] content = new byte[end - start]; - - buf.get(content); - if (tag != Tag.PAD.value()) { - msg.put(tag, content); - } - } - - return msg; - } - - /** - * Send this message over the given socket to the given address and port. - */ - public void send(DatagramSocket socket, InetAddress address, int port) - throws IOException { - byte[] buffer = serialize(); - DatagramPacket message = new DatagramPacket(buffer, buffer.length, - address, port); - socket.send(message); - } - - /** - * Receive a Message object from the given socket. - */ - public static Message receive(DatagramSocket socket) - throws IOException { - byte[] buffer = new byte[MAX_DATAGRAM_SIZE]; - DatagramPacket packet = new DatagramPacket(buffer, buffer.length); - - socket.receive(packet); - - return deserialize(Arrays.copyOf(buffer, packet.getLength())); - } - } - - private MessageDigest messageDigest = null; - - private final ArrayList<Result> results = new ArrayList<Result>(); - private long lastRequest = 0; - - private Message createRequestMessage() { - byte[] nonce = new byte[NONCE_SIZE]; - random.nextBytes(nonce); // TODO: Chain nonces - - assert nonce.length == NONCE_SIZE : - "Nonce must be " + NONCE_SIZE + " bytes."; - - Message msg = new Message(); - - msg.put(Tag.NONC, nonce); - msg.padSize = MIN_REQUEST_SIZE; - - return msg; - } - - /** - * Contact the Roughtime server at the given address and port and collect a - * result time to add to our collection. - * - * @param host host name of the server. - * @param timeout network timeout in milliseconds. - * @return true if the transaction was successful. - */ - public boolean requestTime(String host, int timeout) { - InetAddress address = null; - try { - address = InetAddress.getByName(host); - } catch (Exception e) { - if (ENABLE_DEBUG) { - Log.d(TAG, "request time failed", e); - } - - return false; - } - return requestTime(address, ROUGHTIME_PORT, timeout); - } - - /** - * Contact the Roughtime server at the given address and port and collect a - * result time to add to our collection. - * - * @param address address for the server. - * @param port port to talk to the server on. - * @param timeout network timeout in milliseconds. - * @return true if the transaction was successful. - */ - public boolean requestTime(InetAddress address, int port, int timeout) { - - final long rightNow = SystemClock.elapsedRealtime(); - - if ((rightNow - lastRequest) > timeout) { - results.clear(); - } - - lastRequest = rightNow; - - DatagramSocket socket = null; - try { - if (messageDigest == null) { - messageDigest = MessageDigest.getInstance("SHA-512"); - } - - socket = new DatagramSocket(); - socket.setSoTimeout(timeout); - final long startTime = SystemClock.elapsedRealtime(); - Message request = createRequestMessage(); - request.send(socket, address, port); - final long endTime = SystemClock.elapsedRealtime(); - Message response = Message.receive(socket); - byte[] signedData = response.get(Tag.SREP); - Message signedResponse = Message.deserialize(signedData); - - final Result result = new Result(); - result.midpoint = signedResponse.getLong(Tag.MIDP); - result.radius = signedResponse.getInt(Tag.RADI); - result.collectionTime = (startTime + endTime) / 2; - - final byte[] root = signedResponse.get(Tag.ROOT); - final byte[] path = response.get(Tag.PATH); - final byte[] nonce = request.get(Tag.NONC); - final int index = response.getInt(Tag.INDX); - - if (! verifyNonce(root, path, nonce, index)) { - Log.w(TAG, "failed to authenticate roughtime response."); - return false; - } - - results.add(result); - } catch (Exception e) { - if (ENABLE_DEBUG) { - Log.d(TAG, "request time failed", e); - } - - return false; - } finally { - if (socket != null) { - socket.close(); - } - } - - return true; - } - - /** - * Verify that a reply message corresponds to the nonce sent in the request. - * - * @param root Root of the Merkle tree used to sign the nonce. Received in - * the ROOT tag of the reply. - * @param path Sibling hashes along the path to the root of the Merkle tree. - * Received in the PATH tag of the reply. - * @param nonce The nonce we sent in the request. - * @param index Bitfield indicating whether chunks of the path are left or - * right children. - * @return true if the verification is successful. - */ - private boolean verifyNonce(byte[] root, byte[] path, byte[] nonce, - int index) { - messageDigest.update(new byte[]{ 0 }); - byte[] hash = messageDigest.digest(nonce); - int pos = 0; - byte[] one = new byte[]{ 1 }; - - while (pos < path.length) { - messageDigest.update(one); - - if ((index&1) != 0) { - messageDigest.update(path, pos, 64); - hash = messageDigest.digest(hash); - } else { - messageDigest.update(hash); - messageDigest.update(path, pos, 64); - hash = messageDigest.digest(); - } - - pos += 64; - index >>>= 1; - } - - return Arrays.equals(root, hash); - } -} diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 45011eb49905..02ab30a1cbcd 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -534,7 +534,46 @@ public abstract class NotificationListenerService extends Service { public final void snoozeNotification(String key, long snoozeUntil) { if (!isBound()) return; try { - getNotificationInterface().snoozeNotificationFromListener(mWrapper, key, snoozeUntil); + getNotificationInterface().snoozeNotificationUntilFromListener( + mWrapper, key, snoozeUntil); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } + + /** + * Inform the notification manager about snoozing a specific notification. + * <p> + * Use this to snooze a notification for an indeterminate time. Upon being informed, the + * notification manager will actually remove the notification and you will get an + * {@link #onNotificationRemoved(StatusBarNotification)} callback. When the + * snoozing period expires, you will get a + * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the + * notification. Use {@link #unsnoozeNotification(String)} to restore the notification. + * @param key The key of the notification to snooze + */ + public final void snoozeNotification(String key) { + if (!isBound()) return; + try { + getNotificationInterface().snoozeNotificationFromListener(mWrapper, key); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } + + /** + * Inform the notification manager about un-snoozing a specific notification. + * <p> + * This should only be used for notifications snoozed by this listener using + * {@link #snoozeNotification(String)}. Once un-snoozed, you will get a + * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the + * notification. + * @param key The key of the notification to snooze + */ + public final void unsnoozeNotification(String key) { + if (!isBound()) return; + try { + getNotificationInterface().unsnoozeNotificationFromListener(mWrapper, key); } catch (android.os.RemoteException ex) { Log.v(TAG, "Unable to contact notification manager", ex); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index bccb822a6519..bd1ad8e01b3c 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -20,6 +20,7 @@ import com.android.internal.app.IAssistScreenshotReceiver; import com.android.internal.os.IResultReceiver; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; import android.content.res.CompatibilityInfo; @@ -210,8 +211,7 @@ interface IWindowManager boolean isKeyguardLocked(); boolean isKeyguardSecure(); boolean inKeyguardRestrictedInputMode(); - void dismissKeyguard(); - void keyguardGoingAway(int flags); + void dismissKeyguard(IKeyguardDismissCallback callback); // Requires INTERACT_ACROSS_USERS_FULL permission void setSwitchingUser(boolean switching); diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index c46acae38c2c..0da710a1fb33 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -28,6 +28,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; import android.widget.RemoteViews; +import com.android.internal.widget.CachingIconView; + import java.util.ArrayList; /** @@ -45,7 +47,7 @@ public class NotificationHeaderView extends ViewGroup { private OnClickListener mExpandClickListener; private HeaderTouchListener mTouchListener = new HeaderTouchListener(); private ImageView mExpandButton; - private View mIcon; + private CachingIconView mIcon; private View mProfileBadge; private View mInfo; private int mIconColor; @@ -123,7 +125,7 @@ public class NotificationHeaderView extends ViewGroup { if (mExpandButton != null) { mExpandButton.setAccessibilityDelegate(mExpandDelegate); } - mIcon = findViewById(com.android.internal.R.id.icon); + mIcon = (CachingIconView) findViewById(com.android.internal.R.id.icon); mProfileBadge = findViewById(com.android.internal.R.id.profile_badge); } @@ -311,6 +313,10 @@ public class NotificationHeaderView extends ViewGroup { return mProfileBadge; } + public CachingIconView getIcon() { + return mIcon; + } + public class HeaderTouchListener implements View.OnTouchListener { private final ArrayList<Rect> mTouchRects = new ArrayList<>(); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 2971280f65d9..b8408dd7434b 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.app.KeyguardManager; import android.app.Presentation; import android.content.Context; import android.content.pm.ActivityInfo; @@ -911,16 +912,17 @@ public interface WindowManager extends ViewManager { public static final int FLAG_TURN_SCREEN_ON = 0x00200000; /** Window flag: when set the window will cause the keyguard to - * be dismissed, only if it is not a secure lock keyguard. Because such + * be dismissed, only if it is not a secure lock keyguard. Because such * a keyguard is not needed for security, it will never re-appear if * the user navigates to another window (in contrast to * {@link #FLAG_SHOW_WHEN_LOCKED}, which will only temporarily * hide both secure and non-secure keyguards but ensure they reappear * when the user moves to another UI that doesn't hide them). * If the keyguard is currently active and is secure (requires an - * unlock pattern) than the user will still need to confirm it before + * unlock credential) than the user will still need to confirm it before * seeing this window, unless {@link #FLAG_SHOW_WHEN_LOCKED} has * also been set. + * @see KeyguardManager#dismissKeyguard */ public static final int FLAG_DISMISS_KEYGUARD = 0x00400000; diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 82379c47a679..3171019e1a0e 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -17,6 +17,7 @@ package android.view; import android.annotation.IntDef; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.ActivityManager.StackId; import android.content.Context; @@ -31,6 +32,7 @@ import android.os.Looper; import android.os.RemoteException; import android.view.animation.Animation; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; import java.io.PrintWriter; @@ -1162,8 +1164,10 @@ public interface WindowManagerPolicy { /** * Ask the policy to dismiss the keyguard, if it is currently shown. + * + * @param callback Callback to be informed about the result. */ - public void dismissKeyguardLw(); + public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback); /** * Ask the policy whether the Keyguard has drawn. If the Keyguard is disabled, this method diff --git a/core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl b/core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl new file mode 100644 index 000000000000..635c504fe51c --- /dev/null +++ b/core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl @@ -0,0 +1,23 @@ +/* + * 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.internal.policy; + +oneway interface IKeyguardDismissCallback { + void onDismissError(); + void onDismissSucceeded(); + void onDismissCancelled(); +} diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl index 788103d2cc02..a019ea170484 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -16,6 +16,7 @@ package com.android.internal.policy; import com.android.internal.policy.IKeyguardDrawnCallback; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardStateCallback; import com.android.internal.policy.IKeyguardExitCallback; @@ -34,7 +35,7 @@ oneway interface IKeyguardService { void addStateMonitorCallback(IKeyguardStateCallback callback); void verifyUnlock(IKeyguardExitCallback callback); - void dismiss(boolean allowWhileOccluded); + void dismiss(IKeyguardDismissCallback callback); void onDreamingStarted(); void onDreamingStopped(); diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index b0d45e1d1db9..be10608df2a3 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -1190,6 +1190,26 @@ public class StateMachine { } /** + * Remove a state from the state machine. Will not remove the state if it is currently + * active or if it has any children in the hierarchy. + * @param state the state to remove + */ + private void removeState(State state) { + StateInfo stateInfo = mStateInfo.get(state); + if (stateInfo == null || stateInfo.active) { + return; + } + boolean isParent = mStateInfo.values().stream() + .filter(si -> si.parentStateInfo == stateInfo) + .findAny() + .isPresent(); + if (isParent) { + return; + } + mStateInfo.remove(state); + } + + /** * Constructor * * @param looper for dispatching messages @@ -1337,6 +1357,14 @@ public class StateMachine { } /** + * Removes a state from the state machine, unless it is currently active or if it has children. + * @param state state to remove + */ + public final void removeState(State state) { + mSmHandler.removeState(state); + } + + /** * Set the initial state. This must be invoked before * and messages are sent to the state machine. * diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java index 293b77b91d37..20230cdb87b2 100644 --- a/core/java/com/android/internal/widget/CachingIconView.java +++ b/core/java/com/android/internal/widget/CachingIconView.java @@ -41,6 +41,8 @@ public class CachingIconView extends ImageView { private String mLastPackage; private int mLastResId; private boolean mInternalSetDrawable; + private boolean mForceHidden; + private int mDesiredVisibility; public CachingIconView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -175,4 +177,24 @@ public class CachingIconView extends ImageView { mLastResId = 0; mLastPackage = null; } + + /** + * Set the icon to be forcibly hidden, even when it's visibility is changed to visible. + */ + public void setForceHidden(boolean forceHidden) { + mForceHidden = forceHidden; + updateVisibility(); + } + + @Override + public void setVisibility(int visibility) { + mDesiredVisibility = visibility; + updateVisibility(); + } + + private void updateVisibility() { + int visibility = mDesiredVisibility == VISIBLE && mForceHidden ? INVISIBLE + : mDesiredVisibility; + super.setVisibility(visibility); + } } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index b0bc81bd1af1..63b700bde76b 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1432,7 +1432,8 @@ public class LockPatternUtils { STRONG_AUTH_REQUIRED_AFTER_BOOT, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW, SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, - STRONG_AUTH_REQUIRED_AFTER_LOCKOUT}) + STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, + STRONG_AUTH_REQUIRED_AFTER_TIMEOUT}) @Retention(RetentionPolicy.SOURCE) public @interface StrongAuthFlags {} @@ -1463,6 +1464,12 @@ public class LockPatternUtils { public static final int STRONG_AUTH_REQUIRED_AFTER_LOCKOUT = 0x8; /** + * Strong authentication is required because it hasn't been used for a time required by + * a device admin. + */ + public static final int STRONG_AUTH_REQUIRED_AFTER_TIMEOUT = 0x10; + + /** * Strong auth flags that do not prevent fingerprint from being accepted as auth. * * If any other flags are set, fingerprint is disabled. diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index e43f1ba76370..b2212f92b18f 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Jy sal enige veranderinge verloor en die demonstrasie sal oor <xliff:g id="TIMEOUT">%1$s</xliff:g> sekondes weer begin …"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Kanselleer"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Stel nou terug"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Doen \'n fabriekterugstelling om hierdie toestel sonder beperkinge te gebruik"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Raak om meer te wete te kom."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Het <xliff:g id="LABEL">%1$s</xliff:g> gedeaktiveer"</string> <string name="conference_call" msgid="3751093130790472426">"Konferensie-oproep"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index af8c59263d02..2b927308adf0 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ማንኛቸውም ለውጦች ይጠፋሉ፣ እና ማሳያው በ<xliff:g id="TIMEOUT">%1$s</xliff:g> ሰከንዶች ውስጥ እንደገና ይጀምራል…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ይቅር"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"አሁን ዳግም አስጀምር"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"ይህን መሣሪያ ያለምንም ገደብ ለመጠቀም የፋብሪካ ዳግም ያስጀምሩ"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"የበለጠ ለመረዳት ይንኩ።"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> ተሰናክሏል"</string> <string name="conference_call" msgid="3751093130790472426">"የስብሰባ ጥሪ"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index b3c24e59f6d6..b2835cc19333 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -1825,8 +1825,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ستفقد أي تغييرات وسيبدأ العرض التوضيحي مرة أخرى خلال <xliff:g id="TIMEOUT">%1$s</xliff:g> من الثواني…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"إلغاء"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"إعادة التعيين الآن"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"يمكنك إعادة تعيين بيانات المصنع لاستخدام هذا الجهاز بدون قيود"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"المس للتعرف على مزيد من المعلومات."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"تم تعطيل <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"مكالمة جماعية"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-az-rAZ/strings.xml b/core/res/res/values-az-rAZ/strings.xml index 7e9c5082e2e8..5d09077090b7 100644 --- a/core/res/res/values-az-rAZ/strings.xml +++ b/core/res/res/values-az-rAZ/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Hər hansı dəyişikliyi itirəcəksiniz və demo <xliff:g id="TIMEOUT">%1$s</xliff:g> saniyəyə yenidən başlayacaq…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Ləğv edin"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"İndi sıfırlayın"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Bu cihazı məhdudiyyətsiz istifadə etmək üçün zavod sıfırlaması edin"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Daha çox məlumat üçün toxunun."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> deaktiv edildi"</string> <string name="conference_call" msgid="3751093130790472426">"Konfrans Zəngi"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 6caf561e366c..b65ed4d705dd 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -1717,8 +1717,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Izgubićete sve promene i demonstracija će ponovo početi za <xliff:g id="TIMEOUT">%1$s</xliff:g> sek…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Otkaži"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Resetuj"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Resetujte uređaj na fabrička podešavanja da biste ga koristili bez ograničenja"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Dodirnite da biste saznali više."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Vidžet <xliff:g id="LABEL">%1$s</xliff:g> je onemogućen"</string> <string name="conference_call" msgid="3751093130790472426">"Konferencijski poziv"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-be-rBY/strings.xml b/core/res/res/values-be-rBY/strings.xml index c076db254310..63359a1ae6d0 100644 --- a/core/res/res/values-be-rBY/strings.xml +++ b/core/res/res/values-be-rBY/strings.xml @@ -1753,8 +1753,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Усе змены будуць страчаны, і дэманстрацыя пачнецца зноў праз <xliff:g id="TIMEOUT">%1$s</xliff:g> с…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Скасаваць"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Выканаць скід"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Выканайце скід да заводскіх налад, каб выкарыстоўваць гэту прыладу без абмежаванняў"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Краніце, каб даведацца больш."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Адключаны <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Канферэнц-выклік"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 2d6bdb2ed9f8..a818b7ec03aa 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Ще загубите всички промени и демонстрацията ще започне отново след <xliff:g id="TIMEOUT">%1$s</xliff:g> секунди…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Отказ"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Нулиране сега"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Възстановете фабричните настройки на това устройство, за да го използвате без ограничения"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Докоснете, за да научите повече."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g>: Деактивирано"</string> <string name="conference_call" msgid="3751093130790472426">"Конферентно обаждане"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-bn-rBD/strings.xml b/core/res/res/values-bn-rBD/strings.xml index 21b737aa2b99..20343af4c0b2 100644 --- a/core/res/res/values-bn-rBD/strings.xml +++ b/core/res/res/values-bn-rBD/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"আপনার করা যে কোনো পরিবর্তন মুছে যাবে এবং <xliff:g id="TIMEOUT">%1$s</xliff:g> সেকেন্ডের মধ্যে ডেমো আবার শুরু হবে…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"বাতিল করুন"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"এখনই পুনরায় সেট করুন"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"কোনো বিধিনিষেধ ছাড়াই এই ডিভাইসটিকে ব্যবহার করতে ফ্যাক্টরি রিসেট করুন"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"আরো জানতে স্পর্শ করুন৷"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"অক্ষম করা <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"কনফারেন্স কল"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-bs-rBA/strings.xml b/core/res/res/values-bs-rBA/strings.xml index 80ee40753569..205bffb3f0b0 100644 --- a/core/res/res/values-bs-rBA/strings.xml +++ b/core/res/res/values-bs-rBA/strings.xml @@ -1719,8 +1719,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Nestat će sve izmjene, a demonstracija će početi ponovo za <xliff:g id="TIMEOUT">%1$s</xliff:g> sek…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Otkaži"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Vrati sada na početne postavke"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Vratite uređaj na fabričke postavke kako biste ga koristili bez ograničenja"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Dodirnite da saznate više."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Onemogućen <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Konferencijski poziv"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 35b2918c79e1..a1797cbd7feb 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Perdràs els canvis, i la demostració tornarà a començar d\'aquí a <xliff:g id="TIMEOUT">%1$s</xliff:g> segons…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancel·la"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Restableix ara"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Restableix les dades de fàbrica del dispositiu per utilitzar-lo sense restriccions"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toca per obtenir més informació."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> s\'ha desactivat"</string> <string name="conference_call" msgid="3751093130790472426">"Conferència"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 95a672845b0e..2c8f84381500 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1753,8 +1753,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Ztratíte všechny provedené změny a ukázka se za <xliff:g id="TIMEOUT">%1$s</xliff:g> s spustí znovu…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Zrušit"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Resetovat"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Chcete-li toto zařízení používat bez omezení, obnovte jej do továrního nastavení"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Klepnutím zobrazíte další informace."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> – zakázáno"</string> <string name="conference_call" msgid="3751093130790472426">"Konferenční hovor"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index a628e4244e0a..20b859cdc6c2 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Du mister alle ændringer, og demoen starter igen om <xliff:g id="TIMEOUT">%1$s</xliff:g> sekunder…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Annuller"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Nulstil nu"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Gendan fabriksdataene på enheden for at bruge den uden begrænsninger"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Tryk for at få flere oplysninger."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> – deaktiveret"</string> <string name="conference_call" msgid="3751093130790472426">"Telefonmøde"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 86f2070853ad..fcad7c2c4ae2 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Alle Änderungen gehen verloren und Demo wird in <xliff:g id="TIMEOUT">%1$s</xliff:g> Sekunden neu gestartet…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Abbrechen"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Jetzt zurücksetzen"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Gerät auf Werkseinstellungen zurücksetzen, um es ohne Einschränkungen zu nutzen"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Für weitere Informationen tippen."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> deaktiviert"</string> <string name="conference_call" msgid="3751093130790472426">"Telefonkonferenz"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 4ce3a1f980b3..2117233a37d9 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Τυχόν αλλαγές που πραγματοποιήσατε θα χαθούν και η επίδειξη θα ξεκινήσει ξανά σε <xliff:g id="TIMEOUT">%1$s</xliff:g> δευτερόλεπτα…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Ακύρωση"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Επαναφορά τώρα"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Επαναφέρετε τις εργοστασιακές ρυθμίσεις για να χρησιμοποιήσετε αυτήν τη συσκευή χωρίς περιορισμούς"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Αγγίξτε για να μάθετε περισσότερα."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Απενεργοποιημένο <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Κλήση συνδιάσκεψης"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 72c1271c07d3..47be8cf72a94 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"You\'ll lose any changes and the demo will start again in <xliff:g id="TIMEOUT">%1$s</xliff:g> seconds…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancel"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Reset now"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Factory reset to use this device without restrictions"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Touch to find out more."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Disabled <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Conference Call"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 72c1271c07d3..47be8cf72a94 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"You\'ll lose any changes and the demo will start again in <xliff:g id="TIMEOUT">%1$s</xliff:g> seconds…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancel"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Reset now"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Factory reset to use this device without restrictions"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Touch to find out more."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Disabled <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Conference Call"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 72c1271c07d3..47be8cf72a94 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"You\'ll lose any changes and the demo will start again in <xliff:g id="TIMEOUT">%1$s</xliff:g> seconds…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancel"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Reset now"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Factory reset to use this device without restrictions"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Touch to find out more."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Disabled <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Conference Call"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 738af8c9f17b..50c4a67e46a5 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Se perderán los cambios y la demostración volverá a iniciarse en <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Restablecer ahora"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Restablece la configuración de fábrica para usar este dispositivo sin restricciones"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toca para obtener más información."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Se inhabilitó <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Conferencia"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 3c2915d4d759..a91fc1cf58cb 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Se perderán todos los cambios y la demostración volverá a empezar en <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Restablecer ahora"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Restablece los datos de fábrica para usar este dispositivo sin restricciones"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toca para obtener más información."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> inhabilitado"</string> <string name="conference_call" msgid="3751093130790472426">"Conferencia"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml index 96b060f14d65..dc926d4c5c32 100644 --- a/core/res/res/values-et-rEE/strings.xml +++ b/core/res/res/values-et-rEE/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Kõik muudatused lähevad kaotsi ja demo käivitub uuesti <xliff:g id="TIMEOUT">%1$s</xliff:g> sekundi möödudes …"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Tühista"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Lähtesta kohe"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Seadme piiranguteta kasutamiseks lähtestage see tehaseandmetele"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Lisateabe saamiseks puudutage."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Keelatud <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Konverentskõne"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-eu-rES/strings.xml b/core/res/res/values-eu-rES/strings.xml index 6ccd864ccc39..190d61ba0b08 100644 --- a/core/res/res/values-eu-rES/strings.xml +++ b/core/res/res/values-eu-rES/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Aldaketak galduko dituzu eta <xliff:g id="TIMEOUT">%1$s</xliff:g> segundo barru hasiko da berriro demoa…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Utzi"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Berrezarri"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Berrezarri jatorrizko ezarpenak gailua murriztapenik gabe erabili ahal izateko"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Sakatu informazio gehiago lortzeko."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> desgaituta dago"</string> <string name="conference_call" msgid="3751093130790472426">"Konferentzia-deia"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index e8e61f4bdc06..d1d120f0f160 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"همه تغییرات را از دست خواهید داد و نسخه نمایشی دوباره تا <xliff:g id="TIMEOUT">%1$s</xliff:g> ثانیه دیگر شروع میشود…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"لغو"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"بازنشانی در این لحظه"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"برای استفاده بدون محدودیت از این دستگاه، بازنشانی کارخانهای انجام دهید"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"برای یادگیری بیشتر لمس کنید."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> غیرفعال شد"</string> <string name="conference_call" msgid="3751093130790472426">"تماس کنفرانسی"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index dbfd8ba21351..425b235dbe51 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Muutokset poistetaan ja esittely aloitetaan uudelleen <xliff:g id="TIMEOUT">%1$s</xliff:g> sekunnin kuluttua…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Peruuta"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Palauta nyt"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Palauta tehdasasetukset, jotta voit käyttää tätä laitetta rajoituksitta"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Lue lisätietoja koskettamalla."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> ei ole käytössä."</string> <string name="conference_call" msgid="3751093130790472426">"Puhelinneuvottelu"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 501627d9ae25..4ba4871e7364 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Vous perdrez vos modifications, et la démo recommencera dans <xliff:g id="TIMEOUT">%1$s</xliff:g> secondes…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Annuler"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Réinitialiser maintenant"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Rétablissez la configuration d\'usine de cet appareil pour l\'utiliser sans restrictions"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Touchez ici pour en savoir plus."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Désactivé : <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Conférence téléphonique"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index ccff4f36cb33..3dc8b28fc733 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Vous perdrez vos modifications, et la démo recommencera dans <xliff:g id="TIMEOUT">%1$s</xliff:g> secondes…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Annuler"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Réinitialiser maintenant"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Rétablir la configuration d\'usine pour utiliser cet appareil sans restrictions"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Appuyez ici pour en savoir plus."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Élément \"<xliff:g id="LABEL">%1$s</xliff:g>\" désactivé"</string> <string name="conference_call" msgid="3751093130790472426">"Conférence téléphonique"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-gl-rES/strings.xml b/core/res/res/values-gl-rES/strings.xml index 89a94718f6a7..077f4e0538da 100644 --- a/core/res/res/values-gl-rES/strings.xml +++ b/core/res/res/values-gl-rES/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Perderás os cambios que fixeses e a demostración volverá comezar en <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Restablecer agora"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Restablecemento dos valores de fábrica para usar este dispositivo sen restricións"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toca para acceder a máis información"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Desactivouse <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Conferencia telefónica"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-gu-rIN/strings.xml b/core/res/res/values-gu-rIN/strings.xml index cbb0d9e4a4e8..149e466346a1 100644 --- a/core/res/res/values-gu-rIN/strings.xml +++ b/core/res/res/values-gu-rIN/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"તમે કોઈપણ ફેરફારો ગુમાવશો અને ડેમો <xliff:g id="TIMEOUT">%1$s</xliff:g> સેકન્ડમાં ફરી શરૂ થશે…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"રદ કરો"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"હમણાં ફરીથી સેટ કરો"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"આ ઉપકરણનો પ્રતિબંધો વિના ઉપયોગ કરવા માટે ફેક્ટરી રીસેટ કરો"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"વધુ જાણવા માટે ટચ કરો."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> અક્ષમ કર્યું"</string> <string name="conference_call" msgid="3751093130790472426">"કોન્ફરન્સ કૉલ"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index e83305262c75..c006df95c3eb 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"आपके सभी बदलाव खो जाएंगे और डेमो <xliff:g id="TIMEOUT">%1$s</xliff:g> सेकंड में फिर से शुरू हो जाएगा…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"अभी नहीं"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"अभी रीसेट करें"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"इस डिवाइस को प्रतिबंधों के बिना उपयोग करने के लिए फ़ैक्टरी रीसेट करें"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"अधिक जानने के लिए स्पर्श करें."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"अक्षम <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"कॉन्फ़्रेंस कॉल"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index e811b94f66a9..8d90eb929662 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1717,8 +1717,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Sve će se promjene izbrisati, a demonstracija će se ponovo pokrenuti za <xliff:g id="TIMEOUT">%1$s</xliff:g> s…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Odustani"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Vrati na zadano sada"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Uređaj je vraćen na tvorničke postavke da biste ga mogli upotrebljavati bez ograničenja"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Dodirnite da biste saznali više."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> – onemogućeno"</string> <string name="conference_call" msgid="3751093130790472426">"Konferencijski poziv"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index b0ce160480e9..67e1c0fd79fb 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"A módosítások elvesznek, és a bemutató újra elindul <xliff:g id="TIMEOUT">%1$s</xliff:g> másodperc múlva…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Mégse"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Visszaállítás most"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Állítsa vissza a gyári beállításokat az eszköz korlátozások nélküli használata érdekében"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Érintse meg a további információkért."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"A(z) <xliff:g id="LABEL">%1$s</xliff:g> letiltva"</string> <string name="conference_call" msgid="3751093130790472426">"Konferenciahívás"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml index 2ba22af70052..6444b51586da 100644 --- a/core/res/res/values-hy-rAM/strings.xml +++ b/core/res/res/values-hy-rAM/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Կատարված փոփոխությունները չեն պահվի, իսկ ցուցադրական նյութը կրկին կգործարկվի <xliff:g id="TIMEOUT">%1$s</xliff:g> վայրկյանից…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Չեղարկել"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Վերակայել հիմա"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Սարքն առանց սահմանափակումների օգտագործելու համար կատարեք գործարանային վերակայում"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Հպեք՝ ավելին իմանալու համար:"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Անջատած <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Կոնֆերանս զանգ"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 09223cd5dd7f..873e3ae9150a 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Perubahan yang dibuat akan hilang dan demo akan dimulai lagi dalam <xliff:g id="TIMEOUT">%1$s</xliff:g> detik…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Batal"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Setel ulang sekarang"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Dikembalikan ke setelan pabrik agar perangkat ini dapat digunakan tanpa batasan"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Sentuh untuk mempelajari lebih lanjut."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> dinonaktifkan"</string> <string name="conference_call" msgid="3751093130790472426">"Konferensi Telepon"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-is-rIS/strings.xml b/core/res/res/values-is-rIS/strings.xml index 105a647d72fe..70a41be3ab34 100644 --- a/core/res/res/values-is-rIS/strings.xml +++ b/core/res/res/values-is-rIS/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Þú glatar öllum breytingum og kynningin byrjar aftur eftir <xliff:g id="TIMEOUT">%1$s</xliff:g> sekúndur…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Hætta við"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Endurstilla núna"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Núllstilltu til að nota þetta tæki án takmarkana"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Snertu til að fá frekari upplýsingar."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Slökkt <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Símafundur"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 564472e6adf1..b3521ff049a8 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Perderai tutte le modifiche e la demo verrà riavviata tra <xliff:g id="TIMEOUT">%1$s</xliff:g> secondi…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Annulla"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Ripristina ora"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Esegui il ripristino dei dati di fabbrica per utilizzare il dispositivo senza limitazioni"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Tocca per ulteriori informazioni."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Widget <xliff:g id="LABEL">%1$s</xliff:g> disattivato"</string> <string name="conference_call" msgid="3751093130790472426">"Audioconferenza"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index d4b9bf11b226..dd41f2344a17 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1753,8 +1753,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"תאבד את כל השינויים וההדגמה תתחיל שוב בעוד <xliff:g id="TIMEOUT">%1$s</xliff:g> שניות…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"בטל"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"אפס עכשיו"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"איפוס להגדרות היצרן כדי לאפשר שימוש במכשיר ללא מגבלות"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"גע לקבלת מידע נוסף."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> הושבת"</string> <string name="conference_call" msgid="3751093130790472426">"שיחת ועידה"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index e2432f1080fe..28db181af126 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"変更が失われ、<xliff:g id="TIMEOUT">%1$s</xliff:g> 秒後にデモがもう一度開始されます…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"キャンセル"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"今すぐリセット"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"制限なしでこの端末を使用するには初期状態にリセットしてください"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"タップして詳細をご確認ください。"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"停止済みの「<xliff:g id="LABEL">%1$s</xliff:g>」"</string> <string name="conference_call" msgid="3751093130790472426">"グループ通話"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml index 5e069b6e5535..c58803d3c20d 100644 --- a/core/res/res/values-ka-rGE/strings.xml +++ b/core/res/res/values-ka-rGE/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"შეტანილი ცვლილებები დაიკარგება, ხოლო დემონსტრაცია ხელახლა <xliff:g id="TIMEOUT">%1$s</xliff:g> წამში დაიწყება…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"გაუქმება"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ახლავე გადაყენება"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"ამ მოწყობილობის შეზღუდვების გარეშე გამოსაყენებლად, დააბრუნეთ ქარხნული პარამეტრები"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"შეეხეთ მეტის გასაგებად."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"გათიშული <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"საკონფერენციო ზარი"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-kk-rKZ/strings.xml b/core/res/res/values-kk-rKZ/strings.xml index 729f726a8486..a62cbf0aa3ae 100644 --- a/core/res/res/values-kk-rKZ/strings.xml +++ b/core/res/res/values-kk-rKZ/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Барлық өзгеріс жоғалады және демо нұсқасы <xliff:g id="TIMEOUT">%1$s</xliff:g> секундтан кейін қайта қосылады…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Бас тарту"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Қазір бастапқы күйге қайтару"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Осы құрылғыны шектеусіз пайдалану үшін зауыттық параметрлерді қалпына келтіріңіз"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Қосымша мәліметтер алу үшін түртіңіз."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> өшірулі"</string> <string name="conference_call" msgid="3751093130790472426">"Конференциялық қоңырау"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml index c3e939e6f5ce..08cac8c63bd8 100644 --- a/core/res/res/values-km-rKH/strings.xml +++ b/core/res/res/values-km-rKH/strings.xml @@ -1683,8 +1683,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"អ្នកនឹងបាត់បង់ការផ្លាស់ប្តូរណាមួយ ហើយការបង្ហាញសាកល្បងនឹងចាប់ផ្តើមម្តងទៀតក្នុងរយៈពេល <xliff:g id="TIMEOUT">%1$s</xliff:g> វិនាទីទៀត…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"បោះបង់"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"កំណត់ឡើងវិញឥឡូវនេះ"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"កំណត់ដូចចេញពីរោងចក្រឡើងវិញដើម្បីប្រើឧបករណ៍នេះដោយគ្មានការរឹតបន្តឹង"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"ប៉ះ ដើម្បីស្វែងយល់បន្ថែម។"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> ដែលបានបិទដំណើរការ"</string> <string name="conference_call" msgid="3751093130790472426">"ការហៅជាក្រុម"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-kn-rIN/strings.xml b/core/res/res/values-kn-rIN/strings.xml index 7f80a299cf7a..ab9cd90e258e 100644 --- a/core/res/res/values-kn-rIN/strings.xml +++ b/core/res/res/values-kn-rIN/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ನೀವು ಯಾವುದೇ ಬದಲಾವಣೆಗಳನ್ನು ಕಳೆದುಕೊಳ್ಳುತ್ತೀರಿ ಮತ್ತು <xliff:g id="TIMEOUT">%1$s</xliff:g> ಸೆಕೆಂಡುಗಳಲ್ಲಿ ಡೆಮೋ ಮತ್ತೆ ಪ್ರಾರಂಭವಾಗುತ್ತದೆ..."</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ರದ್ದುಮಾಡಿ"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ಈಗಲೇ ಮರುಹೊಂದಿಸು"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"ನಿರ್ಬಂಧಗಳು ಇಲ್ಲದೆಯೇ ಈ ಸಾಧನವನ್ನು ಬಳಸಲು ಫ್ಯಾಕ್ಟರಿ ಮರುಹೊಂದಿಸಿ"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"ಇನ್ನಷ್ಟು ತಿಳಿಯಲು ಸ್ಪರ್ಶಿಸಿ."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string> <string name="conference_call" msgid="3751093130790472426">"ಕಾನ್ಫರೆನ್ಸ್ ಕರೆ"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 444c83dd7cbb..10bb801d3d5c 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"변경사항이 사라지며 데모가 <xliff:g id="TIMEOUT">%1$s</xliff:g>초 후에 시작됩니다."</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"취소"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"지금 초기화"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"제한 없이 기기를 사용하기 위한 초기화"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"자세한 내용을 보려면 터치하세요."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> 사용 중지됨"</string> <string name="conference_call" msgid="3751093130790472426">"다자간 통화"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ky-rKG/strings.xml b/core/res/res/values-ky-rKG/strings.xml index 2b592bdf4588..0d8a38ea6498 100644 --- a/core/res/res/values-ky-rKG/strings.xml +++ b/core/res/res/values-ky-rKG/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Бардык өзгөртүүлөр жоголуп, демо режим <xliff:g id="TIMEOUT">%1$s</xliff:g> секунддан кийин кайра башталат…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Жокко чыгаруу"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Баштапкы абалга келтирүү"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Бул түзмөктү чектөөсүз колдонуу үчүн аны баштапкы абалга келтириңиз"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Көбүрөөк билүү үчүн тийип коюңуз."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> өчүрүлдү"</string> <string name="conference_call" msgid="3751093130790472426">"Конференц чалуу"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml index 3a96743d569d..ab1a4684e6e4 100644 --- a/core/res/res/values-lo-rLA/strings.xml +++ b/core/res/res/values-lo-rLA/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ທ່ານຈະສູນເສຍການປ່ຽນແປງ ແລະ ເດໂມຈະເລີ່ມອີກຄັ້ງໃນອີກ <xliff:g id="TIMEOUT">%1$s</xliff:g> ວິນາທີ…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ຍົກເລີກ"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ຣີເຊັດດຽວນີ້"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"ຣີເຊັດໃຫ້ເປັນຄ່າໂຮງງານເພື່ອໃຊ້ອຸປະກອນນີ້ໂດຍບໍ່ມີຂໍ້ຈຳກັດ"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"ແຕະເພື່ອສຶກສາເພີ່ມເຕີມ."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"ປິດການນຳໃຊ້ <xliff:g id="LABEL">%1$s</xliff:g> ແລ້ວ"</string> <string name="conference_call" msgid="3751093130790472426">"ການປະຊຸມສາຍ"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index d60a010a6f85..455726430bf2 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1753,8 +1753,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Prarasite visus pakeitimus, o demonstracinė versija bus paleista iš naujo po <xliff:g id="TIMEOUT">%1$s</xliff:g> sek…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Atšaukti"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Nustatyti iš naujo dabar"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Atkurkite gamyklinius nustatymus, kad galėtumėte naudoti šį įrenginį be apribojimų"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Palieskite, kad sužinotumėte daugiau."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Išj. valdiklis „<xliff:g id="LABEL">%1$s</xliff:g>“"</string> <string name="conference_call" msgid="3751093130790472426">"Konferencinis skambutis"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 7afb2ceda3da..0965cf6acf6f 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1717,8 +1717,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Pēc <xliff:g id="TIMEOUT">%1$s</xliff:g> sekundēm zaudēsiet visas izmaiņas un tiks atkārtoti palaista demonstrācija..."</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Atcelt"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Atiestatīt tūlīt"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Rūpnīcas datu atiestatīšana ierīces neierobežotai izmantošanai"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Pieskarieties, lai uzzinātu vairāk."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> atspējots"</string> <string name="conference_call" msgid="3751093130790472426">"Konferences zvans"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-mk-rMK/strings.xml b/core/res/res/values-mk-rMK/strings.xml index 139e02a0db5b..8d96f5c706ec 100644 --- a/core/res/res/values-mk-rMK/strings.xml +++ b/core/res/res/values-mk-rMK/strings.xml @@ -1683,8 +1683,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Ќе ги изгубите измените и демонстрацијата ќе започне повторно по <xliff:g id="TIMEOUT">%1$s</xliff:g> секунди…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Откажи"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Ресетирај сега"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Ресетирајте до фабричките поставки за уредов да го користите без ограничувања"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Допрете за да дознаете повеќе."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Оневозможен <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Конференциски повик"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ml-rIN/strings.xml b/core/res/res/values-ml-rIN/strings.xml index fc3eb40cdc5c..446d4f200b6e 100644 --- a/core/res/res/values-ml-rIN/strings.xml +++ b/core/res/res/values-ml-rIN/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"മാറ്റങ്ങളെല്ലാം നിങ്ങൾക്ക് നഷ്ടപ്പെടും, <xliff:g id="TIMEOUT">%1$s</xliff:g> സെക്കൻഡിൽ ഡെമോ വീണ്ടും തുടങ്ങും…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"റദ്ദാക്കുക"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ഇപ്പോൾ പുനക്രമീകരിക്കുക"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"നിയന്ത്രണങ്ങൾ ഇല്ലാതെ ഈ ഉപകരണം ഉപയോഗിക്കാൻ ഫാക്ടറി റീസെറ്റ് നടത്തുക"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"കൂടുതലറിയുന്നതിന് സ്പർശിക്കുക."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> പ്രവർത്തനരഹിതമാക്കി"</string> <string name="conference_call" msgid="3751093130790472426">"കോൺഫറൻസ് കോൾ"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml index 2faf163488c2..61997276550a 100644 --- a/core/res/res/values-mn-rMN/strings.xml +++ b/core/res/res/values-mn-rMN/strings.xml @@ -1679,8 +1679,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Таны хийсэн өөрчлөлтийг хадгалахгүй бөгөөд жишээ <xliff:g id="TIMEOUT">%1$s</xliff:g> секундын дотор дахин эхлэх болно..."</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Цуцлах"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Одоо шинэчлэх"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Энэ төхөөрөмжийг хязгаарлалтгүй ашиглахын тулд үйлдвэрийн тохиргоонд дахин тохируулна уу"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Дэлгэрэнгүй үзэх бол дарна уу."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g>-г цуцалсан"</string> <string name="conference_call" msgid="3751093130790472426">"Хурлын дуудлага"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-mr-rIN/strings.xml b/core/res/res/values-mr-rIN/strings.xml index 99465cc641ca..8f2fc0f7d6b8 100644 --- a/core/res/res/values-mr-rIN/strings.xml +++ b/core/res/res/values-mr-rIN/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"आपण कोणतेही बदल गमवाल आणि डेमो पुन्हा <xliff:g id="TIMEOUT">%1$s</xliff:g> सेकंदांमध्ये प्रारंभ होईल..."</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"रद्द करा"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"आता रीसेट करा"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"हे डिव्हाइस निर्बंधांशिवाय वापरण्यासाठी फॅक्टरी रीसेट करा"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"अधिक जाणून घेण्यासाठी स्पर्श करा."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> अक्षम केले"</string> <string name="conference_call" msgid="3751093130790472426">"परिषद कॉल"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml index 1e7e2810f623..9b6a20a8588e 100644 --- a/core/res/res/values-ms-rMY/strings.xml +++ b/core/res/res/values-ms-rMY/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Anda akan kehilangan sebarang perubahan yang dibuat dan tunjuk cara akan dimulakan sekali lagi dalam masa <xliff:g id="TIMEOUT">%1$s</xliff:g> saat…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Batal"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Tetapkan semula sekarang"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Lakukan tetapan semula kilang untuk menggunakan peranti ini tanpa sekatan"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Ketik untuk mengetahui lebih lanjut."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> dilumpuhkan"</string> <string name="conference_call" msgid="3751093130790472426">"Panggilan Sidang"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-my-rMM/strings.xml b/core/res/res/values-my-rMM/strings.xml index 99d64230f905..3276dcd87aac 100644 --- a/core/res/res/values-my-rMM/strings.xml +++ b/core/res/res/values-my-rMM/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ပြောင်းလဲမှုများကို ဆုံးရှုံးသွားမည်ဖြစ်ပြီး သရုပ်ပြချက်သည် <xliff:g id="TIMEOUT">%1$s</xliff:g> စက္ကန့်အတွင်း ပြန်လည်စတင်ပါမည်…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"မလုပ်တော့"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ယခုပြန်လည်သတ်မှတ်ပါ"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"ဤစက်ပစ္စည်းကို ကန့်သတ်ချက်များမပါဘဲ အသုံးပြုရန် စက်ရုံထုတ်ဆက်တင်အတိုင်း ပြန်လည်သတ်မှတ်ပါ"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"ပိုမိုလေ့လာရန် တို့ပါ။"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"ပိတ်ထားသည့် <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"လူအမြောက်အမြားတပြိုင်နက် ခေါ်ဆိုမှု"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 324f0d7a85ea..dba56dc9168c 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Du mister eventuelle endringer, og demoen starter på nytt om <xliff:g id="TIMEOUT">%1$s</xliff:g> sekunder."</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Avbryt"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Tilbakestill nå"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Tilbakestill til fabrikkstandard for å bruke denne enheten uten begrensninger"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Trykk for å finne ut mer."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> er slått av"</string> <string name="conference_call" msgid="3751093130790472426">"Konferansesamtale"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ne-rNP/strings.xml b/core/res/res/values-ne-rNP/strings.xml index caf36885db42..27abf1a03712 100644 --- a/core/res/res/values-ne-rNP/strings.xml +++ b/core/res/res/values-ne-rNP/strings.xml @@ -1687,8 +1687,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"तपाईँ सबै परिवर्तनहरू गुमाउनु हुनेछ र <xliff:g id="TIMEOUT">%1$s</xliff:g> सेकेन्डमा डेमो फेरि सुरु हुनेछ…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"रद्द गर्नुहोस्"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"अहिले रिसेट गर्नुहोस्"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"यस यन्त्रलाई सीमितताहरू बिना प्रयोग गर्नका लागि फ्याक्ट्री रिसेट गर्नुहोस्"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"थप जान्नका लागि छुनुहोस्।"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> लाई असक्षम गरियो"</string> <string name="conference_call" msgid="3751093130790472426">"सम्मेलन कल"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 084830426549..b02c9e1d9ce6 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1530,8 +1530,8 @@ <string name="mediasize_japanese_kahu" msgid="6872696027560065173">"Kahu"</string> <string name="mediasize_japanese_kaku2" msgid="2359077233775455405">"Kaku2"</string> <string name="mediasize_japanese_you4" msgid="2091777168747058008">"You4"</string> - <string name="mediasize_unknown_portrait" msgid="3088043641616409762">"Onbekend portret"</string> - <string name="mediasize_unknown_landscape" msgid="4876995327029361552">"Onbekend landschap"</string> + <string name="mediasize_unknown_portrait" msgid="3088043641616409762">"Onbekend staand"</string> + <string name="mediasize_unknown_landscape" msgid="4876995327029361552">"Onbekend liggend"</string> <string name="write_fail_reason_cancelled" msgid="7091258378121627624">"Geannuleerd"</string> <string name="write_fail_reason_cannot_write" msgid="8132505417935337724">"Fout bij schrijven van content"</string> <string name="reason_unknown" msgid="6048913880184628119">"onbekend"</string> @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Je wijzigingen gaan verloren. De demo wordt opnieuw gestart over <xliff:g id="TIMEOUT">%1$s</xliff:g> seconden…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Annuleren"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Nu resetten"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Zet dit apparaat terug op de fabrieksinstellingen om het zonder beperkingen te gebruiken"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Tik voor meer informatie."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> uitgeschakeld"</string> <string name="conference_call" msgid="3751093130790472426">"Telefonische vergadering"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-pa-rIN/strings.xml b/core/res/res/values-pa-rIN/strings.xml index 3e886bccb907..cb63a545175a 100644 --- a/core/res/res/values-pa-rIN/strings.xml +++ b/core/res/res/values-pa-rIN/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ਤੁਸੀਂ ਕਿਸੇ ਵੀ ਤਬਦੀਲੀਆਂ ਨੂੰ ਗੁਆ ਬੈਠੋਂਗੇ ਅਤੇ ਡੈਮੋ <xliff:g id="TIMEOUT">%1$s</xliff:g> ਸਕਿੰਟਾਂ ਵਿੱਚ ਦੁਬਾਰਾ ਚਾਲੂ ਕੀਤਾ ਜਾਵੇਗਾ…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ਰੱਦ ਕਰੋ"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ਹੁਣੇ ਮੁੜ-ਸੈੱਟ ਕਰੋ"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"ਇਸ ਡੀਵਾਈਸ ਨੂੰ ਬਿਨਾਂ ਪਾਬੰਦੀਆਂ ਦੇ ਵਰਤਣ ਲਈ ਫੈਕਟਰੀ ਰੀਸੈੱਟ ਕਰੋ"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"ਹੋਰ ਜਾਣਨ ਲਈ ਸਪਰਸ਼ ਕਰੋ।"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"ਅਯੋਗ ਬਣਾਇਆ ਗਿਆ <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"ਕਾਨਫਰੰਸ ਕਾਲ"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 92a2e2a395ca..3cfb2423e9aa 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1753,8 +1753,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Stracisz wszystkie wprowadzone zmiany, a tryb demo uruchomi się ponownie za <xliff:g id="TIMEOUT">%1$s</xliff:g> s…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Anuluj"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Resetuj teraz"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Aby używać tego urządzenia bez ograniczeń, przywróć ustawienia fabryczne"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Kliknij, by dowiedzieć się więcej."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Wyłączono: <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Połączenie konferencyjne"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index bf9e15d6b1c6..b5b30a861e71 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Você perderá todas as alterações. A demonstração será iniciada novamente em <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Reiniciar agora"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Redefinir para a configuração original para usar este dispositivo sem restrições"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toque para saber mais."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Widget <xliff:g id="LABEL">%1$s</xliff:g> desativado"</string> <string name="conference_call" msgid="3751093130790472426">"Teleconferência"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index e09daa513c41..952df716556a 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Perderá todas as alterações e a demonstração começará novamente dentro de <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Repor agora"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Repor os dados de fábrica para utilizar o dispositivo sem restrições"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toque para saber mais."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> desativado"</string> <string name="conference_call" msgid="3751093130790472426">"Conferência"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index bf9e15d6b1c6..b5b30a861e71 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Você perderá todas as alterações. A demonstração será iniciada novamente em <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Reiniciar agora"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Redefinir para a configuração original para usar este dispositivo sem restrições"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toque para saber mais."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Widget <xliff:g id="LABEL">%1$s</xliff:g> desativado"</string> <string name="conference_call" msgid="3751093130790472426">"Teleconferência"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 7f96d2e6afd8..6f938d9dbb88 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1717,8 +1717,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Veți pierde toate modificările, iar demonstrația va începe din nou peste <xliff:g id="TIMEOUT">%1$s</xliff:g> secunde…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Anulați"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Resetați acum"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Reveniți la setările din fabrică pentru a folosi acest dispozitiv fără restricții"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Atingeți pentru a afla mai multe."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> a fost dezactivat"</string> <string name="conference_call" msgid="3751093130790472426">"Conferință telefonică"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 9e25bb7293cd..83ae58b708fd 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1753,8 +1753,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Все изменения будут утеряны. Деморежим будет перезапущен через <xliff:g id="TIMEOUT">%1$s</xliff:g> сек."</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Отмена"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Сбросить"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Сброс до заводских настроек для работы без ограничений"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Нажмите, чтобы узнать больше."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Виджет <xliff:g id="LABEL">%1$s</xliff:g> отключен"</string> <string name="conference_call" msgid="3751093130790472426">"Конференц-связь"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-si-rLK/strings.xml b/core/res/res/values-si-rLK/strings.xml index b106464cfba7..2a4ee6626e52 100644 --- a/core/res/res/values-si-rLK/strings.xml +++ b/core/res/res/values-si-rLK/strings.xml @@ -1683,8 +1683,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ඔබට යම් වෙනස් කිරීම් අහිමි වනු ඇති අතර ආදර්ශනය තත්පර <xliff:g id="TIMEOUT">%1$s</xliff:g>කින් නැවත ආරම්භ වනු ඇත…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"අවලංගු කරන්න"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"දැන් යළි සකසන්න"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"සීමා කිරීම්වලින් තොරව මෙම උපාංගය භාවිත කිරීමට කර්මාන්ත ශාලා යළි සැකසීම"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"තව දැන ගැනීමට ස්පර්ශ කරන්න."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"අබල කළ <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"සම්මන්ත්රණ ඇමතුම"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 50af3ccbe611..ef7678c9703f 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1753,8 +1753,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Prídete o všetky zmeny a ukážka sa znova spustí o <xliff:g id="TIMEOUT">%1$s</xliff:g> s…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Zrušiť"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Resetovať"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Ak chcete toto zariadenie používať bez obmedzení, obnovte na ňom továrenské nastavenia"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Klepnutím získate ďalšie informácie."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Deaktivovaná miniaplikácia <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Konferenčný hovor"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 5bc756b7554a..f3a2d234534c 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1753,8 +1753,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Morebitne spremembe bodo izgubljene in predstavitev se bo začela znova čez <xliff:g id="TIMEOUT">%1$s</xliff:g> s …"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Prekliči"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Ponastavi"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Ponastavitev naprave na tovarniške nastavitve za uporabo brez omejitev"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Dotaknite se, če želite izvedeti več."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> – onemogočeno"</string> <string name="conference_call" msgid="3751093130790472426">"Konferenčni klic"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-sq-rAL/strings.xml b/core/res/res/values-sq-rAL/strings.xml index 1095088806a9..69f14d5dbd68 100644 --- a/core/res/res/values-sq-rAL/strings.xml +++ b/core/res/res/values-sq-rAL/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Do të humbasësh çdo ndryshim dhe demonstrimi do të niset përsëri për <xliff:g id="TIMEOUT">%1$s</xliff:g> sekonda…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Anulo"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Rivendos tani"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Rivendos cilësimet e fabrikës për ta përdorur këtë pajisje pa kufizime"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Prek për të mësuar më shumë."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> u çaktivizua"</string> <string name="conference_call" msgid="3751093130790472426">"Telefonatë konferencë"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 27d7cad02fb5..881421c6b1b4 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1717,8 +1717,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Изгубићете све промене и демонстрација ће поново почети за <xliff:g id="TIMEOUT">%1$s</xliff:g> сек…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Откажи"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Ресетуј"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Ресетујте уређај на фабричка подешавања да бисте га користили без ограничења"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Додирните да бисте сазнали више."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Виџет <xliff:g id="LABEL">%1$s</xliff:g> је онемогућен"</string> <string name="conference_call" msgid="3751093130790472426">"Конференцијски позив"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index b048762ca934..fdffd4622f34 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Ändringar som du har gjort sparas inte och demon börjar om om <xliff:g id="TIMEOUT">%1$s</xliff:g> sekunder …"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Avbryt"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Återställ nu"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Återställ enheten till standardinställningarna om du vill använda den utan begränsningar"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Tryck här om du vill läsa mer."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> har inaktiverats"</string> <string name="conference_call" msgid="3751093130790472426">"Konferenssamtal"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 6cfd253b6f61..500b33b4e94e 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1679,8 +1679,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Mabadiliko yoyote hayatahifadhiwa. Onyesho litaanza tena baada ya sekunde <xliff:g id="TIMEOUT">%1$s</xliff:g>..."</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Ghairi"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Weka upya sasa"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Rejesha mipangilio iliyotoka nayo kiwandani ili utumie kifaa hiki bila vikwazo"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Gusa ili kupata maelezo zaidi."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> imezimwa"</string> <string name="conference_call" msgid="3751093130790472426">"Simu ya Kongamano"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ta-rIN/strings.xml b/core/res/res/values-ta-rIN/strings.xml index b39191226846..131d03fc512f 100644 --- a/core/res/res/values-ta-rIN/strings.xml +++ b/core/res/res/values-ta-rIN/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"மாற்றங்கள் சேமிக்கப்படாது, <xliff:g id="TIMEOUT">%1$s</xliff:g> வினாடிகளில் டெமோ மீண்டும் தொடங்கும்…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ரத்துசெய்"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"இப்போதே மீட்டமை"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"இந்தச் சாதனத்தைக் கட்டுப்பாடுகளின்றிப் பயன்படுத்த, ஆரம்ப நிலைக்கு மீட்டமைக்கவும்"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"மேலும் அறிய தொடவும்."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"முடக்கப்பட்டது: <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"குழு அழைப்பு"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-te-rIN/strings.xml b/core/res/res/values-te-rIN/strings.xml index 5ec586ffdd1e..2d6b2ad3cb87 100644 --- a/core/res/res/values-te-rIN/strings.xml +++ b/core/res/res/values-te-rIN/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"మీరు చేసిన ఏవైనా మార్పులను కోల్పోతారు మరియు డెమో <xliff:g id="TIMEOUT">%1$s</xliff:g> సెకన్లలో మళ్లీ ప్రారంభమవుతుంది…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"రద్దు చేయి"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ఇప్పుడే రీసెట్ చేయి"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"ఈ పరికరాన్ని ఎటువంటి పరిమితులు లేకుండా ఉపయోగించడానికి ఫ్యాక్టరీ రీసెట్ చేయండి"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"మరింత తెలుసుకోవడానికి తాకండి."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> నిలిపివేయబడింది"</string> <string name="conference_call" msgid="3751093130790472426">"కాన్ఫరెన్స్ కాల్"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 15cbd94291d5..c6a854474616 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"การเปลี่ยนแปลงของคุณจะหายไปและการสาธิตจะเริ่มต้นอีกครั้งใน <xliff:g id="TIMEOUT">%1$s</xliff:g> วินาที…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ยกเลิก"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"รีเซ็ตทันที"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"รีเซ็ตเป็นค่าเริ่มต้นเพื่อใช้อุปกรณ์นี้โดยไร้ข้อจำกัด"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"แตะเพื่อเรียนรู้เพิ่มเติม"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"ปิดใช้ <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"การประชุมสาย"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 31666ee4f062..abcb19a5a36e 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Mawawala mo ang anumang mga pagbabago at magsisimulang muli ang demo pagkalipas ng <xliff:g id="TIMEOUT">%1$s</xliff:g> (na) segundo…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Kanselahin"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"I-reset ngayon"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"I-factory reset upang magamit ang device na ito nang walang mga paghihigpit"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Pindutin upang matuto nang higit pa."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Na-disable ang <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Conference Call"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 012606833bb7..455078421acc 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Değişiklikleri kaybedeceksiniz ve demo <xliff:g id="TIMEOUT">%1$s</xliff:g> saniye içinde tekrar başlayacak…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"İptal"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Şimdi sıfırla"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Bu cihazı kısıtlama olmadan kullanmak için fabrika ayarlarına sıfırlayın"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Daha fazla bilgi edinmek için dokunun."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> devre dışı"</string> <string name="conference_call" msgid="3751093130790472426">"Konferans Çağrısı"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 104badd52633..b5561b9352a5 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1753,8 +1753,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Ви втратите всі зміни, а демонстрація знову почнеться через <xliff:g id="TIMEOUT">%1$s</xliff:g> с…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Скасувати"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Скинути"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Відновіть заводські параметри, щоб використовувати пристрій без обмежень"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Торкніться, щоб дізнатися більше."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> вимкнено"</string> <string name="conference_call" msgid="3751093130790472426">"Конференц-виклик"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-ur-rPK/strings.xml b/core/res/res/values-ur-rPK/strings.xml index f8224f29a948..ecb6ee0b3233 100644 --- a/core/res/res/values-ur-rPK/strings.xml +++ b/core/res/res/values-ur-rPK/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"آپ کی تمام تبدیلیاں ضائع ہو جائیں گی اور ڈیمو <xliff:g id="TIMEOUT">%1$s</xliff:g> سیکنڈز میں دوبارہ شروع ہوگا…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"منسوخ کریں"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ابھی ری سیٹ کریں"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"بغیر کسی حدود کے استعمال کرنے کیلئے اس آلے کو فیکٹری ری سیٹ کریں"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"مزید جاننے کیلئے ٹچ کریں۔"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"غیر فعال کردہ <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"کانفرنس کال"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-uz-rUZ/strings.xml b/core/res/res/values-uz-rUZ/strings.xml index 78b5400d6dc2..dd8b8baa48d6 100644 --- a/core/res/res/values-uz-rUZ/strings.xml +++ b/core/res/res/values-uz-rUZ/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Har qanday o‘zgarishlar o‘chib ketadi va demo <xliff:g id="TIMEOUT">%1$s</xliff:g> soniyadan so‘ng yana qayta ishga tushadi…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Bekor qilish"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Asl holatga qaytarish"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Bu qurilmadan cheklovlarsiz foydalanish uchun zavod sozlamalarini tiklang"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Ko‘proq o‘rganish uchun bosing."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> vidjeti o‘chirilgan"</string> <string name="conference_call" msgid="3751093130790472426">"Konferens-aloqa"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index ca5e3bcc9ff9..37f1a9b08a7e 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Bạn sẽ bị mất mọi thay đổi và bản trình diễn sẽ bắt đầu lại sau <xliff:g id="TIMEOUT">%1$s</xliff:g> giây…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Hủy"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Đặt lại ngay bây giờ"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Khôi phục cài đặt gốc để sử dụng thiết bị này mà không bị hạn chế"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Chạm để tìm hiểu thêm."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Đã tắt <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"Cuộc gọi nhiều bên"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 2126d127967b..9def7dbf6563 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"您将丢失所有更改,而且演示模式将在 <xliff:g id="TIMEOUT">%1$s</xliff:g> 秒后重新启动…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"取消"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"立即重置"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"恢复出厂设置即可正常使用此设备,不受任何限制"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"触摸即可了解详情。"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"已停用的<xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"电话会议"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index bdd9fe3ee551..6f101ab2348c 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"系統將不會儲存變更,示範將於 <xliff:g id="TIMEOUT">%1$s</xliff:g> 秒後重新開始…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"取消"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"立即重設"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"將此裝置回復至原廠設定後,使用將不受限制"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"輕觸以瞭解詳情。"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"「<xliff:g id="LABEL">%1$s</xliff:g>」已停用"</string> <string name="conference_call" msgid="3751093130790472426">"會議通話"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 3c2146bd2789..93acd0ec7772 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"系統不會儲存您所做的變更,示範模式將於 <xliff:g id="TIMEOUT">%1$s</xliff:g> 秒後重新開始…"</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"取消"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"立即重設"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"恢復原廠設定即可正常使用這個裝置"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"輕觸即可瞭解詳情。"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"已停用的<xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"電話會議"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 011e4f744d3b..14ea7386a09b 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1681,8 +1681,8 @@ <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Uzolahlekelwa inoma iluphi ushintsho futhi idemo izoqala futhi kumasekhondi angu-<xliff:g id="TIMEOUT">%1$s</xliff:g>..."</string> <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Khansela"</string> <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Setha kabusha manje"</string> - <string name="audit_safemode_notification" msgid="6416076898350685856">"Setha kabusha ukuze usebenzise idivayisi ngaphandle kwemikhawulo"</string> - <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Thinta ukuze ufunde kabanzi."</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"I-<xliff:g id="LABEL">%1$s</xliff:g> ekhutshaziwe"</string> <string name="conference_call" msgid="3751093130790472426">"Ikholi yengqungquthela"</string> + <!-- no translation found for tooltip_popup_title (8101791425834697618) --> + <skip /> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index baf3cd817d5b..4070d4802732 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4440,11 +4440,6 @@ <!-- Text of button to allow user to abort countdown and immediately start another session in retail demo mode [CHAR LIMIT=40] --> <string name="demo_user_inactivity_timeout_right_button">Reset now</string> - <!-- Title of notification shown when device has been forced to safe mode after a security compromise. --> - <string name="audit_safemode_notification">Factory reset to use this device without restrictions</string> - <!-- Description of notification shown when device has been forced to safe mode after a security compromise. --> - <string name="audit_safemode_notification_details">Touch to learn more.</string> - <!-- Accessibilty string added to a widget that has been suspended [CHAR LIMIT=20] --> <string name="suspended_widget_accessibility">Disabled <xliff:g id="label" example="Calendar">%1$s</xliff:g></string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5b608b84c51d..a28a6fdc219a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1920,8 +1920,6 @@ <java-symbol type="string" name="config_customVpnConfirmDialogComponent" /> <java-symbol type="string" name="config_defaultNetworkScorerPackageName" /> <java-symbol type="string" name="config_persistentDataPackageName" /> - <java-symbol type="string" name="audit_safemode_notification" /> - <java-symbol type="string" name="audit_safemode_notification_details" /> <java-symbol type="string" name="reset_retail_demo_mode_title" /> <java-symbol type="string" name="reset_retail_demo_mode_text" /> <java-symbol type="string" name="demo_user_inactivity_timeout_title" /> diff --git a/core/tests/coretests/src/android/net/RoughtimeClientTest.java b/core/tests/coretests/src/android/net/RoughtimeClientTest.java deleted file mode 100644 index cd26804c453f..000000000000 --- a/core/tests/coretests/src/android/net/RoughtimeClientTest.java +++ /dev/null @@ -1,170 +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 android.net; - -import android.net.RoughtimeClient; -import android.util.Log; -import libcore.util.HexEncoding; - -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.SocketException; -import java.security.MessageDigest; -import java.util.Arrays; -import junit.framework.TestCase; - - -public class RoughtimeClientTest extends TestCase { - private static final String TAG = "RoughtimeClientTest"; - - private static final long TEST_TIME = 8675309; - private static final int TEST_RADIUS = 42; - - private final RoughtimeTestServer mServer = new RoughtimeTestServer(); - private final RoughtimeClient mClient = new RoughtimeClient(); - - public void testBasicWorkingRoughtimeClientQuery() throws Exception { - mServer.shouldRespond(true); - assertTrue(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500)); - assertEquals(1, mServer.numRequestsReceived()); - assertEquals(1, mServer.numRepliesSent()); - } - - public void testDnsResolutionFailure() throws Exception { - mServer.shouldRespond(true); - assertFalse(mClient.requestTime("roughtime.server.doesnotexist.example", 5000)); - } - - public void testTimeoutFailure() throws Exception { - mServer.shouldRespond(false); - assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500)); - assertEquals(1, mServer.numRequestsReceived()); - assertEquals(0, mServer.numRepliesSent()); - } - - private static MessageDigest md = null; - - private static byte[] signedResponse(byte[] nonce) { - RoughtimeClient.Message signed = new RoughtimeClient.Message(); - - try { - if (md == null) { - md = MessageDigest.getInstance("SHA-512"); - } - } catch(Exception e) { - return null; - } - - md.update(new byte[]{0}); - byte[] hash = md.digest(nonce); - signed.put(RoughtimeClient.Tag.ROOT, hash); - signed.putLong(RoughtimeClient.Tag.MIDP, TEST_TIME); - signed.putInt(RoughtimeClient.Tag.RADI, TEST_RADIUS); - - return signed.serialize(); - } - - private static byte[] response(byte[] nonce) { - RoughtimeClient.Message msg = new RoughtimeClient.Message(); - - msg.put(RoughtimeClient.Tag.SREP, signedResponse(nonce)); - msg.putInt(RoughtimeClient.Tag.INDX, 0); - msg.put(RoughtimeClient.Tag.PATH, new byte[0]); - - return msg.serialize(); - } - - private static class RoughtimeTestServer { - private final Object mLock = new Object(); - private final DatagramSocket mSocket; - private final InetAddress mAddress; - private final int mPort; - private int mRcvd; - private int mSent; - private Thread mListeningThread; - private boolean mShouldRespond = true; - - public RoughtimeTestServer() { - mSocket = makeSocket(); - mAddress = mSocket.getLocalAddress(); - mPort = mSocket.getLocalPort(); - Log.d(TAG, "testing server listening on (" + mAddress + ", " + mPort + ")"); - - mListeningThread = new Thread() { - public void run() { - while (true) { - byte[] buffer = new byte[2048]; - DatagramPacket request = new DatagramPacket(buffer, buffer.length); - try { - mSocket.receive(request); - } catch (IOException e) { - Log.e(TAG, "datagram receive error: " + e); - break; - } - synchronized (mLock) { - mRcvd++; - - if (! mShouldRespond) { - continue; - } - - RoughtimeClient.Message msg = - RoughtimeClient.Message.deserialize( - Arrays.copyOf(buffer, request.getLength())); - - byte[] nonce = msg.get(RoughtimeClient.Tag.NONC); - if (nonce.length != 64) { - Log.e(TAG, "Nonce is wrong length."); - } - - try { - request.setData(response(nonce)); - mSocket.send(request); - } catch (IOException e) { - Log.e(TAG, "datagram send error: " + e); - break; - } - mSent++; - } - } - mSocket.close(); - } - }; - mListeningThread.start(); - } - - private DatagramSocket makeSocket() { - DatagramSocket socket; - try { - socket = new DatagramSocket(0, InetAddress.getLoopbackAddress()); - } catch (SocketException e) { - Log.e(TAG, "Failed to create test server socket: " + e); - return null; - } - return socket; - } - - public void shouldRespond(boolean value) { mShouldRespond = value; } - - public InetAddress getAddress() { return mAddress; } - public int getPort() { return mPort; } - public int numRequestsReceived() { synchronized (mLock) { return mRcvd; } } - public int numRepliesSent() { synchronized (mLock) { return mSent; } } - } -} diff --git a/docs/html/reference/images/graphics/colorspace_aces.png b/docs/html/reference/images/graphics/colorspace_aces.png Binary files differnew file mode 100644 index 000000000000..efafe5c851e7 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_aces.png diff --git a/docs/html/reference/images/graphics/colorspace_acescg.png b/docs/html/reference/images/graphics/colorspace_acescg.png Binary files differnew file mode 100644 index 000000000000..55f6ab58c42f --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_acescg.png diff --git a/docs/html/reference/images/graphics/colorspace_adobe_rgb.png b/docs/html/reference/images/graphics/colorspace_adobe_rgb.png Binary files differnew file mode 100644 index 000000000000..cb7d60258cb4 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_adobe_rgb.png diff --git a/docs/html/reference/images/graphics/colorspace_bt2020.png b/docs/html/reference/images/graphics/colorspace_bt2020.png Binary files differnew file mode 100644 index 000000000000..34a3853c1cc5 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_bt2020.png diff --git a/docs/html/reference/images/graphics/colorspace_bt709.png b/docs/html/reference/images/graphics/colorspace_bt709.png Binary files differnew file mode 100644 index 000000000000..ba637f525c43 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_bt709.png diff --git a/docs/html/reference/images/graphics/colorspace_dci_p3.png b/docs/html/reference/images/graphics/colorspace_dci_p3.png Binary files differnew file mode 100644 index 000000000000..19144e768fa6 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_dci_p3.png diff --git a/docs/html/reference/images/graphics/colorspace_display_p3.png b/docs/html/reference/images/graphics/colorspace_display_p3.png Binary files differnew file mode 100644 index 000000000000..a86c60ab9ae6 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_display_p3.png diff --git a/docs/html/reference/images/graphics/colorspace_ntsc_1953.png b/docs/html/reference/images/graphics/colorspace_ntsc_1953.png Binary files differnew file mode 100644 index 000000000000..bce93da60bda --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_ntsc_1953.png diff --git a/docs/html/reference/images/graphics/colorspace_pro_photo_rgb.png b/docs/html/reference/images/graphics/colorspace_pro_photo_rgb.png Binary files differnew file mode 100644 index 000000000000..74c95be64de5 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_pro_photo_rgb.png diff --git a/docs/html/reference/images/graphics/colorspace_scrgb.png b/docs/html/reference/images/graphics/colorspace_scrgb.png Binary files differnew file mode 100644 index 000000000000..2351b8e35fb7 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_scrgb.png diff --git a/docs/html/reference/images/graphics/colorspace_smpte_c.png b/docs/html/reference/images/graphics/colorspace_smpte_c.png Binary files differnew file mode 100644 index 000000000000..360bb73c78c0 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_smpte_c.png diff --git a/docs/html/reference/images/graphics/colorspace_srgb.png b/docs/html/reference/images/graphics/colorspace_srgb.png Binary files differnew file mode 100644 index 000000000000..ba637f525c43 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_srgb.png diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index d114deb0a243..4f2465fd5da4 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -19,6 +19,7 @@ package android.graphics; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Size; +import android.annotation.Nullable; import java.util.Arrays; import java.util.function.DoubleUnaryOperator; @@ -207,6 +208,11 @@ public abstract class ColorSpace { * ColorSpace cs = ColorSpace.get(ColorSpace.Named.DCI_P3); * </pre> * + * <p>The properties of each color space are described below (see {@link #SRGB sRGB} + * for instance). When applicable, the color gamut of each color space is compared + * to the color gamut of sRGB using a CIE 1931 xy chromaticity diagram. This diagram + * shows the location of the color space's primaries and white point.</p> + * * @see ColorSpace#get(Named) */ public enum Named { @@ -240,6 +246,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" /> + * <figcaption style="text-align: center;">sRGB</figcaption> + * </p> */ SRGB, /** @@ -263,6 +273,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" /> + * <figcaption style="text-align: center;">sRGB</figcaption> + * </p> */ LINEAR_SRGB, /** @@ -298,6 +312,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([-0.5..7.5[\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" /> + * <figcaption style="text-align: center;">Extended RGB (orange) vs sRGB (white)</figcaption> + * </p> */ EXTENDED_SRGB, /** @@ -321,6 +339,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([-0.5..7.5[\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" /> + * <figcaption style="text-align: center;">Extended RGB (orange) vs sRGB (white)</figcaption> + * </p> */ LINEAR_EXTENDED_SRGB, /** @@ -352,6 +374,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_bt709.png" /> + * <figcaption style="text-align: center;">BT.709</figcaption> + * </p> */ BT709, /** @@ -383,6 +409,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_bt2020.png" /> + * <figcaption style="text-align: center;">BT.2020 (orange) vs sRGB (white)</figcaption> + * </p> */ BT2020, /** @@ -406,6 +436,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_dci_p3.png" /> + * <figcaption style="text-align: center;">DCI-P3 (orange) vs sRGB (white)</figcaption> + * </p> */ DCI_P3, /** @@ -437,6 +471,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_display_p3.png" /> + * <figcaption style="text-align: center;">Display P3 (orange) vs sRGB (white)</figcaption> + * </p> */ DISPLAY_P3, /** @@ -468,6 +506,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_ntsc_1953.png" /> + * <figcaption style="text-align: center;">NTSC 1953 (orange) vs sRGB (white)</figcaption> + * </p> */ NTSC_1953, /** @@ -499,6 +541,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_smpte_c.png" /> + * <figcaption style="text-align: center;">SMPTE-C (orange) vs sRGB (white)</figcaption> + * </p> */ SMPTE_C, /** @@ -522,6 +568,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_adobe_rgb.png" /> + * <figcaption style="text-align: center;">Adobe RGB (orange) vs sRGB (white)</figcaption> + * </p> */ ADOBE_RGB, /** @@ -553,6 +603,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_pro_photo_rgb.png" /> + * <figcaption style="text-align: center;">ProPhoto RGB (orange) vs sRGB (white)</figcaption> + * </p> */ PRO_PHOTO_RGB, /** @@ -576,6 +630,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_aces.png" /> + * <figcaption style="text-align: center;">ACES (orange) vs sRGB (white)</figcaption> + * </p> */ ACES, /** @@ -599,6 +657,10 @@ public abstract class ColorSpace { * </tr> * <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr> * </table> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_acescg.png" /> + * <figcaption style="text-align: center;">ACEScg (orange) vs sRGB (white)</figcaption> + * </p> */ ACESCG, /** @@ -1110,7 +1172,7 @@ public abstract class ColorSpace { if (source.equals(destination)) return Connector.identity(source); if (source.getModel() == Model.RGB && destination.getModel() == Model.RGB) { - return new Connector.RGB((Rgb) source, (Rgb) destination, intent); + return new Connector.Rgb((Rgb) source, (Rgb) destination, intent); } return new Connector(source, destination, intent); @@ -1162,7 +1224,7 @@ public abstract class ColorSpace { if (source.isSrgb()) return Connector.identity(source); if (source.getModel() == Model.RGB) { - return new Connector.RGB((Rgb) source, (Rgb) get(Named.SRGB), intent); + return new Connector.Rgb((Rgb) source, (Rgb) get(Named.SRGB), intent); } return new Connector(source, get(Named.SRGB), intent); @@ -1740,6 +1802,11 @@ public abstract class ColorSpace { * primaries and white point in the CIE XYZ space. The tristimulus XYZ values * are internally converted to xyY.</p> * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" /> + * <figcaption style="text-align: center;">sRGB primaries and white point</figcaption> + * </p> + * * <h3>Transfer functions</h3> * <p>A transfer function is a color component conversion function, defined as * a single variable, monotonic mathematical function. It is applied to each @@ -1788,6 +1855,11 @@ public abstract class ColorSpace { * range \([-0.5..7.5]\) while {@link Named#ACES ACES} can be used throughout * the range \([-65504, 65504]\).</p> * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" /> + * <figcaption style="text-align: center;">Extended sRGB and its large range</figcaption> + * </p> + * * <h3>Converting between RGB color spaces</h3> * <p>Conversion between two color spaces is achieved by using an intermediate * color space called the profile connection space (PCS). The PCS used by @@ -1854,7 +1926,7 @@ public abstract class ColorSpace { @NonNull @Size(9) float[] toXYZ, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf) { - this(name,computePrimaries(toXYZ, eotf), computeWhitePoint(toXYZ, eotf), + this(name, computePrimaries(toXYZ, eotf), computeWhitePoint(toXYZ, eotf), oetf, eotf, 0.0f, 1.0f, MIN_ID); } @@ -1996,8 +2068,8 @@ public abstract class ColorSpace { // A color space is wide-gamut if its area is >90% of NTSC 1953 and // if it entirely contains the Color space definition in xyY - mIsWideGamut = isWideGamut(primaries, min, max); - mIsSrgb = isSrgb(primaries, whitePoint, oetf, eotf, min, max, id); + mIsWideGamut = isWideGamut(mPrimaries, min, max); + mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id); } /** @@ -2450,7 +2522,7 @@ public abstract class ColorSpace { * If the conditions above are not met, the color space is considered as having * a wide color gamut if its range is larger than [0..1]. * - * @param primaries RGB primaries in CIE xyY or XYZ as an array of 6 or 9 floats + * @param primaries RGB primaries in CIE xyY as an array of 6 floats * @param min The minimum value of the color space's range * @param max The minimum value of the color space's range * @return True if the color space has a wide gamut, false otherwise @@ -2458,7 +2530,7 @@ public abstract class ColorSpace { * @see #isWideGamut() * @see #area(float[]) */ - private static boolean isWideGamut(@NonNull @Size(min = 6, max = 9) float[] primaries, + private static boolean isWideGamut(@NonNull @Size(6) float[] primaries, float min, float max) { return (area(primaries) / area(NTSC_1953_PRIMARIES) > 0.9f && contains(primaries, SRGB_PRIMARIES)) || (min < 0.0f && max > 1.0f); @@ -2643,7 +2715,7 @@ public abstract class ColorSpace { * @return A new array of 6 floats containing the primaries in xyY */ @NonNull - @Size(2) + @Size(6) private static float[] xyPrimaries(@NonNull @Size(min = 6, max = 9) float[] primaries) { float[] xyPrimaries = new float[6]; @@ -2818,7 +2890,7 @@ public abstract class ColorSpace { private Connector( @NonNull ColorSpace source, @NonNull ColorSpace destination, @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination, - @NonNull RenderIntent intent, @NonNull @Size(3) float[] transform) { + @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform) { mSource = source; mDestination = destination; mTransformSource = transformSource; @@ -2938,13 +3010,12 @@ public abstract class ColorSpace { /** * Optimized connector for RGB->RGB conversions. */ - private static class RGB extends Connector { + private static class Rgb extends Connector { @NonNull private final ColorSpace.Rgb mSource; @NonNull private final ColorSpace.Rgb mDestination; @NonNull private final float[] mTransform; - RGB(@NonNull ColorSpace.Rgb source, - @NonNull ColorSpace.Rgb destination, + Rgb(@NonNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination, @NonNull RenderIntent intent) { super(source, destination, source, destination, intent, null); mSource = source; diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk index ad1ead8f1d63..7689256f5b07 100644 --- a/libs/androidfw/Android.mk +++ b/libs/androidfw/Android.mk @@ -65,8 +65,9 @@ LOCAL_MODULE:= libandroidfw LOCAL_SRC_FILES:= $(deviceSources) LOCAL_C_INCLUDES := \ system/core/include -LOCAL_STATIC_LIBRARIES := libziparchive libbase LOCAL_SHARED_LIBRARIES := \ + libziparchive \ + libbase \ libbinder \ liblog \ libcutils \ diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 5e21dfc6db8a..6786a69c8dd1 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -52,16 +52,20 @@ Canvas* Canvas::create_canvas(SkCanvas* skiaCanvas) { SkiaCanvas::SkiaCanvas() {} SkiaCanvas::SkiaCanvas(SkCanvas* canvas) - : mCanvas(SkRef(canvas)) {} + : mCanvas(canvas) {} SkiaCanvas::SkiaCanvas(const SkBitmap& bitmap) { - mCanvas.reset(new SkCanvas(bitmap)); + mCanvasOwned = std::unique_ptr<SkCanvas>(new SkCanvas(bitmap)); + mCanvas = mCanvasOwned.get(); } SkiaCanvas::~SkiaCanvas() {} void SkiaCanvas::reset(SkCanvas* skiaCanvas) { - mCanvas.reset(SkRef(skiaCanvas)); + if (mCanvas != skiaCanvas) { + mCanvas = skiaCanvas; + mCanvasOwned.reset(); + } mSaveStack.reset(nullptr); mHighContrastText = false; } @@ -99,8 +103,9 @@ void SkiaCanvas::setBitmap(const SkBitmap& bitmap) { mCanvas->replayClips(&copier); } - // unrefs the existing canvas - mCanvas.reset(newCanvas); + // deletes the previously owned canvas (if any) + mCanvasOwned = std::unique_ptr<SkCanvas>(newCanvas); + mCanvas = newCanvas; // clean up the old save stack mSaveStack.reset(nullptr); @@ -307,7 +312,7 @@ void SkiaCanvas::applyPersistentClips(size_t clipStartIndex) { const SkMatrix saveMatrix = mCanvas->getTotalMatrix(); for (auto clip = begin; clip != end; ++clip) { - clip->apply(mCanvas.get()); + clip->apply(mCanvas); } mCanvas->setMatrix(saveMatrix); @@ -562,7 +567,7 @@ void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint void SkiaCanvas::drawBitmap(Bitmap& hwuiBitmap, const SkMatrix& matrix, const SkPaint* paint) { SkBitmap bitmap; hwuiBitmap.getSkBitmap(&bitmap); - SkAutoCanvasRestore acr(mCanvas.get(), true); + SkAutoCanvasRestore acr(mCanvas, true); mCanvas->concat(matrix); mCanvas->drawBitmap(bitmap, 0, 0, paint); } diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index a0cdfcbfeab7..4f1d8572eb2d 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -35,15 +35,15 @@ public: * Create a new SkiaCanvas. * * @param canvas SkCanvas to handle calls made to this SkiaCanvas. Must - * not be NULL. This constructor will ref() the SkCanvas, and unref() - * it in its destructor. + * not be NULL. This constructor does not take ownership, so the caller + * must guarantee that it remains valid while the SkiaCanvas is valid. */ explicit SkiaCanvas(SkCanvas* canvas); virtual ~SkiaCanvas(); virtual SkCanvas* asSkCanvas() override { - return mCanvas.get(); + return mCanvas; } virtual void resetRecording(int width, int height, @@ -182,7 +182,9 @@ private: class Clip; - sk_sp<SkCanvas> mCanvas; + std::unique_ptr<SkCanvas> mCanvasOwned; // might own a canvas we allocated + SkCanvas* mCanvas; // we do NOT own this canvas, it must survive us + // unless it is the same as mCanvasOwned.get() std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves. std::vector<Clip> mClipStack; // tracks persistent clips. }; diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 6df544fa9e77..95db2586f1bc 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -32,7 +32,6 @@ namespace skiapipeline { void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, int width, int height) { - mBarrierPending = false; mCurrentBarrier = nullptr; SkASSERT(mDisplayList.get() == nullptr); @@ -76,8 +75,6 @@ void SkiaRecordingCanvas::drawCircle(uirenderer::CanvasPropertyPrimitive* x, } void SkiaRecordingCanvas::insertReorderBarrier(bool enableReorder) { - mBarrierPending = enableReorder; - if (nullptr != mCurrentBarrier) { // finish off the existing chunk SkDrawable* drawable = @@ -86,6 +83,12 @@ void SkiaRecordingCanvas::insertReorderBarrier(bool enableReorder) { mCurrentBarrier = nullptr; drawDrawable(drawable); } + if (enableReorder) { + mCurrentBarrier = (StartReorderBarrierDrawable*) + mDisplayList->allocateDrawable<StartReorderBarrierDrawable>( + mDisplayList.get()); + drawDrawable(mCurrentBarrier); + } } void SkiaRecordingCanvas::drawLayer(uirenderer::DeferredLayerUpdater* layerUpdater) { @@ -97,15 +100,6 @@ void SkiaRecordingCanvas::drawLayer(uirenderer::DeferredLayerUpdater* layerUpdat } void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) { - // lazily create the chunk if needed - if (mBarrierPending) { - mCurrentBarrier = (StartReorderBarrierDrawable*) - mDisplayList->allocateDrawable<StartReorderBarrierDrawable>( - mDisplayList.get()); - drawDrawable(mCurrentBarrier); - mBarrierPending = false; - } - // record the child node mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier); auto& renderNodeDrawable = mDisplayList->mChildNodes.back(); diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index 8aef97f615ce..10829f87efb9 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -76,7 +76,6 @@ class SkiaRecordingCanvas : public SkiaCanvas { private: SkLiteRecorder mRecorder; std::unique_ptr<SkiaDisplayList> mDisplayList; - bool mBarrierPending; StartReorderBarrierDrawable* mCurrentBarrier; /** diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp index 8c956e5bcc84..950b2c45f893 100644 --- a/libs/hwui/tests/unit/FrameBuilderTests.cpp +++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp @@ -1542,6 +1542,14 @@ RENDERTHREAD_TEST(FrameBuilder, zReorder) { canvas.insertReorderBarrier(false); drawOrderedRect(&canvas, 8); drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder + canvas.insertReorderBarrier(true); //reorder a node ahead of drawrect op + drawOrderedRect(&canvas, 11); + drawOrderedNode(&canvas, 10, -1.0f); + canvas.insertReorderBarrier(false); + canvas.insertReorderBarrier(true); //test with two empty reorder sections + canvas.insertReorderBarrier(true); + canvas.insertReorderBarrier(false); + drawOrderedRect(&canvas, 12); }); FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, Caches::getInstance()); @@ -1549,7 +1557,7 @@ RENDERTHREAD_TEST(FrameBuilder, zReorder) { ZReorderTestRenderer renderer; frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(10, renderer.getIndex()); + EXPECT_EQ(13, renderer.getIndex()); }; RENDERTHREAD_TEST(FrameBuilder, projectionReorder) { diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index ae4f0f42e669..c2df9ecfe703 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -114,13 +114,21 @@ TEST(RenderNodeDrawable, zReorder) { canvas.insertReorderBarrier(false); drawOrderedRect(&canvas, 8); drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder + canvas.insertReorderBarrier(true); //reorder a node ahead of drawrect op + drawOrderedRect(&canvas, 11); + drawOrderedNode(&canvas, 10, -1.0f); + canvas.insertReorderBarrier(false); + canvas.insertReorderBarrier(true); //test with two empty reorder sections + canvas.insertReorderBarrier(true); + canvas.insertReorderBarrier(false); + drawOrderedRect(&canvas, 12); }); //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection ZReorderCanvas canvas(100, 100); RenderNodeDrawable drawable(parent.get(), &canvas, false); canvas.drawDrawable(&drawable); - EXPECT_EQ(10, canvas.getIndex()); + EXPECT_EQ(13, canvas.getIndex()); } TEST(RenderNodeDrawable, composeOnLayer) @@ -303,40 +311,42 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) { static const int LAYER_HEIGHT = 200; class ProjectionTestCanvas : public SkCanvas { public: - ProjectionTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {} + ProjectionTestCanvas(int* drawCounter) + : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) + , mDrawCounter(drawCounter) + {} void onDrawArc(const SkRect&, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, const SkPaint&) override { - EXPECT_EQ(0, mIndex++); //part of painting the layer + EXPECT_EQ(0, (*mDrawCounter)++); //part of painting the layer EXPECT_EQ(SkRect::MakeLTRB(0, 0, LAYER_WIDTH, LAYER_HEIGHT), getBounds(this)); } void onDrawRect(const SkRect& rect, const SkPaint& paint) override { - EXPECT_EQ(1, mIndex++); + EXPECT_EQ(1, (*mDrawCounter)++); EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), getBounds(this)); } void onDrawOval(const SkRect&, const SkPaint&) override { - EXPECT_EQ(2, mIndex++); + EXPECT_EQ(2, (*mDrawCounter)++); SkMatrix expectedMatrix; expectedMatrix.setTranslate(100 - SCROLL_X, 100 - SCROLL_Y); EXPECT_EQ(expectedMatrix, getTotalMatrix()); EXPECT_EQ(SkRect::MakeLTRB(-85, -80, 295, 300), getLocalBounds(this)); } - int mIndex = 0; + int* mDrawCounter; }; class ProjectionLayer : public SkSurface_Base { public: - ProjectionLayer(ProjectionTestCanvas *canvas) + ProjectionLayer(int* drawCounter) : SkSurface_Base(SkImageInfo::MakeN32Premul(LAYER_WIDTH, LAYER_HEIGHT), nullptr) - , mCanvas(canvas) { + , mDrawCounter(drawCounter) { } void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) override { - EXPECT_EQ(3, mCanvas->mIndex++); + EXPECT_EQ(3, (*mDrawCounter)++); EXPECT_EQ(SkRect::MakeLTRB(100 - SCROLL_X, 100 - SCROLL_Y, 300 - SCROLL_X, - 300 - SCROLL_Y), getBounds(mCanvas)); + 300 - SCROLL_Y), getBounds(this->getCanvas())); } SkCanvas* onNewCanvas() override { - mCanvas->ref(); - return mCanvas; + return new ProjectionTestCanvas(mDrawCounter); } sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return sk_sp<SkSurface>(); @@ -345,7 +355,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) { return sk_sp<SkImage>(); } void onCopyOnWrite(ContentChangeMode) override {} - ProjectionTestCanvas* mCanvas; + int* mDrawCounter; }; auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, @@ -389,10 +399,10 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) { info.observer = nullptr; parent->prepareTree(info); - sk_sp<ProjectionTestCanvas> canvas(new ProjectionTestCanvas()); + int drawCounter = 0; //set a layer after prepareTree to avoid layer logic there child->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer); - sk_sp<SkSurface> surfaceLayer1(new ProjectionLayer(canvas.get())); + sk_sp<SkSurface> surfaceLayer1(new ProjectionLayer(&drawCounter)); child->setLayerSurface(surfaceLayer1); Matrix4 windowTransform; windowTransform.loadTranslate(100, 100, 0); @@ -402,11 +412,11 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) { layerUpdateQueue.enqueueLayerWithDamage(child.get(), android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT)); SkiaPipeline::renderLayersImpl(layerUpdateQueue, true); - EXPECT_EQ(1, canvas->mIndex); //assert index 0 is drawn on the layer + EXPECT_EQ(1, drawCounter); //assert index 0 is drawn on the layer - RenderNodeDrawable drawable(parent.get(), canvas.get(), true); - canvas->drawDrawable(&drawable); - EXPECT_EQ(4, canvas->mIndex); + RenderNodeDrawable drawable(parent.get(), surfaceLayer1->getCanvas(), true); + surfaceLayer1->getCanvas()->drawDrawable(&drawable); + EXPECT_EQ(4, drawCounter); // clean up layer pointer, so we can safely destruct RenderNode child->setLayerSurface(nullptr); @@ -479,7 +489,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) { info.observer = nullptr; parent->prepareTree(info); - sk_sp<ProjectionChildScrollTestCanvas> canvas(new ProjectionChildScrollTestCanvas()); + std::unique_ptr<ProjectionChildScrollTestCanvas> canvas(new ProjectionChildScrollTestCanvas()); RenderNodeDrawable drawable(parent.get(), canvas.get(), true); canvas->drawDrawable(&drawable); EXPECT_EQ(2, canvas->mIndex); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 435e6ba159e9..65eadb6e59a7 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -801,8 +801,8 @@ public class AudioManager { * management of audio settings or the main telephony application. * * @param streamType The stream type to adjust. One of {@link #STREAM_VOICE_CALL}, - * {@link #STREAM_SYSTEM}, {@link #STREAM_RING}, {@link #STREAM_MUSIC} or - * {@link #STREAM_ALARM} + * {@link #STREAM_SYSTEM}, {@link #STREAM_RING}, {@link #STREAM_MUSIC}, + * {@link #STREAM_ALARM} or {@link #STREAM_ACCESSIBILITY}. * @param direction The direction to adjust the volume. One of * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or * {@link #ADJUST_SAME}. @@ -3195,7 +3195,8 @@ public class AudioManager { * {@link #STREAM_MUSIC}, * {@link #STREAM_ALARM}, * {@link #STREAM_NOTIFICATION}, - * {@link #STREAM_DTMF}. + * {@link #STREAM_DTMF}, + * {@link #STREAM_ACCESSIBILITY}. * * @return The bit-mask "or" of audio output device codes for all enabled devices on this * stream. Zero or more of @@ -3238,6 +3239,7 @@ public class AudioManager { case STREAM_ALARM: case STREAM_NOTIFICATION: case STREAM_DTMF: + case STREAM_ACCESSIBILITY: return AudioSystem.getDevicesForStream(streamType); default: return 0; diff --git a/media/java/android/media/IVolumeController.aidl b/media/java/android/media/IVolumeController.aidl index 90ac4168bba7..7f372654124d 100644 --- a/media/java/android/media/IVolumeController.aidl +++ b/media/java/android/media/IVolumeController.aidl @@ -32,4 +32,11 @@ oneway interface IVolumeController { void setLayoutDirection(int layoutDirection); void dismiss(); + + /** + * Change the a11y mode. + * @param a11yMode one of {@link VolumePolicy#A11Y_MODE_MEDIA_A11Y_VOLUME}, + * {@link VolumePolicy#A11Y_MODE_INDEPENDENT_A11Y_VOLUME} + */ + void setA11yMode(int mode); } diff --git a/media/java/android/media/VolumePolicy.java b/media/java/android/media/VolumePolicy.java index 1d33128ba752..bbcce82f2e2d 100644 --- a/media/java/android/media/VolumePolicy.java +++ b/media/java/android/media/VolumePolicy.java @@ -25,6 +25,17 @@ import java.util.Objects; public final class VolumePolicy implements Parcelable { public static final VolumePolicy DEFAULT = new VolumePolicy(false, false, true, 400); + /** + * Accessibility volume policy where the STREAM_MUSIC volume (i.e. media volume) affects + * the STREAM_ACCESSIBILITY volume, and vice-versa. + */ + public static final int A11Y_MODE_MEDIA_A11Y_VOLUME = 0; + /** + * Accessibility volume policy where the STREAM_ACCESSIBILITY volume is independent from + * any other volume. + */ + public static final int A11Y_MODE_INDEPENDENT_A11Y_VOLUME = 1; + /** Allow volume adjustments lower from vibrate to enter ringer mode = silent */ public final boolean volumeDownToEnterSilent; diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp index 96c12dd8665b..4de1d0090f60 100644 --- a/media/jni/android_media_MediaExtractor.cpp +++ b/media/jni/android_media_MediaExtractor.cpp @@ -607,8 +607,6 @@ static void android_media_MediaExtractor_native_init(JNIEnv *env) { gFields.cryptoInfoSetID = env->GetMethodID(clazz, "set", "(I[I[I[B[BI)V"); - - DataSource::RegisterDefaultSniffers(); } static void android_media_MediaExtractor_native_setup( diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java index 94286ec6a785..5aa673b40124 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -44,6 +44,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout protected View mEcaView; protected boolean mEnableHaptics; private boolean mDismissing; + private CountDownTimer mCountdownTimer = null; // To avoid accidental lockout due to events while the device in in the pocket, ignore // any passwords with length less than or equal to this length. @@ -215,11 +216,13 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout protected void handleAttemptLockout(long elapsedRealtimeDeadline) { setPasswordEntryEnabled(false); long elapsedRealtime = SystemClock.elapsedRealtime(); - new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { + long secondsInFuture = (long) Math.ceil( + (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); + mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { @Override public void onTick(long millisUntilFinished) { - int secondsRemaining = (int) (millisUntilFinished / 1000); + int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); mSecurityMessageDisplay.formatMessage( R.string.kg_too_many_failed_attempts_countdown, secondsRemaining); } @@ -252,6 +255,10 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout @Override public void onPause() { + if (mCountdownTimer != null) { + mCountdownTimer.cancel(); + mCountdownTimer = null; + } if (mPendingLockCheck != null) { mPendingLockCheck.cancel(false); mPendingLockCheck = null; diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java index 330632b701f1..c2b57ffa6113 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java @@ -325,12 +325,13 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit mLockPatternView.clearPattern(); mLockPatternView.setEnabled(false); final long elapsedRealtime = SystemClock.elapsedRealtime(); - - mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { + final long secondsInFuture = (long) Math.ceil( + (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); + mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { @Override public void onTick(long millisUntilFinished) { - final int secondsRemaining = (int) (millisUntilFinished / 1000); + final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); mSecurityMessageDisplay.formatMessage( R.string.kg_too_many_failed_attempts_countdown, secondsRemaining); } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java index cdcc05c65e98..0a7bdbf01e43 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -26,6 +26,7 @@ import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT; import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE; import static android.os.BatteryManager.EXTRA_PLUGGED; import static android.os.BatteryManager.EXTRA_STATUS; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT; import android.app.ActivityManager; import android.app.AlarmManager; @@ -191,8 +192,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { // Password attempts private SparseIntArray mFailedAttempts = new SparseIntArray(); - /** Tracks whether strong authentication hasn't been used since quite some time per user. */ - private ArraySet<Integer> mStrongAuthNotTimedOut = new ArraySet<>(); private final StrongAuthTracker mStrongAuthTracker; private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>> @@ -209,6 +208,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private TrustManager mTrustManager; private UserManager mUserManager; private int mFingerprintRunningState = FINGERPRINT_STATE_STOPPED; + private LockPatternUtils mLockPatternUtils; private final Handler mHandler = new Handler() { @Override @@ -576,8 +576,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } public boolean isUnlockingWithFingerprintAllowed() { - return mStrongAuthTracker.isUnlockingWithFingerprintAllowed() - && !hasFingerprintUnlockTimedOut(sCurrentUser); + return mStrongAuthTracker.isUnlockingWithFingerprintAllowed(); } public boolean needsSlowUnlockTransition() { @@ -588,16 +587,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { return mStrongAuthTracker; } - /** - * @return true if the user hasn't use strong authentication (pattern, PIN, password) since a - * while and thus can't unlock with fingerprint, false otherwise - */ - public boolean hasFingerprintUnlockTimedOut(int userId) { - return !mStrongAuthNotTimedOut.contains(userId); - } - public void reportSuccessfulStrongAuthUnlockAttempt() { - mStrongAuthNotTimedOut.add(sCurrentUser); scheduleStrongAuthTimeout(); if (mFpm != null) { byte[] token = null; /* TODO: pass real auth token once fp HAL supports it */ @@ -738,7 +728,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { public void onReceive(Context context, Intent intent) { if (ACTION_STRONG_AUTH_TIMEOUT.equals(intent.getAction())) { int userId = intent.getIntExtra(USER_ID, -1); - mStrongAuthNotTimedOut.remove(userId); + mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, userId); notifyStrongAuthStateChanged(userId); } } @@ -1110,7 +1100,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { PERMISSION_SELF, null /* handler */); mTrustManager = (TrustManager) context.getSystemService(Context.TRUST_SERVICE); mTrustManager.registerTrustListener(this); - new LockPatternUtils(context).registerStrongAuthTracker(mStrongAuthTracker); + mLockPatternUtils = new LockPatternUtils(context); + mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker); if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); @@ -1839,7 +1830,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { pw.println(" disabled(DPM)=" + isFingerprintDisabled(userId)); pw.println(" possible=" + isUnlockWithFingerprintPossible(userId)); pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); - pw.println(" timedout=" + hasFingerprintUnlockTimedOut(userId)); pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); } } diff --git a/packages/SettingsLib/res/values-hy-rAM/arrays.xml b/packages/SettingsLib/res/values-hy-rAM/arrays.xml index 44cfe922c127..1241beee3e6f 100644 --- a/packages/SettingsLib/res/values-hy-rAM/arrays.xml +++ b/packages/SettingsLib/res/values-hy-rAM/arrays.xml @@ -25,7 +25,7 @@ <item msgid="8934131797783724664">"Սկանավորում…"</item> <item msgid="8513729475867537913">"Միանում է..."</item> <item msgid="515055375277271756">"Նույնականացում…"</item> - <item msgid="1943354004029184381">"IP հասցեն գտնվում է...."</item> + <item msgid="1943354004029184381">"IP հասցեի ստացում…"</item> <item msgid="4221763391123233270">"Միացված է"</item> <item msgid="624838831631122137">"Կասեցված է"</item> <item msgid="7979680559596111948">"Անջատվում է…"</item> @@ -43,7 +43,7 @@ <item msgid="8937994881315223448">"Միացված է <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-ին"</item> <item msgid="1330262655415760617">"Անջատված"</item> <item msgid="7698638434317271902">"Անջատվում է <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-ից…"</item> - <item msgid="197508606402264311">"Անջատված"</item> + <item msgid="197508606402264311">"Անջատած է"</item> <item msgid="8578370891960825148">"Անհաջող"</item> <item msgid="5660739516542454527">"Արգելափակված"</item> <item msgid="1805837518286731242">"Վատ ցանցից ժամանակավոր խուսափում"</item> diff --git a/packages/SettingsLib/res/values-kn-rIN/strings.xml b/packages/SettingsLib/res/values-kn-rIN/strings.xml index cf8f3823d1ed..20d8ae9e36d8 100644 --- a/packages/SettingsLib/res/values-kn-rIN/strings.xml +++ b/packages/SettingsLib/res/values-kn-rIN/strings.xml @@ -155,7 +155,7 @@ <string name="keep_screen_on" msgid="1146389631208760344">"ಎಚ್ಚರವಾಗಿರು"</string> <string name="keep_screen_on_summary" msgid="2173114350754293009">"ಚಾರ್ಜ್ ಮಾಡುವಾಗ ಪರದೆಯು ಎಂದಿಗೂ ನಿದ್ರಾವಸ್ಥೆಗೆ ಹೋಗುವುದಿಲ್ಲ"</string> <string name="bt_hci_snoop_log" msgid="3340699311158865670">"ಬ್ಲೂಟೂತ್ HCI ಸ್ನೂಪ್ಲಾಗ್"</string> - <string name="bt_hci_snoop_log_summary" msgid="730247028210113851">"ಫೈಲ್ನಲ್ಲಿ ಎಲ್ಲ bluetooth HCI ಪ್ಯಾಕೆಟ್ಗಳನ್ನು ಸೆರೆಹಿಡಿಯಿರಿ"</string> + <string name="bt_hci_snoop_log_summary" msgid="730247028210113851">"ಫೈಲ್ನಲ್ಲಿ ಎಲ್ಲ ಬ್ಲೂಟೂತ್ HCI ಪ್ಯಾಕೆಟ್ಗಳನ್ನು ಸೆರೆಹಿಡಿಯಿರಿ"</string> <string name="oem_unlock_enable" msgid="6040763321967327691">"OEM ಅನ್ಲಾಕ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string> <string name="oem_unlock_enable_summary" msgid="4720281828891618376">"ಬೂಟ್ಲೋಡರ್ ಅನ್ಲಾಕ್ ಮಾಡಲು ಅನುಮತಿಸಿ"</string> <string name="confirm_enable_oem_unlock_title" msgid="4802157344812385674">"OEM ಅನ್ಲಾಕ್ ಮಾಡುವಿಕೆಯನ್ನು ಅನುಮತಿಸುವುದೇ?"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java index b16cd0817f5f..c7efb07ed310 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java @@ -79,6 +79,11 @@ public class Tile implements Parcelable { */ public Bundle metaData; + /** + * Optional key to use for this tile. + */ + public String key; + public Tile() { // Empty } @@ -113,6 +118,7 @@ public class Tile implements Parcelable { dest.writeString(category); dest.writeInt(priority); dest.writeBundle(metaData); + dest.writeString(key); } public void readFromParcel(Parcel in) { @@ -132,6 +138,7 @@ public class Tile implements Parcelable { category = in.readString(); priority = in.readInt(); metaData = in.readBundle(); + key = in.readString(); } Tile(Parcel in) { diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java index 944245802d7b..d12e8c09b8c0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java @@ -102,6 +102,12 @@ public class TileUtils { /** * Name of the meta-data item that should be set in the AndroidManifest.xml + * to specify the key that should be used for the preference. + */ + public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint"; + + /** + * Name of the meta-data item that should be set in the AndroidManifest.xml * to specify the icon that should be displayed for the preference. */ public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon"; @@ -292,6 +298,7 @@ public class TileUtils { int icon = 0; CharSequence title = null; String summary = null; + String keyHint = null; // Get the activity's meta-data try { @@ -317,6 +324,13 @@ public class TileUtils { summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY); } } + if (metaData.containsKey(META_DATA_PREFERENCE_KEYHINT)) { + if (metaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) { + keyHint = res.getString(metaData.getInt(META_DATA_PREFERENCE_KEYHINT)); + } else { + keyHint = metaData.getString(META_DATA_PREFERENCE_KEYHINT); + } + } } } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) { if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e); @@ -338,6 +352,8 @@ public class TileUtils { // Replace the intent with this specific activity tile.intent = new Intent().setClassName(activityInfo.packageName, activityInfo.name); + // Suggest a key for this tile + tile.key = keyHint; return true; } 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 651a7cb69387..86b210ac4aef 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 @@ -21,7 +21,9 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.os.Bundle; import android.os.UserHandle; import android.util.ArrayMap; @@ -43,6 +45,7 @@ import java.util.Map; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; @@ -54,11 +57,14 @@ public class TileUtilsTest { private Context mContext; @Mock private PackageManager mPackageManager; + @Mock + private Resources mResources; @Before - public void setUp() { + public void setUp() throws NameNotFoundException { MockitoAnnotations.initMocks(this); when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.getResourcesForApplication(anyString())).thenReturn(mResources); } @Test @@ -82,6 +88,27 @@ public class TileUtilsTest { } @Test + public void getTilesForIntent_shouldParseKeyHintForSystemApp() { + String keyHint = "key"; + 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 */, keyHint); + info.add(resolveInfo); + + when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt())) + .thenReturn(info); + + TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, + null /* defaultCategory */, outTiles, false /* usePriority */, + false /* checkCategory */); + + assertThat(outTiles.size()).isEqualTo(1); + assertThat(outTiles.get(0).key).isEqualTo(keyHint); + } + + @Test public void getTilesForIntent_shouldSkipNonSystemApp() { final String testCategory = "category1"; Intent intent = new Intent(); @@ -100,7 +127,12 @@ public class TileUtilsTest { assertThat(outTiles.isEmpty()).isTrue(); } + private ResolveInfo newInfo(boolean systemApp, String category) { + return newInfo(systemApp, category, null); + } + + private ResolveInfo newInfo(boolean systemApp, String category, String keyHint) { ResolveInfo info = new ResolveInfo(); info.system = systemApp; info.activityInfo = new ActivityInfo(); @@ -108,7 +140,13 @@ public class TileUtilsTest { info.activityInfo.name = "123"; info.activityInfo.metaData = new Bundle(); info.activityInfo.metaData.putString("com.android.settings.category", category); + if (keyHint != null) { + info.activityInfo.metaData.putString("com.android.settings.keyhint", keyHint); + } info.activityInfo.applicationInfo = new ApplicationInfo(); + if (systemApp) { + info.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; + } return info; } } diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml index c5b4e84ebba5..6732e6c62e8e 100644 --- a/packages/SystemUI/res/layout/notification_icon_area.xml +++ b/packages/SystemUI/res/layout/notification_icon_area.xml @@ -19,13 +19,7 @@ android:id="@+id/notification_icon_area_inner" android:layout_width="match_parent" android:layout_height="match_parent" > - <com.android.systemui.statusbar.StatusBarIconView - android:id="@+id/moreIcon" - android:layout_width="@dimen/status_bar_icon_size" - android:layout_height="match_parent" - android:src="@drawable/stat_notify_more" - android:visibility="gone" /> - <com.android.systemui.statusbar.phone.IconMerger + <com.android.systemui.statusbar.phone.NotificationIconContainer android:id="@+id/notificationIcons" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 63af3e0c9f30..6784254796a2 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -42,7 +42,7 @@ <LinearLayout android:id="@+id/status_bar_contents" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingStart="6dp" + android:paddingStart="@dimen/status_bar_padding_start" android:paddingEnd="8dp" android:orientation="horizontal" > diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml deleted file mode 100644 index 7df6bc695555..000000000000 --- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml +++ /dev/null @@ -1,74 +0,0 @@ -<!-- - ~ Copyright (C) 2014 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 - --> - -<!-- Extends FrameLayout --> -<com.android.systemui.statusbar.NotificationOverflowContainer - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/notification_summary_height" - android:focusable="true" - android:clickable="true" - > - - <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundNormal" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundDimmed" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - - <com.android.keyguard.AlphaOptimizedLinearLayout - android:id="@+id/content" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <TextView - android:id="@+id/more_text" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_marginStart="16dp" - android:layout_marginEnd="12dp" - android:layout_gravity="center_vertical" - android:background="@drawable/keyguard_overflow_number_background" - android:gravity="center" - android:textColor="#ff686868" - android:textStyle="bold" - android:textSize="14dp" - /> - <com.android.systemui.statusbar.StatusBarIconView - android:id="@+id/more_icon_overflow" - android:layout_width="@dimen/status_bar_icon_size" - android:layout_height="match_parent" - android:src="@drawable/stat_notify_more" - android:tint="@color/keyguard_overflow_content_color" - android:visibility="gone" - /> - <com.android.systemui.statusbar.NotificationOverflowIconsView - android:id="@+id/overflow_icons_view" - android:layout_gravity="center_vertical" - android:layout_marginEnd="8dp" - android:layout_width="match_parent" - android:layout_height="wrap_content" - /> - </com.android.keyguard.AlphaOptimizedLinearLayout> - - <com.android.systemui.statusbar.notification.FakeShadowView - android:id="@+id/fake_shadow" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - -</com.android.systemui.statusbar.NotificationOverflowContainer> diff --git a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml new file mode 100644 index 000000000000..088deba807f5 --- /dev/null +++ b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml @@ -0,0 +1,47 @@ +<!-- + ~ 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 + --> + +<!-- Extends FrameLayout --> +<com.android.systemui.statusbar.NotificationShelf + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_shelf_height" + android:focusable="true" + android:clickable="true" + > + + <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundNormal" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundDimmed" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + <com.android.systemui.statusbar.phone.NotificationIconContainer + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingStart="13dp" + android:paddingEnd="13dp" + android:gravity="center_vertical" /> + + <com.android.systemui.statusbar.notification.FakeShadowView + android:id="@+id/fake_shadow" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + +</com.android.systemui.statusbar.NotificationShelf> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 2f7174a6a89e..b889343bbbf9 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -81,8 +81,15 @@ <!-- Height of a heads up notification in the status bar --> <dimen name="notification_max_heads_up_height">148dp</dimen> - <!-- Height of a the summary ("more card") notification on keyguard. --> - <dimen name="notification_summary_height">44dp</dimen> + <!-- Height of a the shelf with the notification icons --> + <dimen name="notification_shelf_height">32dp</dimen> + + <!-- The padding of a notification icon on top to the start of the notification. Used for custom + views where the distance can't be measured --> + <dimen name="notification_icon_appear_padding">15dp</dimen> + + <!-- The amount the content shifts upwards when transforming into the icon --> + <dimen name="notification_icon_transform_content_shift">32dp</dimen> <!-- Minimum layouted height of a notification in the statusbar--> <dimen name="min_notification_layout_height">48dp</dimen> @@ -94,7 +101,7 @@ <dimen name="notification_gear_padding">20dp</dimen> <!-- size at which Notification icons will be drawn in the status bar --> - <dimen name="status_bar_icon_drawing_size">17dip</dimen> + <dimen name="status_bar_icon_drawing_size">17dp</dimen> <!-- opacity at which Notification icons will be drawn in the status bar --> <item type="dimen" name="status_bar_icon_drawing_alpha">90%</item> @@ -102,6 +109,15 @@ <!-- gap on either side of status bar notification icons --> <dimen name="status_bar_icon_padding">0dp</dimen> + <!-- the padding on the start of the statusbar --> + <dimen name="status_bar_padding_start">6dp</dimen> + + <!-- the radius of the overflow dot in the status bar --> + <dimen name="overflow_dot_radius">1dp</dimen> + + <!-- the padding between dots in the icon overflow --> + <dimen name="overflow_icon_dot_padding">3dp</dimen> + <!-- The padding on the global screenshot background image --> <dimen name="global_screenshot_bg_padding">20dp</dimen> @@ -257,16 +273,10 @@ <!-- Default distance from each snap target that GlowPadView considers a "hit" --> <dimen name="glowpadview_inner_radius">15dip</dimen> - <!-- Space reserved for the cards behind the top card in the bottom stack --> - <dimen name="bottom_stack_peek_amount">12dp</dimen> - <!-- bottom_stack_peek_amount + notification_min_height + notification_collapse_second_card_padding --> <dimen name="min_stack_height">104dp</dimen> - <!-- The height of the area before the bottom stack in which the notifications slow down --> - <dimen name="bottom_stack_slow_down_length">12dp</dimen> - <!-- Z distance between notifications if they are in the stack --> <dimen name="z_distance_between_notifications">0.5dp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 94d79f27e67f..8c80c71f869e 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -16,18 +16,21 @@ --> <resources> + <item type="id" name="translation_x_animator_tag"/> <item type="id" name="translation_y_animator_tag"/> <item type="id" name="translation_z_animator_tag"/> <item type="id" name="alpha_animator_tag"/> <item type="id" name="top_inset_animator_tag"/> <item type="id" name="height_animator_tag"/> <item type="id" name="shadow_alpha_animator_tag"/> + <item type="id" name="translation_x_animator_end_value_tag"/> <item type="id" name="translation_y_animator_end_value_tag"/> <item type="id" name="translation_z_animator_end_value_tag"/> <item type="id" name="alpha_animator_end_value_tag"/> <item type="id" name="top_inset_animator_end_value_tag"/> <item type="id" name="height_animator_end_value_tag"/> <item type="id" name="shadow_alpha_animator_end_value_tag"/> + <item type="id" name="translation_x_animator_start_value_tag"/> <item type="id" name="translation_y_animator_start_value_tag"/> <item type="id" name="translation_z_animator_start_value_tag"/> <item type="id" name="alpha_animator_start_value_tag"/> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 331d09e4537a..5fec64777a05 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1120,6 +1120,7 @@ <item></item> <!-- STREAM_SYSTEM_ENFORCED --> <item></item> <!-- STREAM_DTMF --> <item></item> <!-- STREAM_TTS --> + <item>Accessibility</item> <!-- STREAM_ACCESSIBILITY --> </string-array> <string name="volume_stream_muted" translatable="false">%s silent</string> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 21f68f558c74..e03612802f9c 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -26,6 +26,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.R; import com.android.systemui.assist.AssistManager; +import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.phone.KeyguardBouncer; @@ -89,9 +90,10 @@ public class SystemUIFactory { } public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback, - LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager, - ViewGroup container) { - return new KeyguardBouncer(context, callback, lockPatternUtils, windowManager, container); + LockPatternUtils lockPatternUtils, + ViewGroup container, DismissCallbackRegistry dismissCallbackRegistry) { + return new KeyguardBouncer(context, callback, lockPatternUtils, container, + dismissCallbackRegistry); } public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java new file mode 100644 index 000000000000..262d29d1acd1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java @@ -0,0 +1,47 @@ +/* + * 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.systemui.keyguard; + +import com.android.internal.policy.IKeyguardDismissCallback; + +import java.util.ArrayList; + +/** + * Registry holding the current set of {@link IKeyguardDismissCallback}s. + */ +public class DismissCallbackRegistry { + + private final ArrayList<DismissCallbackWrapper> mDismissCallbacks = new ArrayList<>(); + + public void addCallback(IKeyguardDismissCallback callback) { + mDismissCallbacks.add(new DismissCallbackWrapper(callback)); + } + + public void notifyDismissCancelled() { + for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) { + mDismissCallbacks.get(i).notifyDismissCancelled(); + } + mDismissCallbacks.clear(); + } + + public void notifyDismissSucceeded() { + for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) { + mDismissCallbacks.get(i).notifyDismissSucceeded(); + } + mDismissCallbacks.clear(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackWrapper.java b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackWrapper.java new file mode 100644 index 000000000000..8a91144ccf33 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackWrapper.java @@ -0,0 +1,60 @@ +/* + * 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.systemui.keyguard; + +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.policy.IKeyguardDismissCallback; + +/** + * A light wrapper around {@link IKeyguardDismissCallback} handling {@link RemoteException}s. + */ +public class DismissCallbackWrapper { + + private static final String TAG = "DismissCallbackWrapper"; + + private IKeyguardDismissCallback mCallback; + + public DismissCallbackWrapper(IKeyguardDismissCallback callback) { + mCallback = callback; + } + + public void notifyDismissError() { + try { + mCallback.onDismissError(); + } catch (RemoteException e) { + Log.i(TAG, "Failed to call callback", e); + } + } + + public void notifyDismissCancelled() { + try { + mCallback.onDismissCancelled(); + } catch (RemoteException e) { + Log.i(TAG, "Failed to call callback", e); + } + } + + public void notifyDismissSucceeded() { + try { + mCallback.onDismissSucceeded(); + } catch (RemoteException e) { + Log.i(TAG, "Failed to call callback", e); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index fe9f55f9a1ee..3532f41ae676 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -26,6 +26,7 @@ import android.os.Process; import android.os.Trace; import android.util.Log; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; @@ -89,9 +90,9 @@ public class KeyguardService extends Service { } @Override // Binder interface - public void dismiss(boolean allowWhileOccluded) { + public void dismiss(IKeyguardDismissCallback callback) { checkPermission(); - mKeyguardViewMediator.dismiss(allowWhileOccluded); + mKeyguardViewMediator.dismiss(callback); } @Override // Binder interface diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 34dc63f12427..2192b8c03c96 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -20,12 +20,12 @@ import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT; import android.app.Activity; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.PendingIntent; -import android.app.SearchManager; import android.app.StatusBarManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; @@ -48,20 +48,18 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.os.storage.StorageManager; import android.provider.Settings; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.EventLog; import android.util.Log; import android.util.Slog; -import android.view.IWindowManager; import android.view.ViewGroup; -import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicy; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardStateCallback; @@ -252,6 +250,7 @@ public class KeyguardViewMediator extends SystemUI { * var being non-null as an indicator that there is an in progress request. */ private IKeyguardExitCallback mExitSecureCallback; + private final DismissCallbackRegistry mDismissCallbackRegistry = new DismissCallbackRegistry(); // the properties of the keyguard @@ -347,7 +346,7 @@ public class KeyguardViewMediator extends SystemUI { UserInfo info = UserManager.get(mContext).getUserInfo(userId); if (info != null && (info.isGuest() || info.isDemo())) { // If we just switched to a guest, try to dismiss keyguard. - dismiss(false /* allowWhileOccluded */); + dismiss(null /* callback */); } } } @@ -515,7 +514,7 @@ public class KeyguardViewMediator extends SystemUI { return; } - tryKeyguardDone(true); + tryKeyguardDone(); if (strongAuth) { mUpdateMonitor.reportSuccessfulStrongAuthUnlockAttempt(); } @@ -565,10 +564,7 @@ public class KeyguardViewMediator extends SystemUI { Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#readyForKeyguardDone"); if (mKeyguardDonePending) { mKeyguardDonePending = false; - - // Somebody has called keyguardDonePending before, which means that we are - // authenticated - tryKeyguardDone(true); + tryKeyguardDone(); } Trace.endSection(); } @@ -600,7 +596,7 @@ public class KeyguardViewMediator extends SystemUI { if (any && !strongAuthTracker.hasUserAuthenticatedSinceBoot()) { return KeyguardSecurityView.PROMPT_REASON_RESTART; - } else if (fingerprint && mUpdateMonitor.hasFingerprintUnlockTimedOut(currentUser)) { + } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) != 0) { return KeyguardSecurityView.PROMPT_REASON_TIMEOUT; } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) != 0) { return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; @@ -1252,16 +1248,21 @@ public class KeyguardViewMediator extends SystemUI { /** * Dismiss the keyguard through the security layers. - * @param allowWhileOccluded if true, dismiss the keyguard even if it's currently occluded. + * @param callback Callback to be informed about the result */ - public void handleDismiss(boolean allowWhileOccluded) { - if (mShowing && (allowWhileOccluded || !mOccluded)) { + private void handleDismiss(IKeyguardDismissCallback callback) { + if (mShowing) { + if (callback != null) { + mDismissCallbackRegistry.addCallback(callback); + } mStatusBarKeyguardViewManager.dismissAndCollapse(); + } else if (callback != null) { + new DismissCallbackWrapper(callback).notifyDismissError(); } } - public void dismiss(boolean allowWhileOccluded) { - mHandler.obtainMessage(DISMISS, allowWhileOccluded ? 1 : 0, 0).sendToTarget(); + public void dismiss(IKeyguardDismissCallback callback) { + mHandler.obtainMessage(DISMISS, callback).sendToTarget(); } /** @@ -1394,12 +1395,12 @@ public class KeyguardViewMediator extends SystemUI { } }; - public void keyguardDone(boolean authenticated) { + public void keyguardDone() { Trace.beginSection("KeyguardViewMediator#keyguardDone"); - if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated +")"); + if (DEBUG) Log.d(TAG, "keyguardDone()"); userActivity(); EventLog.writeEvent(70000, 2); - Message msg = mHandler.obtainMessage(KEYGUARD_DONE, authenticated ? 1 : 0); + Message msg = mHandler.obtainMessage(KEYGUARD_DONE); mHandler.sendMessage(msg); Trace.endSection(); } @@ -1455,7 +1456,7 @@ public class KeyguardViewMediator extends SystemUI { break; case KEYGUARD_DONE: Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE"); - handleKeyguardDone(msg.arg1 != 0); + handleKeyguardDone(); Trace.endSection(); break; case KEYGUARD_DONE_DRAWING: @@ -1474,7 +1475,7 @@ public class KeyguardViewMediator extends SystemUI { } break; case DISMISS: - handleDismiss(msg.arg1 == 1 ? true : false /* allowWhileOccluded */); + handleDismiss((IKeyguardDismissCallback) msg.obj); break; case START_KEYGUARD_EXIT_ANIM: Trace.beginSection("KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM"); @@ -1492,9 +1493,9 @@ public class KeyguardViewMediator extends SystemUI { } }; - private void tryKeyguardDone(boolean authenticated) { + private void tryKeyguardDone() { if (!mKeyguardDonePending && mHideAnimationRun && !mHideAnimationRunning) { - handleKeyguardDone(authenticated); + handleKeyguardDone(); } else if (!mHideAnimationRun) { mHideAnimationRun = true; mHideAnimationRunning = true; @@ -1506,7 +1507,7 @@ public class KeyguardViewMediator extends SystemUI { * @see #keyguardDone * @see #KEYGUARD_DONE */ - private void handleKeyguardDone(boolean authenticated) { + private void handleKeyguardDone() { Trace.beginSection("KeyguardViewMediator#handleKeyguardDone"); final int currentUser = KeyguardUpdateMonitor.getCurrentUser(); if (mLockPatternUtils.isSecure(currentUser)) { @@ -1517,9 +1518,7 @@ public class KeyguardViewMediator extends SystemUI { resetKeyguardDonePendingLocked(); } - if (authenticated) { - mUpdateMonitor.clearFailedUnlockAttempts(); - } + mUpdateMonitor.clearFailedUnlockAttempts(); mUpdateMonitor.clearFingerprintRecognized(); if (mGoingToSleep) { @@ -1528,22 +1527,21 @@ public class KeyguardViewMediator extends SystemUI { } if (mExitSecureCallback != null) { try { - mExitSecureCallback.onKeyguardExitResult(authenticated); + mExitSecureCallback.onKeyguardExitResult(true /* authenciated */); } catch (RemoteException e) { - Slog.w(TAG, "Failed to call onKeyguardExitResult(" + authenticated + ")", e); + Slog.w(TAG, "Failed to call onKeyguardExitResult()", e); } mExitSecureCallback = null; - if (authenticated) { - // after succesfully exiting securely, no need to reshow - // the keyguard when they've released the lock - mExternallyEnabled = true; - mNeedToReshowWhenReenabled = false; - updateInputRestricted(); - } + // after succesfully exiting securely, no need to reshow + // the keyguard when they've released the lock + mExternallyEnabled = true; + mNeedToReshowWhenReenabled = false; + updateInputRestricted(); } + mDismissCallbackRegistry.notifyDismissSucceeded(); handleHide(); Trace.endSection(); } @@ -1690,7 +1688,7 @@ public class KeyguardViewMediator extends SystemUI { private final Runnable mHideAnimationFinishedRunnable = () -> { mHideAnimationRunning = false; - tryKeyguardDone(true); + tryKeyguardDone(); }; /** @@ -1912,7 +1910,7 @@ public class KeyguardViewMediator extends SystemUI { public void onWakeAndUnlocking() { Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking"); mWakeAndUnlocking = true; - keyguardDone(true /* authenticated */); + keyguardDone(); Trace.endSection(); } @@ -1921,7 +1919,8 @@ public class KeyguardViewMediator extends SystemUI { ScrimController scrimController, FingerprintUnlockController fingerprintUnlockController) { mStatusBarKeyguardViewManager.registerStatusBar(phoneStatusBar, container, - statusBarWindowManager, scrimController, fingerprintUnlockController); + statusBarWindowManager, scrimController, fingerprintUnlockController, + mDismissCallbackRegistry); return mStatusBarKeyguardViewManager; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index bc4654823f77..173f160a70d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -41,7 +41,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackStateAnimator; /** - * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer} + * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf} * to implement dimming/activating on Keyguard for the double-tap gesture */ public abstract class ActivatableNotificationView extends ExpandableOutlineView { @@ -86,6 +86,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView */ private static final float DARK_EXIT_SCALE_START = 0.93f; + /** + * A sentinel value when no color should be used. Can be used with {@link #setTintColor(int)} + * or {@link #setOverrideTintColor(int, float)}. + */ + protected static final int NO_COLOR = 0; + private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR = new PathInterpolator(0.6f, 0, 0.5f, 1); private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR @@ -167,6 +173,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private int mCurrentBackgroundTint; private int mTargetTint; private int mStartTint; + private int mOverrideTint; + private float mOverrideAmount; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); @@ -448,9 +456,20 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (below != mIsBelowSpeedBump) { mIsBelowSpeedBump = below; updateBackgroundTint(); + onBelowSpeedBumpChanged(); } } + protected void onBelowSpeedBumpChanged() { + } + + /** + * @return whether we are below the speed bump + */ + public boolean isBelowSpeedBump() { + return mIsBelowSpeedBump; + } + /** * Sets the tint color of the background */ @@ -466,6 +485,23 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView updateBackgroundTint(animated); } + /** + * Set an override tint color that is used for the background. + * + * @param color the color that should be used to tint the background. + * This can be {@link #NO_COLOR} if the tint should be normally computed. + * @param overrideAmount a value from 0 to 1 how much the override tint should be used. The + * background color will then be the interpolation between this and the + * regular background color, where 1 means the overrideTintColor is fully + * used and the background color not at all. + */ + public void setOverrideTintColor(int color, float overrideAmount) { + mOverrideTint = color; + mOverrideAmount = overrideAmount; + int newColor = calculateBgColor(); + setBackgroundTintColor(newColor); + } + protected void updateBackgroundTint() { updateBackgroundTint(false /* animated */); } @@ -673,6 +709,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } @Override + public void setClipBottomAmount(int clipBottomAmount) { + super.setClipBottomAmount(clipBottomAmount); + mBackgroundNormal.setClipBottomAmount(clipBottomAmount); + mBackgroundDimmed.setClipBottomAmount(clipBottomAmount); + } + + @Override public void performRemoveAnimation(long duration, float translationDirection, Runnable onFinishedRunnable) { enableAppearDrawing(true); @@ -841,11 +884,20 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView protected abstract View getContentView(); public int calculateBgColor() { - return calculateBgColor(true /* withTint */); + return calculateBgColor(true /* withTint */, true /* withOverRide */); } - private int calculateBgColor(boolean withTint) { - if (withTint && mBgTint != 0) { + /** + * @param withTint should a possible tint be factored in? + * @param withOverRide should the value be interpolated with {@link #mOverrideTint} + * @return the calculated background color + */ + private int calculateBgColor(boolean withTint, boolean withOverRide) { + if (withOverRide && mOverrideTint != NO_COLOR) { + int defaultTint = calculateBgColor(withTint, false); + return NotificationUtils.interpolateColors(defaultTint, mOverrideTint, mOverrideAmount); + } + if (withTint && mBgTint != NO_COLOR) { return mBgTint; } else if (mShowingLegacyBackground) { return mLegacyColor; @@ -936,7 +988,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } public int getBackgroundColorWithoutTint() { - return calculateBgColor(false /* withTint */); + return calculateBgColor(false /* withTint */, false /* withOverride */); } public interface OnActivatedListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 19e511cff228..f5ca67897c38 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -42,7 +42,6 @@ import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; @@ -258,7 +257,7 @@ public abstract class BaseStatusBar extends SystemUI implements protected boolean mShowLockscreenNotifications; protected boolean mAllowLockscreenRemoteInput; - protected NotificationOverflowContainer mKeyguardIconOverflowContainer; + protected NotificationShelf mNotificationShelf; protected DismissView mDismissView; protected EmptyShadeView mEmptyShadeView; @@ -1025,9 +1024,7 @@ public abstract class BaseStatusBar extends SystemUI implements } } - if (entry.icon != null) { - entry.icon.setTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - } + entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); } public boolean isMediaNotification(NotificationData.Entry entry) { @@ -1485,7 +1482,7 @@ public abstract class BaseStatusBar extends SystemUI implements */ @Override // NotificationData.Environment public boolean shouldHideNotifications(int userId) { - return isLockscreenPublicMode(mCurrentUserId) && !userAllowsNotificationsInPublic(userId) + return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId) || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId)); } @@ -2160,13 +2157,14 @@ public abstract class BaseStatusBar extends SystemUI implements if (DEBUG) { Log.d(TAG, "createNotificationViews(notification=" + sbn); } - final StatusBarIconView iconView = createIcon(sbn); - if (iconView == null) { - return null; + NotificationData.Entry entry = new NotificationData.Entry(sbn); + try { + entry.createIcons(mContext, sbn); + } catch (NotificationData.IconException exception) { + handleNotificationError(sbn, exception.getMessage()); } // Construct the expanded view. - NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView); if (!inflateViews(entry, mStackScroller)) { handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn); return null; @@ -2174,33 +2172,6 @@ public abstract class BaseStatusBar extends SystemUI implements return entry; } - public StatusBarIconView createIcon(StatusBarNotification sbn) { - // Construct the icon. - Notification n = sbn.getNotification(); - final StatusBarIconView iconView = new StatusBarIconView(mContext, - sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n); - iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - - final Icon smallIcon = n.getSmallIcon(); - if (smallIcon == null) { - handleNotificationError(sbn, - "No small icon in notification from " + sbn.getPackageName()); - return null; - } - final StatusBarIcon ic = new StatusBarIcon( - sbn.getUser(), - sbn.getPackageName(), - smallIcon, - n.iconLevel, - n.number, - StatusBarIconView.contentDescForNotification(mContext, n)); - if (!iconView.set(ic)) { - handleNotificationError(sbn, "Couldn't create icon: " + ic); - return null; - } - return iconView; - } - protected void addNotificationViews(Entry entry, RankingMap ranking) { if (entry == null) { return; @@ -2220,17 +2191,16 @@ public abstract class BaseStatusBar extends SystemUI implements * Updates expanded, dimmed and locked states of notification rows. */ protected void updateRowStates() { - mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); final int N = activeNotifications.size(); int visibleNotifications = 0; boolean onKeyguard = mState == StatusBarState.KEYGUARD; - int maxNotifications = 0; + int maxNotifications = -1; if (onKeyguard) { maxNotifications = getMaxKeyguardNotifications(true /* recompute */); } + mStackScroller.setMaxDisplayedNotifications(maxNotifications); for (int i = 0; i < N; i++) { NotificationData.Entry entry = activeNotifications.get(i); boolean childNotification = mGroupManager.isChildInGroupWithSummary(entry.notification); @@ -2249,12 +2219,8 @@ public abstract class BaseStatusBar extends SystemUI implements boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); if (suppressedSummary || (isLockscreenPublicMode(userId) && !mShowLockscreenNotifications) - || (onKeyguard && !childWithVisibleSummary - && (visibleNotifications >= maxNotifications || !showOnKeyguard))) { + || (onKeyguard && !showOnKeyguard)) { entry.row.setVisibility(View.GONE); - if (onKeyguard && showOnKeyguard && !childNotification && !suppressedSummary) { - mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); - } } else { boolean wasGone = entry.row.getVisibility() == View.GONE; entry.row.setVisibility(View.VISIBLE); @@ -2269,13 +2235,9 @@ public abstract class BaseStatusBar extends SystemUI implements } } - mStackScroller.updateOverflowContainerVisibility(onKeyguard - && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0); - mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1); mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2); - mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer, - mStackScroller.getChildCount() - 3); + mStackScroller.changeViewPosition(mNotificationShelf, mStackScroller.getChildCount() - 3); } public boolean shouldShowOnKeyguard(StatusBarNotification sbn) { @@ -2367,48 +2329,28 @@ public abstract class BaseStatusBar extends SystemUI implements mGroupManager.onEntryUpdated(entry, oldNotification); boolean updateSuccessful = false; - if (applyInPlace) { - if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); - try { - if (entry.icon != null) { - // Update the icon - final StatusBarIcon ic = new StatusBarIcon( - notification.getUser(), - notification.getPackageName(), - n.getSmallIcon(), - n.iconLevel, - n.number, - StatusBarIconView.contentDescForNotification(mContext, n)); - entry.icon.setNotification(n); - if (!entry.icon.set(ic)) { - handleNotificationError(notification, "Couldn't update icon: " + ic); - return; - } + try { + if (applyInPlace) { + if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); + try { + entry.updateIcons(mContext, n); + updateNotificationViews(entry, notification); + updateSuccessful = true; + } catch (RuntimeException e) { + // It failed to apply cleanly. + Log.w(TAG, "Couldn't reapply views for package " + + notification.getPackageName(), e); } - updateNotificationViews(entry, notification); - updateSuccessful = true; - } - catch (RuntimeException e) { - // It failed to apply cleanly. - Log.w(TAG, "Couldn't reapply views for package " + - notification.getPackageName(), e); } - } - if (!updateSuccessful) { - if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); - final StatusBarIcon ic = new StatusBarIcon( - notification.getUser(), - notification.getPackageName(), - n.getSmallIcon(), - n.iconLevel, - n.number, - StatusBarIconView.contentDescForNotification(mContext, n)); - entry.icon.setNotification(n); - entry.icon.set(ic); - if (!inflateViews(entry, mStackScroller)) { - handleNotificationError(notification, "Couldn't update remote views for: " - + notification); + if (!updateSuccessful) { + entry.updateIcons(mContext, n); + if (!inflateViews(entry, mStackScroller)) { + handleNotificationError(notification, "Couldn't update remote views for: " + + notification); + } } + } catch (NotificationData.IconException e) { + handleNotificationError(notification, e.getMessage()); } updateHeadsUp(key, entry, shouldPeek, alertAgain); updateNotifications(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java index 1d7bede962d3..543666407fb9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java @@ -22,12 +22,17 @@ import android.util.AttributeSet; import android.view.View; import com.android.systemui.R; +import com.android.systemui.statusbar.stack.ExpandableViewState; +import com.android.systemui.statusbar.stack.StackScrollState; public class DismissView extends StackScrollerDecorView { + private final int mClearAllTopPadding; private DismissViewButton mDismissButton; public DismissView(Context context, AttributeSet attrs) { super(context, attrs); + mClearAllTopPadding = context.getResources().getDimensionPixelSize( + R.dimen.clear_all_padding_top); } @Override @@ -63,4 +68,21 @@ public class DismissView extends StackScrollerDecorView { public boolean isButtonVisible() { return mDismissButton.getAlpha() != 0.0f; } + + @Override + public ExpandableViewState createNewViewState(StackScrollState stackScrollState) { + return new DismissViewState(); + } + + public class DismissViewState extends ExpandableViewState { + @Override + public void applyToView(View view) { + super.applyToView(view); + if (view instanceof DismissView) { + DismissView dismissView = (DismissView) view; + boolean visible = this.clipTopAmount < mClearAllTopPadding; + dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone()); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java index 5db06997eda5..19b32af8b919 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java @@ -23,6 +23,8 @@ import android.view.View; import android.widget.TextView; import com.android.systemui.R; +import com.android.systemui.statusbar.stack.ExpandableViewState; +import com.android.systemui.statusbar.stack.StackScrollState; public class EmptyShadeView extends StackScrollerDecorView { @@ -40,4 +42,22 @@ public class EmptyShadeView extends StackScrollerDecorView { protected View findContentView() { return findViewById(R.id.no_notifications); } + + @Override + public ExpandableViewState createNewViewState(StackScrollState stackScrollState) { + return new EmptyShadeViewState(); + } + + public static class EmptyShadeViewState extends ExpandableViewState { + @Override + public void applyToView(View view) { + super.applyToView(view); + if (view instanceof EmptyShadeView) { + EmptyShadeView emptyShadeView = (EmptyShadeView) view; + boolean visible = this.clipTopAmount <= 0; + emptyShadeView.performVisibilityAnimation( + visible && !emptyShadeView.willBeGone()); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 5173176ffa8f..a1384dcbe66d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -36,6 +36,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.NotificationHeaderView; import android.view.View; +import android.view.ViewGroup; import android.view.ViewStub; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -45,16 +46,18 @@ import android.widget.ImageView; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.NotificationColorUtil; +import com.android.internal.widget.CachingIconView; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.statusbar.notification.HybridNotificationView; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.stack.AnimationProperties; +import com.android.systemui.statusbar.stack.ExpandableViewState; import com.android.systemui.statusbar.stack.NotificationChildrenContainer; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackScrollState; -import com.android.systemui.statusbar.stack.StackStateAnimator; -import com.android.systemui.statusbar.stack.StackViewState; import java.util.ArrayList; import java.util.List; @@ -63,6 +66,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private static final int DEFAULT_DIVIDER_ALPHA = 0x29; private static final int COLORED_DIVIDER_ALPHA = 0x7B; + private int mIconTransformContentShift; + private int mIconTransformContentShiftNoIcon; private int mNotificationMinHeightLegacy; private int mMaxHeadsUpHeightLegacy; private int mMaxHeadsUpHeight; @@ -188,6 +193,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private View mChildAfterViewWhenDismissed; private View mGroupParentWhenDismissed; private boolean mRefocusOnDismiss; + private float mIconTransformationAmount; + private boolean mIconsVisible = true; + private boolean mAboveShelf; + private boolean mIsLastChild; public boolean isGroupExpansionChanging() { if (isChildInGroup()) { @@ -293,6 +302,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { // The public layouts expand button is always visible mPublicLayout.updateExpandButtons(true); updateLimits(); + updateIconVisibilities(); } private void updateLimits() { @@ -318,6 +328,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return mStatusBarNotification; } + public NotificationData.Entry getEntry() { + return mEntry; + } + public boolean isHeadsUp() { return mIsHeadsUp; } @@ -333,6 +347,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (intrinsicBefore != getIntrinsicHeight()) { notifyHeightChanged(false /* needsAnimation */); } + if (isHeadsUp) { + setAboveShelf(true); + } } public void setGroupManager(NotificationGroupManager groupManager) { @@ -400,6 +417,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (mNotificationParent != null) { mNotificationParent.updateBackgroundForGroupState(); } + updateIconVisibilities(); } @Override @@ -459,7 +477,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void getChildrenStates(StackScrollState resultState) { if (mIsSummaryWithChildren) { - StackViewState parentState = resultState.getViewStateForView(this); + ExpandableViewState parentState = resultState.getViewStateForView(this); mChildrenContainer.getState(resultState, parentState); } } @@ -476,11 +494,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } } - public void startChildAnimation(StackScrollState finalState, - StackStateAnimator stateAnimator, long delay, long duration) { + public void startChildAnimation(StackScrollState finalState, AnimationProperties properties) { if (mIsSummaryWithChildren) { - mChildrenContainer.startAnimationToState(finalState, stateAnimator, delay, - duration); + mChildrenContainer.startAnimationToState(finalState, properties); } } @@ -522,12 +538,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return mIsPinned; } + @Override + public int getPinnedHeadsUpHeight() { + return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); + } + /** * @param atLeastMinHeight should the value returned be at least the minimum height. * Used to avoid cyclic calls * @return the height of the heads up notification when pinned */ - public int getPinnedHeadsUpHeight(boolean atLeastMinHeight) { + private int getPinnedHeadsUpHeight(boolean atLeastMinHeight) { if (mIsSummaryWithChildren) { return mChildrenContainer.getIntrinsicHeight(); } @@ -764,9 +785,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return mChildrenContainer; } - public void setHeadsupDisappearRunning(boolean running) { - mHeadsupDisappearRunning = running; - mPrivateLayout.setHeadsupDisappearRunning(running); + public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { + mHeadsupDisappearRunning = headsUpAnimatingAway; + mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway); + } + + /** + * @return if the view was just heads upped and is now animating away. During such a time the + * layout needs to be kept consistent + */ + public boolean isHeadsUpAnimatingAway() { + return mHeadsupDisappearRunning; } public View getChildAfterViewWhenDismissed() { @@ -785,6 +814,105 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mVetoButton.setOnClickListener(listener); } + public View getNotificationIcon() { + NotificationHeaderView notificationHeader = getNotificationHeader(); + if (notificationHeader != null) { + return notificationHeader.getIcon(); + } + return null; + } + + /** + * @return whether the notification is currently showing a view with an icon. + */ + public boolean isShowingIcon() { + if (mIsSummaryWithChildren) { + return true; + } + NotificationContentView showingLayout = getShowingLayout(); + NotificationHeaderView notificationHeader = showingLayout.getVisibleNotificationHeader(); + return notificationHeader != null; + } + + /** + * Set how much this notification is transformed into an icon. + * + * @param iconTransformationAmount A value from 0 to 1 indicating how much we are transformed + * to an icon + * @param isLastChild is this the last child in the list. If true, then the transformation is + * different since it's content fades out. + */ + public void setIconTransformationAmount(float iconTransformationAmount, boolean isLastChild) { + boolean changeTransformation = isLastChild != mIsLastChild; + changeTransformation |= mIconTransformationAmount != iconTransformationAmount; + mIsLastChild = isLastChild; + mIconTransformationAmount = iconTransformationAmount; + if (changeTransformation) { + updateContentTransformation(); + boolean iconsVisible = mIconTransformationAmount == 0.0f; + if (iconsVisible != mIconsVisible) { + mIconsVisible = iconsVisible; + updateIconVisibilities(); + } + } + } + + @Override + protected void onBelowSpeedBumpChanged() { + updateIconVisibilities(); + } + + private void updateContentTransformation() { + float contentAlpha; + float translationY = - mIconTransformationAmount * mIconTransformContentShift; + if (mIsLastChild) { + contentAlpha = 1.0f - mIconTransformationAmount; + contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f); + contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha); + translationY *= 0.4f; + } else { + contentAlpha = 1.0f; + } + mPublicLayout.setAlpha(contentAlpha); + mPrivateLayout.setAlpha(contentAlpha); + mPublicLayout.setTranslationY(translationY); + mPrivateLayout.setTranslationY(translationY); + if (mChildrenContainer != null) { + mChildrenContainer.setAlpha(contentAlpha); + mChildrenContainer.setTranslationY(translationY); + // TODO: handle children fade out better + } + } + + private void updateIconVisibilities() { + boolean visible = isChildInGroup() || isBelowSpeedBump() || mIconsVisible; + mPublicLayout.setIconsVisible(visible); + mPrivateLayout.setIconsVisible(visible); + if (mChildrenContainer != null) { + mChildrenContainer.setIconsVisible(visible); + } + } + + /** + * Get the relative top padding of a view relative to this view. This recursively walks up the + * hierarchy and does the corresponding measuring. + * + * @param view the view to the the padding for. The requested view has to be a child of this + * notification. + * @return the toppadding + */ + public int getRelativeTopPadding(View view) { + int topPadding = 0; + while (view.getParent() instanceof ViewGroup) { + topPadding += view.getTop(); + view = (View) view.getParent(); + if (view instanceof ExpandableNotificationRow) { + return topPadding; + } + } + return topPadding; + } + public interface ExpansionLogger { public void logNotificationExpansion(String key, boolean userAction, boolean expanded); } @@ -804,6 +932,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height); mIncreasedPaddingBetweenElements = getResources() .getDimensionPixelSize(R.dimen.notification_divider_height_increased); + mIconTransformContentShiftNoIcon = getResources().getDimensionPixelSize( + R.dimen.notification_icon_transform_content_shift); } /** @@ -1276,6 +1406,21 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (mSettingsIconRow != null) { mSettingsIconRow.updateVerticalLocation(); } + updateContentShiftHeight(); + } + + /** + * Updates the content shift height such that the header is completely hidden when coming from + * the top. + */ + private void updateContentShiftHeight() { + NotificationHeaderView notificationHeader = getNotificationHeader(); + if (notificationHeader != null) { + CachingIconView icon = notificationHeader.getIcon(); + mIconTransformContentShift = getRelativeTopPadding(icon) + icon.getHeight(); + } else { + mIconTransformContentShift = mIconTransformContentShiftNoIcon; + } } private void updateMaxHeights() { @@ -1532,6 +1677,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } } + @Override + public void setClipBottomAmount(int clipBottomAmount) { + super.setClipBottomAmount(clipBottomAmount); + mPrivateLayout.setClipBottomAmount(clipBottomAmount); + mPublicLayout.setClipBottomAmount(clipBottomAmount); + if (mGuts != null) { + mGuts.setClipBottomAmount(clipBottomAmount); + } + } + public boolean isMaxExpandHeightInitialized() { return mMaxExpandHeight != 0; } @@ -1679,4 +1834,57 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public interface OnExpandClickListener { void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded); } + + @Override + public ExpandableViewState createNewViewState(StackScrollState stackScrollState) { + return new NotificationViewState(stackScrollState); + } + + @Override + public boolean isAboveShelf() { + return mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf); + } + + public void setAboveShelf(boolean aboveShelf) { + mAboveShelf = aboveShelf; + } + + public class NotificationViewState extends ExpandableViewState { + + private final StackScrollState mOverallState; + + + private NotificationViewState(StackScrollState stackScrollState) { + mOverallState = stackScrollState; + } + + @Override + public void applyToView(View view) { + super.applyToView(view); + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (this.isBottomClipped) { + row.setClipToActualHeight(true); + } + row.applyChildrenState(mOverallState); + } + } + + @Override + protected void onYTranslationAnimationFinished() { + super.onYTranslationAnimationFinished(); + if (mHeadsupDisappearRunning) { + setHeadsUpAnimatingAway(false); + } + } + + @Override + public void animateTo(View child, AnimationProperties properties) { + super.animateTo(child, properties); + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + row.startChildAnimation(mOverallState, properties); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java index 9d9f3b9640c4..4b95f073c843 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java @@ -97,13 +97,21 @@ public abstract class ExpandableOutlineView extends ExpandableView { if (mCustomOutline) { return; } - boolean hasOutline = true; + boolean hasOutline = needsOutline(); + setOutlineProvider(hasOutline ? mProvider : null); + } + + /** + * @return whether the view currently needs an outline. This is usually false in case it doesn't + * have a background. + */ + protected boolean needsOutline() { if (isChildInGroup()) { - hasOutline = isGroupExpanded() && !isGroupExpansionChanging(); + return isGroupExpanded() && !isGroupExpansionChanging(); } else if (isSummaryWithChildren()) { - hasOutline = !isGroupExpanded() || isGroupExpansionChanging(); + return !isGroupExpanded() || isGroupExpansionChanging(); } - setOutlineProvider(hasOutline ? mProvider : null); + return true; } public boolean isOutlineShowing() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index 83b0ee079639..0f5981bc8489 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -25,6 +25,8 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; +import com.android.systemui.statusbar.stack.ExpandableViewState; +import com.android.systemui.statusbar.stack.StackScrollState; import java.util.ArrayList; @@ -36,6 +38,7 @@ public abstract class ExpandableView extends FrameLayout { protected OnHeightChangedListener mOnHeightChangedListener; private int mActualHeight; protected int mClipTopAmount; + private float mClipBottomAmount; private boolean mDark; private ArrayList<View> mMatchParentViews = new ArrayList<View>(); private static Rect mClipRect = new Rect(); @@ -44,6 +47,8 @@ public abstract class ExpandableView extends FrameLayout { private boolean mClipToActualHeight = true; private boolean mChangingPosition = false; private ViewGroup mTransientContainer; + private boolean mInShelf; + private boolean mTransformingInShelf; public ExpandableView(Context context, AttributeSet attrs) { super(context, attrs); @@ -220,10 +225,26 @@ public abstract class ExpandableView extends FrameLayout { updateClipping(); } + /** + * Set the amount the the notification is clipped on the bottom in addition to the regular + * clipping. This is mainly used to clip something in a non-animated way without changing the + * actual height of the notification and is purely visual. + * + * @param clipBottomAmount the amount to clip. + */ + public void setClipBottomAmount(int clipBottomAmount) { + mClipBottomAmount = clipBottomAmount; + updateClipping(); + } + public int getClipTopAmount() { return mClipTopAmount; } + public float getClipBottomAmount() { + return mClipBottomAmount; + } + public void setOnHeightChangedListener(OnHeightChangedListener listener) { mOnHeightChangedListener = listener; } @@ -261,9 +282,18 @@ public abstract class ExpandableView extends FrameLayout { public abstract void performAddAnimation(long delay, long duration); + /** + * Set the notification appearance to be below the speed bump. + * @param below true if it is below. + */ public void setBelowSpeedBump(boolean below) { } + public int getPinnedHeadsUpHeight() { + return getIntrinsicHeight(); + } + + /** * Sets the translation of the view. */ @@ -327,7 +357,8 @@ public abstract class ExpandableView extends FrameLayout { if (top >= getActualHeight()) { top = getActualHeight() - 1; } - mClipRect.set(0, top, getWidth(), getActualHeight() + getExtraBottomPadding()); + mClipRect.set(0, top, getWidth(), (int) (getActualHeight() + getExtraBottomPadding() + - mClipBottomAmount)); setClipBounds(mClipRect); } else { setClipBounds(null); @@ -438,6 +469,46 @@ public abstract class ExpandableView extends FrameLayout { public void setActualHeightAnimating(boolean animating) {} + public ExpandableViewState createNewViewState(StackScrollState stackScrollState) { + return new ExpandableViewState(); + } + + /** + * @return whether the current view doesn't add height to the overall content. This means that + * if it is added to a list of items, it's content will still have the same height. + * An example is the notification shelf, that is always placed on top of another view. + */ + public boolean hasNoContentHeight() { + return false; + } + + /** + * @param inShelf whether the view is currently fully in the notification shelf. + */ + public void setInShelf(boolean inShelf) { + mInShelf = inShelf; + } + + public boolean isInShelf() { + return mInShelf; + } + + /** + * @param transformingInShelf whether the view is currently transforming into the shelf in an + * animated way + */ + public void setTransformingInShelf(boolean transformingInShelf) { + mTransformingInShelf = transformingInShelf; + } + + public boolean isTransformingIntoShelf() { + return mTransformingInShelf; + } + + public boolean isAboveShelf() { + return false; + } + /** * A listener notifying when {@link #getActualHeight} changes. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java index 8688c2811ce3..dea9e31fcfc0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java @@ -33,6 +33,7 @@ public class NotificationBackgroundView extends View { private Drawable mBackground; private int mClipTopAmount; private int mActualHeight; + private int mClipBottomAmount; public NotificationBackgroundView(Context context, AttributeSet attrs) { super(context, attrs); @@ -44,8 +45,9 @@ public class NotificationBackgroundView extends View { } private void draw(Canvas canvas, Drawable drawable) { - if (drawable != null && mActualHeight > mClipTopAmount) { - drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight); + int bottom = mActualHeight - mClipBottomAmount; + if (drawable != null && bottom > mClipTopAmount) { + drawable.setBounds(0, mClipTopAmount, getWidth(), bottom); drawable.draw(canvas); } } @@ -120,6 +122,11 @@ public class NotificationBackgroundView extends View { invalidate(); } + public void setClipBottomAmount(int clipBottomAmount) { + mClipBottomAmount = clipBottomAmount; + invalidate(); + } + @Override public boolean hasOverlappingRendering() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 58d57f68d699..ad6a5dbfd818 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -121,7 +121,9 @@ public class NotificationContentView extends FrameLayout { private int mContentHeightAtAnimationStart = UNDEFINED; private boolean mFocusOnVisibilityChange; - private boolean mHeadsupDisappearRunning; + private boolean mHeadsUpAnimatingAway; + private boolean mIconsVisible; + private int mClipBottomAmount; public NotificationContentView(Context context, AttributeSet attrs) { @@ -456,7 +458,7 @@ public class NotificationContentView extends FrameLayout { isTransitioningFromTo(VISIBLE_TYPE_HEADSUP, VISIBLE_TYPE_EXPANDED) || isTransitioningFromTo(VISIBLE_TYPE_EXPANDED, VISIBLE_TYPE_HEADSUP); boolean pinned = !isVisibleOrTransitioning(VISIBLE_TYPE_CONTRACTED) - && (mIsHeadsUp || mHeadsupDisappearRunning); + && (mIsHeadsUp || mHeadsUpAnimatingAway); if (transitioningBetweenHunAndExpanded || pinned) { return Math.min(mHeadsUpChild.getHeight(), mExpandedChild.getHeight()); } @@ -587,9 +589,24 @@ public class NotificationContentView extends FrameLayout { updateClipping(); } + + public void setClipBottomAmount(int clipBottomAmount) { + mClipBottomAmount = clipBottomAmount; + updateClipping(); + } + + @Override + public void setTranslationY(float translationY) { + super.setTranslationY(translationY); + updateClipping(); + } + private void updateClipping() { if (mClipToActualHeight) { - mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight); + int top = (int) (mClipTopAmount - getTranslationY()); + int bottom = (int) (mContentHeight - mClipBottomAmount - getTranslationY()); + bottom = Math.max(top, bottom); + mClipBounds.set(0, top, getWidth(), bottom); setClipBounds(mClipBounds); } else { setClipBounds(null); @@ -840,7 +857,7 @@ public class NotificationContentView extends FrameLayout { return VISIBLE_TYPE_SINGLELINE; } - if ((mIsHeadsUp || mHeadsupDisappearRunning) && mHeadsUpChild != null) { + if ((mIsHeadsUp || mHeadsUpAnimatingAway) && mHeadsUpChild != null) { if (viewHeight <= mHeadsUpChild.getHeight() || noExpandedChild) { return VISIBLE_TYPE_HEADSUP; } else { @@ -1183,12 +1200,38 @@ public class NotificationContentView extends FrameLayout { } } - public void setHeadsupDisappearRunning(boolean headsupDisappearRunning) { - mHeadsupDisappearRunning = headsupDisappearRunning; + public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { + mHeadsUpAnimatingAway = headsUpAnimatingAway; selectLayout(false /* animate */, true /* force */); } public void setFocusOnVisibilityChange() { mFocusOnVisibilityChange = true; } + + public void setIconsVisible(boolean iconsVisible) { + mIconsVisible = iconsVisible; + updateIconVisibilities(); + } + + private void updateIconVisibilities() { + if (mContractedWrapper != null) { + NotificationHeaderView header = mContractedWrapper.getNotificationHeader(); + if (header != null) { + header.getIcon().setForceHidden(!mIconsVisible); + } + } + if (mHeadsUpWrapper != null) { + NotificationHeaderView header = mHeadsUpWrapper.getNotificationHeader(); + if (header != null) { + header.getIcon().setForceHidden(!mIconsVisible); + } + } + if (mExpandedWrapper != null) { + NotificationHeaderView header = mExpandedWrapper.getNotificationHeader(); + if (header != null) { + header.getIcon().setForceHidden(!mIconsVisible); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 7019880784a3..3687f6d3d84e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import android.app.Notification; import android.app.NotificationManager; import android.content.Context; +import android.graphics.drawable.Icon; import android.os.SystemClock; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; @@ -26,8 +27,11 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.view.View; +import android.widget.ImageView; import android.widget.RemoteViews; +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.util.NotificationColorUtil; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -48,9 +52,11 @@ public class NotificationData { public static final class Entry { private static final long LAUNCH_COOLDOWN = 2000; private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN; + private static final int COLOR_INVALID = 1; public String key; public StatusBarNotification notification; public StatusBarIconView icon; + public StatusBarIconView expandedIcon; public ExpandableNotificationRow row; // the outer expanded view private boolean interruption; public boolean autoRedacted; // whether the redacted notification was generated by us @@ -62,11 +68,12 @@ public class NotificationData { public RemoteViews cachedHeadsUpContentView; public RemoteViews cachedPublicContentView; public CharSequence remoteInputText; + private int mCachedContrastColor = COLOR_INVALID; + private int mCachedContrastColorIsFor = COLOR_INVALID; - public Entry(StatusBarNotification n, StatusBarIconView ic) { + public Entry(StatusBarNotification n) { this.key = n.getKey(); this.notification = n; - this.icon = ic; } public void setInterruption() { @@ -165,6 +172,85 @@ public class NotificationData { public boolean hasJustLaunchedFullScreenIntent() { return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN; } + + /** + * Create the icons for a notification + * @param context the context to create the icons with + * @param sbn the notification + * @throws IconException + */ + public void createIcons(Context context, StatusBarNotification sbn) throws IconException { + Notification n = sbn.getNotification(); + final Icon smallIcon = n.getSmallIcon(); + if (smallIcon == null) { + throw new IconException("No small icon in notification from " + + sbn.getPackageName()); + } + + // Construct the icon. + icon = new StatusBarIconView(context, + sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n); + icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + + // Construct the expanded icon. + expandedIcon = new StatusBarIconView(context, + sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n); + expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + final StatusBarIcon ic = new StatusBarIcon( + sbn.getUser(), + sbn.getPackageName(), + smallIcon, + n.iconLevel, + n.number, + StatusBarIconView.contentDescForNotification(context, n)); + if (!icon.set(ic) || !expandedIcon.set(ic)) { + icon = null; + expandedIcon = null; + throw new IconException("Couldn't create icon: " + ic); + } + } + + public void setIconTag(int key, Object tag) { + if (icon != null) { + icon.setTag(key, tag); + expandedIcon.setTag(key, tag); + } + } + + /** + * Update the notification icons. + * @param context the context to create the icons with. + * @param n the notification to read the icon from. + * @throws IconException + */ + public void updateIcons(Context context, Notification n) throws IconException { + if (icon != null) { + // Update the icon + final StatusBarIcon ic = new StatusBarIcon( + notification.getUser(), + notification.getPackageName(), + n.getSmallIcon(), + n.iconLevel, + n.number, + StatusBarIconView.contentDescForNotification(context, n)); + icon.setNotification(n); + expandedIcon.setNotification(n); + if (!icon.set(ic) || !expandedIcon.set(ic)) { + throw new IconException("Couldn't update icon: " + ic); + } + } + } + + public int getContrastedColor(Context context) { + int rawColor = notification.getNotification().color; + if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) { + return mCachedContrastColor; + } + final int contrasted = NotificationColorUtil.resolveContrastColor(context, rawColor); + mCachedContrastColorIsFor = rawColor; + mCachedContrastColor = contrasted; + return mCachedContrastColor; + } } private final ArrayMap<String, Entry> mEntries = new ArrayMap<>(); @@ -472,4 +558,10 @@ public class NotificationData { public String getCurrentMediaNotificationKey(); public NotificationGroupManager getGroupManager(); } + + public static class IconException extends Exception { + IconException(String error) { + super(error); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java index bb327eff5c40..ed1179aa0f63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java @@ -62,6 +62,7 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab private Drawable mBackground; private int mClipTopAmount; + private int mClipBottomAmount; private int mActualHeight; private boolean mExposed; private INotificationManager mINotificationManager; @@ -136,8 +137,10 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab } private void draw(Canvas canvas, Drawable drawable) { - if (drawable != null) { - drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight); + int top = mClipTopAmount; + int bottom = mActualHeight - mClipBottomAmount; + if (drawable != null && top < bottom) { + drawable.setBounds(0, top, getWidth(), bottom); drawable.draw(canvas); } } @@ -423,6 +426,11 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab invalidate(); } + public void setClipBottomAmount(int clipBottomAmount) { + mClipBottomAmount = clipBottomAmount; + invalidate(); + } + @Override public boolean hasOverlappingRendering() { // Prevents this view from creating a layer when alpha is animating. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java deleted file mode 100644 index 8e8ce1a2b5dd..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2014 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; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.TextView; - -import com.android.systemui.R; -import com.android.systemui.ViewInvertHelper; -import com.android.systemui.statusbar.phone.NotificationPanelView; - -/** - * Container view for overflowing notification icons on Keyguard. - */ -public class NotificationOverflowContainer extends ActivatableNotificationView { - - private NotificationOverflowIconsView mIconsView; - private ViewInvertHelper mViewInvertHelper; - private boolean mDark; - private View mContent; - - public NotificationOverflowContainer(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view); - mIconsView.setMoreText((TextView) findViewById(R.id.more_text)); - mIconsView.setOverflowIndicator(findViewById(R.id.more_icon_overflow)); - mContent = findViewById(R.id.content); - mViewInvertHelper = new ViewInvertHelper(mContent, - NotificationPanelView.DOZE_ANIMATION_DURATION); - } - - @Override - public void setDark(boolean dark, boolean fade, long delay) { - super.setDark(dark, fade, delay); - if (mDark == dark) return; - mDark = dark; - if (fade) { - mViewInvertHelper.fade(dark, delay); - } else { - mViewInvertHelper.update(dark); - } - } - - @Override - protected View getContentView() { - return mContent; - } - - public NotificationOverflowIconsView getIconsView() { - return mIconsView; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java deleted file mode 100644 index 88bb714cc4e4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2014 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; - -import android.app.Notification; -import android.content.Context; -import android.graphics.PorterDuff; -import android.util.AttributeSet; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.internal.util.NotificationColorUtil; -import com.android.systemui.R; -import com.android.systemui.statusbar.phone.IconMerger; - -/** - * A view to display all the overflowing icons on Keyguard. - */ -public class NotificationOverflowIconsView extends IconMerger { - - private TextView mMoreText; - private int mTintColor; - private int mIconSize; - private NotificationColorUtil mNotificationColorUtil; - - public NotificationOverflowIconsView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mNotificationColorUtil = NotificationColorUtil.getInstance(getContext()); - mTintColor = getContext().getColor(R.color.keyguard_overflow_content_color); - mIconSize = getResources().getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_icon_size); - } - - public void setMoreText(TextView moreText) { - mMoreText = moreText; - } - - public void addNotification(NotificationData.Entry notification) { - StatusBarIconView v = new StatusBarIconView(getContext(), "", - notification.notification.getNotification()); - v.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - addView(v, mIconSize, mIconSize); - v.set(notification.icon.getStatusBarIcon()); - applyColor(notification.notification.getNotification(), v); - updateMoreText(); - } - - private void applyColor(Notification notification, StatusBarIconView view) { - view.setColorFilter(mTintColor, PorterDuff.Mode.MULTIPLY); - } - - private void updateMoreText() { - mMoreText.setText( - getResources().getString(R.string.keyguard_more_overflow_text, getChildCount())); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java new file mode 100644 index 000000000000..6b9a89ef655b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -0,0 +1,469 @@ +/* + * 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.systemui.statusbar; + +import android.content.Context; +import android.content.res.Configuration; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.ViewInvertHelper; +import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.phone.NotificationIconContainer; +import com.android.systemui.statusbar.phone.NotificationPanelView; +import com.android.systemui.statusbar.stack.AmbientState; +import com.android.systemui.statusbar.stack.AnimationProperties; +import com.android.systemui.statusbar.stack.ExpandableViewState; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; +import com.android.systemui.statusbar.stack.StackScrollState; + +import java.util.ArrayList; +import java.util.WeakHashMap; + +/** + * A notification shelf view that is placed inside the notification scroller. It manages the + * overflow icons that don't fit into the regular list anymore. + */ +public class NotificationShelf extends ActivatableNotificationView { + + private ViewInvertHelper mViewInvertHelper; + private boolean mDark; + private NotificationIconContainer mShelfIcons; + private ArrayList<StatusBarIconView> mIcons = new ArrayList<>(); + private ShelfState mShelfState; + private int[] mTmp = new int[2]; + private boolean mHideBackground; + private int mIconAppearTopPadding; + private int mStatusBarHeight; + private int mStatusBarPaddingStart; + private AmbientState mAmbientState; + private NotificationStackScrollLayout mHostLayout; + private int mMaxLayoutHeight; + private int mPaddingBetweenElements; + private int mNotGoneIndex; + private boolean mHasItemsInStableShelf; + private NotificationIconContainer mCollapsedIcons; + + public NotificationShelf(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mShelfIcons = (NotificationIconContainer) findViewById(R.id.content); + mShelfIcons.setClipChildren(false); + mShelfIcons.setClipToPadding(false); + + setClipToActualHeight(false); + setClipChildren(false); + setClipToPadding(false); + mShelfIcons.setShowAllIcons(false); + mViewInvertHelper = new ViewInvertHelper(mShelfIcons, + NotificationPanelView.DOZE_ANIMATION_DURATION); + mShelfState = new ShelfState(); + initDimens(); + } + + public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) { + mAmbientState = ambientState; + mHostLayout = hostLayout; + } + + private void initDimens() { + mIconAppearTopPadding = getResources().getDimensionPixelSize( + R.dimen.notification_icon_appear_padding); + mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height); + mStatusBarPaddingStart = getResources().getDimensionPixelOffset( + R.dimen.status_bar_padding_start); + mPaddingBetweenElements = getResources().getDimensionPixelSize( + R.dimen.notification_divider_height); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + initDimens(); + } + + @Override + public void setDark(boolean dark, boolean fade, long delay) { + super.setDark(dark, fade, delay); + if (mDark == dark) return; + mDark = dark; + if (fade) { + mViewInvertHelper.fade(dark, delay); + } else { + mViewInvertHelper.update(dark); + } + } + + @Override + protected View getContentView() { + return mShelfIcons; + } + + public NotificationIconContainer getShelfIcons() { + return mShelfIcons; + } + + @Override + public ExpandableViewState createNewViewState(StackScrollState stackScrollState) { + return mShelfState; + } + + public void updateState(StackScrollState resultState, + AmbientState ambientState) { + View lastView = ambientState.getLastVisibleBackgroundChild(); + if (lastView != null) { + float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding() + + ambientState.getStackTranslation(); + ExpandableViewState lastViewState = resultState.getViewStateForView(lastView); + float viewEnd = lastViewState.yTranslation + lastViewState.height; + mShelfState.copyFrom(lastViewState); + mShelfState.height = getIntrinsicHeight(); + mShelfState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - mShelfState.height, + getFullyClosedTranslation()); + mShelfState.zTranslation = ambientState.getBaseZHeight(); + float openedAmount = (mShelfState.yTranslation - getFullyClosedTranslation()) + / (getIntrinsicHeight() * 2); + openedAmount = Math.min(1.0f, openedAmount); + mShelfState.openedAmount = openedAmount; + mShelfState.clipTopAmount = 0; + mShelfState.alpha = 1.0f; + mShelfState.belowSpeedBump = mAmbientState.getSpeedBumpIndex() == 0; + mShelfState.shadowAlpha = 1.0f; + mShelfState.isBottomClipped = false; + mShelfState.hideSensitive = false; + mShelfState.xTranslation = getTranslationX(); + if (mNotGoneIndex != -1) { + mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex); + } + mShelfState.hasItemsInStableShelf = lastViewState.inShelf; + } else { + mShelfState.hidden = true; + mShelfState.location = ExpandableViewState.LOCATION_GONE; + mShelfState.hasItemsInStableShelf = false; + } + } + + /** + * Update the shelf appearance based on the other notifications around it. This transforms + * the icons from the notification area into the shelf. + */ + public void updateAppearance() { + WeakHashMap<View, NotificationIconContainer.IconState> iconStates = + mShelfIcons.resetViewStates(); + float numViewsInShelf = 0.0f; + View lastChild = mAmbientState.getLastVisibleBackgroundChild(); + mNotGoneIndex = -1; + float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2; + float expandAmount = 0.0f; + if (getTranslationY() >= interpolationStart) { + expandAmount = (getTranslationY() - interpolationStart) / getIntrinsicHeight(); + expandAmount = Math.min(1.0f, expandAmount); + } + // find the first view that doesn't overlap with the shelf + int notificationIndex = 0; + int notGoneIndex = 0; + int colorOfViewBeforeLast = 0; + boolean backgroundForceHidden = false; + if (mHideBackground && !mShelfState.hasItemsInStableShelf) { + backgroundForceHidden = true; + } + int colorTwoBefore = NO_COLOR; + int previousColor = NO_COLOR; + float transitionAmount = 0.0f; + while (notificationIndex < mHostLayout.getChildCount()) { + ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex); + notificationIndex++; + if (!(child instanceof ExpandableNotificationRow) + || child.getVisibility() == GONE) { + continue; + } + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + StatusBarIconView icon = row.getEntry().expandedIcon; + NotificationIconContainer.IconState iconState = iconStates.get(icon); + float notificationClipEnd; + float shelfStart = getTranslationY(); + boolean aboveShelf = row.getTranslationZ() > mAmbientState.getBaseZHeight(); + boolean isLastChild = child == lastChild; + if (isLastChild || aboveShelf || backgroundForceHidden) { + notificationClipEnd = shelfStart + getIntrinsicHeight(); + } else { + notificationClipEnd = shelfStart - mPaddingBetweenElements; + float height = notificationClipEnd - row.getTranslationY(); + if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) { + // We want the gap to close when we reached the minimum size and only shrink + // before + notificationClipEnd = Math.min(shelfStart, + row.getTranslationY() + getNotificationMergeSize()); + } + } + updateNotificationClipHeight(row, notificationClipEnd); + float inShelfAmount = updateIconAppearance(row, iconState, icon, expandAmount, + isLastChild); + numViewsInShelf += inShelfAmount; + int ownColorUntinted = row.getBackgroundColorWithoutTint(); + if (row.getTranslationY() >= getTranslationY() && mNotGoneIndex == -1) { + mNotGoneIndex = notGoneIndex; + setTintColor(previousColor); + setOverrideTintColor(colorTwoBefore, transitionAmount); + + } else if (mNotGoneIndex == -1) { + colorTwoBefore = previousColor; + transitionAmount = inShelfAmount; + } + if (isLastChild && colorOfViewBeforeLast != NO_COLOR) { + row.setOverrideTintColor(colorOfViewBeforeLast, inShelfAmount); + } else { + colorOfViewBeforeLast = ownColorUntinted; + row.setOverrideTintColor(NO_COLOR, 0 /* overrideAmount */); + } + if (notGoneIndex != 0 || !aboveShelf) { + row.setAboveShelf(false); + } + notGoneIndex++; + previousColor = ownColorUntinted; + } + mShelfIcons.calculateIconTranslations(); + mShelfIcons.applyIconStates(); + setVisibility(numViewsInShelf != 0.0f && mAmbientState.isShadeExpanded() + ? VISIBLE + : INVISIBLE); + boolean hideBackground = numViewsInShelf < 1.0f; + setHideBackground(hideBackground || backgroundForceHidden); + if (mNotGoneIndex == -1) { + mNotGoneIndex = notGoneIndex; + } + } + + private void updateNotificationClipHeight(ExpandableNotificationRow row, + float notificationClipEnd) { + float viewEnd = row.getTranslationY() + row.getActualHeight(); + if (viewEnd > notificationClipEnd + && (mAmbientState.isShadeExpanded() + || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) { + row.setClipBottomAmount((int) (viewEnd - notificationClipEnd)); + } else { + row.setClipBottomAmount(0); + } + } + + /** + * @return the icon amount how much this notification is in the shelf; + */ + private float updateIconAppearance(ExpandableNotificationRow row, + NotificationIconContainer.IconState iconState, StatusBarIconView icon, + float expandAmount, boolean isLastChild) { + // Let calculate how much the view is in the shelf + float viewStart = row.getTranslationY(); + int transformHeight = row.getActualHeight() + mPaddingBetweenElements; + if (isLastChild) { + transformHeight = + Math.min(transformHeight, row.getMinHeight() - getIntrinsicHeight()); + } + float viewEnd = viewStart + transformHeight; + float iconAppearAmount; + float yTranslation; + float alpha = 1.0f; + if (viewEnd >= getTranslationY() && (mAmbientState.isShadeExpanded() + || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) { + if (viewStart < getTranslationY()) { + float linearAmount = (getTranslationY() - viewStart) / transformHeight; + float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation( + linearAmount); + interpolatedAmount = NotificationUtils.interpolate( + interpolatedAmount, linearAmount, expandAmount); + iconAppearAmount = 1.0f - interpolatedAmount; + } else { + iconAppearAmount = 1.0f; + } + } else { + iconAppearAmount = 0.0f; + } + + // Lets now calculate how much of the transformation has already happened. This is different + // from the above, since we only start transforming when the view is already quite a bit + // pushed in. + View rowIcon = row.getNotificationIcon(); + float notificationIconPosition = viewStart; + float notificationIconSize = 0.0f; + int iconTopPadding; + if (rowIcon != null) { + iconTopPadding = row.getRelativeTopPadding(rowIcon); + notificationIconSize = rowIcon.getHeight(); + } else { + iconTopPadding = mIconAppearTopPadding; + } + notificationIconPosition += iconTopPadding; + float shelfIconPosition = getTranslationY() + icon.getTop(); + shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f; + float transitionDistance = getIntrinsicHeight() * 1.5f; + if (isLastChild) { + transitionDistance = Math.min(transitionDistance, row.getMinHeight() + - getIntrinsicHeight()); + } + float transformationStartPosition = getTranslationY() - transitionDistance; + float transitionAmount = 0.0f; + if (viewStart < transformationStartPosition + || (!mAmbientState.isShadeExpanded() + && (row.isPinned() || row.isHeadsUpAnimatingAway()))) { + // We simply place it on the icon of the notification + yTranslation = notificationIconPosition - shelfIconPosition; + } else { + transitionAmount = (viewStart - transformationStartPosition) + / transitionDistance; + float startPosition = transformationStartPosition + iconTopPadding; + yTranslation = NotificationUtils.interpolate( + startPosition - shelfIconPosition, 0, transitionAmount); + // If we are merging into the shelf, lets make sure the shelf is at least on our height, + // otherwise the icons won't be visible. + setTranslationZ(Math.max(getTranslationZ(), row.getTranslationZ())); + } + float shelfIconSize = icon.getHeight() * icon.getIconScale(); + if (!row.isShowingIcon()) { + // The view currently doesn't have an icon, lets transform it in! + alpha = transitionAmount; + notificationIconSize = shelfIconSize / 2.0f; + } + // The notification size is different from the size in the shelf / statusbar + float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize, + transitionAmount); + row.setIconTransformationAmount(transitionAmount, isLastChild); + if (iconState != null) { + iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale(); + iconState.scaleY = iconState.scaleX; + iconState.hidden = transitionAmount == 0.0f; + iconState.iconAppearAmount = iconAppearAmount; + iconState.alpha = alpha; + iconState.yTranslation = yTranslation; + icon.setVisibility(transitionAmount == 0.0f ? INVISIBLE : VISIBLE); + if (row.isInShelf() && !row.isTransformingIntoShelf()) { + iconState.iconAppearAmount = 1.0f; + iconState.alpha = 1.0f; + iconState.scaleX = 1.0f; + iconState.scaleY = 1.0f; + iconState.hidden = false; + } + } + return iconAppearAmount; + } + + private float getFullyClosedTranslation() { + return - (getIntrinsicHeight() - mStatusBarHeight) / 2; + } + + public int getNotificationMergeSize() { + return getIntrinsicHeight(); + } + + @Override + public boolean hasNoContentHeight() { + return true; + } + + private void setHideBackground(boolean hideBackground) { + mHideBackground = hideBackground; + updateBackground(); + updateOutline(); + } + + public boolean hidesBackground() { + return mHideBackground; + } + + @Override + protected boolean needsOutline() { + return !mHideBackground && super.needsOutline(); + } + + @Override + protected boolean shouldHideBackground() { + return super.shouldHideBackground() || mHideBackground; + } + + private void setOpenedAmount(float openedAmount) { + mCollapsedIcons.getLocationOnScreen(mTmp); + int start = mTmp[0]; + if (isLayoutRtl()) { + start = getWidth() - start - mCollapsedIcons.getWidth(); + } + int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(), + mShelfIcons.getWidth(), + openedAmount); + mShelfIcons.setActualLayoutWidth(width); + float padding = NotificationUtils.interpolate(mCollapsedIcons.getPaddingEnd(), + mShelfIcons.getPaddingEnd(), + openedAmount); + mShelfIcons.setActualPaddingEnd(padding); + float paddingStart = NotificationUtils.interpolate(start, + mShelfIcons.getPaddingStart(), openedAmount); + mShelfIcons.setActualPaddingStart(paddingStart); + } + + public void setMaxLayoutHeight(int maxLayoutHeight) { + mMaxLayoutHeight = maxLayoutHeight; + } + + /** + * @return the index of the notification at which the shelf visually resides + */ + public int getNotGoneIndex() { + return mNotGoneIndex; + } + + private void setHasItemsInStableShelf(boolean hasItemsInStableShelf) { + mHasItemsInStableShelf = hasItemsInStableShelf; + } + + /** + * @return whether the shelf has any icons in it when a potential animation has finished, i.e + * if the current state would be applied right now + */ + public boolean hasItemsInStableShelf() { + return mHasItemsInStableShelf; + } + + public void setCollapsedIcons(NotificationIconContainer collapsedIcons) { + mCollapsedIcons = collapsedIcons; + } + + private class ShelfState extends ExpandableViewState { + private float openedAmount; + private boolean hasItemsInStableShelf; + + @Override + public void applyToView(View view) { + super.applyToView(view); + updateAppearance(); + setOpenedAmount(openedAmount); + setHasItemsInStableShelf(hasItemsInStableShelf); + } + + @Override + public void animateTo(View child, AnimationProperties properties) { + super.animateTo(child, properties); + setOpenedAmount(openedAmount); + updateAppearance(); + setHasItemsInStableShelf(hasItemsInStableShelf); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index cdfdad464065..d635bb06d318 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.app.Notification; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -30,20 +33,55 @@ import android.os.Parcelable; import android.os.UserHandle; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.FloatProperty; import android.util.Log; +import android.util.Property; import android.util.TypedValue; import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; +import android.view.animation.Interpolator; import com.android.internal.statusbar.StatusBarIcon; +import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.NotificationUtils; import java.text.NumberFormat; public class StatusBarIconView extends AnimatedImageView { + public static final int STATE_ICON = 0; + public static final int STATE_DOT = 1; + public static final int STATE_HIDDEN = 2; + private static final String TAG = "StatusBarIconView"; - private boolean mAlwaysScaleIcon; + private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT + = new FloatProperty<StatusBarIconView>("iconAppearAmount") { + + @Override + public void setValue(StatusBarIconView object, float value) { + object.setIconAppearAmount(value); + } + @Override + public Float get(StatusBarIconView object) { + return object.getIconAppearAmount(); + } + }; + private static final Property<StatusBarIconView, Float> DOT_APPEAR_AMOUNT + = new FloatProperty<StatusBarIconView>("dot_appear_amount") { + + @Override + public void setValue(StatusBarIconView object, float value) { + object.setDotAppearAmount(value); + } + + @Override + public Float get(StatusBarIconView object) { + return object.getDotAppearAmount(); + } + }; + + private boolean mAlwaysScaleIcon; private StatusBarIcon mIcon; @ViewDebug.ExportedProperty private String mSlot; private Drawable mNumberBackground; @@ -54,6 +92,16 @@ public class StatusBarIconView extends AnimatedImageView { private Notification mNotification; private final boolean mBlocked; private int mDensity; + private float mIconScale = 1.0f; + private final Paint mDotPaint = new Paint(); + private boolean mDotVisible; + private float mDotRadius; + private int mStaticDotRadius; + private int mVisibleState = STATE_ICON; + private float mIconAppearAmount = 1.0f; + private ObjectAnimator mIconAppearAnimator; + private ObjectAnimator mDotAnimator; + private float mDotAppearAmount; public StatusBarIconView(Context context, String slot, Notification notification) { this(context, slot, notification, false); @@ -72,6 +120,11 @@ public class StatusBarIconView extends AnimatedImageView { maybeUpdateIconScale(); setScaleType(ScaleType.CENTER); mDensity = context.getResources().getDisplayMetrics().densityDpi; + if (mNotification != null) { + setIconTint(getContext().getColor( + com.android.internal.R.color.notification_icon_default_color)); + } + reloadDimens(); } private void maybeUpdateIconScale() { @@ -86,9 +139,11 @@ public class StatusBarIconView extends AnimatedImageView { Resources res = mContext.getResources(); final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size); final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size); - final float scale = (float)imageBounds / (float)outerBounds; - setScaleX(scale); - setScaleY(scale); + mIconScale = (float)imageBounds / (float)outerBounds; + } + + public float getIconScale() { + return mIconScale; } @Override @@ -99,6 +154,15 @@ public class StatusBarIconView extends AnimatedImageView { mDensity = density; maybeUpdateIconScale(); updateDrawable(); + reloadDimens(); + } + } + + private void reloadDimens() { + boolean applyRadius = mDotRadius == mStaticDotRadius; + mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); + if (applyRadius) { + mDotRadius = mStaticDotRadius; } } @@ -259,12 +323,32 @@ public class StatusBarIconView extends AnimatedImageView { @Override protected void onDraw(Canvas canvas) { - super.onDraw(canvas); + if (mIconAppearAmount > 0.0f) { + canvas.save(); + canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount, + getWidth() / 2, getHeight() / 2); + super.onDraw(canvas); + canvas.restore(); + } if (mNumberBackground != null) { mNumberBackground.draw(canvas); canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain); } + if (mDotAppearAmount != 0.0f) { + float radius; + float alpha; + if (mDotAppearAmount <= 1.0f) { + radius = mDotRadius * mDotAppearAmount; + alpha = 1.0f; + } else { + float fadeOutAmount = mDotAppearAmount - 1.0f; + alpha = 1.0f - fadeOutAmount; + radius = NotificationUtils.interpolate(mDotRadius, getWidth() / 4, fadeOutAmount); + } + mDotPaint.setAlpha((int) (alpha * 255)); + canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mDotPaint); + } } @Override @@ -351,4 +435,97 @@ public class StatusBarIconView extends AnimatedImageView { return c.getString(R.string.accessibility_desc_notification_icon, appName, desc); } + public void setIconTint(int iconTint) { + mDotPaint.setColor(iconTint); + } + + public void setVisibleState(int state) { + setVisibleState(state, true /* animate */, null /* endRunnable */); + } + + public void setVisibleState(int state, boolean animate) { + setVisibleState(state, animate, null); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } + + public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable) { + if (visibleState != mVisibleState) { + mVisibleState = visibleState; + if (animate) { + if (mIconAppearAnimator != null) { + mIconAppearAnimator.cancel(); + } + float targetAmount = 0.0f; + Interpolator interpolator = Interpolators.FAST_OUT_LINEAR_IN; + if (visibleState == STATE_ICON) { + targetAmount = 1.0f; + interpolator = Interpolators.LINEAR_OUT_SLOW_IN; + } + mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT, + targetAmount); + mIconAppearAnimator.setInterpolator(interpolator); + mIconAppearAnimator.setDuration(100); + mIconAppearAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mIconAppearAnimator = null; + if (endRunnable != null) { + endRunnable.run(); + } + } + }); + mIconAppearAnimator.start(); + + if (mDotAnimator != null) { + mDotAnimator.cancel(); + } + targetAmount = visibleState == STATE_ICON ? 2.0f : 0.0f; + interpolator = Interpolators.FAST_OUT_LINEAR_IN; + if (visibleState == STATE_DOT) { + targetAmount = 1.0f; + interpolator = Interpolators.LINEAR_OUT_SLOW_IN; + } + mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT, + targetAmount); + mDotAnimator.setInterpolator(interpolator); + mDotAnimator.setDuration(100); + mDotAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mDotAnimator = null; + } + }); + mDotAnimator.start(); + } else { + setIconAppearAmount(visibleState == STATE_ICON ? 1.0f : 0.0f); + setDotAppearAmount(visibleState == STATE_DOT ? 1.0f : 0.0f); + } + } + } + + public void setIconAppearAmount(float iconAppearAmount) { + mIconAppearAmount = iconAppearAmount; + invalidate(); + } + + public float getIconAppearAmount() { + return mIconAppearAmount; + } + + public int getVisibleState() { + return mVisibleState; + } + + public void setDotAppearAmount(float dotAppearAmount) { + mDotAppearAmount = dotAppearAmount; + invalidate(); + } + + public float getDotAppearAmount() { + return mDotAppearAmount; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java deleted file mode 100644 index f86badb2970b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2008 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 android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; - -import com.android.systemui.R; - -public class IconMerger extends LinearLayout { - private static final String TAG = "IconMerger"; - private static final boolean DEBUG = false; - - private int mIconSize; - private int mIconHPadding; - - private View mMoreView; - - public IconMerger(Context context, AttributeSet attrs) { - super(context, attrs); - reloadDimens(); - if (DEBUG) { - setBackgroundColor(0x800099FF); - } - } - - private void reloadDimens() { - Resources res = mContext.getResources(); - mIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size); - mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - reloadDimens(); - } - - public void setOverflowIndicator(View v) { - mMoreView = v; - } - - private int getFullIconWidth() { - return mIconSize + 2 * mIconHPadding; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - // we need to constrain this to an integral multiple of our children - int width = getMeasuredWidth(); - setMeasuredDimension(width - (width % getFullIconWidth()), getMeasuredHeight()); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - checkOverflow(r - l); - } - - private void checkOverflow(int width) { - if (mMoreView == null) return; - - final int N = getChildCount(); - int visibleChildren = 0; - for (int i=0; i<N; i++) { - if (getChildAt(i).getVisibility() != GONE) visibleChildren++; - } - final boolean overflowShown = (mMoreView.getVisibility() == View.VISIBLE); - // let's assume we have one more slot if the more icon is already showing - if (overflowShown) visibleChildren --; - final boolean moreRequired = visibleChildren * getFullIconWidth() > width; - if (moreRequired != overflowShown) { - post(new Runnable() { - @Override - public void run() { - mMoreView.setVisibility(moreRequired ? View.VISIBLE : View.GONE); - } - }); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 34b8371fe20a..21240118e5c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -37,6 +37,7 @@ import com.android.keyguard.R; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.DejankUtils; import com.android.systemui.classifier.FalsingManager; +import com.android.systemui.keyguard.DismissCallbackRegistry; import static com.android.keyguard.KeyguardHostView.OnDismissAction; import static com.android.keyguard.KeyguardSecurityModel.SecurityMode; @@ -48,17 +49,17 @@ public class KeyguardBouncer { final static private String TAG = "KeyguardBouncer"; - protected Context mContext; - protected ViewMediatorCallback mCallback; - protected LockPatternUtils mLockPatternUtils; - protected ViewGroup mContainer; - private StatusBarWindowManager mWindowManager; + protected final Context mContext; + protected final ViewMediatorCallback mCallback; + protected final LockPatternUtils mLockPatternUtils; + protected final ViewGroup mContainer; + private final FalsingManager mFalsingManager; + private final DismissCallbackRegistry mDismissCallbackRegistry; protected KeyguardHostView mKeyguardView; protected ViewGroup mRoot; private boolean mShowingSoon; private int mBouncerPromptReason; - private FalsingManager mFalsingManager; - private KeyguardUpdateMonitorCallback mUpdateMonitorCallback = + private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { @Override public void onStrongAuthStateChanged(int userId) { @@ -67,15 +68,15 @@ public class KeyguardBouncer { }; public KeyguardBouncer(Context context, ViewMediatorCallback callback, - LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager, - ViewGroup container) { + LockPatternUtils lockPatternUtils, ViewGroup container, + DismissCallbackRegistry dismissCallbackRegistry) { mContext = context; mCallback = callback; mLockPatternUtils = lockPatternUtils; mContainer = container; - mWindowManager = windowManager; KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback); mFalsingManager = FalsingManager.getInstance(mContext); + mDismissCallbackRegistry = dismissCallbackRegistry; } public void show(boolean resetSecuritySelection) { @@ -169,6 +170,9 @@ public class KeyguardBouncer { } public void hide(boolean destroyView) { + if (isShowing()) { + mDismissCallbackRegistry.notifyDismissCancelled(); + } mFalsingManager.onBouncerHidden(); cancelShowRunnable(); if (mKeyguardView != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index 784cb48f8707..70beac8ea522 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -80,7 +80,7 @@ public class KeyguardClockPositionAlgorithm { mClockYFractionMin = res.getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1); mClockYFractionMax = res.getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1); mMoreCardNotificationAmount = - (float) res.getDimensionPixelSize(R.dimen.notification_summary_height) / + (float) res.getDimensionPixelSize(R.dimen.notification_shelf_height) / res.getDimensionPixelSize(R.dimen.notification_min_height); mDensity = res.getDisplayMetrics().density; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index 9bb49362c560..695b500363e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -35,6 +35,8 @@ import com.android.systemui.statusbar.policy.AccessibilityController; */ public class LockIcon extends KeyguardAffordanceView { + private static final int FP_DRAW_OFF_TIMEOUT = 800; + private static final int STATE_LOCKED = 0; private static final int STATE_LOCK_OPEN = 1; private static final int STATE_FACE_UNLOCK = 2; @@ -53,6 +55,8 @@ public class LockIcon extends KeyguardAffordanceView { private boolean mHasFingerPrintIcon; private int mDensity; + private final Runnable mDrawOffTimeout = () -> update(true /* forceUpdate */); + public LockIcon(Context context, AttributeSet attrs) { super(context, attrs); mTrustDrawable = new TrustDrawable(context); @@ -116,7 +120,6 @@ public class LockIcon extends KeyguardAffordanceView { } else { mTrustDrawable.stop(); } - // TODO: Real icon for facelock. int state = getState(); boolean anyFingerprintIcon = state == STATE_FINGERPRINT || state == STATE_FINGERPRINT_ERROR; boolean useAdditionalPadding = anyFingerprintIcon; @@ -171,6 +174,14 @@ public class LockIcon extends KeyguardAffordanceView { animation.forceAnimationOnUI(); animation.start(); } + + if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) { + removeCallbacks(mDrawOffTimeout); + postDelayed(mDrawOffTimeout, FP_DRAW_OFF_TIMEOUT); + } else { + removeCallbacks(mDrawOffTimeout); + } + mLastState = state; mLastDeviceInteractive = mDeviceInteractive; mLastScreenOn = mScreenOn; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index cbaab14652fe..0dbff19b6bcf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -8,16 +8,19 @@ import android.graphics.Rect; import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; -import android.widget.ImageView; import android.widget.LinearLayout; import com.android.internal.util.NotificationColorUtil; import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; +import java.util.function.Function; /** * A controller for the space in the status bar to the left of the system icons. This area is @@ -32,13 +35,16 @@ public class NotificationIconAreaController { private PhoneStatusBar mPhoneStatusBar; protected View mNotificationIconArea; - private IconMerger mNotificationIcons; - private ImageView mMoreIcon; + private NotificationIconContainer mNotificationIcons; + private NotificationIconContainer mShelfIcons; private final Rect mTintArea = new Rect(); + private NotificationStackScrollLayout mNotificationScrollLayout; + private Context mContext; public NotificationIconAreaController(Context context, PhoneStatusBar phoneStatusBar) { mPhoneStatusBar = phoneStatusBar; mNotificationColorUtil = NotificationColorUtil.getInstance(context); + mContext = context; initializeNotificationAreaViews(context); } @@ -55,15 +61,14 @@ public class NotificationIconAreaController { LayoutInflater layoutInflater = LayoutInflater.from(context); mNotificationIconArea = inflateIconArea(layoutInflater); + mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById( + R.id.notificationIcons); - mNotificationIcons = - (IconMerger) mNotificationIconArea.findViewById(R.id.notificationIcons); + NotificationShelf shelf = mPhoneStatusBar.getNotificationShelf(); + mShelfIcons = shelf.getShelfIcons(); + shelf.setCollapsedIcons(mNotificationIcons); - mMoreIcon = (ImageView) mNotificationIconArea.findViewById(R.id.moreIcon); - if (mMoreIcon != null) { - mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint)); - mNotificationIcons.setOverflowIndicator(mMoreIcon); - } + mNotificationScrollLayout = mPhoneStatusBar.getNotificationScrollLayout(); } public void onDensityOrFontScaleChanged(Context context) { @@ -109,14 +114,10 @@ public class NotificationIconAreaController { } /** - * Sets the color that should be used to tint any icons in the notification area. If this - * method is not called, the default tint is {@link Color#WHITE}. + * Sets the color that should be used to tint any icons in the notification area. */ public void setIconTint(int iconTint) { mIconTint = iconTint; - if (mMoreIcon != null) { - mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint)); - } applyNotificationIconsTint(); } @@ -144,24 +145,54 @@ public class NotificationIconAreaController { * Updates the notifications with the given list of notifications to display. */ public void updateNotificationIcons(NotificationData notificationData) { - final LinearLayout.LayoutParams params = generateIconLayoutParams(); - ArrayList<NotificationData.Entry> activeNotifications = - notificationData.getActiveNotifications(); - final int size = activeNotifications.size(); - ArrayList<StatusBarIconView> toShow = new ArrayList<>(size); + updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons); + updateIconsForLayout(notificationData, entry -> entry.expandedIcon, mShelfIcons); + + applyNotificationIconsTint(); + ArrayList<NotificationData.Entry> activeNotifications + = notificationData.getActiveNotifications(); + for (int i = 0; i < activeNotifications.size(); i++) { + NotificationData.Entry entry = activeNotifications.get(i); + boolean isPreL = Boolean.TRUE.equals(entry.expandedIcon.getTag(R.id.icon_is_pre_L)); + boolean colorize = !isPreL + || NotificationUtils.isGrayscale(entry.expandedIcon, mNotificationColorUtil); + if (colorize) { + int color = entry.getContrastedColor(mContext); + entry.expandedIcon.setImageTintList(ColorStateList.valueOf(color)); + } + } + } + + /** + * Updates the notification icons for a host layout. This will ensure that the notification + * host layout will have the same icons like the ones in here. + * + * @param notificationData the notification data to look up which notifications are relevant + * @param function A function to look up an icon view based on an entry + * @param hostLayout which layout should be updated + */ + private void updateIconsForLayout(NotificationData notificationData, + Function<NotificationData.Entry, StatusBarIconView> function, + NotificationIconContainer hostLayout) { + ArrayList<StatusBarIconView> toShow = new ArrayList<>( + mNotificationScrollLayout.getChildCount()); // Filter out ambient notifications and notification children. - for (int i = 0; i < size; i++) { - NotificationData.Entry ent = activeNotifications.get(i); - if (shouldShowNotification(ent, notificationData)) { - toShow.add(ent.icon); + for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) { + View view = mNotificationScrollLayout.getChildAt(i); + if (view instanceof ExpandableNotificationRow) { + NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry(); + if (shouldShowNotification(ent, notificationData)) { + toShow.add(function.apply(ent)); + } } } + ArrayList<View> toRemove = new ArrayList<>(); - for (int i = 0; i < mNotificationIcons.getChildCount(); i++) { - View child = mNotificationIcons.getChildAt(i); + for (int i = 0; i < hostLayout.getChildCount(); i++) { + View child = hostLayout.getChildAt(i); if (!toShow.contains(child)) { toRemove.add(child); } @@ -169,29 +200,32 @@ public class NotificationIconAreaController { final int toRemoveCount = toRemove.size(); for (int i = 0; i < toRemoveCount; i++) { - mNotificationIcons.removeView(toRemove.get(i)); + hostLayout.removeView(toRemove.get(i)); } + final LinearLayout.LayoutParams params = generateIconLayoutParams(); for (int i = 0; i < toShow.size(); i++) { View v = toShow.get(i); + // The view might still be transiently added if it was just removed and added again + hostLayout.removeTransientView(v); if (v.getParent() == null) { - mNotificationIcons.addView(v, i, params); + hostLayout.addView(v, i, params); } } + hostLayout.setChangingViewPositions(true); // Re-sort notification icons - final int childCount = mNotificationIcons.getChildCount(); + final int childCount = hostLayout.getChildCount(); for (int i = 0; i < childCount; i++) { - View actual = mNotificationIcons.getChildAt(i); + View actual = hostLayout.getChildAt(i); StatusBarIconView expected = toShow.get(i); if (actual == expected) { continue; } - mNotificationIcons.removeView(expected); - mNotificationIcons.addView(expected, i); + hostLayout.removeView(expected); + hostLayout.addView(expected, i); } - - applyNotificationIconsTint(); + hostLayout.setChangingViewPositions(false); } /** @@ -206,6 +240,7 @@ public class NotificationIconAreaController { v.setImageTintList(ColorStateList.valueOf( StatusBarIconController.getTint(mTintArea, v, mIconTint))); } + v.setIconTint(mIconTint); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java new file mode 100644 index 000000000000..28958908f81b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -0,0 +1,341 @@ +/* + * 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.systemui.statusbar.phone; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +import com.android.systemui.R; +import com.android.systemui.statusbar.AlphaOptimizedFrameLayout; +import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.stack.AnimationFilter; +import com.android.systemui.statusbar.stack.AnimationProperties; +import com.android.systemui.statusbar.stack.ViewState; + +import java.util.WeakHashMap; + +/** + * A container for notification icons. It handles overflowing icons properly and positions them + * correctly on the screen. + */ +public class NotificationIconContainer extends AlphaOptimizedFrameLayout { + private static final String TAG = "NotificationIconContainer"; + private static final boolean DEBUG = false; + private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() { + private AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); + + @Override + public AnimationFilter getAnimationFilter() { + return mAnimationFilter; + } + }.setDuration(200); + + private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() { + private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); + + @Override + public AnimationFilter getAnimationFilter() { + return mAnimationFilter; + } + }.setDuration(200).setDelay(50); + + private boolean mShowAllIcons = true; + private WeakHashMap<View, IconState> mIconStates = new WeakHashMap<>(); + private int mDotPadding; + private int mStaticDotRadius; + private int mActualLayoutWidth = -1; + private float mActualPaddingEnd = -1; + private float mActualPaddingStart = -1; + private boolean mChangingViewPositions; + private int mAnimationStartIndex = -1; + + public NotificationIconContainer(Context context, AttributeSet attrs) { + super(context, attrs); + initDimens(); + setWillNotDraw(!DEBUG); + } + + private void initDimens() { + mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding); + mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + Paint paint = new Paint(); + paint.setColor(Color.RED); + paint.setStyle(Paint.Style.STROKE); + canvas.drawRect(getActualPaddingStart(), 0, getLayoutEnd(), getHeight(), paint); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + initDimens(); + } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + float centerY = getHeight() / 2.0f; + // we layout all our children on the left at the top + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + // We need to layout all children even the GONE ones, such that the heights are + // calculated correctly as they are used to calculate how many we can fit on the screen + int width = child.getMeasuredWidth(); + int height = child.getMeasuredHeight(); + int top = (int) (centerY - height / 2.0f); + child.layout(0, top, width, top + height); + } + if (mShowAllIcons) { + resetViewStates(); + calculateIconTranslations(); + applyIconStates(); + } + } + + public void applyIconStates() { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + ViewState childState = mIconStates.get(child); + if (childState != null) { + childState.applyToView(child); + } + } + mAnimationStartIndex = -1; + } + + @Override + public void onViewAdded(View child) { + super.onViewAdded(child); + if (!mChangingViewPositions) { + mIconStates.put(child, new IconState()); + } + int childIndex = indexOfChild(child); + if (childIndex < getChildCount() - 1 + && mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) { + if (mAnimationStartIndex < 0) { + mAnimationStartIndex = childIndex; + } else { + mAnimationStartIndex = Math.min(mAnimationStartIndex, childIndex); + } + } + } + + @Override + public void onViewRemoved(View child) { + super.onViewRemoved(child); + if (child instanceof StatusBarIconView) { + final StatusBarIconView icon = (StatusBarIconView) child; + if (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN + && child.getVisibility() == VISIBLE) { + int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX()); + if (mAnimationStartIndex < 0) { + mAnimationStartIndex = animationStartIndex; + } else { + mAnimationStartIndex = Math.min(mAnimationStartIndex, animationStartIndex); + } + } + if (!mChangingViewPositions) { + mIconStates.remove(child); + addTransientView(icon, 0); + icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */, + () -> removeTransientView(icon)); + } + } + } + + /** + * Finds the first view with a translation bigger then a given value + */ + private int findFirstViewIndexAfter(float translationX) { + for (int i = 0; i < getChildCount(); i++) { + View view = getChildAt(i); + if (view.getTranslationX() > translationX) { + return i; + } + } + return getChildCount(); + } + + public WeakHashMap<View, IconState> resetViewStates() { + for (int i = 0; i < getChildCount(); i++) { + View view = getChildAt(i); + ViewState iconState = mIconStates.get(view); + iconState.initFrom(view); + iconState.alpha = 1.0f; + } + return mIconStates; + } + + /** + * Calulate the horizontal translations for each notification based on how much the icons + * are inserted into the notification container. + * If this is not a whole number, the fraction means by how much the icon is appearing. + */ + public void calculateIconTranslations() { + float translationX = getActualPaddingStart(); + int overflowingIconIndex = -1; + int lastTwoIconWidth = 0; + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View view = getChildAt(i); + IconState iconState = mIconStates.get(view); + iconState.xTranslation = translationX; + iconState.visibleState = StatusBarIconView.STATE_ICON; + translationX += iconState.iconAppearAmount * view.getWidth(); + if (translationX > getLayoutEnd()) { + // we are overflowing it with this icon + overflowingIconIndex = i - 1; + lastTwoIconWidth = view.getWidth(); + break; + } + } + if (overflowingIconIndex != -1) { + int numDots = 1; + View overflowIcon = getChildAt(overflowingIconIndex); + IconState overflowState = mIconStates.get(overflowIcon); + lastTwoIconWidth += overflowIcon.getWidth(); + int dotWidth = mStaticDotRadius * 2 + mDotPadding; + int totalDotLength = mStaticDotRadius * 6 + 2 * mDotPadding; + translationX = (getLayoutEnd() - lastTwoIconWidth / 2 - totalDotLength / 2) + - overflowIcon.getWidth() * 0.3f + mStaticDotRadius; + float overflowStart = getLayoutEnd() - lastTwoIconWidth; + float overlapAmount = (overflowState.xTranslation - overflowStart) + / overflowIcon.getWidth(); + translationX += overlapAmount * dotWidth; + for (int i = overflowingIconIndex; i < childCount; i++) { + View view = getChildAt(i); + IconState iconState = mIconStates.get(view); + iconState.xTranslation = translationX; + if (numDots <= 3) { + iconState.visibleState = StatusBarIconView.STATE_DOT; + translationX += numDots == 3 ? 3 * dotWidth : dotWidth; + } else { + iconState.visibleState = StatusBarIconView.STATE_HIDDEN; + } + numDots++; + } + } + if (isLayoutRtl()) { + for (int i = 0; i < childCount; i++) { + View view = getChildAt(i); + IconState iconState = mIconStates.get(view); + iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth(); + } + } + } + + private float getLayoutEnd() { + return getActualWidth() - getActualPaddingEnd(); + } + + private float getActualPaddingEnd() { + if (mActualPaddingEnd < 0) { + return getPaddingEnd(); + } + return mActualPaddingEnd; + } + + private float getActualPaddingStart() { + if (mActualPaddingStart < 0) { + return getPaddingStart(); + } + return mActualPaddingStart; + } + + /** + * Sets whether the layout should always show all icons. + * If this is true, the icon positions will be updated on layout. + * If this if false, the layout is managed from the outside and layouting won't trigger a + * repositioning of the icons. + */ + public void setShowAllIcons(boolean showAllIcons) { + mShowAllIcons = showAllIcons; + } + + public void setActualLayoutWidth(int actualLayoutWidth) { + mActualLayoutWidth = actualLayoutWidth; + if (DEBUG) { + invalidate(); + } + } + + public void setActualPaddingEnd(float paddingEnd) { + mActualPaddingEnd = paddingEnd; + if (DEBUG) { + invalidate(); + } + } + + public void setActualPaddingStart(float paddingStart) { + mActualPaddingStart = paddingStart; + if (DEBUG) { + invalidate(); + } + } + + public int getActualWidth() { + if (mActualLayoutWidth < 0) { + return getWidth(); + } + return mActualLayoutWidth; + } + + public void setChangingViewPositions(boolean changingViewPositions) { + mChangingViewPositions = changingViewPositions; + } + + public class IconState extends ViewState { + public float iconAppearAmount = 1.0f; + public int visibleState; + public boolean justAdded = true; + + @Override + public void applyToView(View view) { + if (view instanceof StatusBarIconView) { + StatusBarIconView icon = (StatusBarIconView) view; + AnimationProperties animationProperties = DOT_ANIMATION_PROPERTIES; + if (justAdded) { + super.applyToView(icon); + icon.setAlpha(0.0f); + icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, false /* animate */); + animationProperties = ADD_ICON_PROPERTIES; + } + boolean animate = visibleState != icon.getVisibleState() || justAdded; + if (!animate && mAnimationStartIndex >= 0 + && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN + || visibleState != StatusBarIconView.STATE_HIDDEN)) { + int viewIndex = indexOfChild(view); + animate = viewIndex >= mAnimationStartIndex; + } + icon.setVisibleState(visibleState); + if (animate) { + animateTo(icon, animationProperties); + } else { + super.applyToView(view); + } + } + justAdded = false; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 068631d4c1ff..523528d4d40e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -202,11 +202,12 @@ public class NotificationPanelView extends PanelView implements private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() { @Override public void run() { - mHeadsUpAnimatingAway = false; + setHeadsUpAnimatingAway(false); notifyBarPanelExpansionChanged(); } }; private NotificationGroupManager mGroupManager; + private boolean mOpening; public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -404,10 +405,11 @@ public class NotificationPanelView extends PanelView implements int notificationPadding = Math.max(1, getResources().getDimensionPixelSize( R.dimen.notification_divider_height)); final int overflowheight = getResources().getDimensionPixelSize( - R.dimen.notification_summary_height); - float bottomStackSize = mNotificationStackScroller.getKeyguardBottomStackSize(); + R.dimen.notification_shelf_height); + float shelfSize = mNotificationStackScroller.getNotificationShelf().getIntrinsicHeight() + + notificationPadding; float availableSpace = mNotificationStackScroller.getHeight() - minPadding - overflowheight - - bottomStackSize; + - shelfSize; int count = 0; for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) { ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i); @@ -429,6 +431,16 @@ public class NotificationPanelView extends PanelView implements availableSpace -= child.getMinHeight() + notificationPadding; if (availableSpace >= 0 && count < maximum) { count++; + } else if (availableSpace > -shelfSize) { + // if we are exactly the last view, then we can show us still! + for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) { + if (mNotificationStackScroller.getChildAt(j) + instanceof ExpandableNotificationRow) { + return count; + } + } + count++; + return count; } else { return count; } @@ -546,9 +558,7 @@ public class NotificationPanelView extends PanelView implements protected void flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { mHeadsUpTouchHelper.notifyFling(!expand); - setClosingWithAlphaFadeout(!expand - && mNotificationStackScroller.getFirstChildIntrinsicHeight() <= mMaxFadeoutHeight - && getFadeoutAlpha() == 1.0f); + setClosingWithAlphaFadeout(!expand && getFadeoutAlpha() == 1.0f); super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); } @@ -723,6 +733,11 @@ public class NotificationPanelView extends PanelView implements } @Override + protected float getOpeningHeight() { + return mNotificationStackScroller.getMinExpansionHeight(); + } + + @Override public boolean onTouchEvent(MotionEvent event) { if (mBlockTouches || (mQs != null && mQs.isCustomizing())) { return false; @@ -1415,7 +1430,7 @@ public class NotificationPanelView extends PanelView implements if (mKeyguardShowing) { // On Keyguard, interpolate the QS expansion linearly to the panel expansion - t = expandedHeight / getMaxPanelHeight(); + t = expandedHeight / (getMaxPanelHeight()); } else { // In Shade, interpolate linearly such that QS is closed whenever panel height is @@ -1475,9 +1490,7 @@ public class NotificationPanelView extends PanelView implements // and expanding/collapsing the whole panel from/to quick settings. if (mNotificationStackScroller.getNotGoneChildCount() == 0 && mShadeEmpty) { - notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight() - + mNotificationStackScroller.getBottomStackPeekSize() - + mNotificationStackScroller.getBottomStackSlowDownHeight(); + notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight(); } int maxQsHeight = mQsMaxExpansionHeight; @@ -1508,8 +1521,7 @@ public class NotificationPanelView extends PanelView implements private float getFadeoutAlpha() { float alpha = (getNotificationsTopY() + mNotificationStackScroller.getFirstItemMinHeight()) - / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() - - mNotificationStackScroller.getBottomStackSlowDownHeight()); + / mQsMinExpansionHeight; alpha = Math.max(0, Math.min(alpha, 1)); alpha = (float) Math.pow(alpha, 0.75); return alpha; @@ -1988,9 +2000,9 @@ public class NotificationPanelView extends PanelView implements @Override protected float getCannedFlingDurationFactor() { if (mQsExpanded) { - return 0.7f; + return 0.9f; } else { - return 0.6f; + return 0.8f; } } @@ -2180,16 +2192,22 @@ public class NotificationPanelView extends PanelView implements @Override public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) { + mNotificationStackScroller.setInHeadsUpPinnedMode(inPinnedMode); if (inPinnedMode) { mHeadsUpExistenceChangedRunnable.run(); updateNotificationTranslucency(); } else { - mHeadsUpAnimatingAway = true; + setHeadsUpAnimatingAway(true); mNotificationStackScroller.runAfterAnimationFinished( mHeadsUpExistenceChangedRunnable); } } + public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { + mHeadsUpAnimatingAway = headsUpAnimatingAway; + mNotificationStackScroller.setHeadsUpAnimatingAway(headsUpAnimatingAway); + } + @Override public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true); @@ -2265,6 +2283,14 @@ public class NotificationPanelView extends PanelView implements protected void updateExpandedHeight(float expandedHeight) { mNotificationStackScroller.setExpandedHeight(expandedHeight); updateKeyguardBottomAreaAlpha(); + setOpening(expandedHeight <= getOpeningHeight()); + } + + private void setOpening(boolean opening) { + if (opening != mOpening) { + mOpening = opening; + mStatusBar.recomputeDisableFlags(false); + } } public void setPanelScrimMinFraction(float minFraction) { @@ -2316,7 +2342,18 @@ public class NotificationPanelView extends PanelView implements @Override public void setAlpha(float alpha) { super.setAlpha(alpha); - mNotificationStackScroller.setParentFadingOut(alpha != 1.0f); + updateFullyVisibleState(); + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + updateFullyVisibleState(); + } + + private void updateFullyVisibleState() { + mNotificationStackScroller.setParentNotFullyVisible(getAlpha() != 1.0f + || getVisibility() != VISIBLE); } /** @@ -2358,6 +2395,15 @@ public class NotificationPanelView extends PanelView implements mGroupManager = groupManager; } + public boolean shouldHideNotificationIcons() { + return !mOpening && !isFullyCollapsed(); + } + + public boolean shouldAnimateIconHiding() { + // TODO: handle this correctly, not completely working yet + return mNotificationStackScroller.getTranslationX() != 0; + } + private final FragmentListener mFragmentListener = new FragmentListener() { @Override public void onFragmentViewCreated(String tag, Fragment fragment) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index f2c57e538829..87a384819007 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -118,7 +118,7 @@ public abstract class PanelBar extends FrameLayout { boolean fullyOpened = false; if (SPEW) LOG("panelExpansionChanged: start state=%d", mState); PanelView pv = mPanel; - pv.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE); + pv.setVisibility(expanded ? VISIBLE : INVISIBLE); // adjust any other panels that may be partially visible if (expanded) { if (mState == STATE_CLOSED) { 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 3de03b5859e9..570d5d40064f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -23,6 +23,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.view.InputDevice; @@ -50,6 +51,10 @@ import java.io.PrintWriter; public abstract class PanelView extends FrameLayout { public static final boolean DEBUG = PanelBar.DEBUG; public static final String TAG = PanelView.class.getSimpleName(); + private static final int INITIAL_OPENING_PEEK_DURATION = 200; + private static final int PEEK_ANIMATION_DURATION = 360; + private long mDownTime; + private float mMinExpandHeight; private final void logf(String fmt, Object... args) { Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); @@ -88,6 +93,7 @@ public abstract class PanelView extends FrameLayout { private ObjectAnimator mPeekAnimator; private VelocityTrackerInterface mVelocityTracker; private FlingAnimationUtils mFlingAnimationUtils; + private FlingAnimationUtils mFlingAnimationUtilsClosing; private FalsingManager mFalsingManager; /** @@ -106,9 +112,6 @@ public abstract class PanelView extends FrameLayout { private Interpolator mBounceInterpolator; protected KeyguardBottomAreaView mKeyguardBottomArea; - private boolean mPeekPending; - private boolean mCollapseAfterPeek; - /** * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time. */ @@ -118,13 +121,6 @@ public abstract class PanelView extends FrameLayout { private boolean mGestureWaitForTouchSlop; private boolean mIgnoreXTouchSlop; private boolean mExpandLatencyTracking; - private Runnable mPeekRunnable = new Runnable() { - @Override - public void run() { - mPeekPending = false; - runPeekAnimation(); - } - }; protected void onExpandingFinished() { mBar.onExpandingFinished(); @@ -148,21 +144,17 @@ public abstract class PanelView extends FrameLayout { } } - private void schedulePeek() { - mPeekPending = true; - long timeout = ViewConfiguration.getTapTimeout(); - postOnAnimationDelayed(mPeekRunnable, timeout); - notifyBarPanelExpansionChanged(); - } - - private void runPeekAnimation() { - mPeekHeight = getPeekHeight(); + private void runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished) { + mPeekHeight = peekHeight; if (DEBUG) logf("peek to height=%.1f", mPeekHeight); if (mHeightAnimator != null) { return; } + if (mPeekAnimator != null) { + mPeekAnimator.cancel(); + } mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight) - .setDuration(250); + .setDuration(duration); mPeekAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); mPeekAnimator.addListener(new AnimatorListenerAdapter() { private boolean mCancelled; @@ -175,10 +167,10 @@ public abstract class PanelView extends FrameLayout { @Override public void onAnimationEnd(Animator animation) { mPeekAnimator = null; - if (mCollapseAfterPeek && !mCancelled) { + if (!mCancelled && collapseWhenFinished) { postOnAnimation(mPostCollapseRunnable); } - mCollapseAfterPeek = false; + } }); notifyExpandingStarted(); @@ -189,6 +181,7 @@ public abstract class PanelView extends FrameLayout { public PanelView(Context context, AttributeSet attrs) { super(context, attrs); mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f); + mFlingAnimationUtilsClosing = new FlingAnimationUtils(context, 0.4f); mBounceInterpolator = new BounceInterpolator(); mFalsingManager = FalsingManager.getInstance(context); } @@ -267,11 +260,13 @@ public abstract class PanelView extends FrameLayout { case MotionEvent.ACTION_DOWN: startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); mJustPeeked = false; + mMinExpandHeight = 0.0f; mPanelClosedOnDown = isFullyCollapsed(); mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; mMotionAborted = false; mPeekTouching = mPanelClosedOnDown; + mDownTime = SystemClock.uptimeMillis(); mTouchAboveFalsingThreshold = false; mCollapsedAndHeadsUpOnDown = isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); @@ -279,16 +274,16 @@ public abstract class PanelView extends FrameLayout { initVelocityTracker(); } trackMovement(event); - if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) || - mPeekPending || mPeekAnimator != null) { + if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) + || mPeekAnimator != null) { cancelHeightAnimator(); cancelPeek(); mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning) - || mPeekPending || mPeekAnimator != null; + || mPeekAnimator != null; onTrackingStarted(); } if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()) { - schedulePeek(); + startOpening(); } break; @@ -317,7 +312,7 @@ public abstract class PanelView extends FrameLayout { // y-component of the gesture, as we have no conflicting horizontal gesture. if (Math.abs(h) > mTouchSlop && (Math.abs(h) > Math.abs(x - mInitialTouchX) - || mIgnoreXTouchSlop)) { + || mIgnoreXTouchSlop)) { mTouchSlopExceeded = true; if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) { if (!mJustPeeked && mInitialOffsetOnTouch != 0f) { @@ -325,23 +320,30 @@ public abstract class PanelView extends FrameLayout { h = 0; } cancelHeightAnimator(); - removeCallbacks(mPeekRunnable); - mPeekPending = false; onTrackingStarted(); } } - final float newHeight = Math.max(0, h + mInitialOffsetOnTouch); + float newHeight = Math.max(0, h + mInitialOffsetOnTouch); if (newHeight > mPeekHeight) { if (mPeekAnimator != null) { mPeekAnimator.cancel(); } mJustPeeked = false; + } else if (mPeekAnimator == null && mJustPeeked) { + // The initial peek has finished, but we haven't dragged as far yet, lets + // speed it up by starting at the peek height. + mInitialOffsetOnTouch = mExpandedHeight; + mInitialTouchY = y; + mMinExpandHeight = mExpandedHeight; + mJustPeeked = false; } + newHeight = Math.max(newHeight, mMinExpandHeight); if (-h >= getFalsingThreshold()) { mTouchAboveFalsingThreshold = true; mUpwardsWhenTresholdReached = isDirectionUpwards(x, y); } - if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) { + if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) && + !isTrackingBlocked()) { setExpandedHeightInternal(newHeight); } @@ -357,6 +359,14 @@ public abstract class PanelView extends FrameLayout { return !mGestureWaitForTouchSlop || mTracking; } + private void startOpening() {; + runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(), + false /* collapseWhenFinished */); + notifyBarPanelExpansionChanged(); + } + + protected abstract float getOpeningHeight(); + /** * @return whether the swiping direction is upwards and above a 45 degree angle compared to the * horizontal direction @@ -418,6 +428,15 @@ public abstract class PanelView extends FrameLayout { if (mUpdateFlingOnLayout) { mUpdateFlingVelocity = vel; } + } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking) { + long timePassed = SystemClock.uptimeMillis() - mDownTime; + if (timePassed < ViewConfiguration.getLongPressTimeout()) { + // Lets show the user that he can actually expand the panel + runPeekAnimation(PEEK_ANIMATION_DURATION, getPeekHeight(), true /* collapseWhenFinished */); + } else { + // We need to collapse the panel since we peeked to the small height. + postOnAnimation(mPostCollapseRunnable); + } } else { boolean expands = onEmptySpaceClick(mInitialTouchX); onTrackingStopped(expands); @@ -448,7 +467,6 @@ public abstract class PanelView extends FrameLayout { protected void onTrackingStarted() { endClosing(); mTracking = true; - mCollapseAfterPeek = false; mBar.onTrackingStarted(); notifyExpandingStarted(); notifyBarPanelExpansionChanged(); @@ -482,7 +500,10 @@ public abstract class PanelView extends FrameLayout { case MotionEvent.ACTION_DOWN: mStatusBar.userActivity(); mAnimatingOnDown = mHeightAnimator != null; - if (mAnimatingOnDown && mClosing && !mHintAnimationRunning || mPeekPending || mPeekAnimator != null) { + mMinExpandHeight = 0.0f; + mDownTime = SystemClock.uptimeMillis(); + if (mAnimatingOnDown && mClosing && !mHintAnimationRunning + || mPeekAnimator != null) { cancelHeightAnimator(); cancelPeek(); mTouchSlopExceeded = true; @@ -639,7 +660,7 @@ public abstract class PanelView extends FrameLayout { protected void fling(float vel, boolean expand, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { cancelPeek(); - float target = expand ? getMaxPanelHeight() : 0.0f; + float target = expand ? getMaxPanelHeight() : 0; if (!expand) { mClosing = true; } @@ -672,8 +693,7 @@ public abstract class PanelView extends FrameLayout { animator.setDuration(350); } } else { - mFlingAnimationUtils.applyDismissing(animator, mExpandedHeight, target, vel, - getHeight()); + mFlingAnimationUtilsClosing.apply(animator, mExpandedHeight, target, vel, getHeight()); // Make it shorter if we run a canned animation if (vel == 0) { @@ -742,7 +762,6 @@ public abstract class PanelView extends FrameLayout { && mHeightAnimator == null && !isFullyCollapsed() && currentMaxPanelHeight != mExpandedHeight - && !mPeekPending && mPeekAnimator == null && !mPeekTouching) { setExpandedHeight(currentMaxPanelHeight); @@ -769,10 +788,8 @@ public abstract class PanelView extends FrameLayout { } } - mExpandedHeight = Math.max(0, mExpandedHeight); - mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0 - ? 0 - : mExpandedHeight / fhWithoutOverExpansion); + mExpandedFraction = Math.min(1f, + fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion); onHeightUpdated(mExpandedHeight); notifyBarPanelExpansionChanged(); } @@ -816,7 +833,7 @@ public abstract class PanelView extends FrameLayout { } public boolean isFullyCollapsed() { - return mExpandedHeight <= 0; + return mExpandedFraction <= 0.0f; } public boolean isCollapsing() { @@ -833,16 +850,7 @@ public abstract class PanelView extends FrameLayout { public void collapse(boolean delayed, float speedUpFactor) { if (DEBUG) logf("collapse: " + this); - if (mPeekPending || mPeekAnimator != null) { - mCollapseAfterPeek = true; - if (mPeekPending) { - - // We know that the whole gesture is just a peek triggered by a simple click, so - // better start it now. - removeCallbacks(mPeekRunnable); - mPeekRunnable.run(); - } - } else if (!isFullyCollapsed() && !mTracking && !mClosing) { + if (!isFullyCollapsed() && !mTracking && !mClosing) { cancelHeightAnimator(); notifyExpandingStarted(); @@ -866,13 +874,11 @@ public abstract class PanelView extends FrameLayout { }; public void cancelPeek() { - boolean cancelled = mPeekPending; + boolean cancelled = false; if (mPeekAnimator != null) { cancelled = true; mPeekAnimator.cancel(); } - removeCallbacks(mPeekRunnable); - mPeekPending = false; if (cancelled) { // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also @@ -1048,7 +1054,7 @@ public abstract class PanelView extends FrameLayout { } protected void notifyBarPanelExpansionChanged() { - mBar.panelExpansionChanged(mExpandedFraction, mExpandedFraction > 0f || mPeekPending + mBar.panelExpansionChanged(mExpandedFraction, mExpandedFraction > 0f || mPeekAnimator != null || mInstantExpanding || isPanelVisibleBecauseOfHeadsUp() || mTracking || mHeightAnimator != null); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 6f13ba50b22f..bb86d6fae06c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -164,7 +164,7 @@ import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationData.Entry; -import com.android.systemui.statusbar.NotificationOverflowContainer; +import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.SignalClusterView; @@ -197,7 +197,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout .OnChildLocationsChangedListener; import com.android.systemui.statusbar.stack.StackStateAnimator; -import com.android.systemui.statusbar.stack.StackViewState; +import com.android.systemui.statusbar.stack.ExpandableViewState; import com.android.systemui.volume.VolumeComponent; import java.io.FileDescriptor; @@ -569,8 +569,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, */ protected boolean mStartedGoingToSleep; - private static final int VISIBLE_LOCATIONS = StackViewState.LOCATION_FIRST_HUN - | StackViewState.LOCATION_MAIN_AREA; + private static final int VISIBLE_LOCATIONS = ExpandableViewState.LOCATION_FIRST_HUN + | ExpandableViewState.LOCATION_MAIN_AREA; private final OnChildLocationsChangedListener mNotificationLocationsChangedListener = new OnChildLocationsChangedListener() { @@ -659,10 +659,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, array.clear(); } - private final View.OnClickListener mOverflowClickListener = new View.OnClickListener() { + private final View.OnClickListener mShelfClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - goToLockedShade(null); + if (mState == StatusBarState.KEYGUARD) { + goToLockedShade(null); + } } }; private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap @@ -812,7 +814,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller.setHeadsUpManager(mHeadsUpManager); mGroupManager.setOnGroupChangeListener(mStackScroller); - inflateOverflowContainer(); + inflateShelf(); inflateEmptyShadeView(); inflateDismissView(); mExpandedContents = mStackScroller; @@ -1049,13 +1051,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return new BatteryControllerImpl(mContext); } - private void inflateOverflowContainer() { - mKeyguardIconOverflowContainer = - (NotificationOverflowContainer) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false); - mKeyguardIconOverflowContainer.setOnActivatedListener(this); - mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener); - mStackScroller.setOverflowContainer(mKeyguardIconOverflowContainer); + private void inflateShelf() { + mNotificationShelf = + (NotificationShelf) LayoutInflater.from(mContext).inflate( + R.layout.status_bar_notification_shelf, mStackScroller, false); + mNotificationShelf.setOnActivatedListener(this); + mNotificationShelf.setOnClickListener(mShelfClickListener); + mStackScroller.setShelf(mNotificationShelf); } @Override @@ -1072,7 +1074,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateClearAll(); inflateEmptyShadeView(); updateEmptyShadeView(); - inflateOverflowContainer(); + inflateShelf(); mStatusBarKeyguardViewManager.onDensityOrFontScaleChanged(); mUserInfoController.onDensityOrFontScaleChanged(); if (mUserSwitcherController != null) { @@ -1866,7 +1868,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mTmpChildOrderMap.clear(); updateRowStates(); - updateSpeedbump(); + updateSpeedBumpIndex(); updateClearAll(); updateEmptyShadeView(); @@ -1987,8 +1989,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNotificationPanel.setShadeEmpty(showEmptyShade); } - private void updateSpeedbump() { - int speedbumpIndex = -1; + private void updateSpeedBumpIndex() { + int speedBumpIndex = -1; int currentIndex = 0; final int N = mStackScroller.getChildCount(); for (int i = 0; i < N; i++) { @@ -1998,12 +2000,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } ExpandableNotificationRow row = (ExpandableNotificationRow) view; if (mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) { - speedbumpIndex = currentIndex; + speedBumpIndex = currentIndex; break; } currentIndex++; } - mStackScroller.updateSpeedBumpIndex(speedbumpIndex); + boolean noAmbient = false; + if (speedBumpIndex == -1) { + speedBumpIndex = currentIndex; + noAmbient = true; + } + mStackScroller.updateSpeedBumpIndex(speedBumpIndex, noAmbient); } public static boolean isTopLevelChild(Entry entry) { @@ -2375,8 +2382,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } protected int adjustDisableFlags(int state) { - if (!mLaunchTransitionFadingAway && !mKeyguardFadingAway - && (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit)) { + if (!mLaunchTransitionFadingAway && !mKeyguardFadingAway && shouldHideNotificationIcons()) { state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS; state |= StatusBarManager.DISABLE_SYSTEM_INFO; } @@ -2391,6 +2397,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return state; } + private boolean shouldHideNotificationIcons() { + return mExpandedVisible && mNotificationPanel.shouldHideNotificationIcons(); + } + /** * State is one or more of the DISABLE constants from StatusBarManager. */ @@ -2497,7 +2507,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, * * This needs to be called if state used by {@link #adjustDisableFlags} changes. */ - private void recomputeDisableFlags(boolean animate) { + public void recomputeDisableFlags(boolean animate) { disable(mDisabledUnmodified1, mDisabledUnmodified2, animate); } @@ -2694,6 +2704,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mFalsingManager.onScreenOff(); } + public NotificationShelf getNotificationShelf() { + return mNotificationShelf; + } + + public NotificationStackScrollLayout getNotificationScrollLayout() { + return mStackScroller; + } + public boolean isPulsing() { return mDozeScrimController.isPulsing(); } @@ -2935,7 +2953,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, runPostCollapseRunnables(); setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); showBouncer(); - recomputeDisableFlags(true /* animate */); + recomputeDisableFlags(shouldAnimatIconHiding() /* animate */); // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in // the bouncer appear animation. @@ -2944,6 +2962,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } + private boolean shouldAnimatIconHiding() { + return mNotificationPanel.shouldAnimateIconHiding(); + } + public boolean interceptTouchEvent(MotionEvent event) { if (DEBUG_GESTURES) { if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { @@ -4153,7 +4175,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mScrimController.forceHideScrims(true /* hide */); updateMediaMetaData(false, true); mNotificationPanel.setAlpha(1); - mStackScroller.setParentFadingOut(true); + mStackScroller.setParentNotFullyVisible(true); mNotificationPanel.animate() .alpha(0) .setStartDelay(FADE_KEYGUARD_START_DELAY) 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 a4b76ebb53dd..0e74e579624b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -21,7 +21,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; -import android.graphics.Color; import android.graphics.Rect; import android.support.v4.graphics.ColorUtils; import android.view.View; @@ -37,7 +36,7 @@ import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.stack.StackStateAnimator; +import com.android.systemui.statusbar.stack.ViewState; /** * Controls both the scrim behind the notifications and in front of the notifications (when a @@ -79,7 +78,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private boolean mWakeAndUnlocking; protected boolean mAnimateChange; private boolean mUpdatePending; - private boolean mExpanding; + private boolean mTracking; private boolean mAnimateKeyguardFadingOut; protected long mDurationOverride = -1; private long mAnimationDelay; @@ -123,12 +122,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } public void onTrackingStarted() { - mExpanding = true; + mTracking = true; mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer(); } public void onExpandingFinished() { - mExpanding = false; + mTracking = false; } public void setPanelExpansion(float fraction) { @@ -138,7 +137,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, if (mPinnedHeadsUpCount != 0) { updateHeadsUpScrim(false); } - if (mKeyguardFadeoutAnimation != null) { + if (mKeyguardFadeoutAnimation != null && mTracking) { mKeyguardFadeoutAnimation.cancel(); } } @@ -146,7 +145,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, public void setBouncerShowing(boolean showing) { mBouncerShowing = showing; - mAnimateChange = !mExpanding && !mDontAnimateBouncerChanges; + mAnimateChange = !mTracking && !mDontAnimateBouncerChanges; scheduleUpdate(); } @@ -269,7 +268,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } private void updateScrimKeyguard() { - if (mExpanding && mDarkenWhileDragging) { + if (mTracking && mDarkenWhileDragging) { float behindFraction = Math.max(0, Math.min(mFraction, 1)); float fraction = 1 - behindFraction; fraction = (float) Math.pow(fraction, 0.8f); @@ -278,7 +277,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, setScrimBehindColor(behindFraction * mScrimBehindAlphaKeyguard); } else if (mBouncerShowing && !mBouncerIsKeyguard) { setScrimInFrontColor(getScrimInFrontAlpha()); - setScrimBehindColor(0f); + updateScrimNormal(); } else if (mBouncerShowing) { setScrimInFrontColor(0f); setScrimBehindColor(mScrimBehindAlpha); @@ -474,18 +473,18 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) { - if (mKeyguardFadingOutInProgress) { + if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) { return; } - ValueAnimator previousAnimator = StackStateAnimator.getChildTag(scrim, + ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM); float animEndValue = -1; if (previousAnimator != null) { if (animate || alpha == currentAlpha) { previousAnimator.cancel(); } else { - animEndValue = StackStateAnimator.getChildTag(scrim, TAG_END_ALPHA); + animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA); } } if (alpha != currentAlpha && alpha != animEndValue) { @@ -495,10 +494,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, scrim.setTag(TAG_END_ALPHA, alpha); } else { if (previousAnimator != null) { - float previousStartValue = StackStateAnimator.getChildTag(scrim, - TAG_START_ALPHA); - float previousEndValue = StackStateAnimator.getChildTag(scrim, - TAG_END_ALPHA); + float previousStartValue = ViewState.getChildTag(scrim, TAG_START_ALPHA); + float previousEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA); // we need to increase all animation keyframes of the previous animator by the // relative change to the end value PropertyValuesHolder[] values = previousAnimator.getValues(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index dbe7f96b17fb..a948a0822a43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -44,6 +44,7 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.tuner.TunerService; @@ -74,6 +75,7 @@ public class StatusBarIconController extends StatusBarIconList implements Tunabl private NotificationIconAreaController mNotificationIconAreaController; private View mNotificationIconAreaInner; + private NotificationShelf mNotificationShelf; private BatteryMeterView mBatteryMeterView; private BatteryMeterView mBatteryMeterViewKeyguard; @@ -123,6 +125,7 @@ public class StatusBarIconController extends StatusBarIconList implements Tunabl mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons); mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster); + mNotificationShelf = phoneStatusBar.getNotificationShelf(); mNotificationIconAreaController = SystemUIFactory.getInstance() .createNotificationIconAreaController(context, phoneStatusBar); mNotificationIconAreaInner = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 69decd72c03a..42636708fae3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -33,6 +33,7 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.DejankUtils; import com.android.keyguard.LatencyTracker; import com.android.systemui.SystemUIFactory; +import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.RemoteInputController; @@ -102,14 +103,15 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void registerStatusBar(PhoneStatusBar phoneStatusBar, ViewGroup container, StatusBarWindowManager statusBarWindowManager, ScrimController scrimController, - FingerprintUnlockController fingerprintUnlockController) { + FingerprintUnlockController fingerprintUnlockController, + DismissCallbackRegistry dismissCallbackRegistry) { mPhoneStatusBar = phoneStatusBar; mContainer = container; mStatusBarWindowManager = statusBarWindowManager; mScrimController = scrimController; mFingerprintUnlockController = fingerprintUnlockController; mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext, - mViewMediatorCallback, mLockPatternUtils, mStatusBarWindowManager, container); + mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry); } /** @@ -330,17 +332,21 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb }); } else { executeAfterKeyguardGoneAction(); - if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING) { - mFingerprintUnlockController.startKeyguardFadingAway(); - mPhoneStatusBar.setKeyguardFadingAway(startTime, 0, 240); + boolean wakeUnlockPulsing = + mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING; + if (wakeUnlockPulsing) { + delay = 0; + fadeoutDuration = 240; + } + mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration); + mFingerprintUnlockController.startKeyguardFadingAway(); + mBouncer.hide(true /* destroyView */); + updateStates(); + if (wakeUnlockPulsing) { mStatusBarWindowManager.setKeyguardFadingAway(true); mPhoneStatusBar.fadeKeyguardWhilePulsing(); - animateScrimControllerKeyguardFadingOut(0, 240, new Runnable() { - @Override - public void run() { - mPhoneStatusBar.hideKeyguard(); - } - }, false /* skipFirstFrame */); + animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration, + mPhoneStatusBar::hideKeyguard, false /* skipFirstFrame */); } else { mFingerprintUnlockController.startKeyguardFadingAway(); mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration); @@ -367,9 +373,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } mStatusBarWindowManager.setKeyguardShowing(false); - mBouncer.hide(true /* destroyView */); mViewMediatorCallback.keyguardGone(); - updateStates(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 621743365a6e..f6dd88d9a475 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -235,7 +235,7 @@ public class StatusBarWindowView extends FrameLayout { @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getActionMasked() == MotionEvent.ACTION_DOWN - && mNotificationPanel.getExpandedHeight() == 0f) { + && mNotificationPanel.isFullyCollapsed()) { mNotificationPanel.startExpandLatencyTracking(); } mFalsingManager.onTouchEvent(ev, getWidth(), getHeight()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index f6c0942d7ed0..c21c493f7636 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -103,6 +103,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL private boolean mWaitingOnCollapseWhenGoingAway; private boolean mIsObserving; private boolean mRemoteInputActive; + private float mExpandedHeight; public HeadsUpManager(final Context context, View statusBarWindowView, NotificationGroupManager groupManager) { @@ -513,7 +514,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL row = groupSummary; } } - return row.getPinnedHeadsUpHeight(true /* atLeastMinHeight */); + return row.getPinnedHeadsUpHeight(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java index fafbdd101e5e..039661349910 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java @@ -92,20 +92,6 @@ public class KeyguardMonitor extends KeyguardUpdateMonitorCallback return mCanSkipBouncer; } - public void unlock() { - try { - WindowManagerGlobal.getWindowManagerService().dismissKeyguard(); - } catch (RemoteException e) { - } - } - - public void lock() { - try { - WindowManagerGlobal.getWindowManagerService().lockNow(null /* options */); - } catch (RemoteException e) { - } - } - public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) { if (mShowing == showing && mSecure == secure && mOccluded == occluded) return; mShowing = showing; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java index 81da6720828a..26f74eab7ba7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -16,9 +16,12 @@ package com.android.systemui.statusbar.stack; +import android.content.Context; import android.view.View; +import com.android.systemui.R; import com.android.systemui.statusbar.ActivatableNotificationView; +import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.ArrayList; @@ -44,6 +47,38 @@ public class AmbientState { private float mMaxHeadsUpTranslation; private boolean mDismissAllInProgress; private int mLayoutMinHeight; + private NotificationShelf mShelf; + private int mZDistanceBetweenElements; + private int mBaseZHeight; + private int mMaxLayoutHeight; + private ActivatableNotificationView mLastVisibleBackgroundChild; + + public AmbientState(Context context) { + reload(context); + } + + /** + * Reload the dimens e.g. if the density changed. + */ + public void reload(Context context) { + mZDistanceBetweenElements = Math.max(1, context.getResources() + .getDimensionPixelSize(R.dimen.z_distance_between_notifications)); + mBaseZHeight = 4 * mZDistanceBetweenElements; + } + + /** + * @return the basic Z height on which notifications remain. + */ + public int getBaseZHeight() { + return mBaseZHeight; + } + + /** + * @return the distance in Z between two overlaying notifications. + */ + public int getZDistanceBetweenElements() { + return mZDistanceBetweenElements; + } public int getScrollY() { return mScrollY; @@ -122,8 +157,8 @@ public class AmbientState { return mSpeedBumpIndex; } - public void setSpeedBumpIndex(int speedBumpIndex) { - mSpeedBumpIndex = speedBumpIndex; + public void setSpeedBumpIndex(int shelfIndex) { + mSpeedBumpIndex = shelfIndex; } public void setHeadsUpManager(HeadsUpManager headsUpManager) { @@ -151,7 +186,7 @@ public class AmbientState { } public int getInnerHeight() { - return Math.max(mLayoutHeight - mTopPadding, mLayoutMinHeight); + return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding, mLayoutMinHeight); } public boolean isShadeExpanded() { @@ -181,4 +216,29 @@ public class AmbientState { public void setLayoutMinHeight(int layoutMinHeight) { mLayoutMinHeight = layoutMinHeight; } + + public void setShelf(NotificationShelf shelf) { + mShelf = shelf; + } + + public NotificationShelf getShelf() { + return mShelf; + } + + public void setLayoutMaxHeight(int maxLayoutHeight) { + mMaxLayoutHeight = maxLayoutHeight; + } + + /** + * Sets the last visible view of the host layout, that has a background, i.e the very last + * view in the shade, without the clear all button. + */ + public void setLastVisibleBackgroundChild( + ActivatableNotificationView lastVisibleBackgroundChild) { + mLastVisibleBackgroundChild = lastVisibleBackgroundChild; + } + + public ActivatableNotificationView getLastVisibleBackgroundChild() { + return mLastVisibleBackgroundChild; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java index 561b18a68532..d3d58f90c9e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java @@ -23,6 +23,7 @@ import java.util.ArrayList; */ public class AnimationFilter { boolean animateAlpha; + boolean animateX; boolean animateY; boolean animateZ; boolean animateHeight; @@ -42,6 +43,11 @@ public class AnimationFilter { return this; } + public AnimationFilter animateX() { + animateX = true; + return this; + } + public AnimationFilter animateY() { animateY = true; return this; @@ -116,6 +122,7 @@ public class AnimationFilter { private void combineFilter(AnimationFilter filter) { animateAlpha |= filter.animateAlpha; + animateX |= filter.animateX; animateY |= filter.animateY; animateZ |= filter.animateZ; animateHeight |= filter.animateHeight; @@ -129,6 +136,7 @@ public class AnimationFilter { private void reset() { animateAlpha = false; + animateX = false; animateY = false; animateZ = false; animateHeight = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java new file mode 100644 index 000000000000..0de774dc381b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java @@ -0,0 +1,65 @@ +/* + * 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.systemui.statusbar.stack; + +import android.animation.AnimatorListenerAdapter; +import android.util.Property; +import android.view.View; +import android.view.animation.Interpolator; + +/** + * Properties for a View animation + */ +public class AnimationProperties { + public long duration; + public long delay; + + /** + * @return an animation filter for this animation. + */ + public AnimationFilter getAnimationFilter() { + return new AnimationFilter(); + } + + /** + * @return a listener that should be run whenever any property finished its animation + */ + public AnimatorListenerAdapter getAnimationFinishListener() { + return null; + } + + public boolean wasAdded(View view) { + return false; + } + + /** + * Get a custom interpolator for a property instead of the normal one. + */ + public Interpolator getCustomInterpolator(View child, Property property) { + return null; + } + + public AnimationProperties setDuration(long duration) { + this.duration = duration; + return this; + } + + public AnimationProperties setDelay(long delay) { + this.delay = delay; + return this; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java new file mode 100644 index 000000000000..58e6838c815b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java @@ -0,0 +1,455 @@ +/* + * 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.systemui.statusbar.stack; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.view.View; + +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.ExpandableView; + +/** +* A state of an expandable view +*/ +public class ExpandableViewState extends ViewState { + + private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag; + private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag; + private static final int TAG_ANIMATOR_SHADOW_ALPHA = R.id.shadow_alpha_animator_tag; + private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag; + private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag; + private static final int TAG_END_SHADOW_ALPHA = R.id.shadow_alpha_animator_end_value_tag; + private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag; + private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag; + private static final int TAG_START_SHADOW_ALPHA = R.id.shadow_alpha_animator_start_value_tag; + + // These are flags such that we can create masks for filtering. + + /** + * No known location. This is the default and should not be set after an invocation of the + * algorithm. + */ + public static final int LOCATION_UNKNOWN = 0x00; + + /** + * The location is the first heads up notification, so on the very top. + */ + public static final int LOCATION_FIRST_HUN = 0x01; + + /** + * The location is hidden / scrolled away on the top. + */ + public static final int LOCATION_HIDDEN_TOP = 0x02; + + /** + * The location is in the main area of the screen and visible. + */ + public static final int LOCATION_MAIN_AREA = 0x04; + + /** + * The location is in the bottom stack and it's peeking + */ + public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x08; + + /** + * The location is in the bottom stack and it's hidden. + */ + public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10; + + /** + * The view isn't laid out at all. + */ + public static final int LOCATION_GONE = 0x40; + + public int height; + public boolean dimmed; + public boolean dark; + public boolean hideSensitive; + public boolean belowSpeedBump; + public float shadowAlpha; + public boolean inShelf; + + /** + * How much the child overlaps with the previous child on top. This is used to + * show the background properly when the child on top is translating away. + */ + public int clipTopAmount; + + /** + * The index of the view, only accounting for views not equal to GONE + */ + public int notGoneIndex; + + /** + * The location this view is currently rendered at. + * + * <p>See <code>LOCATION_</code> flags.</p> + */ + public int location; + + /** + * Whether a child in a group is being clipped at the bottom. + */ + public boolean isBottomClipped; + + @Override + public void copyFrom(ViewState viewState) { + super.copyFrom(viewState); + if (viewState instanceof ExpandableViewState) { + ExpandableViewState svs = (ExpandableViewState) viewState; + height = svs.height; + dimmed = svs.dimmed; + shadowAlpha = svs.shadowAlpha; + dark = svs.dark; + hideSensitive = svs.hideSensitive; + belowSpeedBump = svs.belowSpeedBump; + clipTopAmount = svs.clipTopAmount; + notGoneIndex = svs.notGoneIndex; + location = svs.location; + isBottomClipped = svs.isBottomClipped; + } + } + + /** + * Applies a {@link ExpandableViewState} to a {@link ExpandableView}. + */ + @Override + public void applyToView(View view) { + super.applyToView(view); + if (view instanceof ExpandableView) { + ExpandableView expandableView = (ExpandableView) view; + + int height = expandableView.getActualHeight(); + int newHeight = this.height; + + // apply height + if (height != newHeight) { + expandableView.setActualHeight(newHeight, false /* notifyListeners */); + } + + float shadowAlpha = expandableView.getShadowAlpha(); + float newShadowAlpha = this.shadowAlpha; + + // apply shadowAlpha + if (shadowAlpha != newShadowAlpha) { + expandableView.setShadowAlpha(newShadowAlpha); + } + + // apply dimming + expandableView.setDimmed(this.dimmed, false /* animate */); + + // apply hiding sensitive + expandableView.setHideSensitive( + this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */); + + // apply below shelf speed bump + expandableView.setBelowSpeedBump(this.belowSpeedBump); + + // apply dark + expandableView.setDark(this.dark, false /* animate */, 0 /* delay */); + + // apply clipping + float oldClipTopAmount = expandableView.getClipTopAmount(); + if (oldClipTopAmount != this.clipTopAmount) { + expandableView.setClipTopAmount(this.clipTopAmount); + } + + expandableView.setTransformingInShelf(false); + expandableView.setInShelf(inShelf); + } + } + + @Override + public void animateTo(View child, AnimationProperties properties) { + super.animateTo(child, properties); + if (!(child instanceof ExpandableView)) { + return; + } + ExpandableView expandableView = (ExpandableView) child; + AnimationFilter animationFilter = properties.getAnimationFilter(); + + // start height animation + if (this.height != expandableView.getActualHeight()) { + startHeightAnimation(expandableView, properties); + } else { + abortAnimation(child, TAG_ANIMATOR_HEIGHT); + } + + // start shadow alpha animation + if (this.shadowAlpha != expandableView.getShadowAlpha()) { + startShadowAlphaAnimation(expandableView, properties); + } else { + abortAnimation(child, TAG_ANIMATOR_SHADOW_ALPHA); + } + + // start top inset animation + if (this.clipTopAmount != expandableView.getClipTopAmount()) { + startInsetAnimation(expandableView, properties); + } else { + abortAnimation(child, TAG_ANIMATOR_TOP_INSET); + } + + // start dimmed animation + expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed); + + // apply below the speed bump + expandableView.setBelowSpeedBump(this.belowSpeedBump); + + // start hiding sensitive animation + expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive, + properties.delay, properties.duration); + + // start dark animation + expandableView.setDark(this.dark, animationFilter.animateDark, properties.delay); + + if (properties.wasAdded(child) && !hidden) { + expandableView.performAddAnimation(properties.delay, properties.duration); + } + + if (!expandableView.isInShelf() && this.inShelf) { + expandableView.setTransformingInShelf(true); + } + expandableView.setInShelf(this.inShelf); + } + + private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) { + Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT); + Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT); + int newEndValue = this.height; + if (previousEndValue != null && previousEndValue == newEndValue) { + return; + } + ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT); + AnimationFilter filter = properties.getAnimationFilter(); + if (!filter.animateHeight) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + int relativeDiff = newEndValue - previousEndValue; + int newStartValue = previousStartValue + relativeDiff; + values[0].setIntValues(newStartValue, newEndValue); + child.setTag(TAG_START_HEIGHT, newStartValue); + child.setTag(TAG_END_HEIGHT, newEndValue); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + child.setActualHeight(newEndValue, false); + return; + } + } + + ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + child.setActualHeight((int) animation.getAnimatedValue(), + false /* notifyListeners */); + } + }); + animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); + animator.setDuration(newDuration); + if (properties.delay > 0 && (previousAnimator == null + || previousAnimator.getAnimatedFraction() == 0)) { + animator.setStartDelay(properties.delay); + } + AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + if (listener != null) { + animator.addListener(listener); + } + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + boolean mWasCancelled; + + @Override + public void onAnimationEnd(Animator animation) { + child.setTag(TAG_ANIMATOR_HEIGHT, null); + child.setTag(TAG_START_HEIGHT, null); + child.setTag(TAG_END_HEIGHT, null); + child.setActualHeightAnimating(false); + if (!mWasCancelled && child instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) child).setGroupExpansionChanging( + false /* isExpansionChanging */); + } + } + + @Override + public void onAnimationStart(Animator animation) { + mWasCancelled = false; + } + + @Override + public void onAnimationCancel(Animator animation) { + mWasCancelled = true; + } + }); + startAnimator(animator, listener); + child.setTag(TAG_ANIMATOR_HEIGHT, animator); + child.setTag(TAG_START_HEIGHT, child.getActualHeight()); + child.setTag(TAG_END_HEIGHT, newEndValue); + child.setActualHeightAnimating(true); + } + + private void startShadowAlphaAnimation(final ExpandableView child, + AnimationProperties properties) { + Float previousStartValue = getChildTag(child, TAG_START_SHADOW_ALPHA); + Float previousEndValue = getChildTag(child, TAG_END_SHADOW_ALPHA); + float newEndValue = this.shadowAlpha; + if (previousEndValue != null && previousEndValue == newEndValue) { + return; + } + ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SHADOW_ALPHA); + AnimationFilter filter = properties.getAnimationFilter(); + if (!filter.animateShadowAlpha) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + float relativeDiff = newEndValue - previousEndValue; + float newStartValue = previousStartValue + relativeDiff; + values[0].setFloatValues(newStartValue, newEndValue); + child.setTag(TAG_START_SHADOW_ALPHA, newStartValue); + child.setTag(TAG_END_SHADOW_ALPHA, newEndValue); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + child.setShadowAlpha(newEndValue); + return; + } + } + + ValueAnimator animator = ValueAnimator.ofFloat(child.getShadowAlpha(), newEndValue); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + child.setShadowAlpha((float) animation.getAnimatedValue()); + } + }); + animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); + animator.setDuration(newDuration); + if (properties.delay > 0 && (previousAnimator == null + || previousAnimator.getAnimatedFraction() == 0)) { + animator.setStartDelay(properties.delay); + } + AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + if (listener != null) { + animator.addListener(listener); + } + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, null); + child.setTag(TAG_START_SHADOW_ALPHA, null); + child.setTag(TAG_END_SHADOW_ALPHA, null); + } + }); + startAnimator(animator, listener); + child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, animator); + child.setTag(TAG_START_SHADOW_ALPHA, child.getShadowAlpha()); + child.setTag(TAG_END_SHADOW_ALPHA, newEndValue); + } + + private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) { + Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET); + Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET); + int newEndValue = this.clipTopAmount; + if (previousEndValue != null && previousEndValue == newEndValue) { + return; + } + ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET); + AnimationFilter filter = properties.getAnimationFilter(); + if (!filter.animateTopInset) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + int relativeDiff = newEndValue - previousEndValue; + int newStartValue = previousStartValue + relativeDiff; + values[0].setIntValues(newStartValue, newEndValue); + child.setTag(TAG_START_TOP_INSET, newStartValue); + child.setTag(TAG_END_TOP_INSET, newEndValue); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + child.setClipTopAmount(newEndValue); + return; + } + } + + ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + child.setClipTopAmount((int) animation.getAnimatedValue()); + } + }); + animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); + animator.setDuration(newDuration); + if (properties.delay > 0 && (previousAnimator == null + || previousAnimator.getAnimatedFraction() == 0)) { + animator.setStartDelay(properties.delay); + } + AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + if (listener != null) { + animator.addListener(listener); + } + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + child.setTag(TAG_ANIMATOR_TOP_INSET, null); + child.setTag(TAG_START_TOP_INSET, null); + child.setTag(TAG_END_TOP_INSET, null); + } + }); + startAnimator(animator, listener); + child.setTag(TAG_ANIMATOR_TOP_INSET, animator); + child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount()); + child.setTag(TAG_END_TOP_INSET, newEndValue); + } + + /** + * Get the end value of the height animation running on a view or the actualHeight + * if no animation is running. + */ + public static int getFinalActualHeight(ExpandableView view) { + if (view == null) { + return 0; + } + ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT); + if (heightAnimator == null) { + return view.getActualHeight(); + } else { + return getChildTag(view, TAG_END_HEIGHT); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java index d7920a9b1e7b..26e134273f19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -185,6 +185,11 @@ public class NotificationChildrenContainer extends ViewGroup { } @Override + public boolean hasOverlappingRendering() { + return false; + } + + @Override public boolean pointInView(float localX, float localY, float slop) { return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) && localY < (mRealHeight + slop); @@ -207,6 +212,7 @@ public class NotificationChildrenContainer extends ViewGroup { mDividers.add(newIndex, divider); updateGroupOverflow(); + row.setIconTransformationAmount(0, false /* isLastChild */); } public void removeNotification(ExpandableNotificationRow row) { @@ -412,7 +418,7 @@ public class NotificationChildrenContainer extends ViewGroup { * @param resultState the state to update * @param parentState the state of the parent */ - public void getState(StackScrollState resultState, StackViewState parentState) { + public void getState(StackScrollState resultState, ExpandableViewState parentState) { int childCount = mChildren.size(); int yPosition = mNotificationHeaderMargin; boolean firstChild = true; @@ -449,7 +455,7 @@ public class NotificationChildrenContainer extends ViewGroup { firstChild = false; } - StackViewState childState = resultState.getViewStateForView(child); + ExpandableViewState childState = resultState.getViewStateForView(child); int intrinsicHeight = child.getIntrinsicHeight(); if (childrenExpanded) { // When a group is expanded and moving into bottom stack, the bottom visible child @@ -530,7 +536,7 @@ public class NotificationChildrenContainer extends ViewGroup { * @return true if children after this one should be hidden. */ private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child, - int parentHeight, StackViewState childState, int yPosition) { + int parentHeight, ExpandableViewState childState, int yPosition) { final int top = yPosition + child.getClipTopAmount(); final int intrinsicHeight = child.getIntrinsicHeight(); final int bottom = top + intrinsicHeight; @@ -570,8 +576,8 @@ public class NotificationChildrenContainer extends ViewGroup { || mNotificationParent.isGroupExpansionChanging(); for (int i = 0; i < childCount; i++) { ExpandableNotificationRow child = mChildren.get(i); - StackViewState viewState = state.getViewStateForView(child); - state.applyState(child, viewState); + ExpandableViewState viewState = state.getViewStateForView(child); + viewState.applyToView(child); // layout the divider View divider = mDividers.get(i); @@ -584,16 +590,16 @@ public class NotificationChildrenContainer extends ViewGroup { } tmpState.hidden = !dividersVisible; tmpState.alpha = alpha; - state.applyViewState(divider, tmpState); + tmpState.applyToView(divider); // There is no fake shadow to be drawn on the children child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0); } - if (mOverflowNumber != null) { - state.applyViewState(mOverflowNumber, mGroupOverFlowState); + if (mGroupOverFlowState != null) { + mGroupOverFlowState.applyToView(mOverflowNumber); mNeverAppliedGroupState = false; } - if (mNotificationHeader != null) { - state.applyViewState(mNotificationHeader, mHeaderViewState); + if (mHeaderViewState != null) { + mHeaderViewState.applyToView(mNotificationHeader); } } @@ -608,8 +614,7 @@ public class NotificationChildrenContainer extends ViewGroup { return; } - public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator, - long baseDelay, long duration) { + public void startAnimationToState(StackScrollState state, AnimationProperties properties) { int childCount = mChildren.size(); ViewState tmpState = new ViewState(); float expandFraction = getGroupExpandFraction(); @@ -617,8 +622,8 @@ public class NotificationChildrenContainer extends ViewGroup { || mNotificationParent.isGroupExpansionChanging(); for (int i = childCount - 1; i >= 0; i--) { ExpandableNotificationRow child = mChildren.get(i); - StackViewState viewState = state.getViewStateForView(child); - stateAnimator.startStackAnimations(child, viewState, state, -1, baseDelay); + ExpandableViewState viewState = state.getViewStateForView(child); + viewState.animateTo(child, properties); // layout the divider View divider = mDividers.get(i); @@ -631,7 +636,7 @@ public class NotificationChildrenContainer extends ViewGroup { } tmpState.hidden = !dividersVisible; tmpState.alpha = alpha; - stateAnimator.startViewAnimations(divider, tmpState, baseDelay, duration); + tmpState.animateTo(divider, properties); // There is no fake shadow to be drawn on the children child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0); } @@ -639,15 +644,14 @@ public class NotificationChildrenContainer extends ViewGroup { if (mNeverAppliedGroupState) { float alpha = mGroupOverFlowState.alpha; mGroupOverFlowState.alpha = 0; - state.applyViewState(mOverflowNumber, mGroupOverFlowState); + mGroupOverFlowState.applyToView(mOverflowNumber); mGroupOverFlowState.alpha = alpha; mNeverAppliedGroupState = false; } - stateAnimator.startViewAnimations(mOverflowNumber, mGroupOverFlowState, - baseDelay, duration); + mGroupOverFlowState.animateTo(mOverflowNumber, properties); } if (mNotificationHeader != null) { - state.applyViewState(mNotificationHeader, mHeaderViewState); + mHeaderViewState.applyToView(mNotificationHeader); } } @@ -876,4 +880,13 @@ public class NotificationChildrenContainer extends ViewGroup { } return 0; } + + public void setIconsVisible(boolean iconsVisible) { + if (mNotificationHeaderWrapper != null) { + NotificationHeaderView header = mNotificationHeaderWrapper.getNotificationHeader(); + if (header != null) { + header.getIcon().setForceHidden(!iconsVisible); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 72a0e5929a17..49da79388c3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -69,9 +69,9 @@ import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.NotificationGuts; -import com.android.systemui.statusbar.NotificationOverflowContainer; import com.android.systemui.statusbar.NotificationSettingsIconRow; import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener; +import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StackScrollerDecorView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.FakeShadowView; @@ -135,8 +135,6 @@ public class NotificationStackScrollLayout extends ViewGroup private Paint mDebugPaint; private int mContentHeight; private int mCollapsedSize; - private int mBottomStackSlowDownHeight; - private int mBottomStackPeekSize; private int mPaddingBetweenElements; private int mIncreasedPaddingBetweenElements; private int mTopPadding; @@ -151,7 +149,7 @@ public class NotificationStackScrollLayout extends ViewGroup * The current State this Layout is in */ private StackScrollState mCurrentStackScrollState = new StackScrollState(this); - private AmbientState mAmbientState = new AmbientState(); + private final AmbientState mAmbientState; private NotificationGroupManager mGroupManager; private HashSet<View> mChildrenToAddAnimated = new HashSet<>(); private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>(); @@ -261,18 +259,14 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mTrackingHeadsUp; private ScrimController mScrimController; private boolean mForceNoOverlappingRendering; - private NotificationOverflowContainer mOverflowContainer; private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>(); private FalsingManager mFalsingManager; private boolean mAnimationRunning; - private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater + private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { - // if it needs animation - if (!mNeedsAnimation && !mChildrenUpdateRequested) { - updateBackground(); - } + onPreDrawDuringAnimation(); return true; } }; @@ -334,7 +328,7 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mPulsing; private boolean mDrawBackgroundAsSrc; private boolean mFadingOut; - private boolean mParentFadingOut; + private boolean mParentNotFullyVisible; private boolean mGroupExpandedForMeasure; private boolean mScrollable; private View mForcedScroll; @@ -354,6 +348,15 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mQsExpanded; private boolean mForwardScrollable; private boolean mBackwardScrollable; + private NotificationShelf mShelf; + private int mMaxDisplayedNotifications = -1; + private int mStatusBarHeight; + private boolean mNoAmbient; + private final Rect mClipRect = new Rect(); + private boolean mIsClipped; + private Rect mRequestedClipBounds; + private boolean mInHeadsUpPinnedMode; + private boolean mHeadsUpAnimatingAway; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -370,6 +373,7 @@ public class NotificationStackScrollLayout extends ViewGroup public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mAmbientState = new AmbientState(context); mBgColor = context.getColor(R.color.notification_shade_background_color); int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height); @@ -418,11 +422,6 @@ public class NotificationStackScrollLayout extends ViewGroup if (DEBUG) { int y = mTopPadding; canvas.drawLine(0, y, getWidth(), y, mDebugPaint); - y = (int) (getLayoutHeight() - mBottomStackPeekSize - - mBottomStackSlowDownHeight); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); - y = (int) (getLayoutHeight() - mBottomStackPeekSize); - canvas.drawLine(0, y, getWidth(), y, mDebugPaint); y = (int) getLayoutHeight(); canvas.drawLine(0, y, getWidth(), y, mDebugPaint); y = getHeight() - getEmptyBottomMargin(); @@ -459,16 +458,15 @@ public class NotificationStackScrollLayout extends ViewGroup mOverflingDistance = configuration.getScaledOverflingDistance(); mCollapsedSize = context.getResources() .getDimensionPixelSize(R.dimen.notification_min_height); - mBottomStackPeekSize = context.getResources() - .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount); mStackScrollAlgorithm.initView(context); + mAmbientState.reload(context); mPaddingBetweenElements = Math.max(1, context.getResources() .getDimensionPixelSize(R.dimen.notification_divider_height)); mIncreasedPaddingBetweenElements = context.getResources() .getDimensionPixelSize(R.dimen.notification_divider_height_increased); - mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength(); mMinTopOverScrollToEscape = getResources().getDimensionPixelSize( R.dimen.min_top_overscroll_to_qs); + mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height); } public void setDrawBackgroundAsSrc(boolean asSrc) { @@ -477,7 +475,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateSrcDrawing() { - mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && (!mFadingOut && !mParentFadingOut) + mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && !mFadingOut && !mParentNotFullyVisible ? mSrcMode : null); invalidate(); } @@ -529,8 +527,9 @@ public class NotificationStackScrollLayout extends ViewGroup } } - public void updateSpeedBumpIndex(int newIndex) { + public void updateSpeedBumpIndex(int newIndex, boolean noAmbient) { mAmbientState.setSpeedBumpIndex(newIndex); + mNoAmbient = noAmbient; } public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) { @@ -541,21 +540,22 @@ public class NotificationStackScrollLayout extends ViewGroup * Returns the location the given child is currently rendered at. * * @param child the child to get the location for - * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants + * @return one of {@link ExpandableViewState}'s <code>LOCATION_*</code> constants */ public int getChildLocation(View child) { - StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child); + ExpandableViewState childViewState = mCurrentStackScrollState.getViewStateForView(child); if (childViewState == null) { - return StackViewState.LOCATION_UNKNOWN; + return ExpandableViewState.LOCATION_UNKNOWN; } if (childViewState.gone) { - return StackViewState.LOCATION_GONE; + return ExpandableViewState.LOCATION_GONE; } return childViewState.location; } private void setMaxLayoutHeight(int maxLayoutHeight) { mMaxLayoutHeight = maxLayoutHeight; + mShelf.setMaxLayoutHeight(maxLayoutHeight); updateAlgorithmHeightAndPadding(); } @@ -584,6 +584,13 @@ public class NotificationStackScrollLayout extends ViewGroup } } + private void onPreDrawDuringAnimation() { + mShelf.updateAppearance(); + if (!mNeedsAnimation && !mChildrenUpdateRequested) { + updateBackground(); + } + } + private void updateScrollStateForAddedChildren() { if (mChildrenToAddAnimated.isEmpty()) { return; @@ -671,7 +678,18 @@ public class NotificationStackScrollLayout extends ViewGroup */ public void setExpandedHeight(float height) { mExpandedHeight = height; - setIsExpanded(height > 0.0f); + setIsExpanded(height > 0); + int minExpansionHeight = getMinExpansionHeight(); + if (height < minExpansionHeight) { + mClipRect.left = 0; + mClipRect.right = getWidth(); + mClipRect.top = 0; + mClipRect.bottom = (int) height; + height = minExpansionHeight; + setRequestedClipBounds(mClipRect); + } else { + setRequestedClipBounds(null); + } int stackHeight; float translationY; float appearEndPosition = getAppearEndPosition(); @@ -697,6 +715,26 @@ public class NotificationStackScrollLayout extends ViewGroup requestChildrenUpdate(); } setStackTranslation(translationY); + requestChildrenUpdate(); + } + + private void setRequestedClipBounds(Rect clipRect) { + mRequestedClipBounds = clipRect; + updateClipping(); + } + + public void updateClipping() { + boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode + && !mHeadsUpAnimatingAway; + if (mIsClipped != clipped) { + mIsClipped = clipped; + updateFadingState(); + } + if (clipped) { + setClipBounds(mRequestedClipBounds); + } else { + setClipBounds(null); + } } /** @@ -704,13 +742,7 @@ public class NotificationStackScrollLayout extends ViewGroup * Measured relative to the resting position. */ private float getExpandTranslationStart() { - int startPosition = 0; - if (!mTrackingHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) { - startPosition = - Math.min(getFirstChildIntrinsicHeight(), - mMaxLayoutHeight - mIntrinsicPadding - mBottomStackSlowDownHeight - - mBottomStackPeekSize); - } - return startPosition - mTopPadding; + return - mTopPadding; } /** @@ -718,9 +750,14 @@ public class NotificationStackScrollLayout extends ViewGroup * Measured in absolute height. */ private float getAppearStartPosition() { - return mTrackingHeadsUp - ? mHeadsUpManager.getTopHeadsUpPinnedHeight() - : 0; + if (mTrackingHeadsUp && mFirstVisibleBackgroundChild != null) { + if (mFirstVisibleBackgroundChild.isAboveShelf()) { + // If we ever expanded beyond the first notification, it's allowed to merge into + // the shelf + return mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight(); + } + } + return getMinExpansionHeight(); } /** @@ -728,11 +765,16 @@ public class NotificationStackScrollLayout extends ViewGroup * Measured in absolute height. */ private float getAppearEndPosition() { - int firstItemHeight = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp() - ? mHeadsUpManager.getTopHeadsUpPinnedHeight() + mBottomStackPeekSize - + mBottomStackSlowDownHeight - : getLayoutMinHeight(); - return firstItemHeight + (onKeyguard() ? mTopPadding : mIntrinsicPadding); + int appearPosition; + if (mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()) { + appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight(); + } else { + appearPosition = getFirstItemMinHeight(); + } + if (getNotGoneChildCount() > 1) { + appearPosition += mShelf.getIntrinsicHeight(); + } + return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding); } /** @@ -773,14 +815,6 @@ public class NotificationStackScrollLayout extends ViewGroup return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize; } - public int getBottomStackPeekSize() { - return mBottomStackPeekSize; - } - - public int getBottomStackSlowDownHeight() { - return mBottomStackSlowDownHeight; - } - public void setLongPressListener(SwipeHelper.LongPressListener listener) { mSwipeHelper.setLongPressListener(listener); mLongPressListener = listener; @@ -957,7 +991,8 @@ public class NotificationStackScrollLayout extends ViewGroup } float childTop = slidingChild.getTranslationY(); float top = childTop + slidingChild.getClipTopAmount(); - float bottom = childTop + slidingChild.getActualHeight(); + float bottom = childTop + slidingChild.getActualHeight() + - slidingChild.getClipBottomAmount(); float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY)); if (dist < minDist) { @@ -986,7 +1021,8 @@ public class NotificationStackScrollLayout extends ViewGroup } float childTop = slidingChild.getTranslationY(); float top = childTop + slidingChild.getClipTopAmount(); - float bottom = childTop + slidingChild.getActualHeight(); + float bottom = childTop + slidingChild.getActualHeight() + - slidingChild.getClipBottomAmount(); // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and // camera affordance). @@ -1072,7 +1108,7 @@ public class NotificationStackScrollLayout extends ViewGroup row.getStatusBarNotification()); mGroupExpandedForMeasure = false; row.setForceUnlocked(false); - StackViewState viewState = mCurrentStackScrollState.getViewStateForView(view); + ExpandableViewState viewState = mCurrentStackScrollState.getViewStateForView(view); if (viewState != null) { // The view could have been removed return Math.min(viewState.height, maxContentHeight); @@ -1748,8 +1784,7 @@ public class NotificationStackScrollLayout extends ViewGroup private int getScrollRange() { int contentHeight = getContentHeight(); - int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize - + mBottomStackSlowDownHeight); + int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight); int imeInset = getImeInset(); scrollRange += Math.min(imeInset, Math.max(0, getContentHeight() - (getHeight() - imeInset))); @@ -1767,7 +1802,7 @@ public class NotificationStackScrollLayout extends ViewGroup int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); - if (child.getVisibility() != View.GONE) { + if (child.getVisibility() != View.GONE && child != mShelf) { return (ExpandableView) child; } } @@ -1814,7 +1849,7 @@ public class NotificationStackScrollLayout extends ViewGroup int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { View child = getChildAt(i); - if (child.getVisibility() != View.GONE) { + if (child.getVisibility() != View.GONE && child != mShelf) { return child; } } @@ -1829,7 +1864,7 @@ public class NotificationStackScrollLayout extends ViewGroup int count = 0; for (int i = 0; i < childCount; i++) { ExpandableView child = (ExpandableView) getChildAt(i); - if (child.getVisibility() != View.GONE && !child.willBeGone()) { + if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) { count++; } } @@ -1843,9 +1878,17 @@ public class NotificationStackScrollLayout extends ViewGroup private void updateContentHeight() { int height = 0; float previousIncreasedAmount = 0.0f; + int numShownItems = 0; + boolean finish = false; for (int i = 0; i < getChildCount(); i++) { ExpandableView expandableView = (ExpandableView) getChildAt(i); - if (expandableView.getVisibility() != View.GONE) { + if (expandableView.getVisibility() != View.GONE + && !expandableView.hasNoContentHeight()) { + if (mMaxDisplayedNotifications != -1 + && numShownItems >= mMaxDisplayedNotifications) { + expandableView = mShelf; + finish = true; + } float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount(); if (height != 0) { height += (int) NotificationUtils.interpolate( @@ -1855,10 +1898,15 @@ public class NotificationStackScrollLayout extends ViewGroup } previousIncreasedAmount = increasedPaddingAmount; height += expandableView.getIntrinsicHeight(); + numShownItems++; + if (finish) { + break; + } } } mContentHeight = height + mTopPadding; updateScrollability(); + mAmbientState.setLayoutMaxHeight(mContentHeight); } private void updateScrollability() { @@ -2036,7 +2084,7 @@ public class NotificationStackScrollLayout extends ViewGroup private void applyCurrentBackgroundBounds() { mScrimController.setExcludedBackgroundArea( - mFadingOut || mParentFadingOut || mAmbientState.isDark() ? null + mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null : mCurrentBounds); invalidate(); } @@ -2056,7 +2104,7 @@ public class NotificationStackScrollLayout extends ViewGroup ActivatableNotificationView firstView = mFirstVisibleBackgroundChild; int top = 0; if (firstView != null) { - int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(firstView); + int finalTranslationY = (int) ViewState.getFinalTranslationY(firstView); if (mAnimateNextBackgroundTop || mTopAnimator == null && mCurrentBounds.top == finalTranslationY || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) { @@ -2066,11 +2114,18 @@ public class NotificationStackScrollLayout extends ViewGroup top = (int) firstView.getTranslationY(); } } - ActivatableNotificationView lastView = mLastVisibleBackgroundChild; + ActivatableNotificationView lastView = mShelf.hasItemsInStableShelf() + ? mShelf + : mLastVisibleBackgroundChild; int bottom = 0; if (lastView != null) { - int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(lastView); - int finalHeight = StackStateAnimator.getFinalActualHeight(lastView); + int finalTranslationY; + if (lastView == mShelf) { + finalTranslationY = (int) mShelf.getTranslationY(); + } else { + finalTranslationY = (int) ViewState.getFinalTranslationY(lastView); + } + int finalHeight = ExpandableViewState.getFinalActualHeight(lastView); int finalBottom = finalTranslationY + finalHeight; finalBottom = Math.min(finalBottom, getHeight()); if (mAnimateNextBackgroundBottom @@ -2082,6 +2137,7 @@ public class NotificationStackScrollLayout extends ViewGroup bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight()); bottom = Math.min(bottom, getHeight()); } + bottom -= lastView.getClipBottomAmount(); } else { top = mTopPadding; bottom = top; @@ -2115,8 +2171,8 @@ public class NotificationStackScrollLayout extends ViewGroup int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { View child = getChildAt(i); - if (child.getVisibility() != View.GONE - && child instanceof ActivatableNotificationView) { + if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView + && child != mShelf) { return (ActivatableNotificationView) child; } } @@ -2127,8 +2183,8 @@ public class NotificationStackScrollLayout extends ViewGroup int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); - if (child.getVisibility() != View.GONE - && child instanceof ActivatableNotificationView) { + if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView + && child != mShelf) { return (ActivatableNotificationView) child; } } @@ -2211,9 +2267,7 @@ public class NotificationStackScrollLayout extends ViewGroup } public int getLayoutMinHeight() { - int firstChildMinHeight = getFirstChildIntrinsicHeight(); - return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight, - mMaxLayoutHeight - mIntrinsicPadding); + return mShelf.getIntrinsicHeight(); } public int getFirstChildIntrinsicHeight() { @@ -2237,8 +2291,11 @@ public class NotificationStackScrollLayout extends ViewGroup final ExpandableView firstChild = getFirstChildNotGone(); final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight() : mCollapsedSize; - return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize - + mBottomStackSlowDownHeight; + int shelfHeight = 0; + if (mLastVisibleBackgroundChild != null) { + shelfHeight = mShelf.getIntrinsicHeight(); + } + return mIntrinsicPadding + firstChildMinHeight + shelfHeight; } private int clampPadding(int desiredPadding) { @@ -2467,7 +2524,7 @@ public class NotificationStackScrollLayout extends ViewGroup if (hasAddEvent) { // This child was just added lets remove all events. mHeadsUpChangeAnimations.removeAll(mTmpList); - ((ExpandableNotificationRow ) child).setHeadsupDisappearRunning(false); + ((ExpandableNotificationRow ) child).setHeadsUpAnimatingAway(false); } mTmpList.clear(); return hasAddEvent; @@ -2536,7 +2593,7 @@ public class NotificationStackScrollLayout extends ViewGroup for (int i = 0; i < getChildCount(); i++) { ExpandableView child = (ExpandableView) getChildAt(i); boolean notGone = child.getVisibility() != View.GONE; - if (notGone) { + if (notGone && !child.hasNoContentHeight()) { float increasedPaddingAmount = child.getIncreasedPaddingAmount(); if (position != 0) { position += (int) NotificationUtils.interpolate( @@ -2577,6 +2634,7 @@ public class NotificationStackScrollLayout extends ViewGroup } mFirstVisibleBackgroundChild = firstChild; mLastVisibleBackgroundChild = lastChild; + mAmbientState.setLastVisibleBackgroundChild(lastChild); } private void onViewAddedInternal(View child) { @@ -2727,10 +2785,10 @@ public class NotificationStackScrollLayout extends ViewGroup : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; if (row.isChildInGroup()) { // We can otherwise get stuck in there if it was just isolated - row.setHeadsupDisappearRunning(false); + row.setHeadsUpAnimatingAway(false); } } else { - StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row); + ExpandableViewState viewState = mCurrentStackScrollState.getViewStateForView(row); if (viewState == null) { // A view state was never generated for this view, so we don't need to animate // this. This may happen with notification children. @@ -2755,7 +2813,7 @@ public class NotificationStackScrollLayout extends ViewGroup mAddedHeadsUpChildren.clear(); } - private boolean shouldHunAppearFromBottom(StackViewState viewState) { + private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) { if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) { return false; } @@ -3078,14 +3136,7 @@ public class NotificationStackScrollLayout extends ViewGroup } public int getEmptyBottomMargin() { - int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize - - mBottomStackSlowDownHeight; - return Math.max(emptyMargin, 0); - } - - public float getKeyguardBottomStackSize() { - return mBottomStackPeekSize + getResources().getDimensionPixelSize( - R.dimen.bottom_stack_slow_down_length); + return Math.max(mMaxLayoutHeight - mContentHeight, 0); } public void onExpansionStarted() { @@ -3195,20 +3246,18 @@ public class NotificationStackScrollLayout extends ViewGroup if (row.isChildInGroup()) { endPosition += row.getNotificationParent().getTranslationY(); } - int stackEnd = getStackEndPosition(); - if (endPosition > stackEnd) { - setOwnScrollY((int) (mOwnScrollY + endPosition - stackEnd)); + int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation; + if (row != mLastVisibleBackgroundChild) { + layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements; + } + if (endPosition > layoutEnd) { + setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd)); mDisallowScrollingInThisMotion = true; } } } } - private int getStackEndPosition() { - return mMaxLayoutHeight - mBottomStackPeekSize - mBottomStackSlowDownHeight - + mPaddingBetweenElements + (int) mStackTranslation; - } - public void setOnHeightChangedListener( ExpandableView.OnHeightChangedListener mOnHeightChangedListener) { this.mOnHeightChangedListener = mOnHeightChangedListener; @@ -3231,10 +3280,10 @@ public class NotificationStackScrollLayout extends ViewGroup View view = getChildAt(i); if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; - row.setHeadsupDisappearRunning(false); + row.setHeadsUpAnimatingAway(false); if (row.isSummaryWithChildren()) { for (ExpandableNotificationRow child : row.getNotificationChildren()) { - child.setHeadsupDisappearRunning(false); + child.setHeadsUpAnimatingAway(false); } } } @@ -3538,50 +3587,6 @@ public class NotificationStackScrollLayout extends ViewGroup } } - public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) { - int index = -1; - if (mOverflowContainer != null) { - index = indexOfChild(mOverflowContainer); - removeView(mOverflowContainer); - } - mOverflowContainer = overFlowContainer; - addView(mOverflowContainer, index); - } - - public void updateOverflowContainerVisibility(boolean visible) { - int oldVisibility = mOverflowContainer.willBeGone() ? GONE - : mOverflowContainer.getVisibility(); - final int newVisibility = visible ? VISIBLE : GONE; - if (oldVisibility != newVisibility) { - Runnable onFinishedRunnable = new Runnable() { - @Override - public void run() { - mOverflowContainer.setVisibility(newVisibility); - mOverflowContainer.setWillBeGone(false); - updateContentHeight(); - notifyHeightChangeListener(mOverflowContainer); - } - }; - if (!mAnimationsEnabled || !mIsExpanded) { - mOverflowContainer.cancelAppearDrawing(); - onFinishedRunnable.run(); - } else if (newVisibility != GONE) { - mOverflowContainer.performAddAnimation(0, - StackStateAnimator.ANIMATION_DURATION_STANDARD); - mOverflowContainer.setVisibility(newVisibility); - mOverflowContainer.setWillBeGone(false); - updateContentHeight(); - notifyHeightChangeListener(mOverflowContainer); - } else { - mOverflowContainer.performRemoveAnimation( - StackStateAnimator.ANIMATION_DURATION_STANDARD, - 0.0f, - onFinishedRunnable); - mOverflowContainer.setWillBeGone(true); - } - } - } - public void updateDismissView(boolean visible) { int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility(); int newVisibility = visible ? VISIBLE : GONE; @@ -3663,7 +3668,8 @@ public class NotificationStackScrollLayout extends ViewGroup if (child.getVisibility() == GONE) { continue; } - float bottom = child.getTranslationY() + child.getActualHeight(); + float bottom = child.getTranslationY() + child.getActualHeight() + - child.getClipBottomAmount(); if (bottom > max) { max = bottom; } @@ -3701,7 +3707,8 @@ public class NotificationStackScrollLayout extends ViewGroup // we are above a notification entirely let's abort return false; } - boolean belowChild = touchY > childTop + child.getActualHeight(); + boolean belowChild = touchY > childTop + child.getActualHeight() + - child.getClipBottomAmount(); if (child == mDismissView) { if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(), touchY - childTop)) { @@ -3795,7 +3802,7 @@ public class NotificationStackScrollLayout extends ViewGroup // fall through case android.R.id.accessibilityActionScrollUp: final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop - - mBottomStackPeekSize - mBottomStackSlowDownHeight; + - mShelf.getIntrinsicHeight(); final int targetScrollY = Math.max(0, Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange())); if (targetScrollY != mOwnScrollY) { @@ -3835,7 +3842,7 @@ public class NotificationStackScrollLayout extends ViewGroup mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp)); mNeedsAnimation = true; if (!mIsExpanded && !isHeadsUp) { - row.setHeadsupDisappearRunning(true); + row.setHeadsUpAnimatingAway(true); } requestChildrenUpdate(); } @@ -3885,9 +3892,9 @@ public class NotificationStackScrollLayout extends ViewGroup public void setAnimationRunning(boolean animationRunning) { if (animationRunning != mAnimationRunning) { if (animationRunning) { - getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater); + getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater); } else { - getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater); + getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater); } mAnimationRunning = animationRunning; updateContinuousShadowDrawing(); @@ -3910,9 +3917,13 @@ public class NotificationStackScrollLayout extends ViewGroup } } - public void setParentFadingOut(boolean fadingOut) { - if (fadingOut != mParentFadingOut) { - mParentFadingOut = fadingOut; + public void setParentNotFullyVisible(boolean parentNotFullyVisible) { + if (mScrimController == null) { + // we're not set up yet. + return; + } + if (parentNotFullyVisible != mParentNotFullyVisible) { + mParentNotFullyVisible = parentNotFullyVisible; updateFadingState(); } } @@ -3949,6 +3960,45 @@ public class NotificationStackScrollLayout extends ViewGroup } } + public void setShelf(NotificationShelf shelf) { + mShelf = shelf; + int index = -1; + if (mShelf != null) { + index = indexOfChild(mShelf); + removeView(mShelf); + } + addView(mShelf, index); + mAmbientState.setShelf(shelf); + mStateAnimator.setShelf(shelf); + shelf.bind(mAmbientState, this); + } + + public NotificationShelf getNotificationShelf() { + return mShelf; + } + + public void setMaxDisplayedNotifications(int maxDisplayedNotifications) { + if (mMaxDisplayedNotifications != maxDisplayedNotifications) { + mMaxDisplayedNotifications = maxDisplayedNotifications; + updateContentHeight(); + notifyHeightChangeListener(mShelf); + } + } + + public int getMinExpansionHeight() { + return mShelf.getIntrinsicHeight() - (mShelf.getIntrinsicHeight() - mStatusBarHeight) / 2; + } + + public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) { + mInHeadsUpPinnedMode = inHeadsUpPinnedMode; + updateClipping(); + } + + public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { + mHeadsUpAnimatingAway = headsUpAnimatingAway; + updateClipping(); + } + /** * A listener that is notified when some child locations might have changed. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/PiecewiseLinearIndentationFunctor.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/PiecewiseLinearIndentationFunctor.java deleted file mode 100644 index 1c37c35d49dc..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/PiecewiseLinearIndentationFunctor.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2014 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.stack; - -import java.util.ArrayList; - -/** - * A Functor which interpolates the stack distance linearly based on base values. - * The base values are based on an interpolation between a linear function and a - * quadratic function - */ -public class PiecewiseLinearIndentationFunctor extends StackIndentationFunctor { - - private final ArrayList<Float> mBaseValues; - private final float mLinearPart; - - /** - * @param maxItemsInStack The maximum number of items which should be visible at the same time, - * i.e the function returns totalTransitionDistance for the element with - * index maxItemsInStack - * @param peekSize The visual appearance of this is how far the cards in the stack peek - * out below the top card and it is measured in real pixels. - * Note that the visual appearance does not necessarily always correspond to - * the actual visual distance below the top card but is a maximum, - * achieved when the next card just starts transitioning into the stack and - * the stack is full. - * If distanceToPeekStart is 0, we directly start at the peek, otherwise the - * first element transitions between 0 and distanceToPeekStart. - * Visualization: - * --------------------------------------------------- --- - * | | | - * | FIRST ITEM | | <- distanceToPeekStart - * | | | - * |---------------------------------------------------| --- --- - * |__________________SECOND ITEM______________________| | <- peekSize - * |===================================================| _|_ - * - * @param distanceToPeekStart The distance to the start of the peak. - * @param linearPart The interpolation factor between the linear and the quadratic amount taken. - * This factor must be somewhere in [0 , 1] - */ - PiecewiseLinearIndentationFunctor(int maxItemsInStack, - int peekSize, - int distanceToPeekStart, - float linearPart) { - super(maxItemsInStack, peekSize, distanceToPeekStart); - mBaseValues = new ArrayList<Float>(maxItemsInStack+1); - initBaseValues(); - mLinearPart = linearPart; - } - - private void initBaseValues() { - int sumOfSquares = getSumOfSquares(mMaxItemsInStack-1); - int totalWeight = 0; - mBaseValues.add(0.0f); - for (int i = 0; i < mMaxItemsInStack - 1; i++) { - totalWeight += (mMaxItemsInStack - i - 1) * (mMaxItemsInStack - i - 1); - mBaseValues.add((float) totalWeight / sumOfSquares); - } - } - - /** - * Get the sum of squares up to and including n, i.e sum(i * i, 1, n) - * - * @param n the maximum square to include - * @return - */ - private int getSumOfSquares(int n) { - return n * (n + 1) * (2 * n + 1) / 6; - } - - @Override - public float getValue(float itemsBefore) { - if (mStackStartsAtPeek) { - // We directly start at the stack, so no initial interpolation. - itemsBefore++; - } - if (itemsBefore < 0) { - return 0; - } else if (itemsBefore >= mMaxItemsInStack) { - return mTotalTransitionDistance; - } - int below = (int) itemsBefore; - float partialIn = itemsBefore - below; - - if (below == 0) { - return mDistanceToPeekStart * partialIn; - } else { - float result = mDistanceToPeekStart; - float progress = mBaseValues.get(below - 1) * (1 - partialIn) - + mBaseValues.get(below) * partialIn; - result += (progress * (1 - mLinearPart) - + (itemsBefore - 1) / (mMaxItemsInStack - 1) * mLinearPart) * mPeekSize; - return result; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackIndentationFunctor.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackIndentationFunctor.java deleted file mode 100644 index 034eba6836ad..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackIndentationFunctor.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2014 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.stack; - -/** - * A functor which can be queried for offset given the number of items before it. - */ -public abstract class StackIndentationFunctor { - - protected int mTotalTransitionDistance; - protected int mDistanceToPeekStart; - protected int mMaxItemsInStack; - protected int mPeekSize; - protected boolean mStackStartsAtPeek; - - /** - * @param maxItemsInStack The maximum number of items which should be visible at the same time, - * i.e the function returns totalTransitionDistance for the element with - * index maxItemsInStack - * @param peekSize The visual appearance of this is how far the cards in the stack peek - * out below the top card and it is measured in real pixels. - * Note that the visual appearance does not necessarily always correspond to - * the actual visual distance below the top card but is a maximum, - * achieved when the next card just starts transitioning into the stack and - * the stack is full. - * If distanceToPeekStart is 0, we directly start at the peek, otherwise the - * first element transitions between 0 and distanceToPeekStart. - * Visualization: - * --------------------------------------------------- --- - * | | | - * | FIRST ITEM | | <- distanceToPeekStart - * | | | - * |---------------------------------------------------| --- --- - * |__________________SECOND ITEM______________________| | <- peekSize - * |===================================================| _|_ - * - * @param distanceToPeekStart The distance to the start of the peak. - */ - StackIndentationFunctor(int maxItemsInStack, int peekSize, int distanceToPeekStart) { - mDistanceToPeekStart = distanceToPeekStart; - mStackStartsAtPeek = mDistanceToPeekStart == 0; - mMaxItemsInStack = maxItemsInStack; - mPeekSize = peekSize; - updateTotalTransitionDistance(); - - } - - private void updateTotalTransitionDistance() { - mTotalTransitionDistance = mDistanceToPeekStart + mPeekSize; - } - - public void setPeekSize(int mPeekSize) { - this.mPeekSize = mPeekSize; - updateTotalTransitionDistance(); - } - - public void setDistanceToPeekStart(int distanceToPeekStart) { - mDistanceToPeekStart = distanceToPeekStart; - mStackStartsAtPeek = mDistanceToPeekStart == 0; - updateTotalTransitionDistance(); - } - - /** - * Gets the offset of this Functor given a the quantity of items before it - * - * @param itemsBefore how many items are already in the stack before this element - * @return the offset - */ - public abstract float getValue(float itemsBefore); -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index c9e4eacd6fdc..1dc346d7c61b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -22,9 +22,10 @@ import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; +import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.notification.FakeShadowView; +import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.NotificationUtils; import java.util.ArrayList; @@ -40,20 +41,13 @@ public class StackScrollAlgorithm { private static final String LOG_TAG = "StackScrollAlgorithm"; - private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3; - private int mPaddingBetweenElements; private int mIncreasedPaddingBetweenElements; private int mCollapsedSize; - private int mBottomStackPeekSize; - private int mZDistanceBetweenElements; - private int mZBasicHeight; - - private StackIndentationFunctor mBottomStackIndentationFunctor; private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState(); private boolean mIsExpanded; - private int mBottomStackSlowDownLength; + private int mStatusBarHeight; public StackScrollAlgorithm(Context context) { initView(context); @@ -63,29 +57,14 @@ public class StackScrollAlgorithm { initConstants(context); } - public int getBottomStackSlowDownLength() { - return mBottomStackSlowDownLength + mPaddingBetweenElements; - } - private void initConstants(Context context) { - mPaddingBetweenElements = Math.max(1, context.getResources() - .getDimensionPixelSize(R.dimen.notification_divider_height)); + mPaddingBetweenElements = context.getResources().getDimensionPixelSize( + R.dimen.notification_divider_height); mIncreasedPaddingBetweenElements = context.getResources() .getDimensionPixelSize(R.dimen.notification_divider_height_increased); mCollapsedSize = context.getResources() .getDimensionPixelSize(R.dimen.notification_min_height); - mBottomStackPeekSize = context.getResources() - .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount); - mZDistanceBetweenElements = Math.max(1, context.getResources() - .getDimensionPixelSize(R.dimen.z_distance_between_notifications)); - mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements; - mBottomStackSlowDownLength = context.getResources() - .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length); - mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor( - MAX_ITEMS_IN_BOTTOM_STACK, - mBottomStackPeekSize, - getBottomStackSlowDownLength(), - 0.5f); + mStatusBarHeight = context.getResources().getDimensionPixelSize(R.dimen.status_bar_height); } public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) { @@ -107,7 +86,8 @@ public class StackScrollAlgorithm { handleDraggedViews(ambientState, resultState, algorithmState); updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState); updateClipping(resultState, algorithmState, ambientState); - updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex()); + updateSpeedBumpState(resultState, algorithmState, ambientState); + updateShelfState(resultState, ambientState); getNotificationChildrenStates(resultState, algorithmState); } @@ -124,16 +104,22 @@ public class StackScrollAlgorithm { } private void updateSpeedBumpState(StackScrollState resultState, - StackScrollAlgorithmState algorithmState, int speedBumpIndex) { + StackScrollAlgorithmState algorithmState, AmbientState ambientState) { int childCount = algorithmState.visibleChildren.size(); + int belowSpeedBump = ambientState.getSpeedBumpIndex(); for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); - StackViewState childViewState = resultState.getViewStateForView(child); + ExpandableViewState childViewState = resultState.getViewStateForView(child); // The speed bump can also be gone, so equality needs to be taken when comparing // indices. - childViewState.belowSpeedBump = speedBumpIndex != -1 && i >= speedBumpIndex; + childViewState.belowSpeedBump = i >= belowSpeedBump; } + + } + private void updateShelfState(StackScrollState resultState, AmbientState ambientState) { + NotificationShelf shelf = ambientState.getShelf(); + shelf.updateState(resultState, ambientState); } private void updateClipping(StackScrollState resultState, @@ -144,7 +130,7 @@ public class StackScrollAlgorithm { int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); - StackViewState state = resultState.getViewStateForView(child); + ExpandableViewState state = resultState.getViewStateForView(child); if (!child.mustStayOnScreen()) { previousNotificationEnd = Math.max(drawStart, previousNotificationEnd); previousNotificationStart = Math.max(drawStart, previousNotificationStart); @@ -195,13 +181,13 @@ public class StackScrollAlgorithm { int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); - StackViewState childViewState = resultState.getViewStateForView(child); + ExpandableViewState childViewState = resultState.getViewStateForView(child); childViewState.dimmed = dimmed; childViewState.dark = dark; childViewState.hideSensitive = hideSensitive; boolean isActivatedChild = activatedChild == child; if (dimmed && isActivatedChild) { - childViewState.zTranslation += 2.0f * mZDistanceBetweenElements; + childViewState.zTranslation += 2.0f * ambientState.getZDistanceBetweenElements(); } } } @@ -219,7 +205,7 @@ public class StackScrollAlgorithm { if (!draggedViews.contains(nextChild)) { // only if the view is not dragged itself we modify its state to be fully // visible - StackViewState viewState = resultState.getViewStateForView( + ExpandableViewState viewState = resultState.getViewStateForView( nextChild); // The child below the dragged one must be fully visible if (ambientState.isShadeExpanded()) { @@ -229,7 +215,7 @@ public class StackScrollAlgorithm { } // Lets set the alpha to the one it currently has, as its currently being dragged - StackViewState viewState = resultState.getViewStateForView(draggedView); + ExpandableViewState viewState = resultState.getViewStateForView(draggedView); // The dragged child should keep the set alpha viewState.alpha = draggedView.getAlpha(); } @@ -241,8 +227,6 @@ public class StackScrollAlgorithm { */ private void initAlgorithmState(StackScrollState resultState, StackScrollAlgorithmState state, AmbientState ambientState) { - state.itemsInBottomStack = 0.0f; - state.partialInBottom = 0.0f; float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */); int scrollY = ambientState.getScrollY(); @@ -263,6 +247,9 @@ public class StackScrollAlgorithm { for (int i = 0; i < childCount; i++) { ExpandableView v = (ExpandableView) hostView.getChildAt(i); if (v.getVisibility() != View.GONE) { + if (v == ambientState.getShelf()) { + continue; + } notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v); float increasedPadding = v.getIncreasedPaddingAmount(); if (increasedPadding != 0.0f) { @@ -284,7 +271,7 @@ public class StackScrollAlgorithm { if (row.isSummaryWithChildren() && children != null) { for (ExpandableNotificationRow childRow : children) { if (childRow.getVisibility() != View.GONE) { - StackViewState childState + ExpandableViewState childState = resultState.getViewStateForView(childRow); childState.notGoneIndex = notGoneIndex; notGoneIndex++; @@ -300,7 +287,7 @@ public class StackScrollAlgorithm { private int updateNotGoneIndex(StackScrollState resultState, StackScrollAlgorithmState state, int notGoneIndex, ExpandableView v) { - StackViewState viewState = resultState.getViewStateForView(v); + ExpandableViewState viewState = resultState.getViewStateForView(v); viewState.notGoneIndex = notGoneIndex; state.visibleChildren.add(v); notGoneIndex++; @@ -317,72 +304,42 @@ public class StackScrollAlgorithm { private void updatePositionsForState(StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState) { - // The starting position of the bottom stack peek - float bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize; - - // The position where the bottom stack starts. - float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength; - // The y coordinate of the current child. float currentYPosition = -algorithmState.scrollY; int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { currentYPosition = updateChild(i, resultState, algorithmState, ambientState, - currentYPosition, bottomStackStart); + currentYPosition); } } protected float updateChild(int i, StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState, - float currentYPosition, float bottomStackStart) { + float currentYPosition) { ExpandableView child = algorithmState.visibleChildren.get(i); - StackViewState childViewState = resultState.getViewStateForView(child); - childViewState.location = StackViewState.LOCATION_UNKNOWN; + ExpandableViewState childViewState = resultState.getViewStateForView(child); + childViewState.location = ExpandableViewState.LOCATION_UNKNOWN; int paddingAfterChild = getPaddingAfterChild(algorithmState, child); int childHeight = getMaxAllowedChildHeight(child); - int collapsedHeight = child.getCollapsedHeight(); childViewState.yTranslation = currentYPosition; + boolean isDismissView = child instanceof DismissView; if (i == 0) { - updateFirstChildHeight(child, childViewState, childHeight, ambientState); + updateFirstChildHeight(child, childViewState, childHeight, algorithmState, ambientState); } - // The y position after this element - float nextYPosition = currentYPosition + childHeight + - paddingAfterChild; - if (nextYPosition >= bottomStackStart) { - // Case 1: - // We are in the bottom stack. - if (currentYPosition >= bottomStackStart) { - // According to the regular scroll view we are fully translated out of the - // bottom of the screen so we are fully in the bottom stack - updateStateForChildFullyInBottomStack(algorithmState, - bottomStackStart, childViewState, collapsedHeight, ambientState, child); - } else { - // According to the regular scroll view we are currently translating out of / - // into the bottom of the screen - updateStateForChildTransitioningInBottom(algorithmState, - bottomStackStart, child, currentYPosition, - childViewState, childHeight); - } + childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA; + if (isDismissView) { + childViewState.yTranslation = Math.min(childViewState.yTranslation, + ambientState.getInnerHeight() - childHeight); } else { - // Case 2: - // We are in the regular scroll area. - childViewState.location = StackViewState.LOCATION_MAIN_AREA; - clampPositionToBottomStackStart(childViewState, childViewState.height, childHeight, - ambientState); + clampPositionToShelf(childViewState, ambientState); } - if (i == 0 && ambientState.getScrollY() <= 0) { - // The first card can get into the bottom stack if it's the only one - // on the lockscreen which pushes it up. Let's make sure that doesn't happen and - // it stays at the top - childViewState.yTranslation = Math.max(0, childViewState.yTranslation); - } currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild; if (currentYPosition <= 0) { - childViewState.location = StackViewState.LOCATION_HIDDEN_TOP; + childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; } - if (childViewState.location == StackViewState.LOCATION_UNKNOWN) { + if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) { Log.wtf(LOG_TAG, "Failed to assign location for child " + i); } @@ -393,12 +350,7 @@ public class StackScrollAlgorithm { protected int getPaddingAfterChild(StackScrollAlgorithmState algorithmState, ExpandableView child) { - Float paddingValue = algorithmState.increasedPaddingMap.get(child); - return paddingValue == null - ? mPaddingBetweenElements - : (int) NotificationUtils.interpolate(mPaddingBetweenElements, - mIncreasedPaddingBetweenElements, - paddingValue); + return algorithmState.getPaddingAfterChild(child); } private void updateHeadsUpStates(StackScrollState resultState, @@ -414,22 +366,26 @@ public class StackScrollAlgorithm { if (!row.isHeadsUp()) { break; } - StackViewState childState = resultState.getViewStateForView(row); + ExpandableViewState childState = resultState.getViewStateForView(row); if (topHeadsUpEntry == null) { topHeadsUpEntry = row; - childState.location = StackViewState.LOCATION_FIRST_HUN; + childState.location = ExpandableViewState.LOCATION_FIRST_HUN; } boolean isTopEntry = topHeadsUpEntry == row; float unmodifiedEndLocation = childState.yTranslation + childState.height; if (mIsExpanded) { // Ensure that the heads up is always visible even when scrolled off clampHunToTop(ambientState, row, childState); - clampHunToMaxTranslation(ambientState, row, childState); + if (i == 0) { + // the first hun can't get off screen. + clampHunToMaxTranslation(ambientState, row, childState); + } } if (row.isPinned()) { childState.yTranslation = Math.max(childState.yTranslation, 0); childState.height = Math.max(row.getIntrinsicHeight(), childState.height); - StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry); + childState.hidden = false; + ExpandableViewState topState = resultState.getViewStateForView(topHeadsUpEntry); if (!isTopEntry && (!mIsExpanded || unmodifiedEndLocation < topState.yTranslation + topState.height)) { // Ensure that a headsUp doesn't vertically extend further than the heads-up at @@ -439,11 +395,14 @@ public class StackScrollAlgorithm { - childState.height; } } + if (row.isHeadsUpAnimatingAway()) { + childState.hidden = false; + } } } private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row, - StackViewState childState) { + ExpandableViewState childState) { float newTranslation = Math.max(ambientState.getTopPadding() + ambientState.getStackTranslation(), childState.yTranslation); childState.height = (int) Math.max(childState.height - (newTranslation @@ -452,7 +411,7 @@ public class StackScrollAlgorithm { } private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row, - StackViewState childState) { + ExpandableViewState childState) { float newTranslation; float bottomPosition = ambientState.getMaxHeadsUpTranslation() - row.getCollapsedHeight(); newTranslation = Math.min(childState.yTranslation, bottomPosition); @@ -462,26 +421,23 @@ public class StackScrollAlgorithm { } /** - * Clamp the yTranslation of the child down such that its end is at most on the beginning of - * the bottom stack. + * Clamp the height of the child down such that its end is at most on the beginning of + * the shelf. * * @param childViewState the view state of the child - * @param childHeight the height of this child - * @param minHeight the minumum Height of the View + * @param ambientState the ambient state */ - private void clampPositionToBottomStackStart(StackViewState childViewState, - int childHeight, int minHeight, AmbientState ambientState) { - - int bottomStackStart = ambientState.getInnerHeight() - - mBottomStackPeekSize - mBottomStackSlowDownLength; - int childStart = bottomStackStart - childHeight; - if (childStart < childViewState.yTranslation) { - float newHeight = bottomStackStart - childViewState.yTranslation; - if (newHeight < minHeight) { - newHeight = minHeight; - childViewState.yTranslation = bottomStackStart - minHeight; - } - childViewState.height = (int) newHeight; + private void clampPositionToShelf(ExpandableViewState childViewState, + AmbientState ambientState) { + int shelfStart = ambientState.getInnerHeight() + - ambientState.getShelf().getIntrinsicHeight(); + childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart); + if (childViewState.yTranslation >= shelfStart) { + childViewState.hidden = true; + childViewState.inShelf = true; + } + if (!ambientState.isShadeExpanded()) { + childViewState.height = (int) (mStatusBarHeight - childViewState.yTranslation); } } @@ -493,74 +449,26 @@ public class StackScrollAlgorithm { return child == null? mCollapsedSize : child.getHeight(); } - private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState, - float transitioningPositionStart, ExpandableView child, float currentYPosition, - StackViewState childViewState, int childHeight) { - - // This is the transitioning element on top of bottom stack, calculate how far we are in. - algorithmState.partialInBottom = 1.0f - ( - (transitioningPositionStart - currentYPosition) / (childHeight + - getPaddingAfterChild(algorithmState, child))); - - // the offset starting at the transitionPosition of the bottom stack - float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom); - algorithmState.itemsInBottomStack += algorithmState.partialInBottom; - int newHeight = childHeight; - if (childHeight > child.getCollapsedHeight()) { - newHeight = (int) Math.max(Math.min(transitioningPositionStart + offset - - getPaddingAfterChild(algorithmState, child) - currentYPosition, childHeight), - child.getCollapsedHeight()); - childViewState.height = newHeight; - } - childViewState.yTranslation = transitioningPositionStart + offset - newHeight - - getPaddingAfterChild(algorithmState, child); - childViewState.location = StackViewState.LOCATION_MAIN_AREA; - } - - private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState, - float transitioningPositionStart, StackViewState childViewState, - int collapsedHeight, AmbientState ambientState, ExpandableView child) { - float currentYPosition; - algorithmState.itemsInBottomStack += 1.0f; - if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) { - // We are visually entering the bottom stack - currentYPosition = transitioningPositionStart - + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack) - - getPaddingAfterChild(algorithmState, child); - childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_PEEKING; - } else { - // we are fully inside the stack - if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) { - childViewState.hidden = true; - childViewState.shadowAlpha = 0.0f; - } else if (algorithmState.itemsInBottomStack - > MAX_ITEMS_IN_BOTTOM_STACK + 1) { - childViewState.shadowAlpha = 1.0f - algorithmState.partialInBottom; - } - childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_HIDDEN; - currentYPosition = ambientState.getInnerHeight(); - } - childViewState.height = collapsedHeight; - childViewState.yTranslation = currentYPosition - collapsedHeight; - } - - /** * Update the height of the first child i.e clamp it to the bottom stack - * * @param child the child to update * @param childViewState the viewstate of the child * @param childHeight the height of the child + * @param algorithmState the algorithm state * @param ambientState The ambient state of the algorithm */ - protected void updateFirstChildHeight(ExpandableView child, StackViewState childViewState, - int childHeight, AmbientState ambientState) { + protected void updateFirstChildHeight(ExpandableView child, ExpandableViewState childViewState, + int childHeight, StackScrollAlgorithmState algorithmState, + AmbientState ambientState) { - // The starting position of the bottom stack peek - int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize - - mBottomStackSlowDownLength + ambientState.getScrollY(); + int bottomStart= ambientState.getInnerHeight(); + if (algorithmState.visibleChildren.size() > 1) { + bottomStart -= ambientState.getShelf().getIntrinsicHeight() + - mPaddingBetweenElements; + } + bottomStart += ambientState.getScrollY(); // Collapse and expand the first child while the shade is being expanded - childViewState.height = (int) Math.max(Math.min(bottomPeekStart, (float) childHeight), + childViewState.height = (int) Math.max(Math.min(bottomStart, (float) childHeight), child.getCollapsedHeight()); } @@ -576,37 +484,19 @@ public class StackScrollAlgorithm { int childCount = algorithmState.visibleChildren.size(); float childrenOnTop = 0.0f; for (int i = childCount - 1; i >= 0; i--) { - updateChildZValue(i, childCount, childrenOnTop, + updateChildZValue(i, childrenOnTop, resultState, algorithmState, ambientState); } } - protected void updateChildZValue(int i, int childCount, float childrenOnTop, + protected void updateChildZValue(int i, float childrenOnTop, StackScrollState resultState, StackScrollAlgorithmState algorithmState, AmbientState ambientState) { ExpandableView child = algorithmState.visibleChildren.get(i); - StackViewState childViewState = resultState.getViewStateForView(child); - if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) { - // We are in the bottom stack - float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack); - float zSubtraction; - if (numItemsAbove <= 1.0f) { - float factor = 0.2f; - // Lets fade in slower to the threshold to make the shadow fade in look nicer - if (numItemsAbove <= factor) { - zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD - * numItemsAbove * (1.0f / factor); - } else { - zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD - + (numItemsAbove - factor) * (1.0f / (1.0f - factor)) - * (mZDistanceBetweenElements - - FakeShadowView.SHADOW_SIBLING_TRESHOLD); - } - } else { - zSubtraction = numItemsAbove * mZDistanceBetweenElements; - } - childViewState.zTranslation = mZBasicHeight - zSubtraction; - } else if (child.mustStayOnScreen() + ExpandableViewState childViewState = resultState.getViewStateForView(child); + int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements(); + float baseZ = ambientState.getBaseZHeight(); + if (child.mustStayOnScreen() && childViewState.yTranslation < ambientState.getTopPadding() + ambientState.getStackTranslation()) { if (childrenOnTop != 0.0f) { @@ -616,37 +506,34 @@ public class StackScrollAlgorithm { + ambientState.getStackTranslation() - childViewState.yTranslation; childrenOnTop += Math.min(1.0f, overlap / childViewState.height); } - childViewState.zTranslation = mZBasicHeight - + childrenOnTop * mZDistanceBetweenElements; - } else { - childViewState.zTranslation = mZBasicHeight; - } - } - - private boolean isMaxSizeInitialized(ExpandableView child) { - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; - return row.isMaxExpandHeightInitialized(); - } - return child == null || child.getWidth() != 0; - } - - private View findFirstVisibleChild(ViewGroup container) { - int childCount = container.getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = container.getChildAt(i); - if (child.getVisibility() != View.GONE) { - return child; + childViewState.zTranslation = baseZ + + childrenOnTop * zDistanceBetweenElements; + } else if (i == 0 && child.isAboveShelf()) { + // In case this is a new view that has never been measured before, we don't want to + // elevate if we are currently expanded more then the notification + int shelfHeight = ambientState.getShelf().getIntrinsicHeight(); + float shelfStart = ambientState.getInnerHeight() + - shelfHeight + ambientState.getTopPadding() + + ambientState.getStackTranslation(); + float notificationEnd = childViewState.yTranslation + child.getPinnedHeadsUpHeight() + + mPaddingBetweenElements; + if (shelfStart > notificationEnd) { + childViewState.zTranslation = baseZ; + } else { + float factor = (notificationEnd - shelfStart) / shelfHeight; + factor = Math.min(factor, 1.0f); + childViewState.zTranslation = baseZ + factor * zDistanceBetweenElements; } + } else { + childViewState.zTranslation = baseZ; } - return null; } public void setIsExpanded(boolean isExpanded) { this.mIsExpanded = isExpanded; } - protected class StackScrollAlgorithmState { + public class StackScrollAlgorithmState { /** * The scroll position of the algorithm @@ -654,16 +541,6 @@ public class StackScrollAlgorithm { public int scrollY; /** - * The quantity of items which are in the bottom stack. - */ - public float itemsInBottomStack; - - /** - * how far in is the element currently transitioning into the bottom stack - */ - public float partialInBottom; - - /** * The children from the host view which are not gone. */ public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>(); @@ -673,6 +550,15 @@ public class StackScrollAlgorithm { * no increased padding, a value of 1 means full padding. */ public final HashMap<ExpandableView, Float> increasedPaddingMap = new HashMap<>(); + + public int getPaddingAfterChild(ExpandableView child) { + Float paddingValue = increasedPaddingMap.get(child); + return paddingValue == null + ? mPaddingBetweenElements + : (int) NotificationUtils.interpolate(mPaddingBetweenElements, + mIncreasedPaddingBetweenElements, + paddingValue); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index 8f0cd8e63ee0..e3c746bf0d32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -21,8 +21,6 @@ import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; -import com.android.systemui.statusbar.DismissView; -import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; @@ -38,14 +36,11 @@ public class StackScrollState { private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; private final ViewGroup mHostView; - private WeakHashMap<ExpandableView, StackViewState> mStateMap; - private final int mClearAllTopPadding; + private WeakHashMap<ExpandableView, ExpandableViewState> mStateMap; public StackScrollState(ViewGroup hostView) { mHostView = hostView; mStateMap = new WeakHashMap<>(); - mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize( - R.dimen.clear_all_padding_top); } public ViewGroup getHostView() { @@ -73,9 +68,9 @@ public class StackScrollState { } private void resetViewState(ExpandableView view) { - StackViewState viewState = mStateMap.get(view); + ExpandableViewState viewState = mStateMap.get(view); if (viewState == null) { - viewState = new StackViewState(); + viewState = view.createNewViewState(this); mStateMap.put(view, viewState); } // initialize with the default values of the view @@ -84,10 +79,14 @@ public class StackScrollState { viewState.alpha = 1f; viewState.shadowAlpha = 1f; viewState.notGoneIndex = -1; + viewState.xTranslation = view.getTranslationX(); viewState.hidden = false; + viewState.scaleX = view.getScaleX(); + viewState.scaleY = view.getScaleY(); + viewState.inShelf = false; } - public StackViewState getViewStateForView(View requestedView) { + public ExpandableViewState getViewStateForView(View requestedView) { return mStateMap.get(requestedView); } @@ -103,130 +102,16 @@ public class StackScrollState { int numChildren = mHostView.getChildCount(); for (int i = 0; i < numChildren; i++) { ExpandableView child = (ExpandableView) mHostView.getChildAt(i); - StackViewState state = mStateMap.get(child); - if (!applyState(child, state)) { + ExpandableViewState state = mStateMap.get(child); + if (state == null) { + Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " + + "to the hostView"); continue; } - if (child instanceof DismissView) { - DismissView dismissView = (DismissView) child; - boolean visible = state.clipTopAmount < mClearAllTopPadding; - dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone()); - } else if (child instanceof EmptyShadeView) { - EmptyShadeView emptyShadeView = (EmptyShadeView) child; - boolean visible = state.clipTopAmount <= 0; - emptyShadeView.performVisibilityAnimation( - visible && !emptyShadeView.willBeGone()); - } - } - } - - /** - * Applies a {@link StackViewState} to an {@link ExpandableView}. - * - * @return whether the state was applied correctly - */ - public boolean applyState(ExpandableView view, StackViewState state) { - if (state == null) { - Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " + - "to the hostView"); - return false; - } - if (state.gone) { - return false; - } - applyViewState(view, state); - - int height = view.getActualHeight(); - int newHeight = state.height; - - // apply height - if (height != newHeight) { - view.setActualHeight(newHeight, false /* notifyListeners */); - } - - float shadowAlpha = view.getShadowAlpha(); - float newShadowAlpha = state.shadowAlpha; - - // apply shadowAlpha - if (shadowAlpha != newShadowAlpha) { - view.setShadowAlpha(newShadowAlpha); - } - - // apply dimming - view.setDimmed(state.dimmed, false /* animate */); - - // apply hiding sensitive - view.setHideSensitive( - state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */); - - // apply speed bump state - view.setBelowSpeedBump(state.belowSpeedBump); - - // apply dark - view.setDark(state.dark, false /* animate */, 0 /* delay */); - - // apply clipping - float oldClipTopAmount = view.getClipTopAmount(); - if (oldClipTopAmount != state.clipTopAmount) { - view.setClipTopAmount(state.clipTopAmount); - } - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; - if (state.isBottomClipped) { - row.setClipToActualHeight(true); - } - row.applyChildrenState(this); - } - return true; - } - - /** - * Applies a {@link ViewState} to a normal view. - */ - public void applyViewState(View view, ViewState state) { - float alpha = view.getAlpha(); - float yTranslation = view.getTranslationY(); - float xTranslation = view.getTranslationX(); - float zTranslation = view.getTranslationZ(); - float newAlpha = state.alpha; - float newYTranslation = state.yTranslation; - float newZTranslation = state.zTranslation; - boolean becomesInvisible = newAlpha == 0.0f || state.hidden; - if (alpha != newAlpha && xTranslation == 0) { - // apply layer type - boolean becomesFullyVisible = newAlpha == 1.0f; - boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible - && view.hasOverlappingRendering(); - int layerType = view.getLayerType(); - int newLayerType = newLayerTypeIsHardware - ? View.LAYER_TYPE_HARDWARE - : View.LAYER_TYPE_NONE; - if (layerType != newLayerType) { - view.setLayerType(newLayerType, null); - } - - // apply alpha - view.setAlpha(newAlpha); - } - - // apply visibility - int oldVisibility = view.getVisibility(); - int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; - if (newVisibility != oldVisibility) { - if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) { - // We don't want views to change visibility when they are animating to GONE - view.setVisibility(newVisibility); + if (state.gone) { + continue; } - } - - // apply yTranslation - if (yTranslation != newYTranslation) { - view.setTranslationY(newYTranslation); - } - - // apply zTranslation - if (zTranslation != newZTranslation) { - view.setTranslationZ(newZTranslation); + state.applyToView(child); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index 3804b42dc5e4..1f29b4fd7d91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -18,9 +18,8 @@ package com.android.systemui.statusbar.stack; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; +import android.util.Property; import android.view.View; import android.view.ViewGroup; import android.view.animation.Interpolator; @@ -29,7 +28,7 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.NotificationShelf; import java.util.ArrayList; import java.util.HashSet; @@ -54,28 +53,10 @@ public class StackStateAnimator { public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; public static final int ANIMATION_DELAY_HEADS_UP = 120; - private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; - private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; - private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag; - private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag; - private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag; - private static final int TAG_ANIMATOR_SHADOW_ALPHA = R.id.shadow_alpha_animator_tag; - private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag; - private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag; - private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag; - private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag; - private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag; - private static final int TAG_END_SHADOW_ALPHA = R.id.shadow_alpha_animator_end_value_tag; - private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag; - private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag; - private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag; - private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag; - private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag; - private static final int TAG_START_SHADOW_ALPHA = R.id.shadow_alpha_animator_start_value_tag; - private final Interpolator mHeadsUpAppearInterpolator; private final int mGoToFullShadeAppearingTranslation; - private final StackViewState mTmpState = new StackViewState(); + private final ExpandableViewState mTmpState = new ExpandableViewState(); + private final AnimationProperties mAnimationProperties; public NotificationStackScrollLayout mHostLayout; private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents = new ArrayList<>(); @@ -95,6 +76,7 @@ public class StackStateAnimator { private int mHeadsUpAppearHeightBottom; private boolean mShadeExpanded; private ArrayList<View> mChildrenToClearFromOverlay = new ArrayList<>(); + private NotificationShelf mShelf; public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; @@ -102,6 +84,30 @@ public class StackStateAnimator { hostLayout.getContext().getResources().getDimensionPixelSize( R.dimen.go_to_full_shade_appearing_translation); mHeadsUpAppearInterpolator = new HeadsUpAppearInterpolator(); + mAnimationProperties = new AnimationProperties() { + @Override + public AnimationFilter getAnimationFilter() { + return mAnimationFilter; + } + + @Override + public AnimatorListenerAdapter getAnimationFinishListener() { + return getGlobalAnimationFinishedListener(); + } + + @Override + public boolean wasAdded(View view) { + return mNewAddChildren.contains(view); + } + + @Override + public Interpolator getCustomInterpolator(View child, Property property) { + if (mHeadsUpAppearChildren.contains(child) && View.TRANSLATION_Y.equals(property)) { + return mHeadsUpAppearInterpolator; + } + return null; + } + }; } public boolean isRunning() { @@ -122,13 +128,14 @@ public class StackStateAnimator { for (int i = 0; i < childCount; i++) { final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); - StackViewState viewState = finalState.getViewStateForView(child); + ExpandableViewState viewState = finalState.getViewStateForView(child); if (viewState == null || child.getVisibility() == View.GONE || applyWithoutAnimation(child, viewState, finalState)) { continue; } - startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */); + initAnimationProperties(finalState, child, viewState); + viewState.animateTo(child, mAnimationProperties); } if (!isRunning()) { // no child has preformed any animation, lets finish @@ -140,17 +147,47 @@ public class StackStateAnimator { mNewAddChildren.clear(); } + private void initAnimationProperties(StackScrollState finalState, ExpandableView child, + ExpandableViewState viewState) { + boolean wasAdded = mAnimationProperties.wasAdded(child); + mAnimationProperties.duration = mCurrentLength; + adaptDurationWhenGoingToFullShade(child, viewState, wasAdded); + mAnimationProperties.delay = 0; + if (wasAdded || mAnimationFilter.hasDelays + && (viewState.yTranslation != child.getTranslationY() + || viewState.zTranslation != child.getTranslationZ() + || viewState.alpha != child.getAlpha() + || viewState.height != child.getActualHeight() + || viewState.clipTopAmount != child.getClipTopAmount() + || viewState.dark != child.isDark() + || viewState.shadowAlpha != child.getShadowAlpha())) { + mAnimationProperties.delay = mCurrentAdditionalDelay + + calculateChildAnimationDelay(viewState, finalState); + } + } + + private void adaptDurationWhenGoingToFullShade(ExpandableView child, + ExpandableViewState viewState, boolean wasAdded) { + if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) { + child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation); + float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex; + longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f); + mAnimationProperties.duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 + + (long) (100 * longerDurationFactor); + } + } + /** * Determines if a view should not perform an animation and applies it directly. * * @return true if no animation should be performed */ - private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState, + private boolean applyWithoutAnimation(ExpandableView child, ExpandableViewState viewState, StackScrollState finalState) { if (mShadeExpanded) { return false; } - if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) { + if (ViewState.isAnimatingY(child)) { // A Y translation animation is running return false; } @@ -162,7 +199,7 @@ public class StackStateAnimator { // This is another headsUp which might move. Let's animate! return false; } - finalState.applyState(child, viewState); + viewState.applyToView(child); return true; } @@ -171,7 +208,7 @@ public class StackStateAnimator { for (int i = childCount - 1; i >= 0; i--) { final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); - StackViewState viewState = finalState.getViewStateForView(child); + ExpandableViewState viewState = finalState.getViewStateForView(child); if (viewState == null || child.getVisibility() == View.GONE) { continue; } @@ -182,144 +219,7 @@ public class StackStateAnimator { return -1; } - - /** - * Start an animation to the given {@link StackViewState}. - * - * @param child the child to start the animation on - * @param viewState the {@link StackViewState} of the view to animate to - * @param finalState the final state after the animation - * @param i the index of the view; only relevant if the view is the speed bump and is - * ignored otherwise - * @param fixedDelay a fixed delay if desired or -1 if the delay should be calculated - */ - public void startStackAnimations(final ExpandableView child, StackViewState viewState, - StackScrollState finalState, int i, long fixedDelay) { - boolean wasAdded = mNewAddChildren.contains(child); - long duration = mCurrentLength; - if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) { - child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation); - float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex; - longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f); - duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 + - (long) (100 * longerDurationFactor); - } - boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation; - boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation; - boolean alphaChanging = viewState.alpha != child.getAlpha(); - boolean heightChanging = viewState.height != child.getActualHeight(); - boolean shadowAlphaChanging = viewState.shadowAlpha != child.getShadowAlpha(); - boolean darkChanging = viewState.dark != child.isDark(); - boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount(); - boolean hasDelays = mAnimationFilter.hasDelays; - boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || alphaChanging - || heightChanging || topInsetChanging || darkChanging || shadowAlphaChanging; - long delay = 0; - if (fixedDelay != -1) { - delay = fixedDelay; - } else if (hasDelays && isDelayRelevant || wasAdded) { - delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState); - } - - startViewAnimations(child, viewState, delay, duration); - - // start height animation - if (heightChanging) { - startHeightAnimation(child, viewState, duration, delay); - } else { - abortAnimation(child, TAG_ANIMATOR_HEIGHT); - } - - // start shadow alpha animation - if (shadowAlphaChanging) { - startShadowAlphaAnimation(child, viewState, duration, delay); - } else { - abortAnimation(child, TAG_ANIMATOR_SHADOW_ALPHA); - } - - // start top inset animation - if (topInsetChanging) { - startInsetAnimation(child, viewState, duration, delay); - } else { - abortAnimation(child, TAG_ANIMATOR_TOP_INSET); - } - - // start dimmed animation - child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed); - - // apply speed bump state - child.setBelowSpeedBump(viewState.belowSpeedBump); - - // start hiding sensitive animation - child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive, - delay, duration); - - // start dark animation - child.setDark(viewState.dark, mAnimationFilter.animateDark, delay); - - if (wasAdded) { - child.performAddAnimation(delay, mCurrentLength); - } - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; - row.startChildAnimation(finalState, this, delay, duration); - } - } - - /** - * Start an animation to a new {@link ViewState}. - * - * @param child the child to start the animation on - * @param viewState the {@link StackViewState} of the view to animate to - * @param delay a fixed delay - * @param duration the duration of the animation - */ - public void startViewAnimations(View child, ViewState viewState, long delay, long duration) { - boolean wasVisible = child.getVisibility() == View.VISIBLE; - final float alpha = viewState.alpha; - if (!wasVisible && (alpha != 0 || child.getAlpha() != 0) - && !viewState.gone && !viewState.hidden) { - child.setVisibility(View.VISIBLE); - } - boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation; - boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation; - float childAlpha = child.getAlpha(); - boolean alphaChanging = viewState.alpha != childAlpha; - if (child instanceof ExpandableView) { - // We don't want views to change visibility when they are animating to GONE - alphaChanging &= !((ExpandableView) child).willBeGone(); - } - - // start translationY animation - if (yTranslationChanging) { - startYTranslationAnimation(child, viewState, duration, delay); - } else { - abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y); - } - - // start translationZ animation - if (zTranslationChanging) { - startZTranslationAnimation(child, viewState, duration, delay); - } else { - abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z); - } - - // start alpha animation - if (alphaChanging && child.getTranslationX() == 0) { - startAlphaAnimation(child, viewState, duration, delay); - } else { - abortAnimation(child, TAG_ANIMATOR_ALPHA); - } - } - - private void abortAnimation(View child, int animatorTag) { - Animator previousAnimator = getChildTag(child, animatorTag); - if (previousAnimator != null) { - previousAnimator.cancel(); - } - } - - private long calculateChildAnimationDelay(StackViewState viewState, + private long calculateChildAnimationDelay(ExpandableViewState viewState, StackScrollState finalState) { if (mAnimationFilter.hasDarkEvent) { return calculateDelayDark(viewState); @@ -374,7 +274,7 @@ public class StackStateAnimator { return minDelay; } - private long calculateDelayDark(StackViewState viewState) { + private long calculateDelayDark(ExpandableViewState viewState) { int referenceIndex; if (mAnimationFilter.darkAnimationOriginIndex == NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) { @@ -388,400 +288,19 @@ public class StackStateAnimator { return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK; } - private long calculateDelayGoToFullShade(StackViewState viewState) { + private long calculateDelayGoToFullShade(ExpandableViewState viewState) { + int shelfIndex = mShelf.getNotGoneIndex(); float index = viewState.notGoneIndex; - index = (float) Math.pow(index, 0.7f); - return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE); - } - - private void startShadowAlphaAnimation(final ExpandableView child, - StackViewState viewState, long duration, long delay) { - Float previousStartValue = getChildTag(child, TAG_START_SHADOW_ALPHA); - Float previousEndValue = getChildTag(child, TAG_END_SHADOW_ALPHA); - float newEndValue = viewState.shadowAlpha; - if (previousEndValue != null && previousEndValue == newEndValue) { - return; - } - ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SHADOW_ALPHA); - if (!mAnimationFilter.animateShadowAlpha) { - // just a local update was performed - if (previousAnimator != null) { - // we need to increase all animation keyframes of the previous animator by the - // relative change to the end value - PropertyValuesHolder[] values = previousAnimator.getValues(); - float relativeDiff = newEndValue - previousEndValue; - float newStartValue = previousStartValue + relativeDiff; - values[0].setFloatValues(newStartValue, newEndValue); - child.setTag(TAG_START_SHADOW_ALPHA, newStartValue); - child.setTag(TAG_END_SHADOW_ALPHA, newEndValue); - previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); - return; - } else { - // no new animation needed, let's just apply the value - child.setShadowAlpha(newEndValue); - return; - } - } - - ValueAnimator animator = ValueAnimator.ofFloat(child.getShadowAlpha(), newEndValue); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - child.setShadowAlpha((float) animation.getAnimatedValue()); - } - }); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); - animator.setDuration(newDuration); - if (delay > 0 && (previousAnimator == null - || previousAnimator.getAnimatedFraction() == 0)) { - animator.setStartDelay(delay); - } - animator.addListener(getGlobalAnimationFinishedListener()); - // remove the tag when the animation is finished - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, null); - child.setTag(TAG_START_SHADOW_ALPHA, null); - child.setTag(TAG_END_SHADOW_ALPHA, null); - } - }); - startAnimator(animator); - child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, animator); - child.setTag(TAG_START_SHADOW_ALPHA, child.getShadowAlpha()); - child.setTag(TAG_END_SHADOW_ALPHA, newEndValue); - } - - private void startHeightAnimation(final ExpandableView child, - StackViewState viewState, long duration, long delay) { - Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT); - Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT); - int newEndValue = viewState.height; - if (previousEndValue != null && previousEndValue == newEndValue) { - return; - } - ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT); - if (!mAnimationFilter.animateHeight) { - // just a local update was performed - if (previousAnimator != null) { - // we need to increase all animation keyframes of the previous animator by the - // relative change to the end value - PropertyValuesHolder[] values = previousAnimator.getValues(); - int relativeDiff = newEndValue - previousEndValue; - int newStartValue = previousStartValue + relativeDiff; - values[0].setIntValues(newStartValue, newEndValue); - child.setTag(TAG_START_HEIGHT, newStartValue); - child.setTag(TAG_END_HEIGHT, newEndValue); - previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); - return; - } else { - // no new animation needed, let's just apply the value - child.setActualHeight(newEndValue, false); - return; - } - } - - ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - child.setActualHeight((int) animation.getAnimatedValue(), - false /* notifyListeners */); - } - }); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); - animator.setDuration(newDuration); - if (delay > 0 && (previousAnimator == null - || previousAnimator.getAnimatedFraction() == 0)) { - animator.setStartDelay(delay); - } - animator.addListener(getGlobalAnimationFinishedListener()); - // remove the tag when the animation is finished - animator.addListener(new AnimatorListenerAdapter() { - boolean mWasCancelled; - - @Override - public void onAnimationEnd(Animator animation) { - child.setTag(TAG_ANIMATOR_HEIGHT, null); - child.setTag(TAG_START_HEIGHT, null); - child.setTag(TAG_END_HEIGHT, null); - child.setActualHeightAnimating(false); - if (!mWasCancelled && child instanceof ExpandableNotificationRow) { - ((ExpandableNotificationRow) child).setGroupExpansionChanging( - false /* isExpansionChanging */); - } - } - - @Override - public void onAnimationStart(Animator animation) { - mWasCancelled = false; - } - - @Override - public void onAnimationCancel(Animator animation) { - mWasCancelled = true; - } - }); - startAnimator(animator); - child.setTag(TAG_ANIMATOR_HEIGHT, animator); - child.setTag(TAG_START_HEIGHT, child.getActualHeight()); - child.setTag(TAG_END_HEIGHT, newEndValue); - child.setActualHeightAnimating(true); - } - - private void startInsetAnimation(final ExpandableView child, - StackViewState viewState, long duration, long delay) { - Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET); - Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET); - int newEndValue = viewState.clipTopAmount; - if (previousEndValue != null && previousEndValue == newEndValue) { - return; - } - ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET); - if (!mAnimationFilter.animateTopInset) { - // just a local update was performed - if (previousAnimator != null) { - // we need to increase all animation keyframes of the previous animator by the - // relative change to the end value - PropertyValuesHolder[] values = previousAnimator.getValues(); - int relativeDiff = newEndValue - previousEndValue; - int newStartValue = previousStartValue + relativeDiff; - values[0].setIntValues(newStartValue, newEndValue); - child.setTag(TAG_START_TOP_INSET, newStartValue); - child.setTag(TAG_END_TOP_INSET, newEndValue); - previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); - return; - } else { - // no new animation needed, let's just apply the value - child.setClipTopAmount(newEndValue); - return; - } + long result = 0; + if (index > shelfIndex) { + float diff = index - shelfIndex; + diff = (float) Math.pow(diff, 0.7f); + result += (long) (diff * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE * 0.25); + index = shelfIndex; } - - ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - child.setClipTopAmount((int) animation.getAnimatedValue()); - } - }); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); - animator.setDuration(newDuration); - if (delay > 0 && (previousAnimator == null - || previousAnimator.getAnimatedFraction() == 0)) { - animator.setStartDelay(delay); - } - animator.addListener(getGlobalAnimationFinishedListener()); - // remove the tag when the animation is finished - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - child.setTag(TAG_ANIMATOR_TOP_INSET, null); - child.setTag(TAG_START_TOP_INSET, null); - child.setTag(TAG_END_TOP_INSET, null); - } - }); - startAnimator(animator); - child.setTag(TAG_ANIMATOR_TOP_INSET, animator); - child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount()); - child.setTag(TAG_END_TOP_INSET, newEndValue); - } - - private void startAlphaAnimation(final View child, - final ViewState viewState, long duration, long delay) { - Float previousStartValue = getChildTag(child,TAG_START_ALPHA); - Float previousEndValue = getChildTag(child,TAG_END_ALPHA); - final float newEndValue = viewState.alpha; - if (previousEndValue != null && previousEndValue == newEndValue) { - return; - } - ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA); - if (!mAnimationFilter.animateAlpha) { - // just a local update was performed - if (previousAnimator != null) { - // we need to increase all animation keyframes of the previous animator by the - // relative change to the end value - PropertyValuesHolder[] values = previousAnimator.getValues(); - float relativeDiff = newEndValue - previousEndValue; - float newStartValue = previousStartValue + relativeDiff; - values[0].setFloatValues(newStartValue, newEndValue); - child.setTag(TAG_START_ALPHA, newStartValue); - child.setTag(TAG_END_ALPHA, newEndValue); - previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); - return; - } else { - // no new animation needed, let's just apply the value - child.setAlpha(newEndValue); - if (newEndValue == 0) { - child.setVisibility(View.INVISIBLE); - } - } - } - - ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA, - child.getAlpha(), newEndValue); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - // Handle layer type - child.setLayerType(View.LAYER_TYPE_HARDWARE, null); - animator.addListener(new AnimatorListenerAdapter() { - public boolean mWasCancelled; - - @Override - public void onAnimationEnd(Animator animation) { - child.setLayerType(View.LAYER_TYPE_NONE, null); - if (newEndValue == 0 && !mWasCancelled) { - child.setVisibility(View.INVISIBLE); - } - // remove the tag when the animation is finished - child.setTag(TAG_ANIMATOR_ALPHA, null); - child.setTag(TAG_START_ALPHA, null); - child.setTag(TAG_END_ALPHA, null); - } - - @Override - public void onAnimationCancel(Animator animation) { - mWasCancelled = true; - } - - @Override - public void onAnimationStart(Animator animation) { - mWasCancelled = false; - } - }); - long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); - animator.setDuration(newDuration); - if (delay > 0 && (previousAnimator == null - || previousAnimator.getAnimatedFraction() == 0)) { - animator.setStartDelay(delay); - } - animator.addListener(getGlobalAnimationFinishedListener()); - - startAnimator(animator); - child.setTag(TAG_ANIMATOR_ALPHA, animator); - child.setTag(TAG_START_ALPHA, child.getAlpha()); - child.setTag(TAG_END_ALPHA, newEndValue); - } - - private void startZTranslationAnimation(final View child, - final ViewState viewState, long duration, long delay) { - Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z); - Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z); - float newEndValue = viewState.zTranslation; - if (previousEndValue != null && previousEndValue == newEndValue) { - return; - } - ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z); - if (!mAnimationFilter.animateZ) { - // just a local update was performed - if (previousAnimator != null) { - // we need to increase all animation keyframes of the previous animator by the - // relative change to the end value - PropertyValuesHolder[] values = previousAnimator.getValues(); - float relativeDiff = newEndValue - previousEndValue; - float newStartValue = previousStartValue + relativeDiff; - values[0].setFloatValues(newStartValue, newEndValue); - child.setTag(TAG_START_TRANSLATION_Z, newStartValue); - child.setTag(TAG_END_TRANSLATION_Z, newEndValue); - previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); - return; - } else { - // no new animation needed, let's just apply the value - child.setTranslationZ(newEndValue); - } - } - - ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z, - child.getTranslationZ(), newEndValue); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); - animator.setDuration(newDuration); - if (delay > 0 && (previousAnimator == null - || previousAnimator.getAnimatedFraction() == 0)) { - animator.setStartDelay(delay); - } - animator.addListener(getGlobalAnimationFinishedListener()); - // remove the tag when the animation is finished - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null); - child.setTag(TAG_START_TRANSLATION_Z, null); - child.setTag(TAG_END_TRANSLATION_Z, null); - } - }); - startAnimator(animator); - child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator); - child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ()); - child.setTag(TAG_END_TRANSLATION_Z, newEndValue); - } - - private void startYTranslationAnimation(final View child, - ViewState viewState, long duration, long delay) { - Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y); - Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y); - float newEndValue = viewState.yTranslation; - if (previousEndValue != null && previousEndValue == newEndValue) { - return; - } - ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y); - if (!mAnimationFilter.animateY) { - // just a local update was performed - if (previousAnimator != null) { - // we need to increase all animation keyframes of the previous animator by the - // relative change to the end value - PropertyValuesHolder[] values = previousAnimator.getValues(); - float relativeDiff = newEndValue - previousEndValue; - float newStartValue = previousStartValue + relativeDiff; - values[0].setFloatValues(newStartValue, newEndValue); - child.setTag(TAG_START_TRANSLATION_Y, newStartValue); - child.setTag(TAG_END_TRANSLATION_Y, newEndValue); - previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); - return; - } else { - // no new animation needed, let's just apply the value - child.setTranslationY(newEndValue); - return; - } - } - - ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, - child.getTranslationY(), newEndValue); - Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ? - mHeadsUpAppearInterpolator :Interpolators.FAST_OUT_SLOW_IN; - animator.setInterpolator(interpolator); - long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); - animator.setDuration(newDuration); - if (delay > 0 && (previousAnimator == null - || previousAnimator.getAnimatedFraction() == 0)) { - animator.setStartDelay(delay); - } - animator.addListener(getGlobalAnimationFinishedListener()); - final boolean isHeadsUpDisappear = mHeadsUpDisappearChildren.contains(child); - // remove the tag when the animation is finished - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - HeadsUpManager.setIsClickedNotification(child, false); - child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); - child.setTag(TAG_START_TRANSLATION_Y, null); - child.setTag(TAG_END_TRANSLATION_Y, null); - if (isHeadsUpDisappear) { - ((ExpandableNotificationRow) child).setHeadsupDisappearRunning(false); - } - } - }); - startAnimator(animator); - child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator); - child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY()); - child.setTag(TAG_END_TRANSLATION_Y, newEndValue); - } - - private void startAnimator(ValueAnimator animator) { - mAnimatorSet.add(animator); - animator.start(); + index = (float) Math.pow(index, 0.7f); + result += (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE); + return result; } /** @@ -814,33 +333,11 @@ public class StackStateAnimator { @Override public void onAnimationStart(Animator animation) { mWasCancelled = false; + mAnimatorSet.add(animation); } }; } - public static <T> T getChildTag(View child, int tag) { - return (T) child.getTag(tag); - } - - /** - * Cancel the previous animator and get the duration of the new animation. - * - * @param duration the new duration - * @param previousAnimator the animator which was running before - * @return the new duration - */ - private long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) { - long newDuration = duration; - if (previousAnimator != null) { - // We take either the desired length of the new animation or the remaining time of - // the previous animator, whichever is longer. - newDuration = Math.max(previousAnimator.getDuration() - - previousAnimator.getCurrentPlayTime(), newDuration); - previousAnimator.cancel(); - } - return newDuration; - } - private void onAnimationFinished() { mHostLayout.onChildAnimationFinished(); for (View v : mChildrenToClearFromOverlay) { @@ -864,25 +361,25 @@ public class StackStateAnimator { NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) { // This item is added, initialize it's properties. - StackViewState viewState = finalState + ExpandableViewState viewState = finalState .getViewStateForView(changingView); if (viewState == null) { // The position for this child was never generated, let's continue. continue; } - finalState.applyState(changingView, viewState); + viewState.applyToView(changingView); mNewAddChildren.add(changingView); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) { - if (changingView.getVisibility() == View.GONE) { + if (changingView.getVisibility() != View.VISIBLE) { removeFromOverlay(changingView); continue; } // Find the amount to translate up. This is needed in order to understand the // direction of the remove animation (either downwards or upwards) - StackViewState viewState = finalState + ExpandableViewState viewState = finalState .getViewStateForView(event.viewAfterChangingView); int actualHeight = changingView.getActualHeight(); // upwards by default @@ -920,7 +417,7 @@ public class StackStateAnimator { } else if (event.animationType == NotificationStackScrollLayout .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) { // This item is added, initialize it's properties. - StackViewState viewState = finalState.getViewStateForView(changingView); + ExpandableViewState viewState = finalState.getViewStateForView(changingView); mTmpState.copyFrom(viewState); if (event.headsUpFromBottom) { mTmpState.yTranslation = mHeadsUpAppearHeightBottom; @@ -928,7 +425,7 @@ public class StackStateAnimator { mTmpState.yTranslation = -mTmpState.height; } mHeadsUpAppearChildren.add(changingView); - finalState.applyState(changingView, mTmpState); + mTmpState.applyToView(changingView); } else if (event.animationType == NotificationStackScrollLayout .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR || event.animationType == NotificationStackScrollLayout @@ -942,12 +439,13 @@ public class StackStateAnimator { // We temporarily enable Y animations, the real filter will be combined // afterwards anyway mAnimationFilter.animateY = true; - startViewAnimations(changingView, mTmpState, + mAnimationProperties.delay = event.animationType == NotificationStackScrollLayout .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK - ? ANIMATION_DELAY_HEADS_UP - : 0, - ANIMATION_DURATION_HEADS_UP_DISAPPEAR); + ? ANIMATION_DELAY_HEADS_UP + : 0; + mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR; + mTmpState.animateTo(changingView, mAnimationProperties); mChildrenToClearFromOverlay.add(changingView); } } @@ -1007,38 +505,6 @@ public class StackStateAnimator { } } - /** - * Get the end value of the height animation running on a view or the actualHeight - * if no animation is running. - */ - public static int getFinalActualHeight(ExpandableView view) { - if (view == null) { - return 0; - } - ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT); - if (heightAnimator == null) { - return view.getActualHeight(); - } else { - return getChildTag(view, TAG_END_HEIGHT); - } - } - - /** - * Get the end value of the yTranslation animation running on a view or the yTranslation - * if no animation is running. - */ - public static float getFinalTranslationY(View view) { - if (view == null) { - return 0; - } - ValueAnimator yAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y); - if (yAnimator == null) { - return view.getTranslationY(); - } else { - return getChildTag(view, TAG_END_TRANSLATION_Y); - } - } - public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) { mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom; } @@ -1046,4 +512,8 @@ public class StackStateAnimator { public void setShadeExpanded(boolean shadeExpanded) { mShadeExpanded = shadeExpanded; } + + public void setShelf(NotificationShelf shelf) { + mShelf = shelf; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java deleted file mode 100644 index ecdee4eaecba..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.systemui.statusbar.stack; - -/** -* A state of an expandable view -*/ -public class StackViewState extends ViewState { - - // These are flags such that we can create masks for filtering. - - public static final int LOCATION_UNKNOWN = 0x00; - public static final int LOCATION_FIRST_HUN = 0x01; - public static final int LOCATION_HIDDEN_TOP = 0x02; - public static final int LOCATION_MAIN_AREA = 0x04; - public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x08; - public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10; - /** The view isn't layouted at all. */ - public static final int LOCATION_GONE = 0x40; - - public int height; - public boolean dimmed; - public boolean dark; - public boolean hideSensitive; - public boolean belowSpeedBump; - public float shadowAlpha; - - /** - * How much the child overlaps with the previous child on top. This is used to - * show the background properly when the child on top is translating away. - */ - public int clipTopAmount; - - /** - * The index of the view, only accounting for views not equal to GONE - */ - public int notGoneIndex; - - /** - * The location this view is currently rendered at. - * - * <p>See <code>LOCATION_</code> flags.</p> - */ - public int location; - - /** - * Whether a child in a group is being clipped at the bottom. - */ - public boolean isBottomClipped; - - @Override - public void copyFrom(ViewState viewState) { - super.copyFrom(viewState); - if (viewState instanceof StackViewState) { - StackViewState svs = (StackViewState) viewState; - height = svs.height; - dimmed = svs.dimmed; - shadowAlpha = svs.shadowAlpha; - dark = svs.dark; - hideSensitive = svs.hideSensitive; - belowSpeedBump = svs.belowSpeedBump; - clipTopAmount = svs.clipTopAmount; - notGoneIndex = svs.notGoneIndex; - location = svs.location; - isBottomClipped = svs.isBottomClipped; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java index 5beaac378b04..8a5ddd43b24c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java @@ -16,7 +16,18 @@ package com.android.systemui.statusbar.stack; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; import android.view.View; +import android.view.animation.Interpolator; + +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.policy.HeadsUpManager; /** * A state of a view. This can be used to apply a set of view properties to a view with @@ -25,25 +36,519 @@ import android.view.View; */ public class ViewState { + /** + * Some animation properties that can be used to update running animations but not creating + * any new ones. + */ + protected static final AnimationProperties NO_NEW_ANIMATIONS = new AnimationProperties() { + AnimationFilter mAnimationFilter = new AnimationFilter(); + @Override + public AnimationFilter getAnimationFilter() { + return mAnimationFilter; + } + }; + private static final int TAG_ANIMATOR_TRANSLATION_X = R.id.translation_x_animator_tag; + private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; + private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; + private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag; + private static final int TAG_END_TRANSLATION_X = R.id.translation_x_animator_end_value_tag; + private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag; + private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag; + private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag; + private static final int TAG_START_TRANSLATION_X = R.id.translation_x_animator_start_value_tag; + private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag; + private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag; + private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag; + public float alpha; + public float xTranslation; public float yTranslation; public float zTranslation; public boolean gone; public boolean hidden; + public float scaleX = 1.0f; + public float scaleY = 1.0f; public void copyFrom(ViewState viewState) { alpha = viewState.alpha; + xTranslation = viewState.xTranslation; yTranslation = viewState.yTranslation; zTranslation = viewState.zTranslation; gone = viewState.gone; hidden = viewState.hidden; + scaleX = viewState.scaleX; + scaleY = viewState.scaleY; } public void initFrom(View view) { alpha = view.getAlpha(); + xTranslation = view.getTranslationX(); yTranslation = view.getTranslationY(); zTranslation = view.getTranslationZ(); gone = view.getVisibility() == View.GONE; hidden = false; + scaleX = view.getScaleX(); + scaleY = view.getScaleY(); + } + + /** + * Applies a {@link ViewState} to a normal view. + */ + public void applyToView(View view) { + if (this.gone) { + // don't do anything with it + return; + } + boolean becomesInvisible = this.alpha == 0.0f || this.hidden; + boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA); + if (animatingAlpha) { + updateAlphaAnimation(view); + } else if (view.getAlpha() != this.alpha) { + // apply layer type + boolean becomesFullyVisible = this.alpha == 1.0f; + boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible + && view.hasOverlappingRendering(); + int layerType = view.getLayerType(); + int newLayerType = newLayerTypeIsHardware + ? View.LAYER_TYPE_HARDWARE + : View.LAYER_TYPE_NONE; + if (layerType != newLayerType) { + view.setLayerType(newLayerType, null); + } + + // apply alpha + view.setAlpha(this.alpha); + } + + // apply visibility + int oldVisibility = view.getVisibility(); + int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; + if (newVisibility != oldVisibility) { + if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) { + // We don't want views to change visibility when they are animating to GONE + view.setVisibility(newVisibility); + } + } + + // apply xTranslation + boolean animatingX = isAnimating(view, TAG_ANIMATOR_TRANSLATION_X); + if (animatingX) { + updateAnimationX(view); + } else if (view.getTranslationX() != this.xTranslation){ + view.setTranslationX(this.xTranslation); + } + + // apply yTranslation + boolean animatingY = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y); + if (animatingY) { + updateAnimationY(view); + } else if (view.getTranslationY() != this.yTranslation) { + view.setTranslationY(this.yTranslation); + } + + // apply zTranslation + boolean animatingZ = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z); + if (animatingZ) { + updateAnimationZ(view); + } else if (view.getTranslationZ() != this.zTranslation) { + view.setTranslationZ(this.zTranslation); + } + + // apply scaleX + if (view.getScaleX() != this.scaleX) { + view.setScaleX(this.scaleX); + } + + // apply scaleY + if (view.getScaleY() != this.scaleY) { + view.setScaleY(this.scaleY); + } + } + + private boolean isAnimating(View view, int tag) { + return getChildTag(view, tag) != null; + } + + /** + * Start an animation to this viewstate + * @param child the view to animate + * @param animationProperties the properties of the animation + */ + public void animateTo(View child, AnimationProperties animationProperties) { + boolean wasVisible = child.getVisibility() == View.VISIBLE; + final float alpha = this.alpha; + if (!wasVisible && (alpha != 0 || child.getAlpha() != 0) + && !this.gone && !this.hidden) { + child.setVisibility(View.VISIBLE); + } + float childAlpha = child.getAlpha(); + boolean alphaChanging = this.alpha != childAlpha; + if (child instanceof ExpandableView) { + // We don't want views to change visibility when they are animating to GONE + alphaChanging &= !((ExpandableView) child).willBeGone(); + } + + // start translationX animation + if (child.getTranslationX() != this.xTranslation) { + startXTranslationAnimation(child, animationProperties); + } else { + abortAnimation(child, TAG_ANIMATOR_TRANSLATION_X); + } + + // start translationY animation + if (child.getTranslationY() != this.yTranslation) { + startYTranslationAnimation(child, animationProperties); + } else { + abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y); + } + + // start translationZ animation + if (child.getTranslationZ() != this.zTranslation) { + startZTranslationAnimation(child, animationProperties); + } else { + abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z); + } + + // start alpha animation + if (alphaChanging) { + startAlphaAnimation(child, animationProperties); + } else { + abortAnimation(child, TAG_ANIMATOR_ALPHA); + } + } + + private void updateAlphaAnimation(View view) { + startAlphaAnimation(view, NO_NEW_ANIMATIONS); + } + + private void startAlphaAnimation(final View child, AnimationProperties properties) { + Float previousStartValue = getChildTag(child,TAG_START_ALPHA); + Float previousEndValue = getChildTag(child,TAG_END_ALPHA); + final float newEndValue = this.alpha; + if (previousEndValue != null && previousEndValue == newEndValue) { + return; + } + ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA); + AnimationFilter filter = properties.getAnimationFilter(); + if (!filter.animateAlpha) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + float relativeDiff = newEndValue - previousEndValue; + float newStartValue = previousStartValue + relativeDiff; + values[0].setFloatValues(newStartValue, newEndValue); + child.setTag(TAG_START_ALPHA, newStartValue); + child.setTag(TAG_END_ALPHA, newEndValue); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + child.setAlpha(newEndValue); + if (newEndValue == 0) { + child.setVisibility(View.INVISIBLE); + } + } + } + + ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA, + child.getAlpha(), newEndValue); + animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + // Handle layer type + child.setLayerType(View.LAYER_TYPE_HARDWARE, null); + animator.addListener(new AnimatorListenerAdapter() { + public boolean mWasCancelled; + + @Override + public void onAnimationEnd(Animator animation) { + child.setLayerType(View.LAYER_TYPE_NONE, null); + if (newEndValue == 0 && !mWasCancelled) { + child.setVisibility(View.INVISIBLE); + } + // remove the tag when the animation is finished + child.setTag(TAG_ANIMATOR_ALPHA, null); + child.setTag(TAG_START_ALPHA, null); + child.setTag(TAG_END_ALPHA, null); + } + + @Override + public void onAnimationCancel(Animator animation) { + mWasCancelled = true; + } + + @Override + public void onAnimationStart(Animator animation) { + mWasCancelled = false; + } + }); + long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); + animator.setDuration(newDuration); + if (properties.delay > 0 && (previousAnimator == null + || previousAnimator.getAnimatedFraction() == 0)) { + animator.setStartDelay(properties.delay); + } + AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + if (listener != null) { + animator.addListener(listener); + } + + startAnimator(animator, listener); + child.setTag(TAG_ANIMATOR_ALPHA, animator); + child.setTag(TAG_START_ALPHA, child.getAlpha()); + child.setTag(TAG_END_ALPHA, newEndValue); + } + + private void updateAnimationZ(View view) { + startZTranslationAnimation(view, NO_NEW_ANIMATIONS); + } + + private void startZTranslationAnimation(final View child, AnimationProperties properties) { + Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z); + Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z); + float newEndValue = this.zTranslation; + if (previousEndValue != null && previousEndValue == newEndValue) { + return; + } + ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z); + AnimationFilter filter = properties.getAnimationFilter(); + if (!filter.animateZ) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + float relativeDiff = newEndValue - previousEndValue; + float newStartValue = previousStartValue + relativeDiff; + values[0].setFloatValues(newStartValue, newEndValue); + child.setTag(TAG_START_TRANSLATION_Z, newStartValue); + child.setTag(TAG_END_TRANSLATION_Z, newEndValue); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + child.setTranslationZ(newEndValue); + } + } + + ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z, + child.getTranslationZ(), newEndValue); + animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); + animator.setDuration(newDuration); + if (properties.delay > 0 && (previousAnimator == null + || previousAnimator.getAnimatedFraction() == 0)) { + animator.setStartDelay(properties.delay); + } + AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + if (listener != null) { + animator.addListener(listener); + } + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null); + child.setTag(TAG_START_TRANSLATION_Z, null); + child.setTag(TAG_END_TRANSLATION_Z, null); + } + }); + startAnimator(animator, listener); + child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator); + child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ()); + child.setTag(TAG_END_TRANSLATION_Z, newEndValue); + } + + private void updateAnimationX(View view) { + startXTranslationAnimation(view, NO_NEW_ANIMATIONS); + } + + private void startXTranslationAnimation(final View child, AnimationProperties properties) { + Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_X); + Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_X); + float newEndValue = this.xTranslation; + if (previousEndValue != null && previousEndValue == newEndValue) { + return; + } + ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_X); + AnimationFilter filter = properties.getAnimationFilter(); + if (!filter.animateX) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + float relativeDiff = newEndValue - previousEndValue; + float newStartValue = previousStartValue + relativeDiff; + values[0].setFloatValues(newStartValue, newEndValue); + child.setTag(TAG_START_TRANSLATION_X, newStartValue); + child.setTag(TAG_END_TRANSLATION_X, newEndValue); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + child.setTranslationX(newEndValue); + return; + } + } + + ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_X, + child.getTranslationX(), newEndValue); + Interpolator customInterpolator = properties.getCustomInterpolator(child, + View.TRANSLATION_X); + Interpolator interpolator = customInterpolator != null ? customInterpolator + : Interpolators.FAST_OUT_SLOW_IN; + animator.setInterpolator(interpolator); + long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); + animator.setDuration(newDuration); + if (properties.delay > 0 && (previousAnimator == null + || previousAnimator.getAnimatedFraction() == 0)) { + animator.setStartDelay(properties.delay); + } + AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + if (listener != null) { + animator.addListener(listener); + } + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + child.setTag(TAG_ANIMATOR_TRANSLATION_X, null); + child.setTag(TAG_START_TRANSLATION_X, null); + child.setTag(TAG_END_TRANSLATION_X, null); + } + }); + startAnimator(animator, listener); + child.setTag(TAG_ANIMATOR_TRANSLATION_X, animator); + child.setTag(TAG_START_TRANSLATION_X, child.getTranslationX()); + child.setTag(TAG_END_TRANSLATION_X, newEndValue); + } + + private void updateAnimationY(View view) { + startYTranslationAnimation(view, NO_NEW_ANIMATIONS); + } + + private void startYTranslationAnimation(final View child, AnimationProperties properties) { + Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y); + Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y); + float newEndValue = this.yTranslation; + if (previousEndValue != null && previousEndValue == newEndValue) { + return; + } + ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y); + AnimationFilter filter = properties.getAnimationFilter(); + if (!filter.animateY) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + float relativeDiff = newEndValue - previousEndValue; + float newStartValue = previousStartValue + relativeDiff; + values[0].setFloatValues(newStartValue, newEndValue); + child.setTag(TAG_START_TRANSLATION_Y, newStartValue); + child.setTag(TAG_END_TRANSLATION_Y, newEndValue); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + child.setTranslationY(newEndValue); + return; + } + } + + ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, + child.getTranslationY(), newEndValue); + Interpolator customInterpolator = properties.getCustomInterpolator(child, + View.TRANSLATION_Y); + Interpolator interpolator = customInterpolator != null ? customInterpolator + : Interpolators.FAST_OUT_SLOW_IN; + animator.setInterpolator(interpolator); + long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); + animator.setDuration(newDuration); + if (properties.delay > 0 && (previousAnimator == null + || previousAnimator.getAnimatedFraction() == 0)) { + animator.setStartDelay(properties.delay); + } + AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + if (listener != null) { + animator.addListener(listener); + } + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + HeadsUpManager.setIsClickedNotification(child, false); + child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); + child.setTag(TAG_START_TRANSLATION_Y, null); + child.setTag(TAG_END_TRANSLATION_Y, null); + onYTranslationAnimationFinished(); + } + }); + startAnimator(animator, listener); + child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator); + child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY()); + child.setTag(TAG_END_TRANSLATION_Y, newEndValue); + } + + protected void onYTranslationAnimationFinished() { + } + + protected void startAnimator(Animator animator, AnimatorListenerAdapter listener) { + if (listener != null) { + // Even if there's a delay we'd want to notify it of the start immediately. + listener.onAnimationStart(animator); + } + animator.start(); + } + + public static <T> T getChildTag(View child, int tag) { + return (T) child.getTag(tag); + } + + protected void abortAnimation(View child, int animatorTag) { + Animator previousAnimator = getChildTag(child, animatorTag); + if (previousAnimator != null) { + previousAnimator.cancel(); + } + } + + /** + * Cancel the previous animator and get the duration of the new animation. + * + * @param duration the new duration + * @param previousAnimator the animator which was running before + * @return the new duration + */ + protected long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) { + long newDuration = duration; + if (previousAnimator != null) { + // We take either the desired length of the new animation or the remaining time of + // the previous animator, whichever is longer. + newDuration = Math.max(previousAnimator.getDuration() + - previousAnimator.getCurrentPlayTime(), newDuration); + previousAnimator.cancel(); + } + return newDuration; + } + + /** + * Get the end value of the yTranslation animation running on a view or the yTranslation + * if no animation is running. + */ + public static float getFinalTranslationY(View view) { + if (view == null) { + return 0; + } + ValueAnimator yAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y); + if (yAnimator == null) { + return view.getTranslationY(); + } else { + return getChildTag(view, TAG_END_TRANSLATION_Y); + } + } + + public static boolean isAnimatingY(ExpandableView child) { + return getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null; } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java index c20cc842b607..5393d60e4edd 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java @@ -78,6 +78,7 @@ public class VolumeDialogController { AudioSystem.STREAM_SYSTEM_ENFORCED, AudioSystem.STREAM_TTS, AudioSystem.STREAM_VOICE_CALL, + AudioSystem.STREAM_ACCESSIBILITY, }; private final HandlerThread mWorkerThread; @@ -562,6 +563,22 @@ public class VolumeDialogController { .sendToTarget(); mWorker.sendEmptyMessage(W.DISMISS_REQUESTED); } + + @Override + public void setA11yMode(int mode) { + if (D.BUG) Log.d(TAG, "setA11yMode to " + mode); + if (mDestroyed) return; + switch (mode) { + case VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME: + // "legacy" mode + break; + case VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME: + break; + default: + Log.e(TAG, "Invalid accessibility mode " + mode); + break; + } + } } private final class W extends Handler { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java new file mode 100644 index 000000000000..131a70b2c109 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java @@ -0,0 +1,88 @@ +/* + * 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.systemui.keyguard; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.internal.policy.IKeyguardDismissCallback; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * runtest systemui -c com.android.systemui.keyguard.DismissCallbackRegistryTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DismissCallbackRegistryTest { + + private final DismissCallbackRegistry mDismissCallbackRegistry = new DismissCallbackRegistry(); + private @Mock IKeyguardDismissCallback mMockCallback; + private @Mock IKeyguardDismissCallback mMockCallback2; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testCancelled() throws Exception { + mDismissCallbackRegistry.addCallback(mMockCallback); + mDismissCallbackRegistry.notifyDismissCancelled(); + verify(mMockCallback).onDismissCancelled(); + } + + @Test + public void testCancelled_multiple() throws Exception { + mDismissCallbackRegistry.addCallback(mMockCallback); + mDismissCallbackRegistry.addCallback(mMockCallback2); + mDismissCallbackRegistry.notifyDismissCancelled(); + verify(mMockCallback).onDismissCancelled(); + verify(mMockCallback2).onDismissCancelled(); + } + + @Test + public void testSucceeded() throws Exception { + mDismissCallbackRegistry.addCallback(mMockCallback); + mDismissCallbackRegistry.notifyDismissSucceeded(); + verify(mMockCallback).onDismissSucceeded(); + } + + @Test + public void testSucceeded_multiple() throws Exception { + mDismissCallbackRegistry.addCallback(mMockCallback); + mDismissCallbackRegistry.addCallback(mMockCallback2); + mDismissCallbackRegistry.notifyDismissSucceeded(); + verify(mMockCallback).onDismissSucceeded(); + verify(mMockCallback2).onDismissSucceeded(); + } + + @Test + public void testOnlyOnce() throws Exception { + mDismissCallbackRegistry.addCallback(mMockCallback); + mDismissCallbackRegistry.notifyDismissSucceeded(); + mDismissCallbackRegistry.notifyDismissSucceeded(); + verify(mMockCallback, times(1)).onDismissSucceeded(); + } +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c8ed872932c7..d085a4729475 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -23,6 +23,8 @@ import android.app.IActivityManager; import android.app.WaitResult; import android.graphics.PointF; import android.os.IDeviceIdentifiersPolicyService; + +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.telephony.TelephonyIntents; import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -22542,6 +22544,17 @@ public class ActivityManagerService extends IActivityManager.Stub return rInfo != null && rInfo.activityInfo != null; } + @Override + public void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback) + throws RemoteException { + final long callingId = Binder.clearCallingIdentity(); + try { + mKeyguardController.dismissKeyguard(token, callback); + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + /** * Attach an agent to the specified process (proces name or PID) */ diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 90b46edbe3c7..214a35799860 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -207,6 +207,8 @@ final class ActivityRecord { boolean keysPaused; // has key dispatching been paused for it? int launchMode; // the launch mode activity attribute. boolean visible; // does this activity's window need to be shown? + boolean visibleIgnoringKeyguard; // is this activity visible, ignoring the fact that Keyguard + // might hide this activity? boolean sleeping; // have we told the activity to sleep? boolean nowVisible; // is this activity's window visible? boolean idle; // has the activity gone idle? @@ -1248,9 +1250,15 @@ final class ActivityRecord { mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = true; } - /** Return true if the input activity should be made visible */ - boolean shouldBeVisible(boolean behindTranslucentActivity, boolean stackVisibleBehind, - ActivityRecord visibleBehind, boolean behindFullscreenActivity) { + /** + * @return true if the input activity should be made visible, ignoring any effect Keyguard + * might have on the visibility + * + * @see {@link ActivityStack#checkKeyguardVisibility} + */ + boolean shouldBeVisibleIgnoringKeyguard(boolean behindTranslucentActivity, + boolean stackVisibleBehind, ActivityRecord visibleBehind, + boolean behindFullscreenActivity) { if (!okToShowLocked()) { return false; } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 473b1a34a892..005b8aa16bd1 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -1660,13 +1660,15 @@ final class ActivityStack extends ConfigurationContainer { aboveTop = false; // Check whether activity should be visible without Keyguard influence - final boolean shouldBeVisible = r.shouldBeVisible(behindTranslucentActivity, - stackVisibleBehind, visibleBehind, behindFullscreenActivity); + final boolean visibleIgnoringKeyguard = r.shouldBeVisibleIgnoringKeyguard( + behindTranslucentActivity, stackVisibleBehind, visibleBehind, + behindFullscreenActivity); + r.visibleIgnoringKeyguard = visibleIgnoringKeyguard; // Now check whether it's really visible depending on Keyguard state. - final boolean reallyVisible = checkKeyguardVisibility(r, shouldBeVisible, - isTop); - if (shouldBeVisible) { + final boolean reallyVisible = checkKeyguardVisibility(r, + visibleIgnoringKeyguard, isTop); + if (visibleIgnoringKeyguard) { behindFullscreenActivity = updateBehindFullscreen(stackInvisible, behindFullscreenActivity, task, r); if (behindFullscreenActivity && !r.fullscreen) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 5d8d79fd52c2..eb346691acae 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -3779,6 +3779,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer findTaskToMoveToFrontLocked(task, 0, null, reason, lockTaskModeState != LOCK_TASK_MODE_NONE); resumeFocusedStackTopActivityLocked(); + mWindowManager.executeAppTransition(); } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) { handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, task.getStackId(), true /* forceNonResizable */); @@ -4075,7 +4076,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer Settings.Secure.LOCK_TO_APP_EXIT_LOCKED) != 0; if (mLockTaskModeState == LOCK_TASK_MODE_PINNED && shouldLockKeyguard) { mWindowManager.lockNow(null); - mWindowManager.dismissKeyguard(); + mWindowManager.dismissKeyguard(null /* callback */); new LockPatternUtils(mService.mContext) .requireCredentialEntry(UserHandle.USER_ALL); } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 7a122e6e6d20..029b5dd2848e 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -300,15 +300,19 @@ class AppErrors { * @param crashInfo describing the failure */ void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) { + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final long origId = Binder.clearCallingIdentity(); try { - crashApplicationInner(r, crashInfo); + crashApplicationInner(r, crashInfo, callingPid, callingUid); } finally { Binder.restoreCallingIdentity(origId); } } - void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) { + void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo, + int callingPid, int callingUid) { long timeMillis = System.currentTimeMillis(); String shortMsg = crashInfo.exceptionClassName; String longMsg = crashInfo.exceptionMessage; @@ -327,7 +331,7 @@ class AppErrors { * finish now and don't show the app error dialog. */ if (handleAppCrashInActivityController(r, crashInfo, shortMsg, longMsg, stackTrace, - timeMillis)) { + timeMillis, callingPid, callingUid)) { return; } @@ -429,15 +433,16 @@ class AppErrors { private boolean handleAppCrashInActivityController(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo, String shortMsg, String longMsg, - String stackTrace, long timeMillis) { + String stackTrace, long timeMillis, + int callingPid, int callingUid) { if (mService.mController == null) { return false; } try { String name = r != null ? r.processName : null; - int pid = r != null ? r.pid : Binder.getCallingPid(); - int uid = r != null ? r.info.uid : Binder.getCallingUid(); + int pid = r != null ? r.pid : callingPid; + int uid = r != null ? r.info.uid : callingUid; if (!mService.mController.appCrashed(name, pid, shortMsg, longMsg, timeMillis, crashInfo.stackTrace)) { if ("1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")) diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java index 9d8c3835b33f..5e02597f1991 100644 --- a/services/core/java/com/android/server/am/KeyguardController.java +++ b/services/core/java/com/android/server/am/KeyguardController.java @@ -20,6 +20,8 @@ import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; @@ -29,6 +31,11 @@ import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_OCCLUDE; import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE; import static com.android.server.wm.AppTransition.TRANSIT_UNSET; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.server.wm.WindowManagerService; import java.io.PrintWriter; @@ -42,6 +49,8 @@ import java.util.ArrayList; */ class KeyguardController { + private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardController" : TAG_AM; + private final ActivityManagerService mService; private final ActivityStackSupervisor mStackSupervisor; private WindowManagerService mWindowManager; @@ -108,7 +117,6 @@ class KeyguardController { mWindowManager.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */, convertTransitFlags(flags), false /* forceOverride */); - mWindowManager.keyguardGoingAway(flags); mService.updateSleepIfNeededLocked(); // Some stack visibility might change (e.g. docked stack) @@ -122,6 +130,23 @@ class KeyguardController { } } + void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback) { + final ActivityRecord activityRecord = ActivityRecord.forTokenLocked(token); + if (activityRecord == null || !activityRecord.visibleIgnoringKeyguard) { + failCallback(callback); + return; + } + mWindowManager.dismissKeyguard(callback); + } + + private void failCallback(IKeyguardDismissCallback callback) { + try { + callback.onDismissError(); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call callback", e); + } + } + private int convertTransitFlags(int keyguardGoingAwayFlags) { int result = 0; if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_TO_SHADE) != 0) { @@ -215,7 +240,7 @@ class KeyguardController { */ private void handleDismissKeyguard() { if (mDismissingKeyguardActivity != null) { - mWindowManager.dismissKeyguard(); + mWindowManager.dismissKeyguard(null /* callback */); // If we are about to unocclude the Keyguard, but we can dismiss it without security, // we immediately dismiss the Keyguard so the activity gets shown without a flicker. diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index ac9545c06c10..67f361441af7 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -139,7 +139,9 @@ import java.util.Objects; * * @hide */ -public class AudioService extends IAudioService.Stub { +public class AudioService extends IAudioService.Stub + implements AccessibilityManager.TouchExplorationStateChangeListener, + AccessibilityManager.AccessibilityStateChangeListener{ private static final String TAG = "AudioService"; @@ -775,7 +777,7 @@ public class AudioService extends IAudioService.Stub { TAG, SAFE_VOLUME_CONFIGURE_TIMEOUT_MS); - StreamOverride.init(mContext); + initA11yMonitoring(mContext); mControllerService.init(); onIndicateSystemReady(); } @@ -972,6 +974,8 @@ public class AudioService extends IAudioService.Stub { private void updateStreamVolumeAlias(boolean updateVolumes, String caller) { int dtmfStreamAlias; + final int a11yStreamAlias = sIndependentA11yVolume ? + AudioSystem.STREAM_ACCESSIBILITY : AudioSystem.STREAM_MUSIC; if (mIsSingleVolume) { mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION; @@ -1000,9 +1004,13 @@ public class AudioService extends IAudioService.Stub { } mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias; + mStreamVolumeAlias[AudioSystem.STREAM_ACCESSIBILITY] = a11yStreamAlias; + if (updateVolumes) { mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias], caller); + mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setAllIndexes( + mStreamStates[a11yStreamAlias], caller); // apply stream mute states according to new value of mRingerModeAffectedStreams setRingerModeInt(getRingerModeInternal(), false); sendMsg(mAudioHandler, @@ -1011,6 +1019,12 @@ public class AudioService extends IAudioService.Stub { 0, 0, mStreamStates[AudioSystem.STREAM_DTMF], 0); + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + mStreamStates[AudioSystem.STREAM_ACCESSIBILITY], 0); } } @@ -1536,6 +1550,10 @@ public class AudioService extends IAudioService.Stub { private void setStreamVolume(int streamType, int index, int flags, String callingPackage, String caller, int uid) { + if (DEBUG_VOL) { + Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index + + ", calling=" + callingPackage + ")"); + } if (mUseFixedVolume) { return; } @@ -3639,7 +3657,7 @@ public class AudioService extends IAudioService.Stub { return AudioSystem.STREAM_VOICE_CALL; } } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { - if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) { + if (isAfMusicActiveRecently(sStreamOverrideDelayMs)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active"); return AudioSystem.STREAM_MUSIC; @@ -3665,13 +3683,13 @@ public class AudioService extends IAudioService.Stub { return AudioSystem.STREAM_VOICE_CALL; } } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION, - StreamOverride.sDelayMs) || + sStreamOverrideDelayMs) || AudioSystem.isStreamActive(AudioSystem.STREAM_RING, - StreamOverride.sDelayMs)) { + sStreamOverrideDelayMs)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION"); return AudioSystem.STREAM_NOTIFICATION; } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { - if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) { + if (isAfMusicActiveRecently(sStreamOverrideDelayMs)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC"); return AudioSystem.STREAM_MUSIC; } else { @@ -5861,44 +5879,67 @@ public class AudioService extends IAudioService.Stub { } //========================================================================================== - // Accessibility: taking touch exploration into account for selecting the default + // Accessibility + + private void initA11yMonitoring(Context ctxt) { + AccessibilityManager accessibilityManager = + (AccessibilityManager) ctxt.getSystemService(Context.ACCESSIBILITY_SERVICE); + updateDefaultStreamOverrideDelay(accessibilityManager.isTouchExplorationEnabled()); + updateA11yVolumeAlias(accessibilityManager.isEnabled()); + accessibilityManager.addTouchExplorationStateChangeListener(this); + accessibilityManager.addAccessibilityStateChangeListener(this); + } + + //--------------------------------------------------------------------------------- + // A11y: taking touch exploration into account for selecting the default // stream override timeout when adjusting volume - //========================================================================================== - private static class StreamOverride - implements AccessibilityManager.TouchExplorationStateChangeListener { + //--------------------------------------------------------------------------------- - // AudioService.getActiveStreamType() will return: - // - STREAM_NOTIFICATION on tablets during this period after a notification stopped - // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt - // stopped - private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 0; - private static final int TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS = 1000; + // AudioService.getActiveStreamType() will return: + // - STREAM_NOTIFICATION on tablets during this period after a notification stopped + // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt + // stopped + private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 0; + private static final int TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS = 1000; - static int sDelayMs; + private static int sStreamOverrideDelayMs; - static void init(Context ctxt) { - AccessibilityManager accessibilityManager = - (AccessibilityManager) ctxt.getSystemService(Context.ACCESSIBILITY_SERVICE); - updateDefaultStreamOverrideDelay( - accessibilityManager.isTouchExplorationEnabled()); - accessibilityManager.addTouchExplorationStateChangeListener( - new StreamOverride()); - } + @Override + public void onTouchExplorationStateChanged(boolean enabled) { + updateDefaultStreamOverrideDelay(enabled); + } - @Override - public void onTouchExplorationStateChanged(boolean enabled) { - updateDefaultStreamOverrideDelay(enabled); + private void updateDefaultStreamOverrideDelay(boolean touchExploreEnabled) { + if (touchExploreEnabled) { + sStreamOverrideDelayMs = TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS; + } else { + sStreamOverrideDelayMs = DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS; } + if (DEBUG_VOL) Log.d(TAG, "Touch exploration enabled=" + touchExploreEnabled + + " stream override delay is now " + sStreamOverrideDelayMs + " ms"); + } - private static void updateDefaultStreamOverrideDelay(boolean touchExploreEnabled) { - if (touchExploreEnabled) { - sDelayMs = TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS; - } else { - sDelayMs = DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS; - } - if (DEBUG_VOL) Log.d(TAG, "Touch exploration enabled=" + touchExploreEnabled - + " stream override delay is now " + sDelayMs + " ms"); - } + //--------------------------------------------------------------------------------- + // A11y: taking a11y state into account for the handling of a11y prompts volume + //--------------------------------------------------------------------------------- + + private static boolean sIndependentA11yVolume = false; + + @Override + public void onAccessibilityStateChanged(boolean enabled) { + updateA11yVolumeAlias(enabled); + } + + private void updateA11yVolumeAlias(boolean a11Enabled) { + if (DEBUG_VOL) Log.d(TAG, "Accessibility mode changed to " + a11Enabled); + // a11y has its own volume stream when a11y service is enabled + sIndependentA11yVolume = a11Enabled; + // update the volume mapping scheme + updateStreamVolumeAlias(true /*updateVolumes*/, TAG); + // update the volume controller behavior + mVolumeController.setA11yMode(sIndependentA11yVolume ? + VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME : + VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME); } //========================================================================================== @@ -6178,6 +6219,16 @@ public class AudioService extends IAudioService.Stub { Log.w(TAG, "Error calling dismiss", e); } } + + public void setA11yMode(int a11yMode) { + if (mController == null) + return; + try { + mController.setA11yMode(a11yMode); + } catch (RemoteException e) { + Log.w(TAG, "Error calling setA11Mode", e); + } + } } /** diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 273bc6484724..132967c6b26a 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -32,11 +32,14 @@ import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback; import android.os.Binder; +import android.os.Bundle; import android.os.DeadObjectException; import android.os.Environment; import android.os.Handler; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.PowerManager; +import android.os.PowerManager.WakeLock; import android.os.RemoteException; import android.os.SELinux; import android.os.ServiceManager; @@ -625,17 +628,28 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private class FingerprintServiceLockoutResetMonitor { + private static final long WAKELOCK_TIMEOUT_MS = 2000; private final IFingerprintServiceLockoutResetCallback mCallback; + private final WakeLock mWakeLock; public FingerprintServiceLockoutResetMonitor( IFingerprintServiceLockoutResetCallback callback) { mCallback = callback; + mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "lockout reset callback"); } public void sendLockoutReset() { if (mCallback != null) { try { - mCallback.onLockoutReset(mHalDeviceId); + mWakeLock.acquire(WAKELOCK_TIMEOUT_MS); + mCallback.onLockoutReset(mHalDeviceId, new IRemoteCallback.Stub() { + + @Override + public void sendResult(Bundle data) throws RemoteException { + mWakeLock.release(); + } + }); } catch (DeadObjectException e) { Slog.w(TAG, "Death object while invoking onLockoutReset: ", e); mHandler.post(mRemoveCallbackRunnable); @@ -648,6 +662,9 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private final Runnable mRemoveCallbackRunnable = new Runnable() { @Override public void run() { + if (mWakeLock.isHeld()) { + mWakeLock.release(); + } removeLockoutResetCallback(FingerprintServiceLockoutResetMonitor.this); } }; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 604b3ed85e25..84c298b06284 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1837,7 +1837,7 @@ public class NotificationManagerService extends SystemService { * @param token The binder for the listener, to check that the caller is allowed */ @Override - public void snoozeNotificationFromListener(INotificationListener token, String key, + public void snoozeNotificationUntilFromListener(INotificationListener token, String key, long snoozeUntil) { long identity = Binder.clearCallingIdentity(); try { @@ -1849,6 +1849,38 @@ public class NotificationManagerService extends SystemService { } /** + * Allow an INotificationListener to snooze a single notification. + * + * @param token The binder for the listener, to check that the caller is allowed + */ + @Override + public void snoozeNotificationFromListener(INotificationListener token, String key) { + long identity = Binder.clearCallingIdentity(); + try { + final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + snoozeNotificationInt(key, info); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Allow an INotificationListener to un-snooze a single notification. + * + * @param token The binder for the listener, to check that the caller is allowed + */ + @Override + public void unsnoozeNotificationFromListener(INotificationListener token, String key) { + long identity = Binder.clearCallingIdentity(); + try { + final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + unsnoozeNotificationInt(key, info); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** * Allow an INotificationListener to simulate clearing (dismissing) a single notification. * * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear} @@ -2206,7 +2238,6 @@ public class NotificationManagerService extends SystemService { @Override public ComponentName getEffectsSuppressor() { - enforceSystemOrSystemUIOrVolume("INotificationManager.getEffectsSuppressor"); return !mEffectsSuppressors.isEmpty() ? mEffectsSuppressors.get(0) : null; } @@ -3686,6 +3717,34 @@ public class NotificationManagerService extends SystemService { } } + void snoozeNotificationInt(String key, ManagedServiceInfo listener) { + String listenerName = listener == null ? null : listener.component.toShortString(); + // TODO: write to event log + if (DBG) { + Slog.d(TAG, String.format("snooze event(%s, %s)", key, listenerName)); + } + synchronized (mNotificationList) { + final NotificationRecord r = mNotificationsByKey.get(key); + if (r != null) { + mNotificationList.remove(r); + cancelNotificationLocked(r, false, REASON_SNOOZED); + updateLightsLocked(); + mSnoozeHelper.snooze(r, r.getUser().getIdentifier()); + savePolicyFile(); + } + } + } + + void unsnoozeNotificationInt(String key, ManagedServiceInfo listener) { + String listenerName = listener == null ? null : listener.component.toShortString(); + // TODO: write to event log + if (DBG) { + Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName)); + } + mSnoozeHelper.repost(key, Binder.getCallingUid()); + savePolicyFile(); + } + void cancelAllLocked(int callingUid, int callingPid, int userId, int reason, ManagedServiceInfo listener, boolean includeCurrentProfiles) { String listenerName = listener == null ? null : listener.component.toShortString(); diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index 738403ed1b1e..409eef45b557 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; +import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.Log; @@ -62,6 +63,8 @@ public class SnoozeHelper { // User id : package name : notification key : record. private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, NotificationRecord>>> mSnoozedNotifications = new ArrayMap<>(); + // notification key : package. + private ArrayMap<String, String> mPackages = new ArrayMap<>(); private Callback mCallback; public SnoozeHelper(Context context, Callback callback, @@ -82,10 +85,20 @@ public class SnoozeHelper { } /** - * Records a notification that should be snoozed until the given time and schedules an alarm - * to repost at that time. + * Snoozes a notification and schedules an alarm to repost at that time. */ protected void snooze(NotificationRecord record, int userId, long until) { + snooze(record, userId); + scheduleRepost(record.sbn.getPackageName(), record.getKey(), userId, until); + } + + /** + * Records a snoozed notification. + */ + protected void snooze(NotificationRecord record, int userId) { + if (DEBUG) { + Slog.d(TAG, "Snoozing " + record.getKey()); + } ArrayMap<String, ArrayMap<String, NotificationRecord>> records = mSnoozedNotifications.get(userId); if (records == null) { @@ -98,13 +111,9 @@ public class SnoozeHelper { pkgRecords.put(record.getKey(), record); records.put(record.sbn.getPackageName(), pkgRecords); mSnoozedNotifications.put(userId, records); - if (DEBUG) { - Slog.d(TAG, "Snoozing " + record.getKey() + " until " + new Date(until)); - } - scheduleRepost(record.sbn.getPackageName(), record.getKey(), userId, until); + mPackages.put(record.getKey(), record.sbn.getPackageName()); } - protected boolean cancel(int userId, String pkg, String tag, int id) { if (mSnoozedNotifications.containsKey(userId)) { ArrayMap<String, NotificationRecord> recordsForPkg = @@ -121,6 +130,7 @@ public class SnoozeHelper { if (key != null) { recordsForPkg.remove(key); cancelAlarm(userId, pkg, key); + mPackages.remove(key); return true; } } @@ -145,6 +155,7 @@ public class SnoozeHelper { int P = records.size(); for (int k = 0; k < P; k++) { cancelAlarm(userId, snoozedPkgs.keyAt(j), records.keyAt(k)); + mPackages.remove(records.keyAt(k)); } } } @@ -162,6 +173,7 @@ public class SnoozeHelper { int N = records.size(); for (int i = 0; i < N; i++) { cancelAlarm(userId, pkg, records.keyAt(i)); + mPackages.remove(records.keyAt(i)); } return true; } @@ -190,8 +202,8 @@ public class SnoozeHelper { pkgRecords.put(record.getKey(), record); } - @VisibleForTesting - void repost(String pkg, String key, int userId) { + protected void repost(String key, int userId) { + final String pkg = mPackages.remove(key); ArrayMap<String, ArrayMap<String, NotificationRecord>> records = mSnoozedNotifications.get(userId); if (records == null) { @@ -202,6 +214,7 @@ public class SnoozeHelper { return; } final NotificationRecord record = pkgRecords.remove(key); + if (record != null) { mCallback.repost(userId, record); } @@ -213,7 +226,6 @@ public class SnoozeHelper { new Intent(REPOST_ACTION) .setData(new Uri.Builder().scheme(REPOST_SCHEME).appendPath(key).build()) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) - .putExtra(EXTRA_PKG, pkg) .putExtra(EXTRA_KEY, key) .putExtra(EXTRA_USER_ID, userId), PendingIntent.FLAG_UPDATE_CURRENT); @@ -273,8 +285,8 @@ public class SnoozeHelper { Slog.d(TAG, "Reposting notification"); } if (REPOST_ACTION.equals(intent.getAction())) { - repost(intent.getStringExtra(EXTRA_PKG), intent.getStringExtra(EXTRA_KEY), - intent.getIntExtra(EXTRA_USER_ID, 0)); + repost(intent.getStringExtra(EXTRA_KEY), intent.getIntExtra(EXTRA_USER_ID, + UserHandle.USER_SYSTEM)); } } }; diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 1ef4a9fa5849..acdcc72c7f71 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -219,19 +219,20 @@ class PackageDexOptimizer { final String dexoptType; String oatDir = null; - switch (dexoptNeeded) { + boolean isOdexLocation = (dexoptNeeded < 0); + switch (Math.abs(dexoptNeeded)) { case DexFile.NO_DEXOPT_NEEDED: continue; - case DexFile.DEX2OAT_NEEDED: + case DexFile.DEX2OAT_FROM_SCRATCH: + case DexFile.DEX2OAT_FOR_BOOT_IMAGE: + case DexFile.DEX2OAT_FOR_FILTER: + case DexFile.DEX2OAT_FOR_RELOCATION: dexoptType = "dex2oat"; oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet); break; - case DexFile.PATCHOAT_NEEDED: + case DexFile.PATCHOAT_FOR_RELOCATION: dexoptType = "patchoat"; break; - case DexFile.SELF_PATCHOAT_NEEDED: - dexoptType = "self patchoat"; - break; default: throw new IllegalStateException("Invalid dexopt:" + dexoptNeeded); } @@ -383,7 +384,7 @@ class PackageDexOptimizer { protected int adjustDexoptNeeded(int dexoptNeeded) { // Ensure compilation, no matter the current state. // TODO: The return value is wrong when patchoat is needed. - return DexFile.DEX2OAT_NEEDED; + return DexFile.DEX2OAT_FROM_SCRATCH; } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index bc2814270865..f5b4b9b400f3 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -402,8 +402,8 @@ public class PackageManagerService extends IPackageManager.Stub { static final int SCAN_CHECK_ONLY = 1<<13; static final int SCAN_DONT_KILL_APP = 1<<14; static final int SCAN_IGNORE_FROZEN = 1<<15; - static final int REMOVE_CHATTY = 1<<16; + static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<17; private static final int[] EMPTY_INT_ARRAY = new int[0]; @@ -2215,10 +2215,6 @@ public class PackageManagerService extends IPackageManager.Stub { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START, startTime); - // Set flag to monitor and not change apk file paths when - // scanning install directories. - final int scanFlags = SCAN_BOOTING | SCAN_INITIAL; - final String bootClassPath = System.getenv("BOOTCLASSPATH"); final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH"); @@ -2303,6 +2299,14 @@ public class PackageManagerService extends IPackageManager.Stub { } } + // Set flag to monitor and not change apk file paths when + // scanning install directories. + int scanFlags = SCAN_BOOTING | SCAN_INITIAL; + + if (mIsUpgrade || mFirstBoot) { + scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE; + } + // Collect vendor overlay packages. (Do this before scanning any apps.) // For security and version matching reason, only consider // overlay packages if they reside in the right directory. @@ -6670,7 +6674,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) { + private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) { final File[] files = dir.listFiles(); if (ArrayUtils.isEmpty(files)) { Log.d(TAG, "No files in app dir " + dir); @@ -6681,7 +6685,11 @@ public class PackageManagerService extends IPackageManager.Stub { Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags + " flags=0x" + Integer.toHexString(parseFlags)); } + ParallelPackageParser parallelPackageParser = new ParallelPackageParser( + mSeparateProcesses, mOnlyCore, mMetrics); + // Submit files for parsing in parallel + int fileCount = 0; for (File file : files) { final boolean isPackage = (isApkFile(file) || file.isDirectory()) && !PackageInstallerService.isStageName(file.getName()); @@ -6689,20 +6697,43 @@ public class PackageManagerService extends IPackageManager.Stub { // Ignore entries which are not packages continue; } - try { - scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, - scanFlags, currentTime, null); - } catch (PackageManagerException e) { - Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage()); + parallelPackageParser.submit(file, parseFlags); + fileCount++; + } - // Delete invalid userdata apps - if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 && - e.error == PackageManager.INSTALL_FAILED_INVALID_APK) { - logCriticalInfo(Log.WARN, "Deleting invalid package at " + file); - removeCodePathLI(file); - } + // Process results one by one + for (; fileCount > 0; fileCount--) { + ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take(); + Throwable throwable = parseResult.throwable; + int errorCode = PackageManager.INSTALL_SUCCEEDED; + + if (throwable == null) { + try { + scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags, + currentTime, null); + } catch (PackageManagerException e) { + errorCode = e.error; + Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage()); + } + } else if (throwable instanceof PackageParser.PackageParserException) { + PackageParser.PackageParserException e = (PackageParser.PackageParserException) + throwable; + errorCode = e.error; + Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage()); + } else { + throw new IllegalStateException("Unexpected exception occurred while parsing " + + parseResult.scanFile, throwable); + } + + // Delete invalid userdata apps + if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 && + errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) { + logCriticalInfo(Log.WARN, + "Deleting invalid package at " + parseResult.scanFile); + removeCodePathLI(parseResult.scanFile); } } + parallelPackageParser.close(); } private static File getSettingsProblemFile() { @@ -8055,6 +8086,11 @@ public class PackageManagerService extends IPackageManager.Stub { // old setting to restore at the end. PackageSetting nonMutatedPs = null; + // We keep references to the derived CPU Abis from settings in oder to reuse + // them in the case where we're not upgrading or booting for the first time. + String primaryCpuAbiFromSettings = null; + String secondaryCpuAbiFromSettings = null; + // writer synchronized (mPackages) { if (pkg.mSharedUserId != null) { @@ -8131,6 +8167,14 @@ public class PackageManagerService extends IPackageManager.Stub { } } + if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) == 0) { + PackageSetting foundPs = mSettings.getPackageLPr(pkg.packageName); + if (foundPs != null) { + primaryCpuAbiFromSettings = foundPs.primaryCpuAbiString; + secondaryCpuAbiFromSettings = foundPs.secondaryCpuAbiString; + } + } + pkgSetting = mSettings.getPackageLPr(pkg.packageName); if (pkgSetting != null && pkgSetting.sharedUser != suid) { PackageManagerService.reportSettingsProblem(Log.WARN, @@ -8163,7 +8207,11 @@ public class PackageManagerService extends IPackageManager.Stub { } mSettings.addUserToSettingLPw(pkgSetting); } else { - // REMOVE SharedUserSetting from method; update in a separate call + // REMOVE SharedUserSetting from method; update in a separate call. + // + // TODO(narayan): This update is bogus. nativeLibraryDir & primaryCpuAbi, + // secondaryCpuAbi are not known at this point so we always update them + // to null here, only to reset them at a later point. Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, suid, destCodeFile, pkg.applicationInfo.nativeLibraryDir, pkg.applicationInfo.primaryCpuAbi, pkg.applicationInfo.secondaryCpuAbi, pkg.applicationInfo.flags, @@ -8302,18 +8350,34 @@ public class PackageManagerService extends IPackageManager.Stub { final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting); if ((scanFlags & SCAN_NEW_INSTALL) == 0) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi"); - derivePackageAbi( - pkg, scanFile, cpuAbiOverride, true /*extractLibs*/, mAppLib32InstallDir); - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi"); + derivePackageAbi( + pkg, scanFile, cpuAbiOverride, true /*extractLibs*/, mAppLib32InstallDir); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + + // Some system apps still use directory structure for native libraries + // in which case we might end up not detecting abi solely based on apk + // structure. Try to detect abi based on directory structure. + if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() && + pkg.applicationInfo.primaryCpuAbi == null) { + setBundledAppAbisAndRoots(pkg, pkgSetting); + setNativeLibraryPaths(pkg, mAppLib32InstallDir); + } + } else { + // This is not a first boot or an upgrade, don't bother deriving the + // ABI during the scan. Instead, trust the value that was stored in the + // package setting. + pkg.applicationInfo.primaryCpuAbi = primaryCpuAbiFromSettings; + pkg.applicationInfo.secondaryCpuAbi = secondaryCpuAbiFromSettings; - // Some system apps still use directory structure for native libraries - // in which case we might end up not detecting abi solely based on apk - // structure. Try to detect abi based on directory structure. - if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() && - pkg.applicationInfo.primaryCpuAbi == null) { - setBundledAppAbisAndRoots(pkg, pkgSetting); setNativeLibraryPaths(pkg, mAppLib32InstallDir); + + if (DEBUG_ABI_SELECTION) { + Slog.i(TAG, "Using ABIS and native lib paths from settings : " + + pkg.packageName + " " + pkg.applicationInfo.primaryCpuAbi + ", " + + pkg.applicationInfo.secondaryCpuAbi); + } } } else { if ((scanFlags & SCAN_MOVE) != 0) { @@ -9168,11 +9232,6 @@ public class PackageManagerService extends IPackageManager.Stub { String cpuAbiOverride, boolean extractLibs, File appLib32InstallDir) throws PackageManagerException { - // TODO: We can probably be smarter about this stuff. For installed apps, - // we can calculate this information at install time once and for all. For - // system apps, we can probably assume that this information doesn't change - // after the first boot scan. As things stand, we do lots of unnecessary work. - // Give ourselves some initial paths; we'll come back for another // pass once we've determined ABI below. setNativeLibraryPaths(pkg, appLib32InstallDir); diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 3ad2ae1a97ea..6089d2eba960 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -76,14 +76,12 @@ abstract class PackageSettingBase extends SettingBase { String legacyNativeLibraryPathString; /** - * The primary CPU abi for this package. This value is regenerated at every - * boot scan. + * The primary CPU abi for this package. */ String primaryCpuAbiString; /** - * The secondary CPU abi for this package. This value is regenerated at every - * boot scan. + * The secondary CPU abi for this package. */ String secondaryCpuAbiString; diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java new file mode 100644 index 000000000000..158cfc946b73 --- /dev/null +++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java @@ -0,0 +1,158 @@ +/* + * 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.pm; + +import android.content.pm.PackageParser; +import android.os.Process; +import android.os.Trace; +import android.util.DisplayMetrics; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; + +/** + * Helper class for parallel parsing of packages using {@link PackageParser}. + * <p>Parsing requests are processed by a thread-pool of {@link #MAX_THREADS}. + * At any time, at most {@link #QUEUE_CAPACITY} results are kept in RAM</p> + */ +class ParallelPackageParser implements AutoCloseable { + + private static final int QUEUE_CAPACITY = 10; + private static final int MAX_THREADS = 4; + + private final String[] mSeparateProcesses; + private final boolean mOnlyCore; + private final DisplayMetrics mMetrics; + private volatile String mInterruptedInThread; + + private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); + + private final ExecutorService mService = Executors.newFixedThreadPool(MAX_THREADS, + new ThreadFactory() { + private final AtomicInteger threadNum = new AtomicInteger(0); + + @Override + public Thread newThread(final Runnable r) { + return new Thread("package-parsing-thread" + threadNum.incrementAndGet()) { + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); + r.run(); + } + }; + } + }); + + ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps, + DisplayMetrics metrics) { + mSeparateProcesses = separateProcesses; + mOnlyCore = onlyCoreApps; + mMetrics = metrics; + } + + static class ParseResult { + + PackageParser.Package pkg; // Parsed package + File scanFile; // File that was parsed + Throwable throwable; // Set if an error occurs during parsing + + @Override + public String toString() { + return "ParseResult{" + + "pkg=" + pkg + + ", scanFile=" + scanFile + + ", throwable=" + throwable + + '}'; + } + } + + /** + * Take the parsed package from the parsing queue, waiting if necessary until the element + * appears in the queue. + * @return parsed package + */ + public ParseResult take() { + try { + if (mInterruptedInThread != null) { + throw new InterruptedException("Interrupted in " + mInterruptedInThread); + } + return mQueue.take(); + } catch (InterruptedException e) { + // We cannot recover from interrupt here + Thread.currentThread().interrupt(); + throw new IllegalStateException(e); + } + } + + /** + * Submits the file for parsing + * @param scanFile file to scan + * @param parseFlags parse falgs + */ + public void submit(File scanFile, int parseFlags) { + mService.submit(() -> { + ParseResult pr = new ParseResult(); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]"); + try { + PackageParser pp = new PackageParser(); + pp.setSeparateProcesses(mSeparateProcesses); + pp.setOnlyCoreApps(mOnlyCore); + pp.setDisplayMetrics(mMetrics); + pr.scanFile = scanFile; + pr.pkg = parsePackage(pp, scanFile, parseFlags); + } catch (Throwable e) { + pr.throwable = e; + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + try { + mQueue.put(pr); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + // Propagate result to callers of take(). + // This is helpful to prevent main thread from getting stuck waiting on + // ParallelPackageParser to finish in case of interruption + mInterruptedInThread = Thread.currentThread().getName(); + } + }); + } + + @VisibleForTesting + protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile, + int parseFlags) throws PackageParser.PackageParserException { + return packageParser.parsePackage(scanFile, parseFlags); + } + + @Override + public void close() { + List<Runnable> unfinishedTasks = mService.shutdownNow(); + if (!unfinishedTasks.isEmpty()) { + throw new IllegalStateException("Not all tasks finished before calling close: " + + unfinishedTasks); + } + } +} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index ccdda1328a8d..f48db05adbd0 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -216,6 +216,7 @@ import android.view.animation.AnimationUtils; import com.android.internal.R; import com.android.internal.logging.MetricsLogger; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; import com.android.internal.policy.PhoneWindow; import com.android.internal.statusbar.IStatusBarService; @@ -6631,12 +6632,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public void dismissKeyguardLw() { + public void dismissKeyguardLw(IKeyguardDismissCallback callback) { if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) { if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.dismissKeyguardLw"); // ask the keyguard to prompt the user to authenticate if necessary - mKeyguardDelegate.dismiss(true /* allowWhileOccluded */); + mKeyguardDelegate.dismiss(callback); + } else if (callback != null) { + try { + callback.onDismissError(); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call callback", e); + } } } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index f37f98786fed..1b4eaf548809 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -15,6 +15,7 @@ import android.util.Log; import android.util.Slog; import android.view.WindowManagerPolicy.OnKeyguardExitResult; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; @@ -244,9 +245,9 @@ public class KeyguardServiceDelegate { mKeyguardState.occluded = isOccluded; } - public void dismiss(boolean allowWhileOccluded) { + public void dismiss(IKeyguardDismissCallback callback) { if (mKeyguardService != null) { - mKeyguardService.dismiss(allowWhileOccluded); + mKeyguardService.dismiss(callback); } } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index c4a0dd364d76..0b839b84e677 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -22,6 +22,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; @@ -73,9 +74,9 @@ public class KeyguardServiceWrapper implements IKeyguardService { } @Override // Binder interface - public void dismiss(boolean allowWhileOccluded) { + public void dismiss(IKeyguardDismissCallback callback) { try { - mService.dismiss(allowWhileOccluded); + mService.dismiss(callback); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index cd966ef323b7..841e2a158c4f 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -94,9 +94,6 @@ public final class ShutdownThread extends Thread { public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode"; public static final String RO_SAFEMODE_PROPERTY = "ro.sys.safemode"; - // Indicates whether we should stay in safe mode until ro.build.date.utc is newer than this - public static final String AUDIT_SAFEMODE_PROPERTY = "persist.sys.audit_safemode"; - // static instance of this thread private static final ShutdownThread sInstance = new ShutdownThread(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 00d8fba09a58..f51b6a021707 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -17,6 +17,7 @@ package com.android.server.wm; import android.Manifest; +import android.Manifest.permission; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.NonNull; @@ -25,9 +26,6 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.IActivityManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -128,6 +126,7 @@ import android.view.inputmethod.InputMethodManagerInternal; import com.android.internal.R; import com.android.internal.app.IAssistScreenshotReceiver; import com.android.internal.os.IResultReceiver; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; import com.android.internal.util.FastPrintWriter; import com.android.internal.view.IInputContext; @@ -331,8 +330,6 @@ public class WindowManagerService extends IWindowManager.Stub private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f; - private static final String PROPERTY_BUILD_DATE_UTC = "ro.build.date.utc"; - // Enums for animation scale update types. @Retention(RetentionPolicy.SOURCE) @IntDef({WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE, ANIMATION_DURATION_SCALE}) @@ -3940,20 +3937,13 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void dismissKeyguard() { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires DISABLE_KEYGUARD permission"); - } + public void dismissKeyguard(IKeyguardDismissCallback callback) { + checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard"); synchronized(mWindowMap) { - mPolicy.dismissKeyguardLw(); + mPolicy.dismissKeyguardLw(callback); } } - @Override - public void keyguardGoingAway(int flags) { - } - public void onKeyguardOccludedChanged(boolean occluded) { synchronized (mWindowMap) { mPolicy.onKeyguardOccludedChangedLw(occluded); @@ -5974,35 +5964,6 @@ public class WindowManagerService extends IWindowManager.Stub mFocusedApp.mTask.mStack : null; } - private void showAuditSafeModeNotification() { - PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, - new Intent(Intent.ACTION_VIEW, - Uri.parse("https://support.google.com/nexus/answer/2852139")), 0); - - String title = mContext.getString(R.string.audit_safemode_notification); - - Notification notification = new Notification.Builder(mContext) - .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning) - .setWhen(0) - .setOngoing(true) - .setTicker(title) - .setLocalOnly(true) - .setPriority(Notification.PRIORITY_HIGH) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)) - .setContentTitle(title) - .setContentText(mContext.getString(R.string.audit_safemode_notification_details)) - .setContentIntent(pendingIntent) - .build(); - - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - notificationManager.notifyAsUser(null, R.string.audit_safemode_notification, notification, - UserHandle.ALL); - } - public boolean detectSafeMode() { if (!mInputMonitor.waitForInputDevicesReady( INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) { @@ -6030,23 +5991,8 @@ public class WindowManagerService extends IWindowManager.Stub try { if (SystemProperties.getInt(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, 0) != 0 || SystemProperties.getInt(ShutdownThread.RO_SAFEMODE_PROPERTY, 0) != 0) { - int auditSafeMode = SystemProperties.getInt(ShutdownThread.AUDIT_SAFEMODE_PROPERTY, 0); - - if (auditSafeMode == 0) { - mSafeMode = true; - SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, ""); - } else { - // stay in safe mode until we have updated to a newer build - int buildDate = SystemProperties.getInt(PROPERTY_BUILD_DATE_UTC, 0); - - if (auditSafeMode >= buildDate) { - mSafeMode = true; - showAuditSafeModeNotification(); - } else { - SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, ""); - SystemProperties.set(ShutdownThread.AUDIT_SAFEMODE_PROPERTY, ""); - } - } + mSafeMode = true; + SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, ""); } } catch (IllegalArgumentException e) { } diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index 4d43e8e9c368..ac0e622f9664 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -46,6 +46,7 @@ LOCAL_C_INCLUDES += \ LOCAL_SHARED_LIBRARIES += \ libandroid_runtime \ libandroidfw \ + libbase \ libappfuse \ libbinder \ libcutils \ diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c32ee3a042d6..034b0cd0ea5d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -213,6 +213,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String TAG_ADMIN_BROADCAST_PENDING = "admin-broadcast-pending"; + private static final String ATTR_ID = "id"; + private static final String ATTR_VALUE = "value"; private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle"; @@ -544,8 +546,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { new MonitoringCertNotificationTask().execute(userId); } if (Intent.ACTION_USER_ADDED.equals(action)) { + sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_ADDED, userHandle); disableDeviceOwnerManagedSingleUserFeaturesIfNeeded(); } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_REMOVED, userHandle); disableDeviceOwnerManagedSingleUserFeaturesIfNeeded(); removeUserData(userHandle); } else if (Intent.ACTION_USER_STARTED.equals(action)) { @@ -568,6 +572,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { clearWipeProfileNotification(); } } + + private void sendUserAddedOrRemovedCommand(String action, int userHandle) { + synchronized (DevicePolicyManagerService.this) { + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + if (deviceOwner != null) { + Bundle extras = new Bundle(); + extras.putParcelable(Intent.EXTRA_USER, UserHandle.of(userHandle)); + sendAdminCommandLocked(deviceOwner, action, extras, null); + } + } + } }; static class ActiveAdmin { @@ -2367,7 +2382,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { for (String id : policy.mAffiliationIds) { out.startTag(null, TAG_AFFILIATION_ID); - out.attribute(null, "id", id); + out.attribute(null, ATTR_ID, id); out.endTag(null, TAG_AFFILIATION_ID); } @@ -2547,7 +2562,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if (DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML.equals(tag)) { policy.doNotAskCredentialsOnBoot = true; } else if (TAG_AFFILIATION_ID.equals(tag)) { - policy.mAffiliationIds.add(parser.getAttributeValue(null, "id")); + policy.mAffiliationIds.add(parser.getAttributeValue(null, ATTR_ID)); } else if (TAG_LAST_SECURITY_LOG_RETRIEVAL.equals(tag)) { policy.mLastSecurityLogRetrievalTime = Long.parseLong( parser.getAttributeValue(null, ATTR_VALUE)); @@ -8051,13 +8066,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void setLockTaskPackages(ComponentName who, String[] packages) throws SecurityException { Preconditions.checkNotNull(who, "ComponentName is null"); + synchronized (this) { - ActiveAdmin deviceOwner = getActiveAdminWithPolicyForUidLocked( - who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, mInjector.binderGetCallingUid()); - ActiveAdmin profileOwner = getActiveAdminWithPolicyForUidLocked( - who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid()); - if (deviceOwner != null || (profileOwner != null && isAffiliatedUser())) { - int userHandle = mInjector.userHandleGetCallingUserId(); + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + final int userHandle = mInjector.userHandleGetCallingUserId(); + if (isUserAffiliatedWithDevice(userHandle)) { setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages))); } else { throw new SecurityException("Admin " + who + @@ -9150,9 +9163,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setAffiliationIds(ComponentName admin, List<String> ids) { + if (!mHasFeature) { + return; + } + + Preconditions.checkNotNull(admin); + Preconditions.checkCollectionElementsNotNull(ids, "ids"); + final Set<String> affiliationIds = new ArraySet<String>(ids); - final int callingUserId = mInjector.userHandleGetCallingUserId(); + Preconditions.checkArgument( + !affiliationIds.contains(""), "ids must not contain empty strings"); + final int callingUserId = mInjector.userHandleGetCallingUserId(); synchronized (this) { getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); getUserData(callingUserId).mAffiliationIds = affiliationIds; @@ -9167,20 +9189,44 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public List<String> getAffiliationIds(ComponentName admin) { + if (!mHasFeature) { + return Collections.emptyList(); + } + + Preconditions.checkNotNull(admin); + synchronized (this) { + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + return new ArrayList<String>( + getUserData(mInjector.userHandleGetCallingUserId()).mAffiliationIds); + } + } + + @Override public boolean isAffiliatedUser() { - final int callingUserId = mInjector.userHandleGetCallingUserId(); + return isUserAffiliatedWithDevice(mInjector.userHandleGetCallingUserId()); + } + private boolean isUserAffiliatedWithDevice(int userId) { synchronized (this) { - if (mOwners.getDeviceOwnerUserId() == callingUserId) { - // The user that the DO is installed on is always affiliated. + if (!mOwners.hasDeviceOwner()) { + return false; + } + if (userId == mOwners.getDeviceOwnerUserId()) { + // The user that the DO is installed on is always affiliated with the device. + return true; + } + if (userId == UserHandle.USER_SYSTEM) { + // The system user is always affiliated in a DO device, even if the DO is set on a + // different user. This could be the case if the DO is set in the primary user + // of a split user device. return true; } - final ComponentName profileOwner = getProfileOwner(callingUserId); - if (profileOwner == null - || !profileOwner.getPackageName().equals(mOwners.getDeviceOwnerPackageName())) { + final ComponentName profileOwner = getProfileOwner(userId); + if (profileOwner == null) { return false; } - final Set<String> userAffiliationIds = getUserData(callingUserId).mAffiliationIds; + final Set<String> userAffiliationIds = getUserData(userId).mAffiliationIds; final Set<String> deviceAffiliationIds = getUserData(UserHandle.USER_SYSTEM).mAffiliationIds; for (String id : userAffiliationIds) { diff --git a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java index 7a3ee7f4c068..4f6a35c8cf1d 100644 --- a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java @@ -37,6 +37,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -65,7 +66,7 @@ public class SnoozeHelperTest { } @Test - public void testSnooze() throws Exception { + public void testSnoozeForTime() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM, 1000); verify(mAm, times(1)).setExactAndAllowWhileIdle( @@ -75,6 +76,16 @@ public class SnoozeHelperTest { } @Test + public void testSnooze() throws Exception { + NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); + mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM); + verify(mAm, never()).setExactAndAllowWhileIdle( + anyInt(), anyLong(), any(PendingIntent.class)); + assertTrue(mSnoozeHelper.isSnoozed( + UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + } + + @Test public void testCancelByApp() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); @@ -152,7 +163,7 @@ public class SnoozeHelperTest { mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM, 1000); NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL); mSnoozeHelper.snooze(r2 , UserHandle.USER_ALL, 1000); - mSnoozeHelper.repost(r.sbn.getPackageName(), r.getKey(), UserHandle.USER_SYSTEM); + mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM); verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r); } @@ -165,7 +176,7 @@ public class SnoozeHelperTest { mSnoozeHelper.update(UserHandle.USER_SYSTEM, r); verify(mCallback, never()).repost(anyInt(), any(NotificationRecord.class)); - mSnoozeHelper.repost(r.sbn.getPackageName(), r.getKey(), UserHandle.USER_SYSTEM); + mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM); verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index cff5b416f03b..2d21c0815c7d 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -50,6 +50,7 @@ import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -1643,6 +1644,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { /** * Test for: * {@link DevicePolicyManager#setAffiliationIds} + * {@link DevicePolicyManager#getAffiliationIds} * {@link DevicePolicyManager#isAffiliatedUser} */ public void testUserAffiliation() throws Exception { @@ -1659,30 +1661,34 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.setActiveAdmin(admin1, /* replace =*/ false); assertTrue(dpm.setDeviceOwner(admin1, "owner-name")); assertTrue(dpm.isAffiliatedUser()); + assertTrue(dpm.getAffiliationIds(admin1).isEmpty()); - // Install a profile owner whose package name matches the device owner on a test user. Check - // that the test user is unaffiliated. + // Install a profile owner. Check that the test user is unaffiliated. mContext.binder.callingUid = DpmMockContext.CALLER_UID; setAsProfileOwner(admin2); assertFalse(dpm.isAffiliatedUser()); + assertTrue(dpm.getAffiliationIds(admin2).isEmpty()); // Have the profile owner specify a set of affiliation ids. Check that the test user remains // unaffiliated. - final Set<String> userAffiliationIds = new ArraySet<>(); + final List<String> userAffiliationIds = new ArrayList<>(); userAffiliationIds.add("red"); userAffiliationIds.add("green"); userAffiliationIds.add("blue"); dpm.setAffiliationIds(admin2, userAffiliationIds); + MoreAsserts.assertContentsInAnyOrder(dpm.getAffiliationIds(admin2), "red", "green", "blue"); assertFalse(dpm.isAffiliatedUser()); // Have the device owner specify a set of affiliation ids that do not intersect with those // specified by the profile owner. Check that the test user remains unaffiliated. - final Set<String> deviceAffiliationIds = new ArraySet<>(); + final List<String> deviceAffiliationIds = new ArrayList<>(); deviceAffiliationIds.add("cyan"); deviceAffiliationIds.add("yellow"); deviceAffiliationIds.add("magenta"); mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; dpm.setAffiliationIds(admin1, deviceAffiliationIds); + MoreAsserts.assertContentsInAnyOrder( + dpm.getAffiliationIds(admin1), "cyan", "yellow", "magenta"); mContext.binder.callingUid = DpmMockContext.CALLER_UID; assertFalse(dpm.isAffiliatedUser()); @@ -1690,19 +1696,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { // specified by the device owner. Check that the test user becomes affiliated. userAffiliationIds.add("yellow"); dpm.setAffiliationIds(admin2, userAffiliationIds); + MoreAsserts.assertContentsInAnyOrder( + dpm.getAffiliationIds(admin2), "red", "green", "blue", "yellow"); assertTrue(dpm.isAffiliatedUser()); - // Change the profile owner to one whose package name does not match the device owner. Check - // that the test user is not affiliated anymore. - dpm.clearProfileOwner(admin2); - final ComponentName admin = new ComponentName("test", "test"); - - setUpPackageManagerForFakeAdmin(admin, DpmMockContext.CALLER_UID, - /* enabledSetting =*/ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, - /* appTargetSdk = */ null, admin2); - - dpm.setActiveAdmin(admin, /* refreshing =*/ true, DpmMockContext.CALLER_USER_HANDLE); - assertTrue(dpm.setProfileOwner(admin, "owner-name", DpmMockContext.CALLER_USER_HANDLE)); + // Clear affiliation ids for the profile owner. The user becomes unaffiliated. + dpm.setAffiliationIds(admin2, Collections.emptyList()); + assertTrue(dpm.getAffiliationIds(admin2).isEmpty()); assertFalse(dpm.isAffiliatedUser()); // Check that the system user remains affiliated. diff --git a/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java new file mode 100644 index 000000000000..d165b8b2685c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java @@ -0,0 +1,82 @@ +/* + * 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.pm; + +import android.content.pm.PackageParser; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +/** + * Tests for {@link ParallelPackageParser} + */ +@RunWith(AndroidJUnit4.class) +public class ParallelPackageParserTest { + private static final String TAG = ParallelPackageParserTest.class.getSimpleName(); + + private ParallelPackageParser mParser; + + @Before + public void setUp() { + mParser = new TestParallelPackageParser(); + } + + @Test(timeout = 1000) + public void test() { + Set<File> submittedFiles = new HashSet<>(); + int fileCount = 15; + for (int i = 0; i < fileCount; i++) { + File file = new File("f" + i); + mParser.submit(file, 0); + submittedFiles.add(file); + Log.d(TAG, "submitting " + file); + } + for (int i = 0; i < fileCount; i++) { + ParallelPackageParser.ParseResult result = mParser.take(); + Assert.assertNotNull(result); + File parsedFile = result.scanFile; + Log.d(TAG, "took " + parsedFile); + Assert.assertNotNull(parsedFile); + boolean removeSuccessful = submittedFiles.remove(parsedFile); + Assert.assertTrue("Unexpected file " + parsedFile + ". Expected submitted files: " + + submittedFiles, removeSuccessful); + } + } + + class TestParallelPackageParser extends ParallelPackageParser { + + TestParallelPackageParser() { + super(null, false, null); + } + + @Override + protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile, + int parseFlags) throws PackageParser.PackageParserException { + // Do not actually parse the package for testing + return null; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java index ed4c79f91ba2..1853a653ed1a 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -16,9 +16,11 @@ package com.android.server.wm; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; import com.android.server.input.InputManagerService; +import android.annotation.Nullable; import android.content.Context; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -528,8 +530,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void dismissKeyguardLw() { - + public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback) { } @Override diff --git a/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java new file mode 100644 index 000000000000..df858067ba04 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java @@ -0,0 +1,727 @@ +/* + * 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 android.graphics; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.GcSnapshot; +import com.android.layoutlib.bridge.impl.PorterDuffUtility; +import com.android.ninepatch.NinePatchChunk; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.text.TextUtils; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; + +public class BaseCanvas_Delegate { + // ---- delegate manager ---- + protected static DelegateManager<BaseCanvas_Delegate> sManager = + new DelegateManager<>(BaseCanvas_Delegate.class); + + // ---- delegate helper data ---- + private final static boolean[] sBoolOut = new boolean[1]; + + + // ---- delegate data ---- + protected Bitmap_Delegate mBitmap; + protected GcSnapshot mSnapshot; + + // ---- Public Helper methods ---- + + protected BaseCanvas_Delegate(Bitmap_Delegate bitmap) { + mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap); + } + + protected BaseCanvas_Delegate() { + mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/); + } + + /** + * Disposes of the {@link Graphics2D} stack. + */ + protected void dispose() { + mSnapshot.dispose(); + } + + /** + * Returns the current {@link Graphics2D} used to draw. + */ + public GcSnapshot getSnapshot() { + return mSnapshot; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top, + long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + BufferedImage image = bitmapDelegate.getImage(); + float right = left + image.getWidth(); + float bottom = top + image.getHeight(); + + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + 0, 0, image.getWidth(), image.getHeight(), + (int)left, (int)top, (int)right, (int)bottom); + } + + @LayoutlibDelegate + /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft, float srcTop, + float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, + float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, (int) srcLeft, (int) srcTop, + (int) srcRight, (int) srcBottom, (int) dstLeft, (int) dstTop, (int) dstRight, + (int) dstBottom); + } + + @LayoutlibDelegate + /*package*/ static void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride, + final float x, final float y, int width, int height, boolean hasAlpha, + long nativePaintOrZero) { + // create a temp BufferedImage containing the content. + final BufferedImage image = new BufferedImage(width, height, + hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + image.setRGB(0, 0, width, height, colors, offset, stride); + + draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paint) -> { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + graphics.drawImage(image, (int) x, (int) y, null); + }); + } + + @LayoutlibDelegate + /*package*/ static void nDrawColor(long nativeCanvas, final int color, final int mode) { + // get the delegate from the native int. + BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + final int w = canvasDelegate.mBitmap.getImage().getWidth(); + final int h = canvasDelegate.mBitmap.getImage().getHeight(); + draw(nativeCanvas, (graphics, paint) -> { + // reset its transform just in case + graphics.setTransform(new AffineTransform()); + + // set the color + graphics.setColor(new java.awt.Color(color, true /*alpha*/)); + + Composite composite = PorterDuffUtility.getComposite( + PorterDuffUtility.getPorterDuffMode(mode), 0xFF); + if (composite != null) { + graphics.setComposite(composite); + } + + graphics.fillRect(0, 0, w, h); + }); + } + + @LayoutlibDelegate + /*package*/ static void nDrawPaint(long nativeCanvas, long paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPaint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawPoint(long nativeCanvas, float x, float y, + long nativePaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawPoints(long nativeCanvas, float[] pts, int offset, int count, + long nativePaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawLine(long nativeCanvas, + final float startX, final float startY, final float stopX, final float stopY, + long paint) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY)); + } + + @LayoutlibDelegate + /*package*/ static void nDrawLines(long nativeCanvas, + final float[] pts, final int offset, final int count, + long nativePaint) { + draw(nativeCanvas, nativePaint, false /*compositeOnly*/, + false /*forceSrcMode*/, (graphics, paintDelegate) -> { + for (int i = 0; i < count; i += 4) { + graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1], + (int) pts[i + offset + 2], (int) pts[i + offset + 3]); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void nDrawRect(long nativeCanvas, + final float left, final float top, final float right, final float bottom, long paint) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + int style = paintDelegate.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillRect((int)left, (int)top, + (int)(right-left), (int)(bottom-top)); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawRect((int)left, (int)top, + (int)(right-left), (int)(bottom-top)); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void nDrawOval(long nativeCanvas, final float left, + final float top, final float right, final float bottom, long paint) { + if (right > left && bottom > top) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + int style = paintDelegate.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillOval((int)left, (int)top, + (int)(right - left), (int)(bottom - top)); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawOval((int)left, (int)top, + (int)(right - left), (int)(bottom - top)); + } + }); + } + } + + @LayoutlibDelegate + /*package*/ static void nDrawCircle(long nativeCanvas, + float cx, float cy, float radius, long paint) { + nDrawOval(nativeCanvas, + cx - radius, cy - radius, cx + radius, cy + radius, + paint); + } + + @LayoutlibDelegate + /*package*/ static void nDrawArc(long nativeCanvas, + final float left, final float top, final float right, final float bottom, + final float startAngle, final float sweep, + final boolean useCenter, long paint) { + if (right > left && bottom > top) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + int style = paintDelegate.getStyle(); + + Arc2D.Float arc = new Arc2D.Float( + left, top, right - left, bottom - top, + -startAngle, -sweep, + useCenter ? Arc2D.PIE : Arc2D.OPEN); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fill(arc); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.draw(arc); + } + }); + } + } + + @LayoutlibDelegate + /*package*/ static void nDrawRoundRect(long nativeCanvas, + final float left, final float top, final float right, final float bottom, + final float rx, final float ry, long paint) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + int style = paintDelegate.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillRoundRect( + (int)left, (int)top, + (int)(right - left), (int)(bottom - top), + 2 * (int)rx, 2 * (int)ry); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawRoundRect( + (int)left, (int)top, + (int)(right - left), (int)(bottom - top), + 2 * (int)rx, 2 * (int)ry); + } + }); + } + + @LayoutlibDelegate + public static void nDrawPath(long nativeCanvas, long path, long paint) { + final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path); + if (pathDelegate == null) { + return; + } + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + Shape shape = pathDelegate.getJavaShape(); + Rectangle2D bounds = shape.getBounds2D(); + if (bounds.isEmpty()) { + // Apple JRE 1.6 doesn't like drawing empty shapes. + // http://b.android.com/178278 + + if (pathDelegate.isEmpty()) { + // This means that the path doesn't have any lines or curves so + // nothing to draw. + return; + } + + // The stroke width is not consider for the size of the bounds so, + // for example, a horizontal line, would be considered as an empty + // rectangle. + // If the strokeWidth is not 0, we use it to consider the size of the + // path as well. + float strokeWidth = paintDelegate.getStrokeWidth(); + if (strokeWidth <= 0.0f) { + return; + } + bounds.setRect(bounds.getX(), bounds.getY(), + Math.max(strokeWidth, bounds.getWidth()), + Math.max(strokeWidth, bounds.getHeight())); + } + + int style = paintDelegate.getStyle(); + + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fill(shape); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.draw(shape); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void nDrawRegion(long nativeCanvas, long nativeRegion, + long nativePaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Some canvas paths may not be drawn", null, null); + } + + @LayoutlibDelegate + /*package*/ static void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch, + final float dstLeft, final float dstTop, final float dstRight, final float dstBottom, + long nativePaintOrZero, final int screenDensity, final int bitmapDensity) { + + // get the delegate from the native int. + final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap); + if (bitmapDelegate == null) { + return; + } + + byte[] c = NinePatch_Delegate.getChunk(ninePatch); + if (c == null) { + // not a 9-patch? + BufferedImage image = bitmapDelegate.getImage(); + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(), + image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight, + (int) dstBottom); + return; + } + + final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c); + assert chunkObject != null; + if (chunkObject == null) { + return; + } + + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + // this one can be null + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); + + canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { + @Override + public void draw(Graphics2D graphics, Paint_Delegate paint) { + chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop, + (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity, + bitmapDensity); + } + }, paintDelegate, true, false); + + } + + @LayoutlibDelegate + /*package*/ static void nDrawBitmapMatrix(long nCanvas, Bitmap bitmap, + long nMatrix, long nPaint) { + // get the delegate from the native int. + BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + // get the delegate from the native int, which can be null + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); + + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut); + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); + if (matrixDelegate == null) { + return; + } + + final AffineTransform mtx = matrixDelegate.getAffineTransform(); + + canvasDelegate.getSnapshot().draw((graphics, paint) -> { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + //FIXME add support for canvas, screen and bitmap densities. + graphics.drawImage(image, mtx, null); + }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawBitmapMesh(long nCanvas, Bitmap bitmap, + int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, + int colorOffset, long nPaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawBitmapMesh is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawVertices(long nCanvas, int mode, int n, + float[] verts, int vertOffset, + float[] texs, int texOffset, + int[] colors, int colorOffset, + short[] indices, int indexOffset, + int indexCount, long nPaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawVertices is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawText(long nativeCanvas, char[] text, int index, int count, + float startX, float startY, int flags, long paint, long typeface) { + drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0, + paint, typeface); + } + + @LayoutlibDelegate + /*package*/ static void nDrawText(long nativeCanvas, String text, + int start, int end, float x, float y, final int flags, long paint, + long typeface) { + int count = end - start; + char[] buffer = TemporaryBuffer.obtain(count); + TextUtils.getChars(text, start, end, buffer, 0); + + nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface); + } + + @LayoutlibDelegate + /*package*/ static void nDrawTextRun(long nativeCanvas, String text, + int start, int end, int contextStart, int contextEnd, + float x, float y, boolean isRtl, long paint, long typeface) { + int count = end - start; + char[] buffer = TemporaryBuffer.obtain(count); + TextUtils.getChars(text, start, end, buffer, 0); + + drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface); + } + + @LayoutlibDelegate + /*package*/ static void nDrawTextRun(long nativeCanvas, char[] text, + int start, int count, int contextStart, int contextCount, + float x, float y, boolean isRtl, long paint, long typeface) { + drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface); + } + + @LayoutlibDelegate + /*package*/ static void nDrawTextOnPath(long nativeCanvas, + char[] text, int index, + int count, long path, + float hOffset, + float vOffset, int bidiFlags, + long paint, long typeface) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawTextOnPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nDrawTextOnPath(long nativeCanvas, + String text, long path, + float hOffset, + float vOffset, + int bidiFlags, long paint, + long typeface) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawTextOnPath is not supported.", null, null /*data*/); + } + + // ---- Private delegate/helper methods ---- + + /** + * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. + * <p>Note that the drawable may actually be executed several times if there are + * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. + */ + private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode, + GcSnapshot.Drawable drawable) { + // get the delegate from the native int. + BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + // get the paint which can be null if nPaint is 0; + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); + + canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode); + } + + /** + * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided + * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. + * <p>Note that the drawable may actually be executed several times if there are + * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. + */ + private static void draw(long nCanvas, GcSnapshot.Drawable drawable) { + // get the delegate from the native int. + BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.mSnapshot.draw(drawable); + } + + private static void drawText(long nativeCanvas, final char[] text, final int index, + final int count, final float startX, final float startY, final boolean isRtl, + long paint, final long typeface) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + (graphics, paintDelegate) -> { + // WARNING: the logic in this method is similar to Paint_Delegate.measureText. + // Any change to this method should be reflected in Paint.measureText + + // assert that the typeface passed is actually the one stored in paint. + assert (typeface == paintDelegate.mNativeTypeface); + + // Paint.TextAlign indicates how the text is positioned relative to X. + // LEFT is the default and there's nothing to do. + float x = startX; + int limit = index + count; + if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { + RectF bounds = + paintDelegate.measureText(text, index, count, null, 0, isRtl); + float m = bounds.right - bounds.left; + if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { + x -= m / 2; + } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { + x -= m; + } + } + + new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, + startY).renderText(index, limit, isRtl, null, 0, true); + }); + } + + private static void drawBitmap(long nativeCanvas, Bitmap_Delegate bitmap, + long nativePaintOrZero, final int sleft, final int stop, final int sright, + final int sbottom, final int dleft, final int dtop, final int dright, + final int dbottom) { + // get the delegate from the native int. + BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + // get the paint, which could be null if the int is 0 + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); + + final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut); + + draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0], + (graphics, paint) -> { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + //FIXME add support for canvas, screen and bitmap densities. + graphics.drawImage(image, dleft, dtop, dright, dbottom, sleft, stop, sright, + sbottom, null); + }); + } + + /** + * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate. + * The image returns, through a 1-size boolean array, whether the drawing code should + * use a SRC composite no matter what the paint says. + * + * @param bitmap the bitmap + * @param paint the paint that will be used to draw + * @param forceSrcMode whether the composite will have to be SRC + * @return the image to draw + */ + private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint, + boolean[] forceSrcMode) { + BufferedImage image = bitmap.getImage(); + forceSrcMode[0] = false; + + // if the bitmap config is alpha_8, then we erase all color value from it + // before drawing it. + if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) { + fixAlpha8Bitmap(image); + } else if (!bitmap.hasAlpha()) { + // hasAlpha is merely a rendering hint. There can in fact be alpha values + // in the bitmap but it should be ignored at drawing time. + // There is two ways to do this: + // - override the composite to be SRC. This can only be used if the composite + // was going to be SRC or SRC_OVER in the first place + // - Create a different bitmap to draw in which all the alpha channel values is set + // to 0xFF. + if (paint != null) { + PorterDuff.Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode()); + + forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || mode == PorterDuff.Mode.SRC; + } + + // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB + if (!forceSrcMode[0]) { + image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF); + } + } + + return image; + } + + private static void fixAlpha8Bitmap(final BufferedImage image) { + int w = image.getWidth(); + int h = image.getHeight(); + int[] argb = new int[w * h]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); + + final int length = argb.length; + for (int i = 0 ; i < length; i++) { + argb[i] &= 0xFF000000; + } + image.setRGB(0, 0, w, h, argb, 0, w); + } + + protected int save(int saveFlags) { + // get the current save count + int count = mSnapshot.size(); + + mSnapshot = mSnapshot.save(saveFlags); + + // return the old save count + return count; + } + + protected int saveLayerAlpha(RectF rect, int alpha, int saveFlags) { + Paint_Delegate paint = new Paint_Delegate(); + paint.setAlpha(alpha); + return saveLayer(rect, paint, saveFlags); + } + + protected int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) { + // get the current save count + int count = mSnapshot.size(); + + mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags); + + // return the old save count + return count; + } + + /** + * Restores the {@link GcSnapshot} to <var>saveCount</var> + * @param saveCount the saveCount + */ + protected void restoreTo(int saveCount) { + mSnapshot = mSnapshot.restoreTo(saveCount); + } + + /** + * Restores the top {@link GcSnapshot} + */ + protected void restore() { + mSnapshot = mSnapshot.restore(); + } + + protected boolean clipRect(float left, float top, float right, float bottom, int regionOp) { + return mSnapshot.clipRect(left, top, right, bottom, regionOp); + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index 94152cd73ca2..43a0ff5a23dc 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -55,40 +55,27 @@ import libcore.util.NativeAllocationRegistry_Delegate; * @see DelegateManager * */ -public final class Canvas_Delegate { +public final class Canvas_Delegate extends BaseCanvas_Delegate { // ---- delegate manager ---- - private static final DelegateManager<Canvas_Delegate> sManager = - new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class); private static long sFinalizer = -1; - - // ---- delegate helper data ---- - - private final static boolean[] sBoolOut = new boolean[1]; - - - // ---- delegate data ---- - private Bitmap_Delegate mBitmap; - private GcSnapshot mSnapshot; - private DrawFilter_Delegate mDrawFilter = null; - // ---- Public Helper methods ---- /** * Returns the native delegate associated to a given {@link Canvas} object. */ public static Canvas_Delegate getDelegate(Canvas canvas) { - return sManager.getDelegate(canvas.getNativeCanvasWrapper()); + return (Canvas_Delegate) sManager.getDelegate(canvas.getNativeCanvasWrapper()); } /** * Returns the native delegate associated to a given an int referencing a {@link Canvas} object. */ public static Canvas_Delegate getDelegate(long native_canvas) { - return sManager.getDelegate(native_canvas); + return (Canvas_Delegate) sManager.getDelegate(native_canvas); } /** @@ -143,7 +130,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static void nSetBitmap(long canvas, Bitmap bitmap) { - Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas); Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); if (canvasDelegate == null || bitmapDelegate==null) { return; @@ -155,7 +142,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static boolean nIsOpaque(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return false; } @@ -169,7 +156,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static int nGetWidth(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -180,7 +167,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static int nGetHeight(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -191,7 +178,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static int nSave(long nativeCanvas, int saveFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -204,7 +191,7 @@ public final class Canvas_Delegate { float t, float r, float b, long paint, int layerFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -223,7 +210,7 @@ public final class Canvas_Delegate { float t, float r, float b, int alpha, int layerFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -235,7 +222,7 @@ public final class Canvas_Delegate { public static void nRestore(long nativeCanvas, boolean throwOnUnderflow) { // FIXME: implement throwOnUnderflow. // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -248,7 +235,7 @@ public final class Canvas_Delegate { boolean throwOnUnderflow) { // FIXME: implement throwOnUnderflow. // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -259,7 +246,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static int nGetSaveCount(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -270,7 +257,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static void nTranslate(long nativeCanvas, float dx, float dy) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -281,7 +268,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static void nScale(long nativeCanvas, float sx, float sy) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -292,7 +279,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static void nRotate(long nativeCanvas, float degrees) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -303,7 +290,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static void nSkew(long nativeCanvas, float kx, float ky) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -327,7 +314,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static void nConcat(long nCanvas, long nMatrix) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas); if (canvasDelegate == null) { return; } @@ -355,7 +342,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static void nSetMatrix(long nCanvas, long nMatrix) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas); if (canvasDelegate == null) { return; } @@ -388,7 +375,7 @@ public final class Canvas_Delegate { float right, float bottom, int regionOp) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas); if (canvasDelegate == null) { return false; } @@ -400,7 +387,7 @@ public final class Canvas_Delegate { public static boolean nClipPath(long nativeCanvas, long nativePath, int regionOp) { - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return true; } @@ -417,7 +404,7 @@ public final class Canvas_Delegate { public static boolean nClipRegion(long nativeCanvas, long nativeRegion, int regionOp) { - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return true; } @@ -432,7 +419,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static void nSetDrawFilter(long nativeCanvas, long nativeFilter) { - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } @@ -449,7 +436,7 @@ public final class Canvas_Delegate { public static boolean nGetClipBounds(long nativeCanvas, Rect bounds) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { return false; } @@ -469,7 +456,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate public static void nGetCTM(long canvas, long matrix) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas); + Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas); if (canvasDelegate == null) { return; } @@ -498,509 +485,11 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - public static void nDrawColor(long nativeCanvas, final int color, final int mode) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); - if (canvasDelegate == null) { - return; - } - - final int w = canvasDelegate.mBitmap.getImage().getWidth(); - final int h = canvasDelegate.mBitmap.getImage().getHeight(); - draw(nativeCanvas, new GcSnapshot.Drawable() { - - @Override - public void draw(Graphics2D graphics, Paint_Delegate paint) { - // reset its transform just in case - graphics.setTransform(new AffineTransform()); - - // set the color - graphics.setColor(new Color(color, true /*alpha*/)); - - Composite composite = PorterDuffUtility.getComposite( - PorterDuffUtility.getPorterDuffMode(mode), 0xFF); - if (composite != null) { - graphics.setComposite(composite); - } - - graphics.fillRect(0, 0, w, h); - } - }); - } - - @LayoutlibDelegate - public static void nDrawPaint(long nativeCanvas, long paint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPaint is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void nDrawPoint(long nativeCanvas, float x, float y, - long nativePaint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPoint is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void nDrawPoints(long nativeCanvas, float[] pts, int offset, int count, - long nativePaint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPoint is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void nDrawLine(long nativeCanvas, - final float startX, final float startY, final float stopX, final float stopY, - long paint) { - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY); - } - }); - } - - @LayoutlibDelegate - public static void nDrawLines(long nativeCanvas, - final float[] pts, final int offset, final int count, - long nativePaint) { - draw(nativeCanvas, nativePaint, false /*compositeOnly*/, - false /*forceSrcMode*/, new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - for (int i = 0; i < count; i += 4) { - graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1], - (int) pts[i + offset + 2], (int) pts[i + offset + 3]); - } - } - }); - } - - @LayoutlibDelegate - public static void nDrawRect(long nativeCanvas, - final float left, final float top, final float right, final float bottom, long paint) { - - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - int style = paintDelegate.getStyle(); - - // draw - if (style == Paint.Style.FILL.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fillRect((int)left, (int)top, - (int)(right-left), (int)(bottom-top)); - } - - if (style == Paint.Style.STROKE.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.drawRect((int)left, (int)top, - (int)(right-left), (int)(bottom-top)); - } - } - }); - } - - @LayoutlibDelegate - public static void nDrawOval(long nativeCanvas, final float left, - final float top, final float right, final float bottom, long paint) { - if (right > left && bottom > top) { - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - int style = paintDelegate.getStyle(); - - // draw - if (style == Paint.Style.FILL.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fillOval((int)left, (int)top, - (int)(right - left), (int)(bottom - top)); - } - - if (style == Paint.Style.STROKE.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.drawOval((int)left, (int)top, - (int)(right - left), (int)(bottom - top)); - } - } - }); - } - } - - @LayoutlibDelegate - public static void nDrawCircle(long nativeCanvas, - float cx, float cy, float radius, long paint) { - nDrawOval(nativeCanvas, - cx - radius, cy - radius, cx + radius, cy + radius, - paint); - } - - @LayoutlibDelegate - public static void nDrawArc(long nativeCanvas, - final float left, final float top, final float right, final float bottom, - final float startAngle, final float sweep, - final boolean useCenter, long paint) { - if (right > left && bottom > top) { - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - int style = paintDelegate.getStyle(); - - Arc2D.Float arc = new Arc2D.Float( - left, top, right - left, bottom - top, - -startAngle, -sweep, - useCenter ? Arc2D.PIE : Arc2D.OPEN); - - // draw - if (style == Paint.Style.FILL.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fill(arc); - } - - if (style == Paint.Style.STROKE.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.draw(arc); - } - } - }); - } - } - - @LayoutlibDelegate - public static void nDrawRoundRect(long nativeCanvas, - final float left, final float top, final float right, final float bottom, - final float rx, final float ry, long paint) { - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - int style = paintDelegate.getStyle(); - - // draw - if (style == Paint.Style.FILL.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fillRoundRect( - (int)left, (int)top, - (int)(right - left), (int)(bottom - top), - 2 * (int)rx, 2 * (int)ry); - } - - if (style == Paint.Style.STROKE.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.drawRoundRect( - (int)left, (int)top, - (int)(right - left), (int)(bottom - top), - 2 * (int)rx, 2 * (int)ry); - } - } - }); - } - - @LayoutlibDelegate - public static void nDrawPath(long nativeCanvas, long path, long paint) { - final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path); - if (pathDelegate == null) { - return; - } - - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - Shape shape = pathDelegate.getJavaShape(); - Rectangle2D bounds = shape.getBounds2D(); - if (bounds.isEmpty()) { - // Apple JRE 1.6 doesn't like drawing empty shapes. - // http://b.android.com/178278 - - if (pathDelegate.isEmpty()) { - // This means that the path doesn't have any lines or curves so - // nothing to draw. - return; - } - - // The stroke width is not consider for the size of the bounds so, - // for example, a horizontal line, would be considered as an empty - // rectangle. - // If the strokeWidth is not 0, we use it to consider the size of the - // path as well. - float strokeWidth = paintDelegate.getStrokeWidth(); - if (strokeWidth <= 0.0f) { - return; - } - bounds.setRect(bounds.getX(), bounds.getY(), - Math.max(strokeWidth, bounds.getWidth()), - Math.max(strokeWidth, bounds.getHeight())); - } - - int style = paintDelegate.getStyle(); - - if (style == Paint.Style.FILL.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fill(shape); - } - - if (style == Paint.Style.STROKE.nativeInt || - style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.draw(shape); - } - } - }); - } - - @LayoutlibDelegate - public static void nDrawRegion(long nativeCanvas, long nativeRegion, - long nativePaint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Some canvas paths may not be drawn", null, null); - } - - @LayoutlibDelegate - public static void nDrawNinePatch(Canvas thisCanvas, long nativeCanvas, - long nativeBitmap, long ninePatch, final float dstLeft, final float dstTop, - final float dstRight, final float dstBottom, long nativePaintOrZero, - final int screenDensity, final int bitmapDensity) { - - // get the delegate from the native int. - final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap); - if (bitmapDelegate == null) { - return; - } - - byte[] c = NinePatch_Delegate.getChunk(ninePatch); - if (c == null) { - // not a 9-patch? - BufferedImage image = bitmapDelegate.getImage(); - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(), - image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight, - (int) dstBottom); - return; - } - - final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c); - assert chunkObject != null; - if (chunkObject == null) { - return; - } - - Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); - if (canvasDelegate == null) { - return; - } - - // this one can be null - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); - - canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paint) { - chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop, - (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity, - bitmapDensity); - } - }, paintDelegate, true, false); - - } - - @LayoutlibDelegate - public static void nDrawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap, - float left, float top, - long nativePaintOrZero, - int canvasDensity, - int screenDensity, - int bitmapDensity) { - // get the delegate from the native int. - Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); - if (bitmapDelegate == null) { - return; - } - - BufferedImage image = bitmapDelegate.getImage(); - float right = left + image.getWidth(); - float bottom = top + image.getHeight(); - - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - 0, 0, image.getWidth(), image.getHeight(), - (int)left, (int)top, (int)right, (int)bottom); - } - - @LayoutlibDelegate - public static void nDrawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap, - float srcLeft, float srcTop, float srcRight, float srcBottom, - float dstLeft, float dstTop, float dstRight, float dstBottom, - long nativePaintOrZero, int screenDensity, int bitmapDensity) { - // get the delegate from the native int. - Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); - if (bitmapDelegate == null) { - return; - } - - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - (int)srcLeft, (int)srcTop, (int)srcRight, (int)srcBottom, - (int)dstLeft, (int)dstTop, (int)dstRight, (int)dstBottom); - } - - @LayoutlibDelegate - public static void nDrawBitmap(long nativeCanvas, int[] colors, - int offset, int stride, final float x, - final float y, int width, int height, - boolean hasAlpha, - long nativePaintOrZero) { - // create a temp BufferedImage containing the content. - final BufferedImage image = new BufferedImage(width, height, - hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); - image.setRGB(0, 0, width, height, colors, offset, stride); - - draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paint) { - if (paint != null && paint.isFilterBitmap()) { - graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BILINEAR); - } - - graphics.drawImage(image, (int) x, (int) y, null); - } - }); - } - - @LayoutlibDelegate - public static void nativeDrawBitmapMatrix(long nCanvas, Bitmap bitmap, - long nMatrix, long nPaint) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); - if (canvasDelegate == null) { - return; - } - - // get the delegate from the native int, which can be null - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); - - // get the delegate from the native int. - Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); - if (bitmapDelegate == null) { - return; - } - - final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut); - - Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); - if (matrixDelegate == null) { - return; - } - - final AffineTransform mtx = matrixDelegate.getAffineTransform(); - - canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paint) { - if (paint != null && paint.isFilterBitmap()) { - graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BILINEAR); - } - - //FIXME add support for canvas, screen and bitmap densities. - graphics.drawImage(image, mtx, null); - } - }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/); - } - - @LayoutlibDelegate - public static void nativeDrawBitmapMesh(long nCanvas, Bitmap bitmap, - int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, - int colorOffset, long nPaint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawBitmapMesh is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void nativeDrawVertices(long nCanvas, int mode, int n, - float[] verts, int vertOffset, - float[] texs, int texOffset, - int[] colors, int colorOffset, - short[] indices, int indexOffset, - int indexCount, long nPaint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawVertices is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void nDrawText(long nativeCanvas, char[] text, int index, int count, - float startX, float startY, int flags, long paint, long typeface) { - drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0, - paint, typeface); - } - - @LayoutlibDelegate - public static void nDrawText(long nativeCanvas, String text, - int start, int end, float x, float y, final int flags, long paint, - long typeface) { - int count = end - start; - char[] buffer = TemporaryBuffer.obtain(count); - TextUtils.getChars(text, start, end, buffer, 0); - - nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface); - } - - @LayoutlibDelegate - public static void nDrawTextRun(long nativeCanvas, String text, - int start, int end, int contextStart, int contextEnd, - float x, float y, boolean isRtl, long paint, long typeface) { - int count = end - start; - char[] buffer = TemporaryBuffer.obtain(count); - TextUtils.getChars(text, start, end, buffer, 0); - - drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface); - } - - @LayoutlibDelegate - public static void nDrawTextRun(long nativeCanvas, char[] text, - int start, int count, int contextStart, int contextCount, - float x, float y, boolean isRtl, long paint, long typeface) { - drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface); - } - - @LayoutlibDelegate - public static void nDrawTextOnPath(long nativeCanvas, - char[] text, int index, - int count, long path, - float hOffset, - float vOffset, int bidiFlags, - long paint, long typeface) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawTextOnPath is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - public static void nDrawTextOnPath(long nativeCanvas, - String text, long path, - float hOffset, - float vOffset, - int bidiFlags, long paint, - long typeface) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawTextOnPath is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - /*package*/ static long getNativeFinalizer() { + /*package*/ static long nGetNativeFinalizer() { synchronized (Canvas_Delegate.class) { if (sFinalizer == -1) { sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> { - Canvas_Delegate delegate = sManager.getDelegate(nativePtr); + Canvas_Delegate delegate = Canvas_Delegate.getDelegate(nativePtr); if (delegate != null) { delegate.dispose(); } @@ -1011,230 +500,12 @@ public final class Canvas_Delegate { return sFinalizer; } - // ---- Private delegate/helper methods ---- - - /** - * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. - * <p>Note that the drawable may actually be executed several times if there are - * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. - */ - private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode, - GcSnapshot.Drawable drawable) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); - if (canvasDelegate == null) { - return; - } - - // get the paint which can be null if nPaint is 0; - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); - - canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode); - } - - /** - * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided - * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. - * <p>Note that the drawable may actually be executed several times if there are - * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. - */ - private static void draw(long nCanvas, GcSnapshot.Drawable drawable) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); - if (canvasDelegate == null) { - return; - } - - canvasDelegate.mSnapshot.draw(drawable); - } - - private static void drawText(long nativeCanvas, final char[] text, final int index, - final int count, final float startX, final float startY, final boolean isRtl, - long paint, final long typeface) { - - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - // WARNING: the logic in this method is similar to Paint_Delegate.measureText. - // Any change to this method should be reflected in Paint.measureText - - // assert that the typeface passed is actually the one stored in paint. - assert (typeface == paintDelegate.mNativeTypeface); - - // Paint.TextAlign indicates how the text is positioned relative to X. - // LEFT is the default and there's nothing to do. - float x = startX; - int limit = index + count; - if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { - RectF bounds = paintDelegate.measureText(text, index, count, null, 0, - isRtl); - float m = bounds.right - bounds.left; - if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { - x -= m / 2; - } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { - x -= m; - } - } - - new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, startY) - .renderText(index, limit, isRtl, null, 0, true); - } - }); - } - private Canvas_Delegate(Bitmap_Delegate bitmap) { - mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap); + super(bitmap); } private Canvas_Delegate() { - mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/); - } - - /** - * Disposes of the {@link Graphics2D} stack. - */ - private void dispose() { - mSnapshot.dispose(); - } - - private int save(int saveFlags) { - // get the current save count - int count = mSnapshot.size(); - - mSnapshot = mSnapshot.save(saveFlags); - - // return the old save count - return count; - } - - private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) { - Paint_Delegate paint = new Paint_Delegate(); - paint.setAlpha(alpha); - return saveLayer(rect, paint, saveFlags); - } - - private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) { - // get the current save count - int count = mSnapshot.size(); - - mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags); - - // return the old save count - return count; - } - - /** - * Restores the {@link GcSnapshot} to <var>saveCount</var> - * @param saveCount the saveCount - */ - private void restoreTo(int saveCount) { - mSnapshot = mSnapshot.restoreTo(saveCount); - } - - /** - * Restores the top {@link GcSnapshot} - */ - private void restore() { - mSnapshot = mSnapshot.restore(); - } - - private boolean clipRect(float left, float top, float right, float bottom, int regionOp) { - return mSnapshot.clipRect(left, top, right, bottom, regionOp); - } - - private static void drawBitmap( - long nativeCanvas, - Bitmap_Delegate bitmap, - long nativePaintOrZero, - final int sleft, final int stop, final int sright, final int sbottom, - final int dleft, final int dtop, final int dright, final int dbottom) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); - if (canvasDelegate == null) { - return; - } - - // get the paint, which could be null if the int is 0 - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); - - final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut); - - draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0], - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paint) { - if (paint != null && paint.isFilterBitmap()) { - graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BILINEAR); - } - - //FIXME add support for canvas, screen and bitmap densities. - graphics.drawImage(image, dleft, dtop, dright, dbottom, - sleft, stop, sright, sbottom, null); - } - }); - } - - - /** - * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate. - * The image returns, through a 1-size boolean array, whether the drawing code should - * use a SRC composite no matter what the paint says. - * - * @param bitmap the bitmap - * @param paint the paint that will be used to draw - * @param forceSrcMode whether the composite will have to be SRC - * @return the image to draw - */ - private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint, - boolean[] forceSrcMode) { - BufferedImage image = bitmap.getImage(); - forceSrcMode[0] = false; - - // if the bitmap config is alpha_8, then we erase all color value from it - // before drawing it. - if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) { - fixAlpha8Bitmap(image); - } else if (!bitmap.hasAlpha()) { - // hasAlpha is merely a rendering hint. There can in fact be alpha values - // in the bitmap but it should be ignored at drawing time. - // There is two ways to do this: - // - override the composite to be SRC. This can only be used if the composite - // was going to be SRC or SRC_OVER in the first place - // - Create a different bitmap to draw in which all the alpha channel values is set - // to 0xFF. - if (paint != null) { - Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); - if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) { - PorterDuff.Mode mode = - ((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode(); - - forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || - mode == PorterDuff.Mode.SRC; - } - } - - // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB - if (!forceSrcMode[0]) { - image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF); - } - } - - return image; - } - - private static void fixAlpha8Bitmap(final BufferedImage image) { - int w = image.getWidth(); - int h = image.getHeight(); - int[] argb = new int[w * h]; - image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); - - final int length = argb.length; - for (int i = 0 ; i < length; i++) { - argb[i] &= 0xFF000000; - } - image.setRGB(0, 0, w, h, argb, 0, w); + super(); } } diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java index 59ddcc6a6ca8..a459734e805f 100644 --- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java @@ -63,16 +63,8 @@ public class ComposeShader_Delegate extends Shader_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static long nativeCreate1(long native_shaderA, long native_shaderB, - long native_mode) { - // FIXME not supported yet. - ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(); - return sManager.addNewDelegate(newDelegate); - } - - @LayoutlibDelegate - /*package*/ static long nativeCreate2(long native_shaderA, long native_shaderB, - int porterDuffMode) { + /*package*/ static long nativeCreate(long native_shaderA, long native_shaderB, + int native_mode) { // FIXME not supported yet. ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(); return sManager.addNewDelegate(newDelegate); diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index 0bbe33dc58df..e68d8b3e9f93 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -90,10 +90,11 @@ public class Paint_Delegate { private int mHintingMode = Paint.HINTING_ON; private int mHyphenEdit; private float mLetterSpacing; // not used in actual text rendering. + private float mWordSpacing; // not used in actual text rendering. // Variant of the font. A paint's variant can only be compact or elegant. private FontVariant mFontVariant = FontVariant.COMPACT; - private Xfermode_Delegate mXfermode; + private int mPorterDuffMode = Xfermode.DEFAULT; private ColorFilter_Delegate mColorFilter; private Shader_Delegate mShader; private PathEffect_Delegate mPathEffect; @@ -206,12 +207,10 @@ public class Paint_Delegate { } /** - * Returns the {@link Xfermode} delegate or null if none have been set - * - * @return the delegate or null. + * Returns the {@link PorterDuff.Mode} as an int */ - public Xfermode_Delegate getXfermode() { - return mXfermode; + public int getPorterDuffMode() { + return mPorterDuffMode; } /** @@ -841,16 +840,12 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long nSetXfermode(long native_object, long xfermode) { - // get the delegate from the native int. + /*package*/ static void nSetXfermode(long native_object, int xfermode) { Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { - return xfermode; + return; } - - delegate.mXfermode = Xfermode_Delegate.getDelegate(xfermode); - - return xfermode; + delegate.mPorterDuffMode = xfermode; } @LayoutlibDelegate @@ -998,7 +993,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int nGetTextRunCursor(long native_object, char[] text, + /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, char[] text, int contextStart, int contextLength, int flags, int offset, int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1007,7 +1002,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int nGetTextRunCursor(long native_object, String text, + /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, String text, int contextStart, int contextEnd, int flags, int offset, int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1086,6 +1081,26 @@ public class Paint_Delegate { } @LayoutlibDelegate + /*package*/ static float nGetWordSpacing(long nativePaint) { + Paint_Delegate delegate = sManager.getDelegate(nativePaint); + if (delegate == null) { + return 0; + } + return delegate.mWordSpacing; + } + + @LayoutlibDelegate + /*package*/ static void nSetWordSpacing(long nativePaint, float wordSpacing) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING, + "Paint.setWordSpacing() not supported.", null, null); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); + if (delegate == null) { + return; + } + delegate.mWordSpacing = wordSpacing; + } + + @LayoutlibDelegate /*package*/ static void nSetFontFeatureSettings(long nativePaint, String settings) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING, "Paint.setFontFeatureSettings() not supported.", null, null); @@ -1215,7 +1230,7 @@ public class Paint_Delegate { mStrokeWidth = paint.mStrokeWidth; mStrokeMiter = paint.mStrokeMiter; - mXfermode = paint.mXfermode; + mPorterDuffMode = paint.mPorterDuffMode; mColorFilter = paint.mColorFilter; mShader = paint.mShader; mPathEffect = paint.mPathEffect; @@ -1242,7 +1257,7 @@ public class Paint_Delegate { mTextSize = 20.f; mTextScaleX = 1.f; mTextSkewX = 0.f; - mXfermode = null; + mPorterDuffMode = Xfermode.DEFAULT; mColorFilter = null; mShader = null; mPathEffect = null; diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java deleted file mode 100644 index 8825f84995c8..000000000000 --- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2010 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.graphics; - -import com.android.layoutlib.bridge.impl.DelegateManager; -import com.android.layoutlib.bridge.impl.PorterDuffUtility; -import com.android.tools.layoutlib.annotations.LayoutlibDelegate; - -import android.graphics.PorterDuff.Mode; - -import java.awt.Composite; - -import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode; - -/** - * Delegate implementing the native methods of android.graphics.PorterDuffXfermode - * - * Through the layoutlib_create tool, the original native methods of PorterDuffXfermode have been - * replaced by calls to methods of the same name in this delegate class. - * - * This class behaves like the original native implementation, but in Java, keeping previously - * native data into its own objects and mapping them to int that are sent back and forth between - * it and the original PorterDuffXfermode class. - * - * Because this extends {@link Xfermode_Delegate}, there's no need to use a - * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by - * {@link Xfermode_Delegate}. - * - */ -public class PorterDuffXfermode_Delegate extends Xfermode_Delegate { - - // ---- delegate data ---- - - private final Mode mMode; - - // ---- Public Helper methods ---- - - public Mode getMode() { - return mMode; - } - - @Override - public Composite getComposite(int alpha) { - return PorterDuffUtility.getComposite(mMode, alpha); - } - - @Override - public boolean isSupported() { - return true; - } - - @Override - public String getSupportMessage() { - // no message since isSupported returns true; - return null; - } - - - // ---- native methods ---- - - @LayoutlibDelegate - /*package*/ static long nativeCreateXfermode(int mode) { - PorterDuffXfermode_Delegate newDelegate = new PorterDuffXfermode_Delegate(mode); - return sManager.addNewDelegate(newDelegate); - } - - // ---- Private delegate/helper methods ---- - - private PorterDuffXfermode_Delegate(int mode) { - mMode = getPorterDuffMode(mode); - } - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java deleted file mode 100644 index 94a6d76fe8dc..000000000000 --- a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2010 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.graphics; - -import com.android.layoutlib.bridge.impl.DelegateManager; -import com.android.tools.layoutlib.annotations.LayoutlibDelegate; - -import java.awt.Composite; - -/** - * Delegate implementing the native methods of android.graphics.Xfermode - * - * Through the layoutlib_create tool, the original native methods of Xfermode have been replaced - * by calls to methods of the same name in this delegate class. - * - * This class behaves like the original native implementation, but in Java, keeping previously - * native data into its own objects and mapping them to int that are sent back and forth between - * it and the original Xfermode class. - * - * This also serve as a base class for all Xfermode delegate classes. - * - * @see DelegateManager - * - */ -public abstract class Xfermode_Delegate { - - // ---- delegate manager ---- - protected static final DelegateManager<Xfermode_Delegate> sManager = - new DelegateManager<Xfermode_Delegate>(Xfermode_Delegate.class); - - // ---- delegate helper data ---- - - // ---- delegate data ---- - - // ---- Public Helper methods ---- - - public static Xfermode_Delegate getDelegate(long native_instance) { - return sManager.getDelegate(native_instance); - } - - public abstract Composite getComposite(int alpha); - public abstract boolean isSupported(); - public abstract String getSupportMessage(); - - - // ---- native methods ---- - - @LayoutlibDelegate - /*package*/ static void finalizer(long native_instance) { - sManager.removeJavaReferenceFor(native_instance); - } - - // ---- Private delegate/helper methods ---- - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java index 200fe3b1d192..ad2c5647eef2 100644 --- a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java @@ -58,8 +58,13 @@ public class AnimatedVectorDrawable_Delegate { } @LayoutlibDelegate + /*package*/ static void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr) { + // TODO: implement + } + @LayoutlibDelegate /*package*/ static void nAddAnimator(long setPtr, long propertyValuesHolder, - long nativeInterpolator, long startDelay, long duration, int repeatCount) { + long nativeInterpolator, long startDelay, long duration, int repeatCount, + int repeatMode) { PropertySetter holder = sHolders.getDelegate(propertyValuesHolder); if (holder == null || holder.getValues() == null) { return; @@ -72,6 +77,7 @@ public class AnimatedVectorDrawable_Delegate { animator.setStartDelay(startDelay); animator.setDuration(duration); animator.setRepeatCount(repeatCount); + animator.setRepeatMode(repeatMode); animator.setTarget(holder); animator.setPropertyName(holder.getValues().getPropertyName()); @@ -137,6 +143,14 @@ public class AnimatedVectorDrawable_Delegate { } @LayoutlibDelegate + /*package*/ static void nSetPropertyHolderData(long nativePtr, int[] data, int length) { + PropertySetter setter = sHolders.getDelegate(nativePtr); + assert setter != null; + + setter.setValues(data); + } + + @LayoutlibDelegate /*package*/ static void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) { AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr); assert animatorSet != null; diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java index 3d78931a152c..fc848d9af956 100644 --- a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java @@ -18,6 +18,7 @@ package android.graphics.drawable; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import android.graphics.Canvas; import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT; public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate { @@ -25,4 +26,9 @@ public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate { /*package*/ static boolean useLastSeenTarget(VectorDrawableAnimatorRT thisDrawableAnimator) { return true; } + + @LayoutlibDelegate + /*package*/ static void onDraw(VectorDrawableAnimatorRT thisDrawableAnimator, Canvas canvas) { + // Do not attempt to record as we are not using a DisplayListCanvas + } } diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java index 99042638102a..cee679a1d469 100644 --- a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.BaseCanvas_Delegate; import android.graphics.Canvas_Delegate; import android.graphics.Color; import android.graphics.Matrix; @@ -1197,7 +1198,7 @@ public class VectorDrawable_Delegate { fillPaintDelegate.setColorFilter(filterPtr); fillPaintDelegate.setShader(fullPath.mFillGradient); Path_Delegate.native_setFillType(mRenderPath.mNativePath, fullPath.mFillType); - Canvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, fillPaint + BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, fillPaint .getNativeInstance()); } @@ -1228,7 +1229,7 @@ public class VectorDrawable_Delegate { final float finalStrokeScale = minScale * matrixScale; strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale); strokePaintDelegate.setShader(fullPath.mStrokeGradient); - Canvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, strokePaint + BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, strokePaint .getNativeInstance()); } } diff --git a/tools/layoutlib/bridge/src/android/os/ServiceManager.java b/tools/layoutlib/bridge/src/android/os/ServiceManager.java index 3e1ec070c7d9..34c78455f85d 100644 --- a/tools/layoutlib/bridge/src/android/os/ServiceManager.java +++ b/tools/layoutlib/bridge/src/android/os/ServiceManager.java @@ -31,6 +31,13 @@ public final class ServiceManager { } /** + * Is not supposed to return null, but that is fine for layoutlib. + */ + public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException { + throw new ServiceNotFoundException(name); + } + + /** * Place a new @a service called @a name into the service * manager. * diff --git a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java index af0c4569b08b..d299add05eab 100644 --- a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java +++ b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java @@ -102,4 +102,9 @@ public class SystemProperties_Delegate { /*package*/ static void native_add_change_callback() { // pass. } + + @LayoutlibDelegate + /*package*/ static void native_report_sysprop_change() { + // pass. + } } diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 4596210b82d5..a0ded8745508 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -20,6 +20,7 @@ import android.graphics.Point; import android.graphics.Rect; import com.android.internal.app.IAssistScreenshotReceiver; import com.android.internal.os.IResultReceiver; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; @@ -516,11 +517,7 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void dismissKeyguard() { - } - - @Override - public void keyguardGoingAway(int flags) throws RemoteException { + public void dismissKeyguard(IKeyguardDismissCallback callback) throws RemoteException { } @Override diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java index 24f788766cc9..a801cb0606e0 100644 --- a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java @@ -21,6 +21,8 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.graphics.Matrix; +import libcore.util.NativeAllocationRegistry_Delegate; + /** * Delegate implementing the native methods of {@link RenderNode} * <p/> @@ -35,7 +37,7 @@ public class RenderNode_Delegate { // ---- delegate manager ---- private static final DelegateManager<RenderNode_Delegate> sManager = new DelegateManager<RenderNode_Delegate>(RenderNode_Delegate.class); - + private static long sFinalizer = -1; private float mLift; private float mTranslationX; @@ -62,8 +64,13 @@ public class RenderNode_Delegate { } @LayoutlibDelegate - /*package*/ static void nDestroyRenderNode(long renderNode) { - sManager.removeJavaReferenceFor(renderNode); + /*package*/ static long nGetNativeFinalizer() { + synchronized (RenderNode_Delegate.class) { + if (sFinalizer == -1) { + sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor); + } + } + return sFinalizer; } @LayoutlibDelegate diff --git a/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java b/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java new file mode 100644 index 000000000000..06874bd57d2b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java @@ -0,0 +1,342 @@ +/* + * 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 android.view.textservice; + +import com.android.internal.textservice.ISpellCheckerSessionListener; +import com.android.internal.textservice.ITextServicesManager; +import com.android.internal.textservice.ITextServicesSessionListener; + +import android.content.Context; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener; + +import java.util.Locale; + +/** + * System API to the overall text services, which arbitrates interaction between applications + * and text services. You can retrieve an instance of this interface with + * {@link Context#getSystemService(String) Context.getSystemService()}. + * + * The user can change the current text services in Settings. And also applications can specify + * the target text services. + * + * <h3>Architecture Overview</h3> + * + * <p>There are three primary parties involved in the text services + * framework (TSF) architecture:</p> + * + * <ul> + * <li> The <strong>text services manager</strong> as expressed by this class + * is the central point of the system that manages interaction between all + * other parts. It is expressed as the client-side API here which exists + * in each application context and communicates with a global system service + * that manages the interaction across all processes. + * <li> A <strong>text service</strong> implements a particular + * interaction model allowing the client application to retrieve information of text. + * The system binds to the current text service that is in use, causing it to be created and run. + * <li> Multiple <strong>client applications</strong> arbitrate with the text service + * manager for connections to text services. + * </ul> + * + * <h3>Text services sessions</h3> + * <ul> + * <li>The <strong>spell checker session</strong> is one of the text services. + * {@link android.view.textservice.SpellCheckerSession}</li> + * </ul> + * + */ +public final class TextServicesManager { + private static final String TAG = TextServicesManager.class.getSimpleName(); + private static final boolean DBG = false; + + private static TextServicesManager sInstance; + + private final ITextServicesManager mService; + + private TextServicesManager() { + mService = new FakeTextServicesManager(); + } + + /** + * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist. + * @hide + */ + public static TextServicesManager getInstance() { + synchronized (TextServicesManager.class) { + if (sInstance == null) { + sInstance = new TextServicesManager(); + } + return sInstance; + } + } + + /** + * Returns the language component of a given locale string. + */ + private static String parseLanguageFromLocaleString(String locale) { + final int idx = locale.indexOf('_'); + if (idx < 0) { + return locale; + } else { + return locale.substring(0, idx); + } + } + + /** + * Get a spell checker session for the specified spell checker + * @param locale the locale for the spell checker. If {@code locale} is null and + * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be + * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true, + * the locale specified in Settings will be returned only when it is same as {@code locale}. + * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is + * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be + * selected. + * @param listener a spell checker session lister for getting results from a spell checker. + * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled + * languages in settings will be returned. + * @return the spell checker session of the spell checker + */ + public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale, + SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) { + if (listener == null) { + throw new NullPointerException(); + } + if (!referToSpellCheckerLanguageSettings && locale == null) { + throw new IllegalArgumentException("Locale should not be null if you don't refer" + + " settings."); + } + + if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) { + return null; + } + + final SpellCheckerInfo sci; + try { + sci = mService.getCurrentSpellChecker(null); + } catch (RemoteException e) { + return null; + } + if (sci == null) { + return null; + } + SpellCheckerSubtype subtypeInUse = null; + if (referToSpellCheckerLanguageSettings) { + subtypeInUse = getCurrentSpellCheckerSubtype(true); + if (subtypeInUse == null) { + return null; + } + if (locale != null) { + final String subtypeLocale = subtypeInUse.getLocale(); + final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale); + if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) { + return null; + } + } + } else { + final String localeStr = locale.toString(); + for (int i = 0; i < sci.getSubtypeCount(); ++i) { + final SpellCheckerSubtype subtype = sci.getSubtypeAt(i); + final String tempSubtypeLocale = subtype.getLocale(); + final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale); + if (tempSubtypeLocale.equals(localeStr)) { + subtypeInUse = subtype; + break; + } else if (tempSubtypeLanguage.length() >= 2 && + locale.getLanguage().equals(tempSubtypeLanguage)) { + subtypeInUse = subtype; + } + } + } + if (subtypeInUse == null) { + return null; + } + final SpellCheckerSession session = new SpellCheckerSession( + sci, mService, listener, subtypeInUse); + try { + mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(), + session.getTextServicesSessionListener(), + session.getSpellCheckerSessionListener(), bundle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return session; + } + + /** + * @hide + */ + public SpellCheckerInfo[] getEnabledSpellCheckers() { + try { + final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers(); + if (DBG) { + Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null")); + } + return retval; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public SpellCheckerInfo getCurrentSpellChecker() { + try { + // Passing null as a locale for ICS + return mService.getCurrentSpellChecker(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public void setCurrentSpellChecker(SpellCheckerInfo sci) { + try { + if (sci == null) { + throw new NullPointerException("SpellCheckerInfo is null."); + } + mService.setCurrentSpellChecker(null, sci.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public SpellCheckerSubtype getCurrentSpellCheckerSubtype( + boolean allowImplicitlySelectedSubtype) { + try { + // Passing null as a locale until we support multiple enabled spell checker subtypes. + return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public void setSpellCheckerSubtype(SpellCheckerSubtype subtype) { + try { + final int hashCode; + if (subtype == null) { + hashCode = 0; + } else { + hashCode = subtype.hashCode(); + } + mService.setCurrentSpellCheckerSubtype(null, hashCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public void setSpellCheckerEnabled(boolean enabled) { + try { + mService.setSpellCheckerEnabled(enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public boolean isSpellCheckerEnabled() { + try { + return mService.isSpellCheckerEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static class FakeTextServicesManager implements ITextServicesManager { + + @Override + public void finishSpellCheckerService(ISpellCheckerSessionListener arg0) + throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1) + throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void getSpellCheckerService(String arg0, String arg1, + ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4) + throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public boolean isSpellCheckerEnabled() throws RemoteException { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public void setSpellCheckerEnabled(boolean arg0) throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public IBinder asBinder() { + // TODO Auto-generated method stub + return null; + } + + } +}
\ No newline at end of file diff --git a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java deleted file mode 100644 index 3017292d8337..000000000000 --- a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2011 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.textservice; - -import com.android.tools.layoutlib.annotations.LayoutlibDelegate; - -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.view.textservice.SpellCheckerInfo; -import android.view.textservice.SpellCheckerSubtype; - - -/** - * Delegate used to provide new implementation of a select few methods of - * {@link ITextServicesManager$Stub} - * - * Through the layoutlib_create tool, the original methods of Stub have been replaced - * by calls to methods of the same name in this delegate class. - * - */ -public class ITextServicesManager_Stub_Delegate { - - @LayoutlibDelegate - public static ITextServicesManager asInterface(IBinder obj) { - // ignore the obj and return a fake interface implementation - return new FakeTextServicesManager(); - } - - private static class FakeTextServicesManager implements ITextServicesManager { - - @Override - public void finishSpellCheckerService(ISpellCheckerSessionListener arg0) - throws RemoteException { - // TODO Auto-generated method stub - - } - - @Override - public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - @Override - public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1) - throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - @Override - public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - @Override - public void getSpellCheckerService(String arg0, String arg1, - ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4) - throws RemoteException { - // TODO Auto-generated method stub - - } - - @Override - public boolean isSpellCheckerEnabled() throws RemoteException { - // TODO Auto-generated method stub - return false; - } - - @Override - public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException { - // TODO Auto-generated method stub - - } - - @Override - public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException { - // TODO Auto-generated method stub - - } - - @Override - public void setSpellCheckerEnabled(boolean arg0) throws RemoteException { - // TODO Auto-generated method stub - - } - - @Override - public IBinder asBinder() { - // TODO Auto-generated method stub - return null; - } - - } - } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java index e80cbf27f232..7526e090be50 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java @@ -24,12 +24,13 @@ import android.graphics.Canvas; import android.graphics.ColorFilter_Delegate; import android.graphics.Paint; import android.graphics.Paint_Delegate; +import android.graphics.PorterDuff; +import android.graphics.PorterDuff.Mode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.graphics.Region_Delegate; import android.graphics.Shader_Delegate; -import android.graphics.Xfermode_Delegate; import java.awt.AlphaComposite; import java.awt.Color; @@ -827,28 +828,9 @@ public class GcSnapshot { g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f)); return; } - Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); - if (xfermodeDelegate != null) { - if (xfermodeDelegate.isSupported()) { - Composite composite = xfermodeDelegate.getComposite(alpha); - assert composite != null; - if (composite != null) { - g.setComposite(composite); - return; - } - } else { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE, - xfermodeDelegate.getSupportMessage(), - null /*throwable*/, null /*data*/); - } - } - // if there was no custom xfermode, but we have alpha (due to a shader and a non - // opaque alpha channel in the paint color), then we create an AlphaComposite anyway - // that will handle the alpha. - if (alpha != 0xFF) { - g.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, (float) alpha / 255.f)); - } + Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode()); + Composite composite = PorterDuffUtility.getComposite(mode, alpha); + g.setComposite(composite); } private void mapRect(AffineTransform matrix, RectF dst, RectF src) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java index 80d7c68bcf06..70e2eb17794a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java @@ -24,14 +24,12 @@ import android.graphics.BlendComposite.BlendingMode; import android.graphics.PorterDuff; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter_Delegate; -import android.graphics.PorterDuffXfermode_Delegate; import java.awt.AlphaComposite; import java.awt.Composite; /** - * Provides various utility methods for {@link PorterDuffColorFilter_Delegate} and {@link - * PorterDuffXfermode_Delegate}. + * Provides various utility methods for {@link PorterDuffColorFilter_Delegate}. */ public final class PorterDuffUtility { diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk index 565feb68e6b5..9ee416a3b4ca 100644 --- a/tools/layoutlib/bridge/tests/Android.mk +++ b/tools/layoutlib/bridge/tests/Android.mk @@ -30,7 +30,7 @@ LOCAL_JAVA_LIBRARIES := layoutlib \ layoutlib_api-prebuilt \ tools-common-prebuilt \ sdk-common \ - junit + junit-host include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index bf61f7efc4bd..a23bad6ed480 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -180,6 +180,7 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.BitmapFactory#finishDecode", "android.graphics.BitmapFactory#setDensityFromOptions", "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget", + "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#onDraw", "android.graphics.drawable.GradientDrawable#buildRing", "android.graphics.FontFamily#addFont", "android.graphics.Typeface#getSystemFontConfigLocation", @@ -206,7 +207,7 @@ public final class CreateInfo implements ICreateInfo { "android.view.MenuInflater#registerMenu", "android.view.RenderNode#getMatrix", "android.view.RenderNode#nCreate", - "android.view.RenderNode#nDestroyRenderNode", + "android.view.RenderNode#nGetNativeFinalizer", "android.view.RenderNode#nSetElevation", "android.view.RenderNode#nGetElevation", "android.view.RenderNode#nSetTranslationX", @@ -234,7 +235,6 @@ public final class CreateInfo implements ICreateInfo { "android.view.ViewGroup#drawChild", "com.android.internal.view.menu.MenuBuilder#createNewMenuItem", "com.android.internal.util.XmlUtils#convertValueToInt", - "com.android.internal.textservice.ITextServicesManager$Stub#asInterface", "dalvik.system.VMRuntime#newUnpaddedArray", "libcore.io.MemoryMappedFile#mmapRO", "libcore.io.MemoryMappedFile#close", @@ -247,6 +247,7 @@ public final class CreateInfo implements ICreateInfo { */ public final static String[] DELEGATE_CLASS_NATIVES = new String[] { "android.animation.PropertyValuesHolder", + "android.graphics.BaseCanvas", "android.graphics.Bitmap", "android.graphics.BitmapFactory", "android.graphics.BitmapShader", @@ -275,7 +276,6 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.PathEffect", "android.graphics.PathMeasure", "android.graphics.PorterDuffColorFilter", - "android.graphics.PorterDuffXfermode", "android.graphics.RadialGradient", "android.graphics.Rasterizer", "android.graphics.Region", @@ -283,7 +283,6 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.SumPathEffect", "android.graphics.SweepGradient", "android.graphics.Typeface", - "android.graphics.Xfermode", "android.graphics.drawable.AnimatedVectorDrawable", "android.graphics.drawable.VectorDrawable", "android.os.SystemClock", @@ -304,6 +303,7 @@ public final class CreateInfo implements ICreateInfo { private final static String[] RENAMED_CLASSES = new String[] { "android.os.ServiceManager", "android.os._Original_ServiceManager", + "android.view.textservice.TextServicesManager", "android.view.textservice._Original_TextServicesManager", "android.util.LruCache", "android.util._Original_LruCache", "android.view.SurfaceView", "android.view._Original_SurfaceView", "android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager", diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index 9bb91e599d77..4b6cf4354d91 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -124,6 +124,7 @@ public class Main { "android.annotation.NonNull", // annotations "android.annotation.Nullable", // annotations "com.android.internal.transition.EpicenterTranslateClipReveal", + "com.android.internal.graphics.drawable.AnimationScaleListDrawable", }, excludeClasses, new String[] { diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk index dafb9c6f9402..61e381d48b16 100644 --- a/tools/layoutlib/create/tests/Android.mk +++ b/tools/layoutlib/create/tests/Android.mk @@ -23,7 +23,7 @@ LOCAL_JAVA_RESOURCE_DIRS := data mock_data LOCAL_MODULE := layoutlib-create-tests LOCAL_MODULE_TAGS := optional -LOCAL_JAVA_LIBRARIES := layoutlib_create junit +LOCAL_JAVA_LIBRARIES := layoutlib_create junit-host LOCAL_STATIC_JAVA_LIBRARIES := asm-5.0 include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/tools/preload2/Android.mk b/tools/preload2/Android.mk index 09d95ffe3506..769db6bd3ead 100644 --- a/tools/preload2/Android.mk +++ b/tools/preload2/Android.mk @@ -12,7 +12,7 @@ LOCAL_STATIC_JAVA_LIBRARIES += perflib-prebuilt trove-prebuilt guavalib # For JDWP access we use the framework in the JDWP tests from Apache Harmony, for # convenience (and to not depend on internal JDK APIs). -LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit +LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit-host LOCAL_MODULE:= preload2 diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java index ca5b0e005a1d..cc54a8d9c715 100644 --- a/tools/preload2/src/com/android/preload/Main.java +++ b/tools/preload2/src/com/android/preload/Main.java @@ -32,7 +32,8 @@ import com.android.preload.actions.ShowDataAction; import com.android.preload.classdataretrieval.ClassDataRetriever; import com.android.preload.classdataretrieval.hprof.Hprof; import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever; -import com.android.preload.ui.UI; +import com.android.preload.ui.IUI; +import com.android.preload.ui.SwingUI; import java.util.ArrayList; import java.util.Collection; @@ -66,7 +67,7 @@ public class Main { private DumpTableModel dataTableModel; private DefaultListModel<Client> clientListModel; - private UI ui; + private IUI ui; // Actions that need to be updated once a device is selected. private Collection<DeviceSpecific> deviceSpecificActions; @@ -89,13 +90,15 @@ public class Main { * @param args */ public static void main(String[] args) { - Main m = new Main(); + Main m = new Main(new SwingUI()); top = m; m.startUp(); } - public Main() { + public Main(IUI ui) { + this.ui = ui; + clientListModel = new DefaultListModel<Client>(); dataTableModel = new DumpTableModel(); @@ -124,11 +127,10 @@ public class Main { } } - ui = new UI(clientListModel, dataTableModel, actions); - ui.setVisible(true); + ui.prepare(clientListModel, dataTableModel, actions); } - public static UI getUI() { + public static IUI getUI() { return top.ui; } @@ -176,6 +178,7 @@ public class Main { new ReloadListAction(clientUtils, getDevice(), clientListModel).run(); getUI().hideWaitDialog(); + getUI().ready(); } private void initDevice() { diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java index fbf83d2e2339..5787d8507230 100644 --- a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java +++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java @@ -16,6 +16,7 @@ package com.android.preload.actions; +import com.android.preload.Main; import java.awt.event.ActionEvent; import javax.swing.AbstractAction; @@ -28,7 +29,11 @@ public abstract class AbstractThreadedAction extends AbstractAction implements R @Override public void actionPerformed(ActionEvent e) { - new Thread(this).start(); + if (Main.getUI().isSingleThreaded()) { + run(); + } else { + new Thread(this).start(); + } } } diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java index b524716fc2cb..3a7f7f74d755 100644 --- a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java +++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java @@ -32,14 +32,13 @@ import java.util.TreeSet; import java.util.regex.Pattern; import javax.swing.AbstractAction; -import javax.swing.JFileChooser; /** * Compute an intersection of classes from the given data. A class is in the intersection if it * appears in at least the number of threshold given packages. An optional blacklist can be * used to filter classes from the intersection. */ -public class ComputeThresholdAction extends AbstractAction implements Runnable { +public class ComputeThresholdAction extends AbstractThreadedAction { protected int threshold; private Pattern blacklist; private DumpTableModel dataTableModel; @@ -72,7 +71,7 @@ public class ComputeThresholdAction extends AbstractAction implements Runnable { return; } - new Thread(this).start(); + super.actionPerformed(e); } @Override @@ -92,10 +91,8 @@ public class ComputeThresholdAction extends AbstractAction implements Runnable { boolean ret = Main.getUI().showConfirmDialog("Computed a set with " + result.size() + " classes, would you like to save to disk?", "Save?"); if (ret) { - JFileChooser jfc = new JFileChooser(); - int ret2 = jfc.showSaveDialog(Main.getUI()); - if (ret2 == JFileChooser.APPROVE_OPTION) { - File f = jfc.getSelectedFile(); + File f = Main.getUI().showSaveDialog(); + if (f != null) { saveSet(result, f); } } diff --git a/tools/preload2/src/com/android/preload/actions/ExportAction.java b/tools/preload2/src/com/android/preload/actions/ExportAction.java index cb8b3df75b18..848a56826788 100644 --- a/tools/preload2/src/com/android/preload/actions/ExportAction.java +++ b/tools/preload2/src/com/android/preload/actions/ExportAction.java @@ -19,14 +19,11 @@ package com.android.preload.actions; import com.android.preload.DumpDataIO; import com.android.preload.DumpTableModel; import com.android.preload.Main; - import java.awt.event.ActionEvent; import java.io.File; import java.io.PrintWriter; -import javax.swing.AbstractAction; - -public class ExportAction extends AbstractAction implements Runnable { +public class ExportAction extends AbstractThreadedAction { private File lastSaveFile; private DumpTableModel dataTableModel; @@ -39,7 +36,7 @@ public class ExportAction extends AbstractAction implements Runnable { public void actionPerformed(ActionEvent e) { lastSaveFile = Main.getUI().showSaveDialog(); if (lastSaveFile != null) { - new Thread(this).start(); + super.actionPerformed(e); } } diff --git a/tools/preload2/src/com/android/preload/actions/ImportAction.java b/tools/preload2/src/com/android/preload/actions/ImportAction.java index 5c1976580f94..bfeeb836fd45 100644 --- a/tools/preload2/src/com/android/preload/actions/ImportAction.java +++ b/tools/preload2/src/com/android/preload/actions/ImportAction.java @@ -27,7 +27,7 @@ import java.util.Collection; import javax.swing.AbstractAction; -public class ImportAction extends AbstractAction implements Runnable { +public class ImportAction extends AbstractThreadedAction { private File[] lastOpenFiles; private DumpTableModel dataTableModel; @@ -40,7 +40,7 @@ public class ImportAction extends AbstractAction implements Runnable { public void actionPerformed(ActionEvent e) { lastOpenFiles = Main.getUI().showOpenDialog(true); if (lastOpenFiles != null) { - new Thread(this).start(); + super.actionPerformed(e); } } diff --git a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java index 385e8577b1c8..29464fc7abdf 100644 --- a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java +++ b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java @@ -58,7 +58,12 @@ public class RunMonkeyAction extends AbstractAction implements DeviceSpecific { if (packages.isEmpty()) { packages = DEFAULT_MONKEY_PACKAGES; } - new Thread(new RunMonkeyRunnable(packages)).start(); + Runnable r = new RunMonkeyRunnable(packages); + if (Main.getUI().isSingleThreaded()) { + r.run(); + } else { + new Thread(r).start(); + } } private class RunMonkeyRunnable implements Runnable { diff --git a/tools/preload2/src/com/android/preload/ui/IUI.java b/tools/preload2/src/com/android/preload/ui/IUI.java new file mode 100644 index 000000000000..9371463e9a79 --- /dev/null +++ b/tools/preload2/src/com/android/preload/ui/IUI.java @@ -0,0 +1,45 @@ +package com.android.preload.ui; + +import com.android.ddmlib.Client; +import java.io.File; +import java.util.List; +import javax.swing.Action; +import javax.swing.ListModel; +import javax.swing.table.TableModel; + +/** + * UI abstraction for the tool. This allows a graphical mode, command line mode, + * or silent mode. + */ +public interface IUI { + + void prepare(ListModel<Client> clientListModel, TableModel dataTableModel, + List<Action> actions); + + void ready(); + + boolean isSingleThreaded(); + + Client getSelectedClient(); + + int getSelectedDataTableRow(); + + void showWaitDialog(); + + void updateWaitDialog(String s); + + void hideWaitDialog(); + + void showMessageDialog(String s); + + boolean showConfirmDialog(String title, String message); + + String showInputDialog(String message); + + <T> T showChoiceDialog(String title, String message, T[] choices); + + File showSaveDialog(); + + File[] showOpenDialog(boolean multi); + +} diff --git a/tools/preload2/src/com/android/preload/ui/UI.java b/tools/preload2/src/com/android/preload/ui/SwingUI.java index 47174ddd0e07..cab3744ad74c 100644 --- a/tools/preload2/src/com/android/preload/ui/UI.java +++ b/tools/preload2/src/com/android/preload/ui/SwingUI.java @@ -41,7 +41,7 @@ import javax.swing.ListModel; import javax.swing.SwingUtilities; import javax.swing.table.TableModel; -public class UI extends JFrame { +public class SwingUI extends JFrame implements IUI { private JList<Client> clientList; private JTable dataTable; @@ -49,11 +49,18 @@ public class UI extends JFrame { // Shared file chooser, means the directory is retained. private JFileChooser jfc; - public UI(ListModel<Client> clientListModel, - TableModel dataTableModel, - List<Action> actions) { + public SwingUI() { super("Preloaded-classes computation"); + } + + @Override + public boolean isSingleThreaded() { + return false; + } + @Override + public void prepare(ListModel<Client> clientListModel, TableModel dataTableModel, + List<Action> actions) { getContentPane().add(new JScrollPane(clientList = new JList<Client>(clientListModel)), BorderLayout.WEST); clientList.setCellRenderer(new ClientListCellRenderer()); @@ -74,18 +81,27 @@ public class UI extends JFrame { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(100, 100, 800, 600); + + setVisible(true); + } + + @Override + public void ready() { } + @Override public Client getSelectedClient() { return clientList.getSelectedValue(); } + @Override public int getSelectedDataTableRow() { return dataTable.getSelectedRow(); } private JDialog currentWaitDialog = null; + @Override public void showWaitDialog() { if (currentWaitDialog == null) { currentWaitDialog = new JDialog(this, "Please wait...", true); @@ -111,6 +127,7 @@ public class UI extends JFrame { }); } + @Override public void updateWaitDialog(String s) { if (currentWaitDialog != null) { ((JLabel) currentWaitDialog.getContentPane().getComponent(0)).setText(s); @@ -124,6 +141,7 @@ public class UI extends JFrame { } } + @Override public void hideWaitDialog() { if (currentWaitDialog != null) { currentWaitDialog.setVisible(false); @@ -131,6 +149,7 @@ public class UI extends JFrame { } } + @Override public void showMessageDialog(String s) { // Hide the wait dialog... if (currentWaitDialog != null) { @@ -147,6 +166,7 @@ public class UI extends JFrame { } } + @Override public boolean showConfirmDialog(String title, String message) { // Hide the wait dialog... if (currentWaitDialog != null) { @@ -164,6 +184,7 @@ public class UI extends JFrame { } } + @Override public String showInputDialog(String message) { // Hide the wait dialog... if (currentWaitDialog != null) { @@ -180,6 +201,7 @@ public class UI extends JFrame { } } + @Override @SuppressWarnings("unchecked") public <T> T showChoiceDialog(String title, String message, T[] choices) { // Hide the wait dialog... @@ -203,6 +225,7 @@ public class UI extends JFrame { } } + @Override public File showSaveDialog() { // Hide the wait dialog... if (currentWaitDialog != null) { @@ -228,6 +251,7 @@ public class UI extends JFrame { } } + @Override public File[] showOpenDialog(boolean multi) { // Hide the wait dialog... if (currentWaitDialog != null) { |