Merge "Create actions for permission and log revokations"
diff --git a/Android.mk b/Android.mk
index 552103d..7103f67 100644
--- a/Android.mk
+++ b/Android.mk
@@ -338,6 +338,7 @@
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 @@
-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 6bec747..6b140b3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4824,6 +4824,7 @@
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 @@
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);
}
@@ -6005,6 +6013,7 @@
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);
@@ -6080,6 +6089,7 @@
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);
@@ -6169,6 +6179,7 @@
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";
diff --git a/api/system-current.txt b/api/system-current.txt
index 1f3fd7d..a71c0d3 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4978,6 +4978,7 @@
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 @@
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);
}
@@ -6177,6 +6185,7 @@
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);
@@ -6263,6 +6272,7 @@
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);
@@ -6356,6 +6366,7 @@
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";
diff --git a/api/test-current.txt b/api/test-current.txt
index 4a4bf70..b44715b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4834,6 +4834,7 @@
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 @@
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);
}
@@ -6022,6 +6030,7 @@
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);
@@ -6100,6 +6109,7 @@
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);
@@ -6189,6 +6199,7 @@
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";
diff --git a/cmds/wm/src/com/android/commands/wm/Wm.java b/cmds/wm/src/com/android/commands/wm/Wm.java
index 84fb626..8defb33 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 @@
}
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 e222fee..abb098f 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -534,6 +534,12 @@
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 088fd08..3324448 100644
--- a/core/java/android/app/FragmentTransition.java
+++ b/core/java/android/app/FragmentTransition.java
@@ -367,9 +367,11 @@
}
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 @@
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 @@
}
/**
+ * 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 82be7ab..1a36d1a 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -31,6 +31,7 @@
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.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 @@
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/IInstrumentationWatcher.aidl b/core/java/android/app/IInstrumentationWatcher.aidl
index 405a3d8..6c8c4d6 100644
--- a/core/java/android/app/IInstrumentationWatcher.aidl
+++ b/core/java/android/app/IInstrumentationWatcher.aidl
@@ -21,7 +21,7 @@
import android.os.Bundle;
/** @hide */
-oneway interface IInstrumentationWatcher
+interface IInstrumentationWatcher
{
void instrumentationStatus(in ComponentName name, int resultCode,
in Bundle results);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 725cc29..036b47c 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 @@
* {@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 @@
}
/**
- * @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 @@
}
/**
+ * @deprecated Use {@link KeyguardDismissCallback}
* Callback passed to {@link KeyguardManager#exitKeyguardSecurely} to notify
* caller of result.
*/
+ @Deprecated
public interface OnKeyguardExitResult {
/**
@@ -202,19 +215,41 @@
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 @@
* @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 @@
* @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/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java
index e05feaf..c1646bb 100644
--- a/core/java/android/app/admin/ConnectEvent.java
+++ b/core/java/android/app/admin/ConnectEvent.java
@@ -75,6 +75,11 @@
};
@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/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e0ec1b1..866a551 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -79,7 +79,6 @@
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 @@
* <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 @@
"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 @@
}
/**
- * @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 @@
/**
* @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 0ec134a..63ea8db 100644
--- a/core/java/android/app/admin/DnsEvent.java
+++ b/core/java/android/app/admin/DnsEvent.java
@@ -95,6 +95,11 @@
};
@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 b7e0e92..3bc8cd0 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -299,6 +299,7 @@
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 ec7ed00..1dbff20 100644
--- a/core/java/android/app/admin/NetworkEvent.java
+++ b/core/java/android/app/admin/NetworkEvent.java
@@ -26,14 +26,14 @@
*/
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 @@
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 cdf7013..aa109de 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.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 @@
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 e027a2b3..971e14c 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl
@@ -17,14 +17,17 @@
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/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index bccb822..bd1ad8e 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -20,6 +20,7 @@
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 @@
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 c46acae..0da710a 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -28,6 +28,8 @@
import android.widget.ImageView;
import android.widget.RemoteViews;
+import com.android.internal.widget.CachingIconView;
+
import java.util.ArrayList;
/**
@@ -45,7 +47,7 @@
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 @@
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 @@
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 2971280..b8408dd 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -18,6 +18,7 @@
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 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 82379c4..3171019 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.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 @@
/**
* 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 0000000..635c504
--- /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 788103d..a019ea1 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 @@
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 b0d45e1d1..be10608df 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -1190,6 +1190,26 @@
}
/**
+ * 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 @@
}
/**
+ * 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 293b77b..20230cd 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -41,6 +41,8 @@
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 @@
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 b0bc81b..63b700b 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1432,7 +1432,8 @@
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 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 e43f1ba..b2212f9 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 af8c592..2b92730 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 b3c24e5..b2835cc 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 7e9c508..5d09077 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 6caf561..b65ed4d 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 c076db2..63359a1 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 2d6bdb2..a818b7e 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 21b737a..20343af 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 80ee407..205bffb 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 35b2918..a1797cb 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 95a6728..2c8f843 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 a628e42..20b859c 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 86f2070..fcad7c2 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 4ce3a1f..2117233 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 72c1271..47be8cf 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 72c1271..47be8cf 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 72c1271..47be8cf 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 738af8c..50c4a67 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 3c2915d..a91fc1c 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 96b060f..dc926d4 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 6ccd864..190d61b 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 e8e61f4..d1d120f 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 dbfd8ba..425b235 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 501627d..4ba4871 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 ccff4f3..3dc8b28 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 89a9471..077f4e0 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 cbb0d9e4a4..149e466 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 e833052..c006df9 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 e811b94..8d90eb92 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 b0ce160..67e1c0f 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 2ba22af..6444b51 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 09223cd..873e3ae 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 105a647..70a41be 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 564472e..b3521ff 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 d4b9bf1..dd41f23 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 e2432f1..28db181 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 5e069b6..c58803d 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 729f726..a62cbf0 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 c3e939e..08cac8c 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 7f80a29..ab9cd90e 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 444c83d..10bb801 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 2b592bd..0d8a38e 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 3a96743..ab1a468 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 d60a010..4557264 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 7afb2ce..0965cf6 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 139e02a..8d96f5c7 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 fc3eb40..446d4f2 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 2faf1634..6199727 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 99465cc..8f2fc0f 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 1e7e281..9b6a20a8 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 99d6423..3276dcd 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 324f0d7..dba56dc 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 caf3688..27abf1a 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 0848304..b02c9e1 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 3e886bc..cb63a54 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 92a2e2a..3cfb242 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 bf9e15d..b5b30a8 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 e09daa5..952df71 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 bf9e15d..b5b30a8 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 7f96d2e..6f938d9 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 9e25bb7..83ae58b 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 b106464..2a4ee66 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 50af3cc..ef7678c 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 5bc756b..f3a2d23 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 1095088..69f14d5 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 27d7cad..881421c 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 b048762..fdffd46 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 6cfd253..500b33b 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 b391912..131d03f 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 5ec586f..2d6b2ad 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 15cbd94..c6a8544 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 31666ee..abcb19a 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 0126068..4550784 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 104badd..b5561b9 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 f8224f2..ecb6ee0 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 78b5400..dd8b8ba 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 ca5e3bc..37f1a9b 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 2126d12..9def7db 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 bdd9fe3..6f101ab 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 3c2146b..93acd0e 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 011e4f7..14ea738 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/docs/html/reference/images/graphics/colorspace_aces.png b/docs/html/reference/images/graphics/colorspace_aces.png
new file mode 100644
index 0000000..efafe5c
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_aces.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_acescg.png b/docs/html/reference/images/graphics/colorspace_acescg.png
new file mode 100644
index 0000000..55f6ab5
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_acescg.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_adobe_rgb.png b/docs/html/reference/images/graphics/colorspace_adobe_rgb.png
new file mode 100644
index 0000000..cb7d602
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_adobe_rgb.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_bt2020.png b/docs/html/reference/images/graphics/colorspace_bt2020.png
new file mode 100644
index 0000000..34a3853
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_bt2020.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_bt709.png b/docs/html/reference/images/graphics/colorspace_bt709.png
new file mode 100644
index 0000000..ba637f5
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_bt709.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_dci_p3.png b/docs/html/reference/images/graphics/colorspace_dci_p3.png
new file mode 100644
index 0000000..19144e7
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_dci_p3.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_display_p3.png b/docs/html/reference/images/graphics/colorspace_display_p3.png
new file mode 100644
index 0000000..a86c60a
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_display_p3.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_ntsc_1953.png b/docs/html/reference/images/graphics/colorspace_ntsc_1953.png
new file mode 100644
index 0000000..bce93da
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_ntsc_1953.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_pro_photo_rgb.png b/docs/html/reference/images/graphics/colorspace_pro_photo_rgb.png
new file mode 100644
index 0000000..74c95be
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_pro_photo_rgb.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_scrgb.png b/docs/html/reference/images/graphics/colorspace_scrgb.png
new file mode 100644
index 0000000..2351b8e
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_scrgb.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_smpte_c.png b/docs/html/reference/images/graphics/colorspace_smpte_c.png
new file mode 100644
index 0000000..360bb73
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_smpte_c.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_srgb.png b/docs/html/reference/images/graphics/colorspace_srgb.png
new file mode 100644
index 0000000..ba637f5
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_srgb.png
Binary files differ
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index d114deb..4f2465f 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -19,6 +19,7 @@
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 @@
* 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 @@
* </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 @@
* </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 @@
* </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 @@
* </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 @@
* </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 @@
* </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 @@
* </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 @@
* </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 @@
* </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 @@
* </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 @@
* </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 @@
* </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 @@
* </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 @@
* </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 @@
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 @@
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 @@
* 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 @@
* 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 @@
@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 @@
// 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 @@
* 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 @@
* @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 @@
* @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 @@
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 @@
/**
* 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 ad1ead8..7689256 100644
--- a/libs/androidfw/Android.mk
+++ b/libs/androidfw/Android.mk
@@ -65,8 +65,9 @@
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 5e21dfc..6786a69 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -52,16 +52,20 @@
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 @@
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 @@
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& 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 a0cdfcb..4f1d857 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -35,15 +35,15 @@
* 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 @@
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 6df544f..95db258 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -32,7 +32,6 @@
void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, int width,
int height) {
- mBarrierPending = false;
mCurrentBarrier = nullptr;
SkASSERT(mDisplayList.get() == nullptr);
@@ -76,8 +75,6 @@
}
void SkiaRecordingCanvas::insertReorderBarrier(bool enableReorder) {
- mBarrierPending = enableReorder;
-
if (nullptr != mCurrentBarrier) {
// finish off the existing chunk
SkDrawable* drawable =
@@ -86,6 +83,12 @@
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::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 8aef97f..10829f8 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -76,7 +76,6 @@
private:
SkLiteRecorder mRecorder;
std::unique_ptr<SkiaDisplayList> mDisplayList;
- bool mBarrierPending;
StartReorderBarrierDrawable* mCurrentBarrier;
/**
diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp
index 177a729..9dc2b59 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.cpp
+++ b/libs/hwui/renderthread/OpenGLPipeline.cpp
@@ -88,7 +88,7 @@
caches.tessellationCache.trim();
#if DEBUG_MEMORY_USAGE
- mCaches.dumpMemoryUsage();
+ caches.dumpMemoryUsage();
#else
if (CC_UNLIKELY(Properties::debugLevel & kDebugMemory)) {
caches.dumpMemoryUsage();
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 8c956e5..950b2c4 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -1542,6 +1542,14 @@
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 @@
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 ae4f0f4..c2df9ec 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -114,13 +114,21 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 435e6ba..65eadb6 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -801,8 +801,8 @@
* 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 @@
* {@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 @@
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 90ac416..7f37265 100644
--- a/media/java/android/media/IVolumeController.aidl
+++ b/media/java/android/media/IVolumeController.aidl
@@ -32,4 +32,11 @@
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 1d33128..bbcce82 100644
--- a/media/java/android/media/VolumePolicy.java
+++ b/media/java/android/media/VolumePolicy.java
@@ -25,6 +25,17 @@
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 96c12dd..4de1d00 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -607,8 +607,6 @@
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 94286ec..5aa673b 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -44,6 +44,7 @@
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 @@
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 @@
@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 330632b..c2b57ff 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
@@ -325,12 +325,13 @@
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 cdcc05c..0a7bdbf 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_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 @@
// 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 @@
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 boolean isUnlockingWithFingerprintAllowed() {
- return mStrongAuthTracker.isUnlockingWithFingerprintAllowed()
- && !hasFingerprintUnlockTimedOut(sCurrentUser);
+ return mStrongAuthTracker.isUnlockingWithFingerprintAllowed();
}
public boolean needsSlowUnlockTransition() {
@@ -588,16 +587,7 @@
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 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 @@
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 @@
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 44cfe92..1241bee 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 cf8f382..20d8ae9 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 b16cd08..c7efb07 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 Bundle metaData;
+ /**
+ * Optional key to use for this tile.
+ */
+ public String key;
+
public Tile() {
// Empty
}
@@ -113,6 +118,7 @@
dest.writeString(category);
dest.writeInt(priority);
dest.writeBundle(metaData);
+ dest.writeString(key);
}
public void readFromParcel(Parcel in) {
@@ -132,6 +138,7 @@
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 9442458..d12e8c0 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 @@
/**
* 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 @@
int icon = 0;
CharSequence title = null;
String summary = null;
+ String keyHint = null;
// Get the activity's meta-data
try {
@@ -317,6 +324,13 @@
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 @@
// 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 651a7cb..86b210a 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.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 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 @@
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 @@
}
@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 @@
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 @@
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 c5b4e84..6732e6c 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 63af3e0..6784254 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 7df6bc6..0000000
--- 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 0000000..088deba
--- /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 2f7174a..b889343 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 94d79f2..8c80c71 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 331d09e..5fec647 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 21f68f5..e036128 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.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 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 0000000..262d29d
--- /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 0000000..8a91144
--- /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 fe9f55f..3532f41 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.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 @@
}
@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 34dc63f..2192b8c 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 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.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 @@
* 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 @@
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 @@
return;
}
- tryKeyguardDone(true);
+ tryKeyguardDone();
if (strongAuth) {
mUpdateMonitor.reportSuccessfulStrongAuthUnlockAttempt();
}
@@ -565,10 +564,7 @@
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 @@
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 @@
/**
* 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 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 @@
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 @@
}
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 @@
}
};
- 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 @@
* @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 @@
resetKeyguardDonePendingLocked();
}
- if (authenticated) {
- mUpdateMonitor.clearFailedUnlockAttempts();
- }
+ mUpdateMonitor.clearFailedUnlockAttempts();
mUpdateMonitor.clearFingerprintRecognized();
if (mGoingToSleep) {
@@ -1528,22 +1527,21 @@
}
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 @@
private final Runnable mHideAnimationFinishedRunnable = () -> {
mHideAnimationRunning = false;
- tryKeyguardDone(true);
+ tryKeyguardDone();
};
/**
@@ -1912,7 +1910,7 @@
public void onWakeAndUnlocking() {
Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking");
mWakeAndUnlocking = true;
- keyguardDone(true /* authenticated */);
+ keyguardDone();
Trace.endSection();
}
@@ -1921,7 +1919,8 @@
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 bc46548..173f160 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.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 @@
*/
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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 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 20193f7..f5ca678 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.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 @@
protected boolean mShowLockscreenNotifications;
protected boolean mAllowLockscreenRemoteInput;
- protected NotificationOverflowContainer mKeyguardIconOverflowContainer;
+ protected NotificationShelf mNotificationShelf;
protected DismissView mDismissView;
protected EmptyShadeView mEmptyShadeView;
@@ -1025,9 +1024,7 @@
}
}
- 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) {
@@ -2160,13 +2157,14 @@
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 @@
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 @@
* 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 @@
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 @@
}
}
- 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 @@
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) {
+ entry.updateIcons(mContext, n);
+ if (!inflateViews(entry, mStackScroller)) {
+ handleNotificationError(notification, "Couldn't update remote views for: "
+ + notification);
+ }
}
- }
- 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);
- }
+ } 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 1d7bede..5436664 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.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 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 5db0699..19b32af 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.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 @@
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 5173176..a1384dcb 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.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 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 @@
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 @@
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 @@
// The public layouts expand button is always visible
mPublicLayout.updateExpandButtons(true);
updateLimits();
+ updateIconVisibilities();
}
private void updateLimits() {
@@ -318,6 +328,10 @@
return mStatusBarNotification;
}
+ public NotificationData.Entry getEntry() {
+ return mEntry;
+ }
+
public boolean isHeadsUp() {
return mIsHeadsUp;
}
@@ -333,6 +347,9 @@
if (intrinsicBefore != getIntrinsicHeight()) {
notifyHeightChanged(false /* needsAnimation */);
}
+ if (isHeadsUp) {
+ setAboveShelf(true);
+ }
}
public void setGroupManager(NotificationGroupManager groupManager) {
@@ -400,6 +417,7 @@
if (mNotificationParent != null) {
mNotificationParent.updateBackgroundForGroupState();
}
+ updateIconVisibilities();
}
@Override
@@ -459,7 +477,7 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ @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 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 9d9f3b9..4b95f07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -97,15 +97,23 @@
if (mCustomOutline) {
return;
}
- boolean hasOutline = true;
- if (isChildInGroup()) {
- hasOutline = isGroupExpanded() && !isGroupExpansionChanging();
- } else if (isSummaryWithChildren()) {
- hasOutline = !isGroupExpanded() || isGroupExpansionChanging();
- }
+ 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()) {
+ return isGroupExpanded() && !isGroupExpansionChanging();
+ } else if (isSummaryWithChildren()) {
+ return !isGroupExpanded() || isGroupExpansionChanging();
+ }
+ return true;
+ }
+
public boolean isOutlineShowing() {
ViewOutlineProvider op = getOutlineProvider();
return op != null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 83b0ee0..0f5981b 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.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 @@
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 @@
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 @@
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 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 @@
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 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 8688c28..dea9e31f 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 @@
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 @@
}
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 @@
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 58d57f6..ad6a5db 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 @@
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 @@
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 @@
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 @@
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 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 7019880..3687f6d 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 @@
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.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 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 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 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 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 bb327ef..ed1179a 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 @@
private Drawable mBackground;
private int mClipTopAmount;
+ private int mClipBottomAmount;
private int mActualHeight;
private boolean mExposed;
private INotificationManager mINotificationManager;
@@ -136,8 +137,10 @@
}
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 @@
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 8e8ce1a..0000000
--- 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 88bb714cc4..0000000
--- 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 0000000..6b9a89ef
--- /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 cdfdad4..d635bb0 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.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 {
- private static final String TAG = "StatusBarIconView";
- private boolean mAlwaysScaleIcon;
+ 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 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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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 f86badb..0000000
--- 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 34b8371..2124011 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.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 @@
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 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 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 784cb48..70beac8ea 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 @@
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 9bb4936..695b500 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 @@
*/
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 @@
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 @@
} 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 @@
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 cbaab14..0dbff19 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.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 @@
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 @@
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 @@
}
/**
- * 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 @@
* 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 @@
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 @@
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 0000000..2895890
--- /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 068631d..523528d 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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
// 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 @@
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 @@
@Override
protected float getCannedFlingDurationFactor() {
if (mQsExpanded) {
- return 0.7f;
+ return 0.9f;
} else {
- return 0.6f;
+ return 0.8f;
}
}
@@ -2180,16 +2192,22 @@
@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 @@
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 @@
@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 @@
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 f2c57e5..87a3848 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 @@
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 3de03b5..570d5d4 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.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 @@
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 @@
private ObjectAnimator mPeekAnimator;
private VelocityTrackerInterface mVelocityTracker;
private FlingAnimationUtils mFlingAnimationUtils;
+ private FlingAnimationUtils mFlingAnimationUtilsClosing;
private FalsingManager mFalsingManager;
/**
@@ -106,9 +112,6 @@
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 @@
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 @@
}
}
- 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 @@
@Override
public void onAnimationEnd(Animator animation) {
mPeekAnimator = null;
- if (mCollapseAfterPeek && !mCancelled) {
+ if (!mCancelled && collapseWhenFinished) {
postOnAnimation(mPostCollapseRunnable);
}
- mCollapseAfterPeek = false;
+
}
});
notifyExpandingStarted();
@@ -189,6 +181,7 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
protected void onTrackingStarted() {
endClosing();
mTracking = true;
- mCollapseAfterPeek = false;
mBar.onTrackingStarted();
notifyExpandingStarted();
notifyBarPanelExpansionChanged();
@@ -482,7 +500,10 @@
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 @@
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 @@
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 @@
&& mHeightAnimator == null
&& !isFullyCollapsed()
&& currentMaxPanelHeight != mExpandedHeight
- && !mPeekPending
&& mPeekAnimator == null
&& !mPeekTouching) {
setExpandedHeight(currentMaxPanelHeight);
@@ -769,10 +788,8 @@
}
}
- 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 boolean isFullyCollapsed() {
- return mExpandedHeight <= 0;
+ return mExpandedFraction <= 0.0f;
}
public boolean isCollapsing() {
@@ -833,16 +850,7 @@
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 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 @@
}
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 6f13ba5..bb86d6f 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.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
.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 @@
*/
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 @@
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 @@
mStackScroller.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setOnGroupChangeListener(mStackScroller);
- inflateOverflowContainer();
+ inflateShelf();
inflateEmptyShadeView();
inflateDismissView();
mExpandedContents = mStackScroller;
@@ -1049,13 +1051,13 @@
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 @@
updateClearAll();
inflateEmptyShadeView();
updateEmptyShadeView();
- inflateOverflowContainer();
+ inflateShelf();
mStatusBarKeyguardViewManager.onDensityOrFontScaleChanged();
mUserInfoController.onDensityOrFontScaleChanged();
if (mUserSwitcherController != null) {
@@ -1866,7 +1868,7 @@
mTmpChildOrderMap.clear();
updateRowStates();
- updateSpeedbump();
+ updateSpeedBumpIndex();
updateClearAll();
updateEmptyShadeView();
@@ -1987,8 +1989,8 @@
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 @@
}
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 @@
}
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 @@
return state;
}
+ private boolean shouldHideNotificationIcons() {
+ return mExpandedVisible && mNotificationPanel.shouldHideNotificationIcons();
+ }
+
/**
* State is one or more of the DISABLE constants from StatusBarManager.
*/
@@ -2497,7 +2507,7 @@
*
* 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 @@
mFalsingManager.onScreenOff();
}
+ public NotificationShelf getNotificationShelf() {
+ return mNotificationShelf;
+ }
+
+ public NotificationStackScrollLayout getNotificationScrollLayout() {
+ return mStackScroller;
+ }
+
public boolean isPulsing() {
return mDozeScrimController.isPulsing();
}
@@ -2935,7 +2953,7 @@
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 @@
}
}
+ private boolean shouldAnimatIconHiding() {
+ return mNotificationPanel.shouldAnimateIconHiding();
+ }
+
public boolean interceptTouchEvent(MotionEvent event) {
if (DEBUG_GESTURES) {
if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
@@ -4153,7 +4175,7 @@
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 a4b76ebb..0e74e57 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.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.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 @@
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 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 @@
if (mPinnedHeadsUpCount != 0) {
updateHeadsUpScrim(false);
}
- if (mKeyguardFadeoutAnimation != null) {
+ if (mKeyguardFadeoutAnimation != null && mTracking) {
mKeyguardFadeoutAnimation.cancel();
}
}
@@ -146,7 +145,7 @@
public void setBouncerShowing(boolean showing) {
mBouncerShowing = showing;
- mAnimateChange = !mExpanding && !mDontAnimateBouncerChanges;
+ mAnimateChange = !mTracking && !mDontAnimateBouncerChanges;
scheduleUpdate();
}
@@ -269,7 +268,7 @@
}
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 @@
setScrimBehindColor(behindFraction * mScrimBehindAlphaKeyguard);
} else if (mBouncerShowing && !mBouncerIsKeyguard) {
setScrimInFrontColor(getScrimInFrontAlpha());
- setScrimBehindColor(0f);
+ updateScrimNormal();
} else if (mBouncerShowing) {
setScrimInFrontColor(0f);
setScrimBehindColor(mScrimBehindAlpha);
@@ -474,18 +473,18 @@
}
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 @@
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 dbe7f96..a948a08 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.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 @@
private NotificationIconAreaController mNotificationIconAreaController;
private View mNotificationIconAreaInner;
+ private NotificationShelf mNotificationShelf;
private BatteryMeterView mBatteryMeterView;
private BatteryMeterView mBatteryMeterViewKeyguard;
@@ -123,6 +125,7 @@
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 69decd7..4263670 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.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 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 @@
});
} 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 @@
}
}
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 6217433..f6dd88d9 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 @@
@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 f6c0942..c21c493 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 @@
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 @@
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 fafbdd1..0396613 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 @@
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 81da672..26f74ea 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 @@
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 @@
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 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 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 561b18a..d3d58f9 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 @@
*/
public class AnimationFilter {
boolean animateAlpha;
+ boolean animateX;
boolean animateY;
boolean animateZ;
boolean animateHeight;
@@ -42,6 +43,11 @@
return this;
}
+ public AnimationFilter animateX() {
+ animateX = true;
+ return this;
+ }
+
public AnimationFilter animateY() {
animateY = true;
return this;
@@ -116,6 +122,7 @@
private void combineFilter(AnimationFilter filter) {
animateAlpha |= filter.animateAlpha;
+ animateX |= filter.animateX;
animateY |= filter.animateY;
animateZ |= filter.animateZ;
animateHeight |= filter.animateHeight;
@@ -129,6 +136,7 @@
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 0000000..0de774d
--- /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 0000000..58e6838
--- /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 d7920a9..26e1342 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 @@
}
@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 @@
mDividers.add(newIndex, divider);
updateGroupOverflow();
+ row.setIconTransformationAmount(0, false /* isLastChild */);
}
public void removeNotification(ExpandableNotificationRow row) {
@@ -412,7 +418,7 @@
* @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 @@
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 @@
* @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 @@
|| 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 @@
}
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 @@
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 @@
|| 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 @@
}
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 @@
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 @@
}
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 72a0e59..49da793 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.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 @@
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 @@
* 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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
}
private void updateSrcDrawing() {
- mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && (!mFadingOut && !mParentFadingOut)
+ mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && !mFadingOut && !mParentNotFullyVisible
? mSrcMode : null);
invalidate();
}
@@ -529,8 +527,9 @@
}
}
- 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 @@
* 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 @@
}
}
+ private void onPreDrawDuringAnimation() {
+ mShelf.updateAppearance();
+ if (!mNeedsAnimation && !mChildrenUpdateRequested) {
+ updateBackground();
+ }
+ }
+
private void updateScrollStateForAddedChildren() {
if (mChildrenToAddAnimated.isEmpty()) {
return;
@@ -671,7 +678,18 @@
*/
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 @@
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 @@
* 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 @@
* 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 @@
* 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 @@
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 @@
}
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
previousIncreasedAmount = increasedPaddingAmount;
height += expandableView.getIntrinsicHeight();
+ numShownItems++;
+ if (finish) {
+ break;
+ }
}
}
mContentHeight = height + mTopPadding;
updateScrollability();
+ mAmbientState.setLayoutMaxHeight(mContentHeight);
}
private void updateScrollability() {
@@ -2036,7 +2084,7 @@
private void applyCurrentBackgroundBounds() {
mScrimController.setExcludedBackgroundArea(
- mFadingOut || mParentFadingOut || mAmbientState.isDark() ? null
+ mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
: mCurrentBounds);
invalidate();
}
@@ -2056,7 +2104,7 @@
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 @@
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 @@
bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
bottom = Math.min(bottom, getHeight());
}
+ bottom -= lastView.getClipBottomAmount();
} else {
top = mTopPadding;
bottom = top;
@@ -2115,8 +2171,8 @@
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 @@
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 int getLayoutMinHeight() {
- int firstChildMinHeight = getFirstChildIntrinsicHeight();
- return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight,
- mMaxLayoutHeight - mIntrinsicPadding);
+ return mShelf.getIntrinsicHeight();
}
public int getFirstChildIntrinsicHeight() {
@@ -2237,8 +2291,11 @@
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 @@
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 @@
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 @@
}
mFirstVisibleBackgroundChild = firstChild;
mLastVisibleBackgroundChild = lastChild;
+ mAmbientState.setLastVisibleBackgroundChild(lastChild);
}
private void onViewAddedInternal(View child) {
@@ -2727,10 +2785,10 @@
: 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 @@
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 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 @@
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 @@
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 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 @@
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 @@
// 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 @@
// 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 @@
mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
mNeedsAnimation = true;
if (!mIsExpanded && !isHeadsUp) {
- row.setHeadsupDisappearRunning(true);
+ row.setHeadsUpAnimatingAway(true);
}
requestChildrenUpdate();
}
@@ -3885,9 +3892,9 @@
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 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 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 1c37c35..0000000
--- 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 034eba6..0000000
--- 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 c9e4eac..1dc346d 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.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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
}
// 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 @@
*/
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
- 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 @@
}
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 @@
}
/**
- * 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 @@
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 @@
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 @@
+ 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 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 @@
* 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 8f0cd8e..e3c746b 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.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 @@
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 @@
}
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 @@
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 @@
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());
+ if (state.gone) {
+ continue;
}
- }
- }
-
- /**
- * 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);
- }
- }
-
- // 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 3804b42..1f29b4f 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 @@
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.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 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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;
+ 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;
+ }
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;
- }
- }
-
- 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();
+ result += (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
+ return result;
}
/**
@@ -814,33 +333,11 @@
@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 @@
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 @@
} 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 @@
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 @@
// 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 @@
}
}
- /**
- * 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 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 ecdee4e..0000000
--- 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 5beaac3..8a5ddd4 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 @@
*/
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 c20cc84..5393d60 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 @@
AudioSystem.STREAM_SYSTEM_ENFORCED,
AudioSystem.STREAM_TTS,
AudioSystem.STREAM_VOICE_CALL,
+ AudioSystem.STREAM_ACCESSIBILITY,
};
private final HandlerThread mWorkerThread;
@@ -562,6 +563,22 @@
.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 0000000..131a70b
--- /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 c8ed872..d085a47 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.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 @@
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 90b46ed..214a357 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 @@
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 @@
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 473b1a3..005b8aa 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 @@
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 5d8d79f..eb34669 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 @@
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 @@
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 7a122e6..029b5dd 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 @@
* @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 @@
* 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 @@
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 9d8c383..5e02597 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.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_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 @@
*/
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 @@
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 @@
}
}
+ 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 @@
*/
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 ac9545c..67f3614 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 @@
*
* @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 @@
TAG,
SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
- StreamOverride.init(mContext);
+ initA11yMonitoring(mContext);
mControllerService.init();
onIndicateSystemReady();
}
@@ -972,6 +974,8 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
}
//==========================================================================================
- // 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);
+ }
+
+ 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");
+ }
- @Override
- public void onTouchExplorationStateChanged(boolean enabled) {
- updateDefaultStreamOverrideDelay(enabled);
- }
+ //---------------------------------------------------------------------------------
+ // A11y: taking a11y state into account for the handling of a11y prompts volume
+ //---------------------------------------------------------------------------------
- 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");
- }
+ 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 @@
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 273bc64..132967c 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.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 @@
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 @@
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/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c7f298a..e31df52 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -404,8 +404,8 @@
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];
@@ -2245,10 +2245,6 @@
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");
@@ -2333,6 +2329,14 @@
}
}
+ // 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.
@@ -6768,7 +6772,7 @@
}
}
- 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);
@@ -6779,7 +6783,11 @@
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());
@@ -6787,20 +6795,43 @@
// 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() {
@@ -8153,6 +8184,11 @@
// 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) {
@@ -8229,6 +8265,14 @@
}
}
+ 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,
@@ -8261,7 +8305,11 @@
}
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,
@@ -8400,18 +8448,34 @@
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);
+ // 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;
+
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) {
@@ -9266,11 +9330,6 @@
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 3ad2ae1..6089d2e 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 @@
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 0000000..158cfc94
--- /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 ccdda13..f48db05 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 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 @@
}
@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 f37f987..1b4eaf5 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.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 @@
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 c4a0dd3..0b839b8 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.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 @@
}
@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/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9b089ec..f51b6a0 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;
@@ -125,6 +126,7 @@
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;
@@ -3935,20 +3937,13 @@
}
@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);
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 4d43e8e..ac0e622 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -46,6 +46,7 @@
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 895497c..034b0cd 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 @@
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";
@@ -2380,7 +2382,7 @@
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);
}
@@ -2560,7 +2562,7 @@
} 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));
@@ -8064,13 +8066,11 @@
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 +
@@ -9163,9 +9163,18 @@
@Override
public void setAffiliationIds(ComponentName admin, List<String> ids) {
- final Set<String> affiliationIds = new ArraySet<String>(ids);
- final int callingUserId = mInjector.userHandleGetCallingUserId();
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(admin);
+ Preconditions.checkCollectionElementsNotNull(ids, "ids");
+
+ final Set<String> affiliationIds = new ArraySet<String>(ids);
+ 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;
@@ -9180,20 +9189,44 @@
}
@Override
- public boolean isAffiliatedUser() {
- final int callingUserId = mInjector.userHandleGetCallingUserId();
+ public List<String> getAffiliationIds(ComponentName admin) {
+ if (!mHasFeature) {
+ return Collections.emptyList();
+ }
+ Preconditions.checkNotNull(admin);
synchronized (this) {
- if (mOwners.getDeviceOwnerUserId() == callingUserId) {
- // The user that the DO is installed on is always affiliated.
- return true;
- }
- final ComponentName profileOwner = getProfileOwner(callingUserId);
- if (profileOwner == null
- || !profileOwner.getPackageName().equals(mOwners.getDeviceOwnerPackageName())) {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ return new ArrayList<String>(
+ getUserData(mInjector.userHandleGetCallingUserId()).mAffiliationIds);
+ }
+ }
+
+ @Override
+ public boolean isAffiliatedUser() {
+ return isUserAffiliatedWithDevice(mInjector.userHandleGetCallingUserId());
+ }
+
+ private boolean isUserAffiliatedWithDevice(int userId) {
+ synchronized (this) {
+ if (!mOwners.hasDeviceOwner()) {
return false;
}
- final Set<String> userAffiliationIds = getUserData(callingUserId).mAffiliationIds;
+ 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(userId);
+ if (profileOwner == null) {
+ return false;
+ }
+ 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/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index cff5b41..2d21c08 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 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 @@
/**
* Test for:
* {@link DevicePolicyManager#setAffiliationIds}
+ * {@link DevicePolicyManager#getAffiliationIds}
* {@link DevicePolicyManager#isAffiliatedUser}
*/
public void testUserAffiliation() throws Exception {
@@ -1659,30 +1661,34 @@
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 @@
// 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 0000000..d165b8b
--- /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 ed4c79f..1853a65 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 @@
}
@Override
- public void dismissKeyguardLw() {
-
+ public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback) {
}
@Override
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 4596210..a0ded87 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.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 @@
}
@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/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk
index 565feb6..9ee416a 100644
--- a/tools/layoutlib/bridge/tests/Android.mk
+++ b/tools/layoutlib/bridge/tests/Android.mk
@@ -30,7 +30,7 @@
layoutlib_api-prebuilt \
tools-common-prebuilt \
sdk-common \
- junit
+ junit-host
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk
index dafb9c6..61e381d 100644
--- a/tools/layoutlib/create/tests/Android.mk
+++ b/tools/layoutlib/create/tests/Android.mk
@@ -23,7 +23,7 @@
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 09d95ff..769db6b 100644
--- a/tools/preload2/Android.mk
+++ b/tools/preload2/Android.mk
@@ -12,7 +12,7 @@
# 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 ca5b0e0..cc54a8d 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.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 @@
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 @@
* @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 @@
}
}
- 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 @@
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 fbf83d2..5787d85 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 @@
@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 b524716..3a7f7f7 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.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 @@
return;
}
- new Thread(this).start();
+ super.actionPerformed(e);
}
@Override
@@ -92,10 +91,8 @@
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 cb8b3df..848a568 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 @@
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 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 5c19765..bfeeb83 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 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 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 385e857..29464fc 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 @@
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 0000000..9371463
--- /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
similarity index 93%
rename from tools/preload2/src/com/android/preload/ui/UI.java
rename to tools/preload2/src/com/android/preload/ui/SwingUI.java
index 47174dd..cab3744 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.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 @@
// 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 @@
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 @@
});
}
+ @Override
public void updateWaitDialog(String s) {
if (currentWaitDialog != null) {
((JLabel) currentWaitDialog.getContentPane().getComponent(0)).setText(s);
@@ -124,6 +141,7 @@
}
}
+ @Override
public void hideWaitDialog() {
if (currentWaitDialog != null) {
currentWaitDialog.setVisible(false);
@@ -131,6 +149,7 @@
}
}
+ @Override
public void showMessageDialog(String s) {
// Hide the wait dialog...
if (currentWaitDialog != null) {
@@ -147,6 +166,7 @@
}
}
+ @Override
public boolean showConfirmDialog(String title, String message) {
// Hide the wait dialog...
if (currentWaitDialog != null) {
@@ -164,6 +184,7 @@
}
}
+ @Override
public String showInputDialog(String message) {
// Hide the wait dialog...
if (currentWaitDialog != null) {
@@ -180,6 +201,7 @@
}
}
+ @Override
@SuppressWarnings("unchecked")
public <T> T showChoiceDialog(String title, String message, T[] choices) {
// Hide the wait dialog...
@@ -203,6 +225,7 @@
}
}
+ @Override
public File showSaveDialog() {
// Hide the wait dialog...
if (currentWaitDialog != null) {
@@ -228,6 +251,7 @@
}
}
+ @Override
public File[] showOpenDialog(boolean multi) {
// Hide the wait dialog...
if (currentWaitDialog != null) {