summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/system-current.txt1
-rw-r--r--core/java/android/app/ContextImpl.java6
-rw-r--r--core/java/android/app/IActivityManager.aidl5
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java7
-rw-r--r--core/java/android/content/Context.java7
-rw-r--r--core/java/android/content/ContextWrapper.java8
-rw-r--r--core/java/android/os/HwRemoteBinder.java7
-rw-r--r--core/java/android/os/IHwBinder.java12
-rw-r--r--core/jni/android_os_HwRemoteBinder.cpp271
-rw-r--r--core/jni/android_os_HwRemoteBinder.h28
-rw-r--r--core/res/AndroidManifest.xml8
-rw-r--r--packages/SystemUI/res/layout/notification_guts.xml205
-rw-r--r--packages/SystemUI/res/layout/notification_guts_importance_text.xml38
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml43
-rw-r--r--packages/SystemUI/res/values/styles.xml12
-rw-r--r--packages/SystemUI/src/com/android/systemui/ExpandHelper.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java111
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java373
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java6
-rw-r--r--packages/SystemUI/tests/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java301
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java5
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java5
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java9
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java3
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java64
-rw-r--r--services/core/java/com/android/server/wm/Task.java24
-rw-r--r--services/core/java/com/android/server/wm/TaskStack.java102
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java64
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java35
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java69
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java59
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java112
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java23
-rw-r--r--test-runner/src/android/test/mock/MockContext.java6
51 files changed, 1755 insertions, 650 deletions
diff --git a/api/system-current.txt b/api/system-current.txt
index 86aa5b87d08c..61c22375addc 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -41,6 +41,7 @@ package android {
field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
field public static final java.lang.String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET";
field public static final java.lang.String BIND_MIDI_DEVICE_SERVICE = "android.permission.BIND_MIDI_DEVICE_SERVICE";
+ field public static final java.lang.String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 5f706dcf8fb1..547c71043a11 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1470,6 +1470,12 @@ class ContextImpl extends Context {
return mMainThread.getApplicationThread();
}
+ /** @hide */
+ @Override
+ public Handler getMainThreadHandler() {
+ return mMainThread.getHandler();
+ }
+
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler
handler, UserHandle user) {
// Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser.
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index fcc6e3d1e913..28ad01e8178d 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -453,6 +453,11 @@ interface IActivityManager {
// Stop Binder transaction tracking for all applications and dump trace data to the given file
// descriptor.
boolean stopBinderTrackingAndDump(in ParcelFileDescriptor fd);
+ /**
+ * Try to place task to provided position. The final position might be different depending on
+ * current user and stacks state. The task will be moved to target stack if it's currently in
+ * different stack.
+ */
void positionTaskInStack(int taskId, int stackId, int position);
int getActivityStackId(in IBinder token);
void exitFreeformMode(in IBinder token);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 617288428916..6f73388ff139 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7111,8 +7111,8 @@ public class DevicePolicyManager {
* @param serviceIntent Identifies the service to connect to. The Intent must specify either an
* explicit component name or a package name to match an
* {@link IntentFilter} published by a service.
- * @param conn Receives information as the service is started and stopped. This must be a
- * valid {@link ServiceConnection} object; it must not be {@code null}.
+ * @param conn Receives information as the service is started and stopped in main thread. This
+ * must be a valid {@link ServiceConnection} object; it must not be {@code null}.
* @param flags Operation options for the binding operation. See
* {@link Context#bindService(Intent, ServiceConnection, int)}.
* @param targetUser Which user to bind to. Must be one of the users returned by
@@ -7131,7 +7131,8 @@ public class DevicePolicyManager {
throwIfParentInstance("bindDeviceAdminServiceAsUser");
// Keep this in sync with ContextImpl.bindServiceCommon.
try {
- final IServiceConnection sd = mContext.getServiceDispatcher(conn, null, flags);
+ final IServiceConnection sd = mContext.getServiceDispatcher(
+ conn, mContext.getMainThreadHandler(), flags);
serviceIntent.prepareToLeaveProcess(mContext);
return mService.bindDeviceAdminServiceAsUser(admin,
mContext.getIApplicationThread(), mContext.getActivityToken(), serviceIntent,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f0f1d99b9f96..9dc60ab82f2d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4403,4 +4403,11 @@ public abstract class Context {
public IApplicationThread getIApplicationThread() {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
+
+ /**
+ * @hide
+ */
+ public Handler getMainThreadHandler() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
}
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 75336559088b..4b6076b37fdf 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -885,4 +885,12 @@ public class ContextWrapper extends Context {
public IApplicationThread getIApplicationThread() {
return mBase.getIApplicationThread();
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public Handler getMainThreadHandler() {
+ return mBase.getMainThreadHandler();
+ }
}
diff --git a/core/java/android/os/HwRemoteBinder.java b/core/java/android/os/HwRemoteBinder.java
index 83866b3cceb7..e617e0a52bc6 100644
--- a/core/java/android/os/HwRemoteBinder.java
+++ b/core/java/android/os/HwRemoteBinder.java
@@ -39,6 +39,9 @@ public class HwRemoteBinder implements IHwBinder {
public native final void transact(
int code, HwParcel request, HwParcel reply, int flags);
+ public native boolean linkToDeath(DeathRecipient recipient, long cookie);
+ public native boolean unlinkToDeath(DeathRecipient recipient);
+
private static native final long native_init();
private native final void native_setup_empty();
@@ -52,5 +55,9 @@ public class HwRemoteBinder implements IHwBinder {
128 /* size */);
}
+ private static final void sendDeathNotice(DeathRecipient recipient, long cookie) {
+ recipient.serviceDied(cookie);
+ }
+
private long mNativeContext;
}
diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java
index 76e881eda8af..f93bfd79f625 100644
--- a/core/java/android/os/IHwBinder.java
+++ b/core/java/android/os/IHwBinder.java
@@ -26,4 +26,16 @@ public interface IHwBinder {
int code, HwParcel request, HwParcel reply, int flags);
public IHwInterface queryLocalInterface(String descriptor);
+
+ /**
+ * Interface for receiving a callback when the process hosting a service
+ * has gone away.
+ */
+ public interface DeathRecipient {
+ public void serviceDied(long cookie);
+ }
+
+ public boolean linkToDeath(DeathRecipient recipient, long cookie);
+
+ public boolean unlinkToDeath(DeathRecipient recipient);
}
diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp
index 1d5d6d59639a..0a7d84d9c8c5 100644
--- a/core/jni/android_os_HwRemoteBinder.cpp
+++ b/core/jni/android_os_HwRemoteBinder.cpp
@@ -25,6 +25,7 @@
#include <JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
#include <hidl/Status.h>
+#include <ScopedUtfChars.h>
#include <nativehelper/ScopedLocalRef.h>
#include "core_jni_helpers.h"
@@ -38,26 +39,196 @@ using android::AndroidRuntime;
namespace android {
static struct fields_t {
+ jclass proxy_class;
jfieldID contextID;
jmethodID constructID;
+ jmethodID sendDeathNotice;
+} gProxyOffsets;
+
+static struct class_offsets_t
+{
+ jmethodID mGetName;
+} gClassOffsets;
+
+static JavaVM* jnienv_to_javavm(JNIEnv* env)
+{
+ JavaVM* vm;
+ return env->GetJavaVM(&vm) >= 0 ? vm : NULL;
+}
+
+static JNIEnv* javavm_to_jnienv(JavaVM* vm)
+{
+ JNIEnv* env;
+ return vm->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL;
+}
+
+// ----------------------------------------------------------------------------
+class HwBinderDeathRecipient : public hardware::IBinder::DeathRecipient
+{
+public:
+ HwBinderDeathRecipient(JNIEnv* env, jobject object, jlong cookie, const sp<HwBinderDeathRecipientList>& list)
+ : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)),
+ mObjectWeak(NULL), mCookie(cookie), mList(list)
+ {
+ // These objects manage their own lifetimes so are responsible for final bookkeeping.
+ // The list holds a strong reference to this object.
+ list->add(this);
+ }
+
+ void binderDied(const wp<hardware::IBinder>& who)
+ {
+ if (mObject != NULL) {
+ JNIEnv* env = javavm_to_jnienv(mVM);
+
+ env->CallStaticVoidMethod(gProxyOffsets.proxy_class, gProxyOffsets.sendDeathNotice, mObject, mCookie);
+ if (env->ExceptionCheck()) {
+ ALOGE("Uncaught exception returned from death notification.");
+ env->ExceptionClear();
+ }
+
+ // Serialize with our containing HwBinderDeathRecipientList so that we can't
+ // delete the global ref on mObject while the list is being iterated.
+ sp<HwBinderDeathRecipientList> list = mList.promote();
+ if (list != NULL) {
+ AutoMutex _l(list->lock());
+
+ // Demote from strong ref to weak after binderDied() has been delivered,
+ // to allow the DeathRecipient and BinderProxy to be GC'd if no longer needed.
+ mObjectWeak = env->NewWeakGlobalRef(mObject);
+ env->DeleteGlobalRef(mObject);
+ mObject = NULL;
+ }
+ }
+ }
+
+ void clearReference()
+ {
+ sp<HwBinderDeathRecipientList> list = mList.promote();
+ if (list != NULL) {
+ list->remove(this);
+ } else {
+ ALOGE("clearReference() on JDR %p but DRL wp purged", this);
+ }
+ }
+
+ bool matches(jobject obj) {
+ bool result;
+ JNIEnv* env = javavm_to_jnienv(mVM);
+
+ if (mObject != NULL) {
+ result = env->IsSameObject(obj, mObject);
+ } else {
+ jobject me = env->NewLocalRef(mObjectWeak);
+ result = env->IsSameObject(obj, me);
+ env->DeleteLocalRef(me);
+ }
+ return result;
+ }
+
+ void warnIfStillLive() {
+ if (mObject != NULL) {
+ // Okay, something is wrong -- we have a hard reference to a live death
+ // recipient on the VM side, but the list is being torn down.
+ JNIEnv* env = javavm_to_jnienv(mVM);
+ ScopedLocalRef<jclass> objClassRef(env, env->GetObjectClass(mObject));
+ ScopedLocalRef<jstring> nameRef(env,
+ (jstring) env->CallObjectMethod(objClassRef.get(), gClassOffsets.mGetName));
+ ScopedUtfChars nameUtf(env, nameRef.get());
+ if (nameUtf.c_str() != NULL) {
+ ALOGW("BinderProxy is being destroyed but the application did not call "
+ "unlinkToDeath to unlink all of its death recipients beforehand. "
+ "Releasing leaked death recipient: %s", nameUtf.c_str());
+ } else {
+ ALOGW("BinderProxy being destroyed; unable to get DR object name");
+ env->ExceptionClear();
+ }
+ }
+ }
+
+protected:
+ virtual ~HwBinderDeathRecipient()
+ {
+ JNIEnv* env = javavm_to_jnienv(mVM);
+ if (mObject != NULL) {
+ env->DeleteGlobalRef(mObject);
+ } else {
+ env->DeleteWeakGlobalRef(mObjectWeak);
+ }
+ }
+
+private:
+ JavaVM* const mVM;
+ jobject mObject;
+ jweak mObjectWeak; // will be a weak ref to the same VM-side DeathRecipient after binderDied()
+ jlong mCookie;
+ wp<HwBinderDeathRecipientList> mList;
+};
+// ----------------------------------------------------------------------------
+
+HwBinderDeathRecipientList::HwBinderDeathRecipientList() {
+}
+
+HwBinderDeathRecipientList::~HwBinderDeathRecipientList() {
+ AutoMutex _l(mLock);
+
+ for (const sp<HwBinderDeathRecipient>& deathRecipient : mList) {
+ deathRecipient->warnIfStillLive();
+ }
+}
+
+void HwBinderDeathRecipientList::add(const sp<HwBinderDeathRecipient>& recipient) {
+ AutoMutex _l(mLock);
+
+ mList.push_back(recipient);
+}
-} gFields;
+void HwBinderDeathRecipientList::remove(const sp<HwBinderDeathRecipient>& recipient) {
+ AutoMutex _l(mLock);
+
+ List< sp<HwBinderDeathRecipient> >::iterator iter;
+ for (iter = mList.begin(); iter != mList.end(); iter++) {
+ if (*iter == recipient) {
+ mList.erase(iter);
+ return;
+ }
+ }
+}
+
+sp<HwBinderDeathRecipient> HwBinderDeathRecipientList::find(jobject recipient) {
+ AutoMutex _l(mLock);
+
+ for (const sp<HwBinderDeathRecipient>& deathRecipient : mList) {
+ if (deathRecipient->matches(recipient)) {
+ return deathRecipient;
+ }
+ }
+ return NULL;
+}
+
+Mutex& HwBinderDeathRecipientList::lock() {
+ return mLock;
+}
// static
void JHwRemoteBinder::InitClass(JNIEnv *env) {
- ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH));
+ jclass clazz = FindClassOrDie(env, CLASS_PATH);
- gFields.contextID =
- GetFieldIDOrDie(env, clazz.get(), "mNativeContext", "J");
+ gProxyOffsets.proxy_class = MakeGlobalRefOrDie(env, clazz);
+ gProxyOffsets.contextID =
+ GetFieldIDOrDie(env, clazz, "mNativeContext", "J");
+ gProxyOffsets.constructID = GetMethodIDOrDie(env, clazz, "<init>", "()V");
+ gProxyOffsets.sendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
+ "(Landroid/os/IHwBinder$DeathRecipient;J)V");
- gFields.constructID = GetMethodIDOrDie(env, clazz.get(), "<init>", "()V");
+ clazz = FindClassOrDie(env, "java/lang/Class");
+ gClassOffsets.mGetName = GetMethodIDOrDie(env, clazz, "getName", "()Ljava/lang/String;");
}
// static
sp<JHwRemoteBinder> JHwRemoteBinder::SetNativeContext(
JNIEnv *env, jobject thiz, const sp<JHwRemoteBinder> &context) {
sp<JHwRemoteBinder> old =
- (JHwRemoteBinder *)env->GetLongField(thiz, gFields.contextID);
+ (JHwRemoteBinder *)env->GetLongField(thiz, gProxyOffsets.contextID);
if (context != NULL) {
context->incStrong(NULL /* id */);
@@ -67,7 +238,7 @@ sp<JHwRemoteBinder> JHwRemoteBinder::SetNativeContext(
old->decStrong(NULL /* id */);
}
- env->SetLongField(thiz, gFields.contextID, (long)context.get());
+ env->SetLongField(thiz, gProxyOffsets.contextID, (long)context.get());
return old;
}
@@ -75,7 +246,7 @@ sp<JHwRemoteBinder> JHwRemoteBinder::SetNativeContext(
// static
sp<JHwRemoteBinder> JHwRemoteBinder::GetNativeContext(
JNIEnv *env, jobject thiz) {
- return (JHwRemoteBinder *)env->GetLongField(thiz, gFields.contextID);
+ return (JHwRemoteBinder *)env->GetLongField(thiz, gProxyOffsets.contextID);
}
// static
@@ -84,7 +255,7 @@ jobject JHwRemoteBinder::NewObject(
ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH));
// XXX Have to look up the constructor here because otherwise that static
- // class initializer isn't called and gFields.constructID is undefined :(
+ // class initializer isn't called and gProxyOffsets.constructID is undefined :(
jmethodID constructID = GetMethodIDOrDie(env, clazz.get(), "<init>", "()V");
@@ -97,6 +268,7 @@ jobject JHwRemoteBinder::NewObject(
JHwRemoteBinder::JHwRemoteBinder(
JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder)
: mBinder(binder) {
+ mDeathRecipientList = new HwBinderDeathRecipientList();
jclass clazz = env->GetObjectClass(thiz);
CHECK(clazz != NULL);
@@ -114,7 +286,7 @@ JHwRemoteBinder::~JHwRemoteBinder() {
mClass = NULL;
}
-sp<hardware::IBinder> JHwRemoteBinder::getBinder() {
+sp<hardware::IBinder> JHwRemoteBinder::getBinder() const {
return mBinder;
}
@@ -122,6 +294,10 @@ void JHwRemoteBinder::setBinder(const sp<hardware::IBinder> &binder) {
mBinder = binder;
}
+sp<HwBinderDeathRecipientList> JHwRemoteBinder::getDeathRecipientList() const {
+ return mDeathRecipientList;
+}
+
} // namespace android
////////////////////////////////////////////////////////////////////////////////
@@ -174,6 +350,73 @@ static void JHwRemoteBinder_native_transact(
signalExceptionForError(env, err);
}
+static jboolean JHwRemoteBinder_linkToDeath(JNIEnv* env, jobject thiz,
+ jobject recipient, jlong cookie)
+{
+ if (recipient == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return JNI_FALSE;
+ }
+
+ sp<JHwRemoteBinder> context = JHwRemoteBinder::GetNativeContext(env, thiz);
+ sp<hardware::IBinder> binder = context->getBinder();
+
+ if (!binder->localBinder()) {
+ HwBinderDeathRecipientList* list = (context->getDeathRecipientList()).get();
+ sp<HwBinderDeathRecipient> jdr = new HwBinderDeathRecipient(env, recipient, cookie, list);
+ status_t err = binder->linkToDeath(jdr, NULL, 0);
+ if (err != NO_ERROR) {
+ // Failure adding the death recipient, so clear its reference
+ // now.
+ jdr->clearReference();
+ return JNI_FALSE;
+ }
+ }
+
+ return JNI_TRUE;
+}
+
+static jboolean JHwRemoteBinder_unlinkToDeath(JNIEnv* env, jobject thiz,
+ jobject recipient)
+{
+ jboolean res = JNI_FALSE;
+ if (recipient == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return res;
+ }
+
+ sp<JHwRemoteBinder> context = JHwRemoteBinder::GetNativeContext(env, thiz);
+ sp<hardware::IBinder> binder = context->getBinder();
+
+ if (!binder->localBinder()) {
+ status_t err = NAME_NOT_FOUND;
+
+ // If we find the matching recipient, proceed to unlink using that
+ HwBinderDeathRecipientList* list = (context->getDeathRecipientList()).get();
+ sp<HwBinderDeathRecipient> origJDR = list->find(recipient);
+ if (origJDR != NULL) {
+ wp<hardware::IBinder::DeathRecipient> dr;
+ err = binder->unlinkToDeath(origJDR, NULL, 0, &dr);
+ if (err == NO_ERROR && dr != NULL) {
+ sp<hardware::IBinder::DeathRecipient> sdr = dr.promote();
+ HwBinderDeathRecipient* jdr = static_cast<HwBinderDeathRecipient*>(sdr.get());
+ if (jdr != NULL) {
+ jdr->clearReference();
+ }
+ }
+ }
+
+ if (err == NO_ERROR || err == DEAD_OBJECT) {
+ res = JNI_TRUE;
+ } else {
+ jniThrowException(env, "java/util/NoSuchElementException",
+ "Death link does not exist");
+ }
+ }
+
+ return res;
+}
+
static JNINativeMethod gMethods[] = {
{ "native_init", "()J", (void *)JHwRemoteBinder_native_init },
@@ -183,6 +426,14 @@ static JNINativeMethod gMethods[] = {
{ "transact",
"(IL" PACKAGE_PATH "/HwParcel;L" PACKAGE_PATH "/HwParcel;I)V",
(void *)JHwRemoteBinder_native_transact },
+
+ {"linkToDeath",
+ "(Landroid/os/IHwBinder$DeathRecipient;J)Z",
+ (void*)JHwRemoteBinder_linkToDeath},
+
+ {"unlinkToDeath",
+ "(Landroid/os/IHwBinder$DeathRecipient;)Z",
+ (void*)JHwRemoteBinder_unlinkToDeath},
};
namespace android {
diff --git a/core/jni/android_os_HwRemoteBinder.h b/core/jni/android_os_HwRemoteBinder.h
index fd33338986a0..77a02784926d 100644
--- a/core/jni/android_os_HwRemoteBinder.h
+++ b/core/jni/android_os_HwRemoteBinder.h
@@ -20,10 +20,33 @@
#include <android-base/macros.h>
#include <hwbinder/Binder.h>
#include <jni.h>
+#include <utils/List.h>
+#include <utils/Mutex.h>
#include <utils/RefBase.h>
namespace android {
+// Per-IBinder death recipient bookkeeping. This is how we reconcile local jobject
+// death recipient references passed in through JNI with the permanent corresponding
+// HwBinderDeathRecipient objects.
+
+class HwBinderDeathRecipient;
+
+class HwBinderDeathRecipientList : public RefBase {
+ List< sp<HwBinderDeathRecipient> > mList;
+ Mutex mLock;
+
+public:
+ HwBinderDeathRecipientList();
+ ~HwBinderDeathRecipientList();
+
+ void add(const sp<HwBinderDeathRecipient>& recipient);
+ void remove(const sp<HwBinderDeathRecipient>& recipient);
+ sp<HwBinderDeathRecipient> find(jobject recipient);
+
+ Mutex& lock(); // Use with care; specifically for mutual exclusion during binder death
+};
+
struct JHwRemoteBinder : public RefBase {
static void InitClass(JNIEnv *env);
@@ -37,8 +60,9 @@ struct JHwRemoteBinder : public RefBase {
JHwRemoteBinder(
JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder);
- sp<hardware::IBinder> getBinder();
+ sp<hardware::IBinder> getBinder() const;
void setBinder(const sp<hardware::IBinder> &binder);
+ sp<HwBinderDeathRecipientList> getDeathRecipientList() const;
protected:
virtual ~JHwRemoteBinder();
@@ -48,7 +72,7 @@ private:
jobject mObject;
sp<hardware::IBinder> mBinder;
-
+ sp<HwBinderDeathRecipientList> mDeathRecipientList;
DISALLOW_COPY_AND_ASSIGN(JHwRemoteBinder);
};
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 88aac3b5be68..9a783b0bc815 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1836,8 +1836,7 @@
android:description="@string/permdesc_install_shortcut"
android:protectionLevel="normal"/>
- <!-- Allows an application to uninstall a shortcut in Launcher.
- <p>Protection level: normal
+ <!--This permission is no longer supported.
-->
<permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"
android:label="@string/permlab_uninstall_shortcut"
@@ -2070,6 +2069,11 @@
<permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING"
android:protectionLevel="signature" />
+ <!-- Allows the system to bind to the discovered Network Recommendation Service.
+ @SystemApi @hide -->
+ <permission android:name="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- ========================================= -->
<!-- Permissions for special development tools -->
<!-- ========================================= -->
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index e84ed23fbc86..3948dc4b8771 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -16,150 +16,109 @@
-->
<com.android.systemui.statusbar.NotificationGuts
- xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/notification_guts"
+ android:visibility="gone"
+ android:clickable="true"
+ android:gravity="top|start"
+ android:orientation="vertical"
+ android:paddingStart="@*android:dimen/notification_content_margin_start"
+ android:paddingEnd="8dp"
+ android:background="@color/notification_guts_bg_color"
+ android:theme="@*android:style/Theme.DeviceDefault.Light">
+
+ <!-- header -->
+ <RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:id="@+id/notification_guts"
- android:visibility="gone"
- android:clickable="true"
- android:gravity="top|start"
- android:orientation="vertical"
- android:paddingStart="@*android:dimen/notification_content_margin_start"
+ android:paddingTop="20dp"
android:paddingEnd="8dp"
- android:background="@color/notification_guts_bg_color"
- android:theme="@*android:style/Theme.DeviceDefault.Light">
-
- <!-- header -->
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="15dp"
- android:paddingEnd="8dp"
- android:id="@+id/notification_guts_header"
- android:orientation="horizontal"
- android:layout_gravity="start">
-
- <ImageView
- android:id="@+id/app_icon"
- android:layout_width="18dp"
- android:layout_height="18dp"
- android:layout_marginEnd="6dp"
- android:src="@android:drawable/arrow_down_float" />
+ android:paddingBottom="15dp"
+ android:id="@+id/notification_guts_header">
<TextView
- android:id="@+id/pkgname"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.NotificationGuts.Header" />
+ android:id="@+id/pkgname"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ style="@style/TextAppearance.NotificationGuts.Secondary" />
<TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/debug_info"
- android:layout_weight="0"
- style="@style/TextAppearance.NotificationGuts.Header"
- android:layout_gravity="bottom|start"
- android:visibility="gone" />
- </LinearLayout>
+ android:id="@+id/channel_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/pkgname"
+ style="@style/TextAppearance.NotificationGuts.Header" />
+ <Switch
+ android:id="@+id/channel_enabled_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:background="@null" />
+ </RelativeLayout>
<!-- Importance radio buttons -->
- <RadioGroup
+ <LinearLayout
+ android:id="@+id/importance"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <RadioGroup
android:id="@+id/importance_buttons"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingStart="3dp"
- android:paddingTop="4dp"
- android:paddingEnd="8dp" >
- <RadioButton
- android:id="@+id/silent_importance"
+ android:paddingEnd="@*android:dimen/notification_content_margin_end">
+ <RadioButton
+ android:id="@+id/high_importance"
android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:paddingStart="22dp"
- android:text="@string/show_silently"
+ android:layout_height="@dimen/notification_inline_importance_height"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
- <RadioButton
- android:id="@+id/block_importance"
+ <RadioButton
+ android:id="@+id/default_importance"
android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:paddingStart="22dp"
- android:text="@string/block"
+ android:layout_height="@dimen/notification_inline_importance_height"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
- <RadioButton
- android:id="@+id/reset_importance"
+ <RadioButton
+ android:id="@+id/low_importance"
android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:paddingStart="22dp"
+ android:layout_height="@dimen/notification_inline_importance_height"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
- </RadioGroup>
- <!-- Importance slider -->
- <LinearLayout
- android:id="@+id/importance_slider"
+ <RadioButton
+ android:id="@+id/min_importance"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_inline_importance_height"
+ style="@style/TextAppearance.NotificationGuts.Radio"
+ android:buttonTint="@color/notification_guts_buttons" />
+ </RadioGroup>
+ <LinearLayout
+ android:id="@+id/importance_buttons_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:orientation="vertical"
- android:clickable="false"
- android:focusable="false"
- android:paddingEnd="8dp"
- android:paddingTop="4dp"
- android:visibility="gone">
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- style="@style/TextAppearance.NotificationGuts.Primary"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:paddingBottom="2dp"/>
-
- <TextView
- android:id="@+id/summary"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignStart="@android:id/title"
- android:textAlignment="viewStart"
- style="@style/TextAppearance.NotificationGuts.Secondary"
- android:maxLines="3"
- android:minLines="2"
- android:paddingBottom="4dp" />
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="8dp" >
-
- <ImageView
- android:id="@+id/auto_importance"
- android:src="@drawable/notification_auto_importance"
- android:layout_gravity="center_vertical|start"
- android:layout_width="48dp"
- android:layout_height="48dp" />
-
- <SeekBar
- android:id="@+id/seekbar"
- android:layout_marginStart="56dp"
- android:layout_marginEnd="32dp"
- android:layout_gravity="center_vertical"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:focusable="true"
- android:background="#00ffffff"
- android:progressBackgroundTint="@color/notification_guts_secondary_slider_color"
- android:thumbTint="?android:attr/colorAccent"
- android:progressTint="?android:attr/colorAccent"
- style="@android:style/Widget.Material.SeekBar.Discrete"
- android:tickMarkTint="@android:color/black" />
-
- </FrameLayout>
+ android:orientation="vertical">
+ <include layout="@layout/notification_guts_importance_text"/>
+ <include layout="@layout/notification_guts_importance_text"/>
+ <include layout="@layout/notification_guts_importance_text"/>
+ <include layout="@layout/notification_guts_importance_text"/>
+ </LinearLayout>
</LinearLayout>
- <!-- buttons -->
+ <!-- Channel Disabled Text -->
+ <TextView
+ android:id="@+id/channel_disabled"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/notification_channel_disabled"
+ style="@style/TextAppearance.NotificationGuts.Secondary" />
+ <!-- Settings and Done buttons -->
<LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="end"
- android:paddingTop="16dp"
- android:paddingBottom="8dp" >
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp" >
<TextView
android:id="@+id/more_settings"
diff --git a/packages/SystemUI/res/layout/notification_guts_importance_text.xml b/packages/SystemUI/res/layout/notification_guts_importance_text.xml
new file mode 100644
index 000000000000..5df4e0a96ba6
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_guts_importance_text.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_inline_importance_height"
+ android:paddingTop="4dp"
+ android:orientation="vertical">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:textAlignment="viewStart"
+ android:singleLine="true"
+ android:text="@string/high_importance"
+ android:textAppearance="@style/TextAppearance.NotificationGuts.Primary" />
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:textAlignment="viewStart"
+ android:singleLine="true"
+ android:text="@string/notification_importance_high"
+ android:textAppearance="@style/TextAppearance.NotificationGuts.Secondary" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ff4ec5bdaf3f..ca4214bda2cf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -302,6 +302,9 @@
<!-- The height of the divider between the individual notifications when the notification wants it to be increased. This is currently the case for notification groups -->
<dimen name="notification_divider_height_increased">6dp</dimen>
+ <!-- The height of an importance selection in the inline notification controls -->
+ <dimen name="notification_inline_importance_height">55dp</dimen>
+
<!-- The minimum amount of top overscroll to go to the quick settings. -->
<dimen name="min_top_overscroll_to_qs">36dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d3744789826d..194653ba15ce 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1334,41 +1334,38 @@
\n- Block all notifications from the app
</string>
- <!-- Notification importance title, user unspecified status-->
- <string name="user_unspecified_importance">Importance: Automatic</string>
- <!-- Notification importance title, blocked status-->
- <string name="blocked_importance">Importance: Level 0</string>
+ <!-- Notification Inline Controls: Header for apps that are not yet using notification channels. -->
+ <string name="notification_header_default_channel">Notifications</string>
+
+ <!-- Notification Inline Controls: Shown when a channel's notifications are currently blocked -->
+ <string name="notification_channel_disabled">You won\'t get these notifications anymore.</string>
+
+ <!-- Notification Inline Controls: Header text for describing from which app this notification
+ originates. The line below this in the layout will display the channel name.
+ Note for localization: For languages in which the two separate lines cannot be a continuous
+ sentence, translate this as a separate statement: "[Calendar] notifications" -->
+ <string name="notification_importance_header_app"><xliff:g id="app" example="Calendar">%s</xliff:g> notifications for</string>
+
<!-- Notification importance title, min status-->
- <string name="min_importance">Importance: Level 1</string>
+ <string name="min_importance">Low</string>
<!-- Notification importance title, low status-->
- <string name="low_importance">Importance: Level 2</string>
+ <string name="low_importance">Medium</string>
<!-- Notification importance title, normal status-->
- <string name="default_importance">Importance: Level 3</string>
+ <string name="default_importance">High</string>
<!-- Notification importance title, high status-->
- <string name="high_importance">Importance: Level 4</string>
- <!-- Notification importance title, max status-->
- <string name="max_importance">Importance: Level 5</string>
-
- <!-- [CHAR LIMIT=100] Notification Importance slider: blocked importance level description -->
- <string name="notification_importance_user_unspecified">App determines importance for each notification.</string>
-
- <!-- [CHAR LIMIT=100] Notification Importance slider: blocked importance level description -->
- <string name="notification_importance_blocked">Never show notifications from this app.</string>
+ <string name="high_importance">Urgent</string>
<!-- [CHAR LIMIT=100] Notification Importance slider: min importance level description -->
- <string name="notification_importance_min">No full screen interruption, peeking, sound, or vibration. Hide from lock screen and status bar.</string>
+ <string name="notification_importance_min">No sound or visual interruption</string>
<!-- [CHAR LIMIT=100] Notification Importance slider: low importance level description -->
- <string name="notification_importance_low">No full screen interruption, peeking, sound, or vibration.</string>
+ <string name="notification_importance_low">Show silently</string>
<!-- [CHAR LIMIT=100] Notification Importance slider: normal importance level description -->
- <string name="notification_importance_default">No full screen interruption or peeking.</string>
+ <string name="notification_importance_default">Make sound</string>
<!-- [CHAR LIMIT=100] Notification Importance slider: high importance level description -->
- <string name="notification_importance_high">Always peek. No full screen interruption.</string>
-
- <!-- [CHAR LIMIT=100] Notification Importance slider: max importance level description -->
- <string name="notification_importance_max">Always peek, and allow full screen interruption.</string>
+ <string name="notification_importance_high">Make sound and pop on screen</string>
<!-- Notification: Control panel: Label for button that launches notification settings. [CHAR LIMIT=NONE] -->
<string name="notification_more_settings">More settings</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 93b696591c1f..fc1d816993b7 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -4,9 +4,9 @@
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.
@@ -359,12 +359,14 @@
</style>
<style name="TextAppearance.NotificationGuts.Header">
- <item name="android:alpha">.38</item>
- <item name="android:textSize">12sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">20sp</item>
</style>
<style name="TextAppearance.NotificationGuts.Secondary">
- <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:alpha">.38</item>
+ <item name="android:textSize">12sp</item>
</style>
<style name="TextAppearance.NotificationGuts.Primary">
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 7a5e32227db1..d98bb23d88da 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -326,7 +326,8 @@ public class ExpandHelper implements Gefingerpoken {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (DEBUG) Log.d(TAG, "up/cancel");
- finishExpanding(false, getCurrentVelocity());
+ finishExpanding(ev.getActionMasked() == MotionEvent.ACTION_CANCEL,
+ getCurrentVelocity());
clearView();
break;
}
@@ -390,7 +391,8 @@ public class ExpandHelper implements Gefingerpoken {
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (!isEnabled()) {
+ if (!isEnabled() && !mExpanding) {
+ // In case we're expanding we still want to finish the current motion.
return false;
}
trackVelocity(ev);
@@ -485,7 +487,8 @@ public class ExpandHelper implements Gefingerpoken {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (DEBUG) Log.d(TAG, "up/cancel");
- finishExpanding(false, getCurrentVelocity());
+ finishExpanding(!isEnabled() || ev.getActionMasked() == MotionEvent.ACTION_CANCEL,
+ getCurrentVelocity());
clearView();
break;
}
@@ -526,28 +529,37 @@ public class ExpandHelper implements Gefingerpoken {
return true;
}
- private void finishExpanding(boolean force, float velocity) {
+ /**
+ * Finish the current expand motion
+ * @param forceAbort whether the expansion should be forcefully aborted and returned to the old
+ * state
+ * @param velocity the velocity this was expanded/ collapsed with
+ */
+ private void finishExpanding(boolean forceAbort, float velocity) {
if (!mExpanding) return;
if (DEBUG) Log.d(TAG, "scale in finishing on view: " + mResizedView);
float currentHeight = mScaler.getHeight();
- float h = mScaler.getHeight();
final boolean wasClosed = (mOldHeight == mSmallSize);
boolean nowExpanded;
- int naturalHeight = mScaler.getNaturalHeight();
- if (wasClosed) {
- nowExpanded = (force || currentHeight > mOldHeight && velocity >= 0);
+ if (!forceAbort) {
+ if (wasClosed) {
+ nowExpanded = currentHeight > mOldHeight && velocity >= 0;
+ } else {
+ nowExpanded = currentHeight >= mOldHeight || velocity > 0;
+ }
+ nowExpanded |= mNaturalHeight == mSmallSize;
} else {
- nowExpanded = !force && (currentHeight >= mOldHeight || velocity > 0);
+ nowExpanded = !wasClosed;
}
- nowExpanded |= mNaturalHeight == mSmallSize;
if (mScaleAnimation.isRunning()) {
mScaleAnimation.cancel();
}
mCallback.expansionStateChanged(false);
+ int naturalHeight = mScaler.getNaturalHeight();
float targetHeight = nowExpanded ? naturalHeight : mSmallSize;
- if (targetHeight != currentHeight) {
+ if (targetHeight != currentHeight && mEnabled) {
mScaleAnimation.setFloatValues(targetHeight);
mScaleAnimation.setupStartValues();
final View scaledView = mResizedView;
@@ -597,7 +609,7 @@ public class ExpandHelper implements Gefingerpoken {
* Use this to abort any pending expansions in progress.
*/
public void cancel() {
- finishExpanding(true, 0f /* velocity */);
+ finishExpanding(true /* forceAbort */, 0f /* velocity */);
clearView();
// reset the gesture detector
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 6b24a1eeef1e..9feaa0aaf820 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -351,7 +351,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback {
@Override
public void onAnnouncementRequested(CharSequence announcement) {
- announceForAccessibility(announcement);
+ if (announcement != null) {
+ mHandler.obtainMessage(H.ANNOUNCE_FOR_ACCESSIBILITY, announcement)
+ .sendToTarget();
+ }
}
};
r.tile.addCallback(callback);
@@ -526,10 +529,13 @@ public class QSPanel extends LinearLayout implements Tunable, Callback {
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
+ private static final int ANNOUNCE_FOR_ACCESSIBILITY = 3;
@Override
public void handleMessage(Message msg) {
if (msg.what == SHOW_DETAIL) {
handleShowDetail((Record)msg.obj, msg.arg1 != 0);
+ } else if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) {
+ announceForAccessibility((CharSequence)msg.obj);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index cce15387c425..7547bc37d424 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -204,6 +204,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
Resources res = mContext.getResources();
reloadResources();
mDummyStackView.reloadOnConfigurationChange();
+ mDummyStackView.getStackAlgorithm().getGridState().setHasDockedTasks(
+ Recents.getSystemServices().hasDockedTask());
}
/**
@@ -721,7 +723,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
if (task.isFreeformTask()) {
mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
stackScroller.getStackScroll(), mTmpTransform, null,
- windowOverrideRect, false /* useGridLayout */);
+ windowOverrideRect);
Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform,
mThumbTransitionBitmapCache);
Rect toTaskRect = new Rect();
@@ -772,8 +774,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
stackView.updateToInitialState();
boolean isInSplitScreen = Recents.getSystemServices().hasDockedTask();
stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
- stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect,
- Recents.getConfiguration().isGridEnabled && !isInSplitScreen);
+ stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
return mTmpTransform;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index d8fdd7a281e8..ee79330f816a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -91,6 +91,7 @@ public class RecentsView extends FrameLayout {
private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
private static final float DEFAULT_SCRIM_ALPHA = 0.33f;
+ private static final float GRID_LAYOUT_SCRIM_ALPHA = 0.45f;
private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134;
private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
@@ -106,8 +107,8 @@ public class RecentsView extends FrameLayout {
Rect mSystemInsets = new Rect();
private int mDividerSize;
- private Drawable mBackgroundScrim = new ColorDrawable(
- Color.argb((int) (DEFAULT_SCRIM_ALPHA * 255), 0, 0, 0)).mutate();
+ private final float mScrimAlpha;
+ private final Drawable mBackgroundScrim;
private Animator mBackgroundScrimAnimator;
private RecentsTransitionHelper mTransitionHelper;
@@ -136,6 +137,10 @@ public class RecentsView extends FrameLayout {
mDividerSize = ssp.getDockedDividerSize(context);
mTouchHandler = new RecentsViewTouchHandler(this);
mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
+ mScrimAlpha = Recents.getConfiguration().isGridEnabled
+ ? GRID_LAYOUT_SCRIM_ALPHA : DEFAULT_SCRIM_ALPHA;
+ mBackgroundScrim = new ColorDrawable(
+ Color.argb((int) (mScrimAlpha * 255), 0, 0, 0)).mutate();
LayoutInflater inflater = LayoutInflater.from(context);
if (RecentsDebugFlags.Static.EnableStackActionButton) {
@@ -337,8 +342,7 @@ public class RecentsView extends FrameLayout {
if (RecentsDebugFlags.Static.EnableStackActionButton) {
// Measure the stack action button within the constraints of the space above the stack
- Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(
- mTaskStackView.useGridLayout());
+ Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
measureChild(mStackActionButton,
MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
@@ -758,7 +762,7 @@ public class RecentsView extends FrameLayout {
private void animateBackgroundScrim(float alpha, int duration) {
Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator);
// Calculate the absolute alpha to animate from
- int fromAlpha = (int) ((mBackgroundScrim.getAlpha() / (DEFAULT_SCRIM_ALPHA * 255)) * 255);
+ int fromAlpha = (int) ((mBackgroundScrim.getAlpha() / (mScrimAlpha * 255)) * 255);
int toAlpha = (int) (alpha * 255);
mBackgroundScrimAnimator = ObjectAnimator.ofInt(mBackgroundScrim, Utilities.DRAWABLE_ALPHA,
fromAlpha, toAlpha);
@@ -773,8 +777,7 @@ public class RecentsView extends FrameLayout {
* @return the bounds of the stack action button.
*/
private Rect getStackActionButtonBoundsFromStackLayout() {
- Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(
- mTaskStackView.useGridLayout()));
+ Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
int left = isLayoutRtl()
? actionButtonRect.left - mStackActionButton.getPaddingLeft()
: actionButtonRect.right + mStackActionButton.getPaddingRight()
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index c1f4c8a4c1dd..493e6187c83e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -157,7 +157,7 @@ public class TaskStackAnimationHelper {
// Get the current transform for the task, which will be used to position it offscreen
stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
- null, mStackView.useGridLayout());
+ null);
if (hideTask) {
tv.setVisibility(View.INVISIBLE);
@@ -230,7 +230,7 @@ public class TaskStackAnimationHelper {
// Get the current transform for the task, which will be updated to the final transform
// to animate to depending on how recents was invoked
stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
- null, mStackView.useGridLayout());
+ null);
if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
if (task.isLaunchTarget) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 052985647453..be1844d10043 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -212,6 +212,41 @@ public class TaskStackLayoutAlgorithm {
}
}
+ /**
+ * The state telling the algorithm whether to use grid layout or not.
+ */
+ public static class GridState {
+ private boolean mDraggingOverDockedState;
+ private boolean mHasDockedTask;
+
+ private GridState() {
+ mDraggingOverDockedState = false;
+ mHasDockedTask = false;
+ }
+
+ /**
+ * Check whether we should use the grid layout.
+ * We use the grid layout for Recents iff all the following is true:
+ * 1. Grid-mode is enabled.
+ * 2. The activity is not in split screen mode (there's no docked task).
+ * 3. The user is not dragging a task view over the dock state.
+ * @return True if we should use the grid layout.
+ */
+ boolean useGridLayout() {
+ return Recents.getConfiguration().isGridEnabled &&
+ !mDraggingOverDockedState &&
+ !mHasDockedTask;
+ }
+
+ public void setDragging(boolean draggingOverDockedState) {
+ mDraggingOverDockedState = draggingOverDockedState;
+ }
+
+ public void setHasDockedTasks(boolean hasDockedTask) {
+ mHasDockedTask = hasDockedTask;
+ }
+ }
+
// A report of the visibility state of the stack
public class VisibilityReport {
public int numVisibleTasks;
@@ -226,6 +261,7 @@ public class TaskStackLayoutAlgorithm {
Context mContext;
private StackState mState = StackState.SPLIT;
+ private GridState mGridState = new GridState();
private TaskStackLayoutAlgorithmCallbacks mCb;
// The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
@@ -732,8 +768,8 @@ public class TaskStackLayoutAlgorithm {
}
}
- public Rect getStackActionButtonRect(boolean useGridLayout) {
- return useGridLayout
+ public Rect getStackActionButtonRect() {
+ return mGridState.useGridLayout()
? mTaskGridLayoutAlgorithm.getStackActionButtonRect() : mStackActionButtonRect;
}
@@ -759,6 +795,13 @@ public class TaskStackLayoutAlgorithm {
}
/**
+ * Returns the current grid layout state.
+ */
+ public GridState getGridState() {
+ return mGridState;
+ }
+
+ /**
* Returns whether this stack layout has been initialized.
*/
public boolean isInitialized() {
@@ -841,26 +884,25 @@ public class TaskStackLayoutAlgorithm {
* is what the view is measured and laid out with.
*/
public TaskViewTransform getStackTransform(Task task, float stackScroll,
- TaskViewTransform transformOut, TaskViewTransform frontTransform,
- boolean useGridLayout) {
+ TaskViewTransform transformOut, TaskViewTransform frontTransform) {
return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
- false /* forceUpdate */, false /* ignoreTaskOverrides */, useGridLayout);
+ false /* forceUpdate */, false /* ignoreTaskOverrides */);
}
public TaskViewTransform getStackTransform(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform frontTransform,
- boolean ignoreTaskOverrides, boolean useGridLayout) {
+ boolean ignoreTaskOverrides) {
return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
- false /* forceUpdate */, ignoreTaskOverrides, useGridLayout);
+ false /* forceUpdate */, ignoreTaskOverrides);
}
public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
- boolean ignoreTaskOverrides, boolean useGridLayout) {
+ boolean ignoreTaskOverrides) {
if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
return transformOut;
- } else if (useGridLayout) {
+ } else if (mGridState.useGridLayout()) {
int taskIndex = mTaskIndexMap.get(task.key.id);
int taskCount = mTaskIndexMap.size();
mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this);
@@ -886,10 +928,10 @@ public class TaskStackLayoutAlgorithm {
*/
public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform frontTransform,
- Rect windowOverrideRect, boolean useGridLayout) {
+ Rect windowOverrideRect) {
TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
transformOut, frontTransform, true /* forceUpdate */,
- false /* ignoreTaskOverrides */, useGridLayout);
+ false /* ignoreTaskOverrides */);
return transformToScreenCoordinates(transform, windowOverrideRect);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 9e98962a30c3..375f01ee08d3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -159,7 +159,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
private int mTaskCornerRadiusPx;
private int mDividerSize;
private int mStartTimerIndicatorDuration;
- private boolean mDraggingOverDockState;
@ViewDebug.ExportedProperty(category="recents")
private boolean mTaskViewsClipDirty = true;
@@ -501,13 +500,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Calculate the current and (if necessary) the target transform for the task
transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
- taskTransforms.get(i), frontTransform, ignoreTaskOverrides, useGridLayout());
+ taskTransforms.get(i), frontTransform, ignoreTaskOverrides);
if (useTargetStackScroll && !transform.visible) {
// If we have a target stack scroll and the task is not currently visible, then we
// just update the transform at the new scroll
// TODO: Optimize this
transformAtTarget = mLayoutAlgorithm.getStackTransform(task, targetStackScroll,
- new TaskViewTransform(), frontTransformAtTarget, useGridLayout());
+ new TaskViewTransform(), frontTransformAtTarget);
if (transformAtTarget.visible) {
transform.copyFrom(transformAtTarget);
}
@@ -738,7 +737,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
} else {
mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
focusState, transform, null, true /* forceUpdate */,
- false /* ignoreTaskOverrides */, useGridLayout());
+ false /* ignoreTaskOverrides */);
}
transform.visible = true;
}
@@ -755,7 +754,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Task task = tasks.get(i);
TaskViewTransform transform = transformsOut.get(i);
mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null,
- true /* forceUpdate */, ignoreTaskOverrides, useGridLayout());
+ true /* forceUpdate */, ignoreTaskOverrides);
transform.visible = true;
}
}
@@ -1598,7 +1597,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// If the doze trigger has already fired, then update the state for this task view
if (mUIDozeTrigger.isAsleep() ||
- Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
+ Recents.getSystemServices().hasFreeformWorkspaceSupport() ||
+ useGridLayout()) {
tv.setNoUserInteractionState();
}
@@ -1835,7 +1835,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Enlarge the dragged view slightly
float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR;
mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
- mTmpTransform, null, useGridLayout());
+ mTmpTransform, null);
mTmpTransform.scale = finalScale;
mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
mTmpTransform.dimAlpha = 0f;
@@ -1856,7 +1856,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Interpolators.FAST_OUT_SLOW_IN);
boolean ignoreTaskOverrides = false;
if (event.dropTarget instanceof TaskStack.DockState) {
- mDraggingOverDockState = true;
+ mLayoutAlgorithm.getGridState().setDragging(true);
// Calculate the new task stack bounds that matches the window size that Recents will
// have after the drop
final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
@@ -1876,7 +1876,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
updateLayoutAlgorithm(true /* boundScroll */);
ignoreTaskOverrides = true;
} else {
- mDraggingOverDockState = false;
+ mLayoutAlgorithm.getGridState().setDragging(false);
// Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
// task view, so add it back to the ignore set after updating the layout
removeIgnoreTask(event.task);
@@ -1887,7 +1887,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
public final void onBusEvent(final DragEndEvent event) {
- mDraggingOverDockState = false;
+ mLayoutAlgorithm.getGridState().setDragging(false);
// We don't handle drops on the dock regions
if (event.dropTarget instanceof TaskStack.DockState) {
// However, we do need to reset the overrides, since the last state of this task stack
@@ -2077,6 +2077,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public void reloadOnConfigurationChange() {
mStableLayoutAlgorithm.reloadOnConfigurationChange(getContext());
mLayoutAlgorithm.reloadOnConfigurationChange(getContext());
+
+ boolean hasDockedTask = Recents.getSystemServices().hasDockedTask();
+ mStableLayoutAlgorithm.getGridState().setHasDockedTasks(hasDockedTask);
+ mLayoutAlgorithm.getGridState().setHasDockedTasks(hasDockedTask);
}
/**
@@ -2131,16 +2135,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/**
* Check whether we should use the grid layout.
- * We use the grid layout for Recents iff all the following is true:
- * 1. Grid-mode is enabled.
- * 2. The activity is not in split screen mode (there's no docked task).
- * 3. The user is not dragging a task view over the dock state.
- * @return True if we should use the grid layout.
*/
public boolean useGridLayout() {
- return Recents.getConfiguration().isGridEnabled
- && !Recents.getSystemServices().hasDockedTask()
- && !mDraggingOverDockState;
+ return mLayoutAlgorithm.getGridState().useGridLayout();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
index 65a8ee27c7a1..714b6fb0c859 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -52,6 +52,69 @@ public class TaskGridLayoutAlgorithm {
private float mAppAspectRatio;
private Rect mSystemInsets = new Rect();
+ /**
+ * When the amount of tasks is determined, the size and position of every task view can be
+ * decided. Each instance of TaskGridRectInfo store the task view information for a certain
+ * amount of tasks.
+ */
+ class TaskGridRectInfo {
+ Rect size;
+ int[] xOffsets;
+ int[] yOffsets;
+
+ public TaskGridRectInfo(int taskCount) {
+ size = new Rect();
+ xOffsets = new int[taskCount];
+ yOffsets = new int[taskCount];
+
+ int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount);
+
+ int tasksPerLine = layoutTaskCount < 2 ? 1 : (
+ layoutTaskCount < 5 ? 2 : (
+ layoutTaskCount < 7 ? 3 : 4));
+ int lines = layoutTaskCount < 3 ? 1 : 2;
+
+ int taskWidth, taskHeight;
+ int maxTaskWidth = (mDisplayRect.width() - 2 * mPaddingLeftRight
+ - (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine;
+ int maxTaskHeight = (mDisplayRect.height() - 2 * mPaddingTopBottom
+ - (lines - 1) * mPaddingTaskView) / lines;
+
+ if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) {
+ // Width bound.
+ taskWidth = maxTaskWidth;
+ taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight);
+ } else {
+ // Height bound.
+ taskHeight = maxTaskHeight;
+ taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio);
+ }
+ size.set(0, 0, taskWidth, taskHeight);
+
+ int emptySpaceX = mDisplayRect.width() - 2 * mPaddingLeftRight
+ - (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView;
+ int emptySpaceY = mDisplayRect.height() - 2 * mPaddingTopBottom
+ - (lines * taskHeight) - (lines - 1) * mPaddingTaskView;
+ for (int taskIndex = 0; taskIndex < taskCount; taskIndex++) {
+ // We also need to invert the index in order to display the most recent tasks first.
+ int taskLayoutIndex = taskCount - taskIndex - 1;
+
+ int xIndex = taskLayoutIndex % tasksPerLine;
+ int yIndex = taskLayoutIndex / tasksPerLine;
+ xOffsets[taskIndex] =
+ emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex;
+ yOffsets[taskIndex] =
+ emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex;
+ }
+ }
+ }
+
+ /**
+ * We can find task view sizes and positions from mTaskGridRectInfoList[k - 1] when there
+ * are k tasks.
+ */
+ TaskGridRectInfo[] mTaskGridRectInfoList;
+
public TaskGridLayoutAlgorithm(Context context) {
reloadOnConfigurationChange(context);
}
@@ -75,46 +138,17 @@ public class TaskGridLayoutAlgorithm {
public TaskViewTransform getTransform(int taskIndex, int taskCount,
TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
- int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount);
-
- // We also need to invert the index in order to display the most recent tasks first.
- int taskLayoutIndex = taskCount - taskIndex - 1;
-
- int tasksPerLine = layoutTaskCount < 2 ? 1 : (
- layoutTaskCount < 5 ? 2 : (
- layoutTaskCount < 7 ? 3 : 4));
- int lines = layoutTaskCount < 3 ? 1 : 2;
-
- int taskWidth, taskHeight;
- int maxTaskWidth = (mDisplayRect.width() - 2 * mPaddingLeftRight
- - (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine;
- int maxTaskHeight = (mDisplayRect.height() - 2 * mPaddingTopBottom
- - (lines - 1) * mPaddingTaskView) / lines;
-
- if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) {
- // Width bound.
- taskWidth = maxTaskWidth;
- taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight);
- } else {
- // Height bound.
- taskHeight = maxTaskHeight;
- taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio);
- }
- int emptySpaceX = mDisplayRect.width() - 2 * mPaddingLeftRight
- - (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView;
- int emptySpaceY = mDisplayRect.height() - 2 * mPaddingTopBottom
- - (lines * taskHeight) - (lines - 1) * mPaddingTaskView;
-
- mTaskGridRect.set(0, 0, taskWidth, taskHeight);
+ TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
+ mTaskGridRect.set(gridInfo.size);
- int xIndex = taskLayoutIndex % tasksPerLine;
- int yIndex = taskLayoutIndex / tasksPerLine;
- int x = emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex;
- int y = emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex;
+ int x = gridInfo.xOffsets[taskIndex];
+ int y = gridInfo.yOffsets[taskIndex];
float z = stackLayout.mMaxTranslationZ;
float dimAlpha = 0f;
float viewOutlineAlpha = 0f;
+ // We also need to invert the index in order to display the most recent tasks first.
+ int taskLayoutIndex = taskCount - taskIndex - 1;
boolean isTaskViewVisible = (taskLayoutIndex < MAX_LAYOUT_TASK_COUNT);
// Fill out the transform
@@ -134,6 +168,13 @@ public class TaskGridLayoutAlgorithm {
public void initialize(Rect displayRect, Rect windowRect) {
mDisplayRect = displayRect;
mWindowRect = windowRect;
+
+ // Pre-calculate the positions and offsets of task views so that we can reuse them directly
+ // in the future.
+ mTaskGridRectInfoList = new TaskGridRectInfo[MAX_LAYOUT_TASK_COUNT];
+ for (int i = 0; i < MAX_LAYOUT_TASK_COUNT; i++) {
+ mTaskGridRectInfoList[i] = new TaskGridRectInfo(i + 1);
+ }
}
public void setSystemInsets(Rect systemInsets) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 352d26271035..80b43e6e813b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter;
import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
+import android.app.INotificationManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -1058,74 +1059,43 @@ public abstract class BaseStatusBar extends SystemUI implements
row.setTag(sbn.getPackageName());
final NotificationGuts guts = row.getGuts();
guts.setClosedListener(this);
+
+ final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+
final String pkg = sbn.getPackageName();
- String appname = pkg;
- Drawable pkgicon = null;
- int appUid = -1;
- try {
- final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS);
- if (info != null) {
- appname = String.valueOf(pmUser.getApplicationLabel(info));
- pkgicon = pmUser.getApplicationIcon(info);
- appUid = info.uid;
- }
- } catch (NameNotFoundException e) {
- // app is gone, just show package name and generic icon
- pkgicon = pmUser.getDefaultActivityIcon();
- }
-
- ((ImageView) guts.findViewById(R.id.app_icon)).setImageDrawable(pkgicon);
- ((TextView) guts.findViewById(R.id.pkgname)).setText(appname);
-
- final TextView settingsButton = (TextView) guts.findViewById(R.id.more_settings);
- if (appUid >= 0) {
- final int appUidF = appUid;
- settingsButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
+ final NotificationGuts.OnSettingsClickListener onSettingsClick =
+ (View v, int appUid) -> {
MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
guts.resetFalsingCheck();
- startAppNotificationSettingsActivity(pkg, appUidF);
- }
- });
- settingsButton.setText(R.string.notification_more_settings);
- } else {
- settingsButton.setVisibility(View.GONE);
- }
-
- guts.bindImportance(pmUser, sbn, mNonBlockablePkgs,
- mNotificationData.getImportance(sbn.getKey()));
-
- final TextView doneButton = (TextView) guts.findViewById(R.id.done);
- doneButton.setText(R.string.notification_done);
- doneButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // If the user has security enabled, show challenge if the setting is changed.
- if (guts.hasImportanceChanged()
- && isLockscreenPublicMode(sbn.getUser().getIdentifier())
- && (mState == StatusBarState.KEYGUARD
- || mState == StatusBarState.SHADE_LOCKED)) {
- OnDismissAction dismissAction = new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- saveImportanceCloseControls(sbn, row, guts, v);
- return true;
- }
- };
- onLockedNotificationImportanceChange(dismissAction);
- } else {
- saveImportanceCloseControls(sbn, row, guts, v);
- }
- }
- });
+ startAppNotificationSettingsActivity(pkg, appUid);
+ };
+ final View.OnClickListener onDoneClick =
+ (View v) -> {
+ // If the user has security enabled, show challenge if the setting is changed.
+ if (guts.hasImportanceChanged()
+ && isLockscreenPublicMode(sbn.getUser().getIdentifier())
+ && (mState == StatusBarState.KEYGUARD
+ || mState == StatusBarState.SHADE_LOCKED)) {
+ OnDismissAction dismissAction = new OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ closeControls(row, guts, v);
+ return true;
+ }
+ };
+ onLockedNotificationImportanceChange(dismissAction);
+ } else {
+ closeControls(row, guts, v);
+ }
+ };
+ guts.bindNotification(pmUser, iNotificationManager, sbn, onSettingsClick, onDoneClick,
+ mNonBlockablePkgs);
}
- private void saveImportanceCloseControls(StatusBarNotification sbn,
+ private void closeControls(
ExpandableNotificationRow row, NotificationGuts guts, View done) {
guts.resetFalsingCheck();
- guts.saveImportance(sbn);
int[] rowLocation = new int[2];
int[] doneLocation = new int[2];
@@ -1222,7 +1192,7 @@ public abstract class BaseStatusBar extends SystemUI implements
public void dismissPopups(int x, int y, boolean resetGear, boolean animate) {
if (mNotificationGutsExposed != null) {
- mNotificationGutsExposed.closeControls(x, y, true /* notify */);
+ mNotificationGutsExposed.closeControls(x, y, true /* save */);
}
if (resetGear) {
mStackScroller.resetExposedGearView(animate, true /* force */);
@@ -1722,13 +1692,6 @@ public abstract class BaseStatusBar extends SystemUI implements
}
entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
- if (MULTIUSER_DEBUG) {
- TextView debug = (TextView) row.findViewById(R.id.debug_info);
- if (debug != null) {
- debug.setVisibility(View.VISIBLE);
- debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId());
- }
- }
entry.row = row;
entry.row.setOnActivatedListener(this);
entry.row.setExpandable(bigContentViewLocal != null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 2dabf5d32016..19132da9b3bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -228,6 +228,10 @@ public class DragDownHelper implements Gefingerpoken {
return mCallback.getChildAtRawPosition(x, y);
}
+ public boolean isDraggingDown() {
+ return mDraggingDown;
+ }
+
public interface DragDownCallback {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index 19b32af8b919..92b0890a5d2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -28,6 +28,8 @@ import com.android.systemui.statusbar.stack.StackScrollState;
public class EmptyShadeView extends StackScrollerDecorView {
+ private TextView mEmptyText;
+
public EmptyShadeView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -35,7 +37,7 @@ public class EmptyShadeView extends StackScrollerDecorView {
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- ((TextView) findViewById(R.id.no_notifications)).setText(R.string.empty_shade_text);
+ mEmptyText.setText(R.string.empty_shade_text);
}
@Override
@@ -44,17 +46,23 @@ public class EmptyShadeView extends StackScrollerDecorView {
}
@Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mEmptyText = (TextView) findContentView();
+ }
+
+ @Override
public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
return new EmptyShadeViewState();
}
- public static class EmptyShadeViewState extends ExpandableViewState {
+ public 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;
+ boolean visible = this.clipTopAmount <= mEmptyText.getPaddingTop() * 0.6f;
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 077c084acc01..8980580e4378 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -1338,7 +1338,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
if (isUserLocked()) {
return getActualHeight();
}
- if (mGuts != null && mGuts.areGutsExposed()) {
+ if (mGuts != null && mGuts.isExposed()) {
return mGuts.getHeight();
} else if ((isChildInGroup() && !isGroupExpanded())) {
return mPrivateLayout.getMinHeight();
@@ -1589,7 +1589,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
public boolean areGutsExposed() {
- return (mGuts != null && mGuts.areGutsExposed());
+ return (mGuts != null && mGuts.isExposed());
}
@Override
@@ -1634,7 +1634,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
@Override
public void setActualHeight(int height, boolean notifyListeners) {
super.setActualHeight(height, notifyListeners);
- if (mGuts != null && mGuts.areGutsExposed()) {
+ if (mGuts != null && mGuts.isExposed()) {
mGuts.setActualHeight(height);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 58b828456b6a..088f5382b684 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -19,8 +19,10 @@ package com.android.systemui.statusbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.INotificationManager;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
@@ -33,13 +35,17 @@ import android.os.ServiceManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.SeekBar;
+import android.widget.Switch;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
@@ -48,16 +54,14 @@ import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.stack.StackStateAnimator;
-import com.android.systemui.tuner.TunerService;
import java.util.Set;
/**
* The guts of a notification revealed when performing a long press.
*/
-public class NotificationGuts extends LinearLayout implements TunerService.Tunable {
- public static final String SHOW_SLIDER = "show_importance_slider";
-
+public class NotificationGuts extends LinearLayout {
+ private static final String TAG = "NotificationGuts";
private static final long CLOSE_GUTS_DELAY = 8000;
private Drawable mBackground;
@@ -67,22 +71,20 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab
private boolean mExposed;
private INotificationManager mINotificationManager;
private int mStartingUserImportance;
- private int mNotificationImportance;
- private boolean mShowSlider;
+ private StatusBarNotification mStatusBarNotification;
- private SeekBar mSeekBar;
private ImageView mAutoButton;
- private ColorStateList mActiveSliderTint;
- private ColorStateList mInactiveSliderTint;
- private float mActiveSliderAlpha = 1.0f;
- private float mInactiveSliderAlpha;
private TextView mImportanceSummary;
private TextView mImportanceTitle;
private boolean mAuto;
- private RadioButton mBlock;
- private RadioButton mSilent;
- private RadioButton mReset;
+ private View mImportanceGroup;
+ private View mChannelDisabled;
+ private Switch mChannelEnabledSwitch;
+ private RadioButton mMinImportanceButton;
+ private RadioButton mLowImportanceButton;
+ private RadioButton mDefaultImportanceButton;
+ private RadioButton mHighImportanceButton;
private Handler mHandler;
private Runnable mFalsingCheck;
@@ -101,29 +103,15 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab
@Override
public void run() {
if (mNeedsFalsingProtection && mExposed) {
- closeControls(-1 /* x */, -1 /* y */, true /* notify */);
+ closeControls(-1 /* x */, -1 /* y */, false /* save */);
}
}
};
final TypedArray ta =
context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Theme, 0, 0);
- mInactiveSliderAlpha =
- ta.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f);
ta.recycle();
}
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- TunerService.get(mContext).addTunable(this, SHOW_SLIDER);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- TunerService.get(mContext).removeTunable(this);
- super.onDetachedFromWindow();
- }
-
public void resetFalsingCheck() {
mHandler.removeCallbacks(mFalsingCheck);
if (mNeedsFalsingProtection && mExposed) {
@@ -177,203 +165,213 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab
}
}
- void bindImportance(final PackageManager pm, final StatusBarNotification sbn,
- final Set<String> nonBlockablePkgs, final int importance) {
- mINotificationManager = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
- mStartingUserImportance = NotificationManager.IMPORTANCE_UNSPECIFIED;
- mNotificationImportance = importance;
+ interface OnSettingsClickListener {
+ void onClick(View v, int appUid);
+ }
+
+ void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
+ final StatusBarNotification sbn, OnSettingsClickListener onSettingsClick,
+ OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
+ mINotificationManager = iNotificationManager;
+ mStatusBarNotification = sbn;
+ final NotificationChannel channel = sbn.getNotificationChannel();
+ mStartingUserImportance = channel.getImportance();
+
+ final String pkg = sbn.getPackageName();
+ int appUid = -1;
+ String appname = pkg;
+ Drawable pkgicon = null;
+ try {
+ final ApplicationInfo info = pm.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (info != null) {
+ appUid = info.uid;
+ appname = String.valueOf(pm.getApplicationLabel(info));
+ pkgicon = pm.getApplicationIcon(info);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // app is gone, just show package name and generic icon
+ pkgicon = pm.getDefaultActivityIcon();
+ }
+
+ // If this is the placeholder channel, don't use our channel-specific text.
+ String appNameText;
+ CharSequence channelNameText;
+ if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ appNameText = appname;
+ channelNameText = mContext.getString(R.string.notification_header_default_channel);
+ } else {
+ appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
+ channelNameText = channel.getName();
+ }
+ ((TextView) findViewById(R.id.pkgname)).setText(appNameText);
+ ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
+
+ // Settings button.
+ final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
+ if (appUid >= 0 && onSettingsClick != null) {
+ final int appUidF = appUid;
+ settingsButton.setOnClickListener(
+ (View view) -> { onSettingsClick.onClick(view, appUidF); });
+ settingsButton.setText(R.string.notification_more_settings);
+ } else {
+ settingsButton.setVisibility(View.GONE);
+ }
+
+ // Done button.
+ final TextView doneButton = (TextView) findViewById(R.id.done);
+ doneButton.setText(R.string.notification_done);
+ doneButton.setOnClickListener(onDoneClick);
+
boolean nonBlockable = false;
try {
- final PackageInfo info =
- pm.getPackageInfo(sbn.getPackageName(), PackageManager.GET_SIGNATURES);
+ final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
} catch (PackageManager.NameNotFoundException e) {
// unlikely.
}
if (nonBlockablePkgs != null) {
- nonBlockable |= nonBlockablePkgs.contains(sbn.getPackageName());
+ nonBlockable |= nonBlockablePkgs.contains(pkg);
}
- final View importanceSlider = findViewById(R.id.importance_slider);
final View importanceButtons = findViewById(R.id.importance_buttons);
- if (mShowSlider) {
- bindSlider(importanceSlider, nonBlockable);
- importanceSlider.setVisibility(View.VISIBLE);
- importanceButtons.setVisibility(View.GONE);
- } else {
- bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
- importanceButtons.setVisibility(View.VISIBLE);
- importanceSlider.setVisibility(View.GONE);
+ bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
+
+ // Importance Text (hardcoded to 4 importance levels)
+ final ViewGroup importanceTextGroup =
+ (ViewGroup) findViewById(R.id.importance_buttons_text);
+ final int size = importanceTextGroup.getChildCount();
+ for (int i = 0; i < size; i++) {
+ int importanceNameResId = 0;
+ int importanceDescResId = 0;
+ switch (i) {
+ case 0:
+ importanceNameResId = R.string.high_importance;
+ importanceDescResId = R.string.notification_importance_high;
+ break;
+ case 1:
+ importanceNameResId = R.string.default_importance;
+ importanceDescResId = R.string.notification_importance_default;
+ break;
+ case 2:
+ importanceNameResId = R.string.low_importance;
+ importanceDescResId = R.string.notification_importance_low;
+ break;
+ case 3:
+ importanceNameResId = R.string.min_importance;
+ importanceDescResId = R.string.notification_importance_min;
+ break;
+ default:
+ Log.e(TAG, "Too many importance groups in this layout.");
+ break;
+ }
+ final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
+ ((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
+ ((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
}
+
+ // Top-level importance group
+ mImportanceGroup = findViewById(R.id.importance);
+ mChannelDisabled = findViewById(R.id.channel_disabled);
+ updateImportanceGroup();
}
public boolean hasImportanceChanged() {
return mStartingUserImportance != getSelectedImportance();
}
- void saveImportance(final StatusBarNotification sbn) {
- int progress = getSelectedImportance();
+ private void saveImportance() {
+ int selectedImportance = getSelectedImportance();
+ if (selectedImportance == mStartingUserImportance) {
+ return;
+ }
+ final NotificationChannel channel = mStatusBarNotification.getNotificationChannel();
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
- progress - mStartingUserImportance);
+ selectedImportance - mStartingUserImportance);
+ channel.setImportance(selectedImportance);
+ try {
+ mINotificationManager.updateNotificationChannelForPackage(
+ mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
+ channel);
+ } catch (RemoteException e) {
+ // :(
+ }
}
private int getSelectedImportance() {
- if (mSeekBar!= null && mSeekBar.isShown()) {
- if (mSeekBar.isEnabled()) {
- return mSeekBar.getProgress();
- } else {
- return NotificationManager.IMPORTANCE_UNSPECIFIED;
- }
+ if (!mChannelEnabledSwitch.isChecked()) {
+ return NotificationManager.IMPORTANCE_NONE;
+ } else if (mMinImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_MIN;
+ } else if (mLowImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_LOW;
+ } else if (mDefaultImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_DEFAULT;
+ } else if (mHighImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_HIGH;
} else {
- if (mBlock.isChecked()) {
- return NotificationManager.IMPORTANCE_NONE;
- } else if (mSilent.isChecked()) {
- return NotificationManager.IMPORTANCE_LOW;
- } else {
- return NotificationManager.IMPORTANCE_UNSPECIFIED;
- }
+ return NotificationManager.IMPORTANCE_NONE;
}
}
private void bindToggles(final View importanceButtons, final int importance,
final boolean nonBlockable) {
- ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
- new RadioGroup.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(RadioGroup group, int checkedId) {
- resetFalsingCheck();
- }
- });
- mBlock = (RadioButton) importanceButtons.findViewById(R.id.block_importance);
- mSilent = (RadioButton) importanceButtons.findViewById(R.id.silent_importance);
- mReset = (RadioButton) importanceButtons.findViewById(R.id.reset_importance);
- if (nonBlockable) {
- mBlock.setVisibility(View.GONE);
- mReset.setText(mContext.getString(R.string.do_not_silence));
- } else {
- mReset.setText(mContext.getString(R.string.do_not_silence_block));
- }
- mBlock.setText(mContext.getString(R.string.block));
- mSilent.setText(mContext.getString(R.string.show_silently));
- if (importance == NotificationManager.IMPORTANCE_LOW) {
- mSilent.setChecked(true);
- } else {
- mReset.setChecked(true);
- }
- }
-
- private void bindSlider(final View importanceSlider, final boolean nonBlockable) {
- mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
- mInactiveSliderTint = loadColorStateList(R.color.notification_guts_disabled_slider_color);
-
- mImportanceSummary = ((TextView) importanceSlider.findViewById(R.id.summary));
- mImportanceTitle = ((TextView) importanceSlider.findViewById(R.id.title));
- mSeekBar = (SeekBar) importanceSlider.findViewById(R.id.seekbar);
-
- final int minProgress = nonBlockable ?
- NotificationManager.IMPORTANCE_MIN
- : NotificationManager.IMPORTANCE_NONE;
- mSeekBar.setMax(NotificationManager.IMPORTANCE_HIGH);
- mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- resetFalsingCheck();
- if (progress < minProgress) {
- seekBar.setProgress(minProgress);
- progress = minProgress;
- }
- updateTitleAndSummary(progress);
- if (fromUser) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_MODIFY_IMPORTANCE_SLIDER);
- }
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- resetFalsingCheck();
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- // no-op
- }
-
-
- });
- mSeekBar.setProgress(mNotificationImportance);
-
- mAutoButton = (ImageView) importanceSlider.findViewById(R.id.auto_importance);
- mAutoButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mAuto = !mAuto;
- applyAuto();
- }
- });
- mAuto = mStartingUserImportance == NotificationManager.IMPORTANCE_UNSPECIFIED;
- applyAuto();
- }
-
- private void applyAuto() {
- mSeekBar.setEnabled(!mAuto);
-
- final ColorStateList starTint = mAuto ? mActiveSliderTint : mInactiveSliderTint;
- final float alpha = mAuto ? mInactiveSliderAlpha : mActiveSliderAlpha;
- Drawable icon = mAutoButton.getDrawable().mutate();
- icon.setTintList(starTint);
- mAutoButton.setImageDrawable(icon);
- mSeekBar.setAlpha(alpha);
-
- if (mAuto) {
- mSeekBar.setProgress(mNotificationImportance);
- mImportanceSummary.setText(mContext.getString(
- R.string.notification_importance_user_unspecified));
- mImportanceTitle.setText(mContext.getString(
- R.string.user_unspecified_importance));
- } else {
- updateTitleAndSummary(mSeekBar.getProgress());
- }
- }
-
- private void updateTitleAndSummary(int progress) {
- switch (progress) {
+ // Enabled Switch
+ mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
+ mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
+ mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
+
+ // Importance Buttons
+ mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
+ mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
+ mDefaultImportanceButton =
+ (RadioButton) importanceButtons.findViewById(R.id.default_importance);
+ mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
+
+ // Set to current importance setting
+ switch (importance) {
case NotificationManager.IMPORTANCE_NONE:
- mImportanceSummary.setText(mContext.getString(
- R.string.notification_importance_blocked));
- mImportanceTitle.setText(mContext.getString(R.string.blocked_importance));
break;
case NotificationManager.IMPORTANCE_MIN:
- mImportanceSummary.setText(mContext.getString(
- R.string.notification_importance_min));
- mImportanceTitle.setText(mContext.getString(R.string.min_importance));
+ case NotificationManager.IMPORTANCE_UNSPECIFIED:
+ mMinImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_LOW:
- mImportanceSummary.setText(mContext.getString(
- R.string.notification_importance_low));
- mImportanceTitle.setText(mContext.getString(R.string.low_importance));
+ mLowImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_DEFAULT:
- mImportanceSummary.setText(mContext.getString(
- R.string.notification_importance_default));
- mImportanceTitle.setText(mContext.getString(R.string.default_importance));
+ mDefaultImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_HIGH:
case NotificationManager.IMPORTANCE_MAX:
- mImportanceSummary.setText(mContext.getString(
- R.string.notification_importance_high));
- mImportanceTitle.setText(mContext.getString(R.string.high_importance));
+ mHighImportanceButton.setChecked(true);
break;
}
+
+ // Callback when checked.
+ mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ resetFalsingCheck();
+ updateImportanceGroup();
+ });
+ ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
+ (buttonView, isChecked) -> { resetFalsingCheck(); });
}
- private ColorStateList loadColorStateList(int colorResId) {
- return ColorStateList.valueOf(mContext.getColor(colorResId));
+ private void updateImportanceGroup() {
+ final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
+ mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
+ mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
}
- public void closeControls(int x, int y, boolean notify) {
+ public void closeControls(int x, int y, boolean saveImportance) {
+ if (saveImportance) {
+ saveImportance();
+ }
if (getWindowToken() == null) {
- if (notify && mListener != null) {
+ if (mListener != null) {
mListener.onGutsClosed(this);
}
return;
@@ -398,7 +396,7 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab
});
a.start();
setExposed(false, mNeedsFalsingProtection);
- if (notify && mListener != null) {
+ if (mListener != null) {
mListener.onGutsClosed(this);
}
}
@@ -442,14 +440,7 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab
}
}
- public boolean areGutsExposed() {
+ public boolean isExposed() {
return mExposed;
}
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (SHOW_SLIDER.equals(key)) {
- mShowSlider = newValue != null && Integer.parseInt(newValue) != 0;
- }
- }
}
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 ea4e10297eda..ca7cf1845c1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -4692,7 +4692,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
/* Only ever called as a consequence of a lockscreen expansion gesture. */
@Override
public boolean onDraggedDown(View startingChild, int dragLengthY) {
- if (hasActiveNotifications()) {
+ if (hasActiveNotifications() && (!isDozing() || isPulsing())) {
EventLogTags.writeSysuiLockscreenGesture(
EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_FULL_SHADE,
(int) (dragLengthY / mDisplayMetrics.density),
@@ -4706,8 +4706,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
return true;
} else {
-
- // No notifications - abort gesture.
+ // abort gesture.
return false;
}
}
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 487f0e73959c..1b73a3f53997 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -285,13 +285,10 @@ public class StatusBarWindowView extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (mService.isDozing() && !mService.isPulsing()) {
- // Discard all touch events in always-on.
- return true;
- }
-
- boolean handled = false;
- if (mService.getBarState() == StatusBarState.KEYGUARD) {
+ boolean handled = mService.isDozing() && !mService.isPulsing();
+ if (mService.getBarState() == StatusBarState.KEYGUARD
+ && (!handled || mDragDownHelper.isDraggingDown())) {
+ // we still want to finish our drag down gesture when locking the screen
handled = mDragDownHelper.onTouchEvent(ev);
}
if (!handled) {
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 b4cbec1296aa..1a2d778d206b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -138,15 +138,14 @@ public class NotificationChildrenContainer extends ViewGroup {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int ownMaxHeight = mMaxNotificationHeight;
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
int size = MeasureSpec.getSize(heightMeasureSpec);
+ int newHeightSpec = heightMeasureSpec;
if (hasFixedHeight || isHeightLimited) {
- ownMaxHeight = Math.min(ownMaxHeight, size);
+ newHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
}
- int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
int width = MeasureSpec.getSize(widthMeasureSpec);
if (mOverflowNumber != null) {
mOverflowNumber.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
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 ec44f19e8bfc..e7c2507dbf18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -768,15 +768,19 @@ public class NotificationStackScrollLayout extends ViewGroup
*/
private float getAppearEndPosition() {
int appearPosition;
- int minNotificationsForShelf = 1;
- if (mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()) {
- appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight();
- minNotificationsForShelf = 2;
+ if (mEmptyShadeView.getVisibility() == GONE) {
+ int minNotificationsForShelf = 1;
+ if (mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()) {
+ appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight();
+ minNotificationsForShelf = 2;
+ } else {
+ appearPosition = 0;
+ }
+ if (getNotGoneChildCount() >= minNotificationsForShelf) {
+ appearPosition += mShelf.getIntrinsicHeight();
+ }
} else {
- appearPosition = 0;
- }
- if (getNotGoneChildCount() >= minNotificationsForShelf) {
- appearPosition += mShelf.getIntrinsicHeight();
+ appearPosition = mEmptyShadeView.getHeight();
}
return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
}
@@ -1064,6 +1068,19 @@ public class NotificationStackScrollLayout extends ViewGroup
public void setUserExpandedChild(View v, boolean userExpanded) {
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (userExpanded && onKeyguard()) {
+ // Due to a race when locking the screen while touching, a notification may be
+ // expanded even after we went back to keyguard. An example of this happens if
+ // you click in the empty space while expanding a group.
+
+ // We also need to un-user lock it here, since otherwise the content height
+ // calculated might be wrong. We also can't invert the two calls since
+ // un-userlocking it will trigger a layout switch in the content view.
+ row.setUserLocked(false);
+ updateContentHeight();
+ notifyHeightChangeListener(row);
+ return;
+ }
row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */);
row.onExpandedByGesture(userExpanded);
}
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 50b6d70f437c..7ff100081677 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -23,9 +23,11 @@ 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;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import java.util.ArrayList;
@@ -324,11 +326,15 @@ public class StackScrollAlgorithm {
int childHeight = getMaxAllowedChildHeight(child);
childViewState.yTranslation = currentYPosition;
boolean isDismissView = child instanceof DismissView;
+ boolean isEmptyShadeView = child instanceof EmptyShadeView;
childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
if (isDismissView) {
childViewState.yTranslation = Math.min(childViewState.yTranslation,
ambientState.getInnerHeight() - childHeight);
+ } else if (isEmptyShadeView) {
+ childViewState.yTranslation = ambientState.getInnerHeight() - childHeight
+ + ambientState.getStackTranslation() * 0.25f;
} else {
clampPositionToShelf(childViewState, ambientState);
}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index b03189cb77ce..dec8ba6c05db 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -27,6 +27,7 @@
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
new file mode 100644
index 000000000000..2cd6dbd17a7c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
@@ -0,0 +1,301 @@
+/*
+ * 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 static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RadioButton;
+import android.widget.Switch;
+import android.widget.TextView;
+import com.android.systemui.R;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.Mockito;
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotificationGutsTest {
+ private static final String TEST_PACKAGE_NAME = "test_package";
+ private static final String TEST_CHANNEL = "test_channel";
+ private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
+
+ private NotificationGuts mNotificationGuts;
+ private final INotificationManager mMockINotificationManager = mock(INotificationManager.class);
+ private final PackageManager mMockPackageManager = mock(PackageManager.class);
+ private NotificationChannel mNotificationChannel;
+ private final StatusBarNotification mMockStatusBarNotification =
+ mock(StatusBarNotification.class);
+
+ @Before
+ @UiThreadTest
+ public void setUp() throws Exception {
+ // Inflate the layout
+ final LayoutInflater layoutInflater =
+ LayoutInflater.from(InstrumentationRegistry.getTargetContext());
+ mNotificationGuts = (NotificationGuts) layoutInflater.inflate(R.layout.notification_guts,
+ null);
+
+ // PackageManager must return a packageInfo and applicationInfo.
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = TEST_PACKAGE_NAME;
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = 1; // non-zero
+ when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
+ applicationInfo);
+
+ // mMockStatusBarNotification with a test channel.
+ mNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
+ when(mMockStatusBarNotification.getNotificationChannel()).thenReturn(mNotificationChannel);
+ when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testBindNotification_SetsTextApplicationName() throws Exception {
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+ final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.pkgname);
+ assertTrue(textView.getText().toString().contains("App Name"));
+ }
+
+ @Test
+ @UiThreadTest
+ public void testBindNotification_SetsTextChannelName() throws Exception {
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+ final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.channel_name);
+ assertEquals(TEST_CHANNEL_NAME, textView.getText());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, (View v, int appUid) -> { latch.countDown(); },
+ null, null);
+
+ final TextView settingsButton =
+ (TextView) mNotificationGuts.findViewById(R.id.more_settings);
+ settingsButton.performClick();
+ // Verify that listener was triggered.
+ assertEquals(0, latch.getCount());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null,
+ (View v) -> { latch.countDown(); },
+ null);
+
+ final TextView doneButton = (TextView) mNotificationGuts.findViewById(R.id.done);
+ doneButton.performClick();
+ // Verify that listener was triggered.
+ assertEquals(0, latch.getCount());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testHasImportanceChanged_DefaultsToFalse() throws Exception {
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+ assertFalse(mNotificationGuts.hasImportanceChanged());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testHasImportanceChanged_ReturnsTrueAfterButtonChecked() throws Exception {
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+ // Find the high button and check it.
+ RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ highButton.setChecked(true);
+ assertTrue(mNotificationGuts.hasImportanceChanged());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testImportanceButtonCheckedBasedOnInitialImportance() throws Exception {
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_HIGH);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+
+ RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ assertTrue(highButton.isChecked());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+
+ RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ highButton.setChecked(true);
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception {
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+
+ mNotificationGuts.closeControls(-1, -1, true);
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCloseControls_CallsUpdateNotificationChannelIfChanged() throws Exception {
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+
+ RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ highButton.setChecked(true);
+ mNotificationGuts.closeControls(-1, -1, true);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
+ assertEquals(NotificationManager.IMPORTANCE_HIGH, mNotificationChannel.getImportance());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCloseControls_DoesNotUpdateNotificationChannelIfSaveFalse() throws Exception {
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+
+ RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ highButton.setChecked(true);
+ mNotificationGuts.closeControls(-1, -1, false);
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testEnabledSwitchOnByDefault() throws Exception {
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+
+ Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ assertTrue(enabledSwitch.isChecked());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testEnabledSwitchVisibleByDefault() throws Exception {
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+
+ Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ assertEquals(View.VISIBLE, enabledSwitch.getVisibility());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception {
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
+
+ Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception {
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
+
+ Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ enabledSwitch.setChecked(false);
+ mNotificationGuts.closeControls(-1, -1, true);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
+ }
+
+ @Test
+ @UiThreadTest
+ public void testEnabledSwitchOverridesOtherButtons() throws Exception {
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, null, null, null);
+
+ Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ RadioButton lowButton = (RadioButton) mNotificationGuts.findViewById(R.id.low_importance);
+ lowButton.setChecked(true);
+ enabledSwitch.setChecked(false);
+ mNotificationGuts.closeControls(-1, -1, true);
+ assertEquals(NotificationManager.IMPORTANCE_NONE, mNotificationChannel.getImportance());
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 19dcf41f542c..1583023b48e1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10044,6 +10044,11 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ /**
+ * Try to place task to provided position. The final position might be different depending on
+ * current user and stacks state. The task will be moved to target stack if it's currently in
+ * different stack.
+ */
@Override
public void positionTaskInStack(int taskId, int stackId, int position) {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "positionTaskInStack()");
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 5bdae574421b..ba95abd10f36 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2547,6 +2547,10 @@ final class ActivityStack extends ConfigurationContainer {
return null;
}
+ /**
+ * Used from {@link ActivityStack#positionTask(TaskRecord, int)}.
+ * @see ActivityManagerService#positionTaskInStack(int, int, int).
+ */
private void insertTaskAtPosition(TaskRecord task, int position) {
if (position >= mTaskHistory.size()) {
insertTaskAtTop(task, null);
@@ -4935,6 +4939,7 @@ final class ActivityStack extends ConfigurationContainer {
postAddTask(task, prevStack);
}
+ /** @see ActivityManagerService#positionTaskInStack(int, int, int). */
void positionTask(final TaskRecord task, int position) {
final ActivityRecord topRunningActivity = task.topRunningActivityLocked();
final boolean wasResumed = topRunningActivity == task.getStack().mResumedActivity;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index b4b346576507..235325b8b259 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2200,7 +2200,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer
} else {
for (int i = size - 1; i >= 0; i--) {
positionTaskInStackLocked(tasks.get(i).taskId,
- FULLSCREEN_WORKSPACE_STACK_ID, 0);
+ FULLSCREEN_WORKSPACE_STACK_ID, 0 /* position */);
}
}
} finally {
@@ -2872,6 +2872,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer
return true;
}
+ /** @see ActivityManagerService#positionTaskInStack(int, int, int). */
void positionTaskInStackLocked(int taskId, int stackId, int position) {
final TaskRecord task = anyTaskForIdLocked(taskId);
if (task == null) {
@@ -2882,6 +2883,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer
task.updateOverrideConfigurationForStack(stack);
+ // TODO: Return final position from WM for AM to use instead of duplicating computations in
+ // ActivityStack#insertTaskAtPosition.
mWindowManager.positionTaskInStack(
taskId, stackId, position, task.mBounds, task.getOverrideConfiguration());
stack.positionTask(task, position);
@@ -4322,7 +4325,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer
if (activityDisplay == null) {
return;
}
- addToDisplayLocked(activityDisplay, true);
+ addToDisplayLocked(activityDisplay, true /* onTop */);
}
}
@@ -4538,7 +4541,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer
new VirtualActivityDisplay(width, height, density);
mActivityDisplay = virtualActivityDisplay;
mActivityDisplays.put(virtualActivityDisplay.mDisplayId, virtualActivityDisplay);
- addToDisplayLocked(virtualActivityDisplay, true);
+ addToDisplayLocked(virtualActivityDisplay, true /* onTop */);
}
if (mSurface != null) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a3181e4146bb..039fc50aa4f8 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1547,8 +1547,9 @@ public class NotificationManagerService extends SystemService {
@Override
public void updateNotificationChannelForPackage(String pkg, int uid,
NotificationChannel channel) {
- checkCallerIsSystem();
+ enforceSystemOrSystemUI("Caller not system or systemui");
if (!channel.isAllowed()) {
+ // cancel
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
null);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 203137d4d481..ff841b147d9c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -712,19 +712,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
if (prevDc == this) {
return;
}
- if (prevDc != null && prevDc.mTokenMap.remove(token.token) != null) {
- switch (token.windowType) {
- case TYPE_WALLPAPER:
- prevDc.mBelowAppWindowsContainers.removeChild(token);
- break;
- case TYPE_INPUT_METHOD:
- case TYPE_INPUT_METHOD_DIALOG:
- prevDc.mImeWindowsContainers.removeChild(token);
- break;
- default:
- prevDc.mAboveAppWindowsContainers.removeChild(token);
- break;
- }
+ if (prevDc != null && prevDc.mTokenMap.remove(token.token) != null
+ && token.asAppWindowToken() == null) {
+ // Removed the token from the map, but made sure it's not an app token before removing
+ // from parent.
+ token.getParent().removeChild(token);
}
addWindowToken(token.token, token);
@@ -979,7 +971,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// It's already attached to the display...clear mDeferRemoval and move stack to
// appropriate z-order on display as needed.
stack.mDeferRemoval = false;
- moveStack(stack, onTop);
+ // We're not moving the display to front when we're adding stacks, only when
+ // requested to change the position of stack explicitly.
+ mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
+ false /* includingParents */);
attachedToDisplay = true;
} else {
stack = new TaskStack(mService, stackId);
@@ -1031,10 +1026,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return addStackToDisplay(stack.mStackId, true /* onTop */);
}
- void moveStack(TaskStack stack, boolean toTop) {
- mTaskStackContainers.moveStack(stack, toTop);
- }
-
@Override
protected void addChild(DisplayChildWindowContainer child,
Comparator<DisplayChildWindowContainer> comparator) {
@@ -1057,6 +1048,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
throw new UnsupportedOperationException("See DisplayChildWindowContainer");
}
+ @Override
+ void positionChildAt(int position, DisplayChildWindowContainer child, boolean includingParents) {
+ // Children of the display are statically ordered, so the real intention here is to perform
+ // the operation on the display and not the static direct children.
+ getParent().positionChildAt(position, this, includingParents);
+ }
+
int taskIdFromPoint(int x, int y) {
for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
final TaskStack stack = mTaskStackContainers.get(stackNdx);
@@ -2499,20 +2497,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
stack.onRemovedFromDisplay();
}
- void moveStack(TaskStack stack, boolean toTop) {
- if (StackId.isAlwaysOnTop(stack.mStackId) && !toTop) {
- // This stack is always-on-top silly...
- Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + stack + " to bottom");
- return;
- }
-
- if (!mChildren.contains(stack)) {
- Slog.wtf(TAG_WM, "moving stack that was not added: " + stack, new Throwable());
- }
- removeChild(stack);
- addChild(stack, toTop);
- }
-
private void addChild(TaskStack stack, boolean toTop) {
int addIndex = toTop ? mChildren.size() : 0;
@@ -2532,6 +2516,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
@Override
+ void positionChildAt(int position, TaskStack child, boolean includingParents) {
+ if (StackId.isAlwaysOnTop(child.mStackId) && position != POSITION_TOP) {
+ // This stack is always-on-top, override the default behavior.
+ Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + this + " to bottom");
+
+ // Moving to its current position, as we must call super but we don't want to
+ // perform any meaningful action.
+ final int currentPosition = mChildren.indexOf(child);
+ super.positionChildAt(currentPosition, child, false /* includingParents */);
+ return;
+ }
+
+ super.positionChildAt(position, child, includingParents);
+ }
+
+ @Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback,
boolean traverseTopToBottom) {
if (traverseTopToBottom) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index aa6e3c7c3e45..6005a991e474 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -23,6 +23,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSC
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
@@ -154,7 +155,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
mService.mTaskIdToTask.delete(mTaskId);
}
- // Change to use re-parenting in WC when TaskStack is switched to use WC.
+ // TODO: Change to use re-parenting in WC.
void moveTaskToStack(TaskStack stack, boolean toTop) {
if (stack == mStack) {
return;
@@ -166,15 +167,20 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
stack.addTask(this, toTop);
}
+ /** @see com.android.server.am.ActivityManagerService#positionTaskInStack(int, int, int). */
void positionTaskInStack(TaskStack stack, int position, Rect bounds,
Configuration overrideConfig) {
if (mStack != null && stack != mStack) {
+ // Task is already attached to a different stack. First we need to remove it from there
+ // and add to top of the target stack. We will move it proper position afterwards.
if (DEBUG_STACK) Slog.i(TAG, "positionTaskInStack: removing taskId=" + mTaskId
+ " from stack=" + mStack);
EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
mStack.removeChild(this);
+ stack.addTask(this, true /* toTop */);
}
- stack.positionTask(this, position, showForAllUsers());
+
+ stack.positionChildAt(position, this, true /* includingParents */);
resizeLocked(bounds, overrideConfig, false /* force */);
for (int activityNdx = mChildren.size() - 1; activityNdx >= 0; --activityNdx) {
@@ -183,6 +189,20 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
}
@Override
+ void onParentSet() {
+ // Update task bounds if needed.
+ updateDisplayInfo(getDisplayContent());
+
+ if (StackId.windowsAreScaleable(mStack.mStackId)) {
+ // We force windows out of SCALING_MODE_FREEZE so that we can continue to animate them
+ // while a resize is pending.
+ forceWindowsScaleable(true /* force */);
+ } else {
+ forceWindowsScaleable(false /* force */);
+ }
+ }
+
+ @Override
void removeChild(AppWindowToken token) {
if (!mChildren.contains(token)) {
Slog.e(TAG, "removeChild: token=" + this + " not found.");
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 74c0919699fc..d7c7cfa319e1 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -512,27 +512,67 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
/**
- * Put a Task in this stack. Used for adding and moving.
+ * Put a Task in this stack. Used for adding only.
+ * When task is added to top of the stack, the entire branch of the hierarchy (including stack
+ * and display) will be brought to top.
* @param task The task to add.
* @param toTop Whether to add it to the top or bottom.
* @param showForAllUsers Whether to show the task regardless of the current user.
*/
void addTask(Task task, boolean toTop, boolean showForAllUsers) {
- positionTask(task, toTop ? mChildren.size() : 0, showForAllUsers);
+ final TaskStack currentStack = task.mStack;
+ // TODO: We pass stack to task's constructor, but we still need to call this method.
+ // This doesn't make sense, mStack will already be set equal to "this" at this point.
+ if (currentStack != null && currentStack.mStackId != mStackId) {
+ throw new IllegalStateException("Trying to add taskId=" + task.mTaskId
+ + " to stackId=" + mStackId
+ + ", but it is already attached to stackId=" + task.mStack.mStackId);
+ }
+
+ final int targetPosition = toTop ? mChildren.size() : 0;
+
+ // Add child task.
+ task.mStack = this;
+ addChild(task, targetPosition);
+
+ // Move child to a proper position, as some restriction for position might apply.
+ positionChildAt(targetPosition, task, true /* includingParents */, showForAllUsers);
+ }
+
+ @Override
+ void positionChildAt(int position, Task child, boolean includingParents) {
+ positionChildAt(position, child, includingParents, child.showForAllUsers());
+ }
+
+ /**
+ * Overridden version of {@link TaskStack#positionChildAt(int, Task, boolean)}. Used in
+ * {@link TaskStack#addTask(Task, boolean, boolean showForAllUsers)}, as it can receive
+ * showForAllUsers param from {@link AppWindowToken} instead of {@link Task#showForAllUsers()}.
+ */
+ private void positionChildAt(int position, Task child, boolean includingParents,
+ boolean showForAllUsers) {
+ final int targetPosition = findPositionForTask(child, position, showForAllUsers,
+ false /* addingNew */);
+ super.positionChildAt(targetPosition, child, includingParents);
+
+ // Log positioning.
+ if (DEBUG_TASK_MOVEMENT)
+ Slog.d(TAG_WM, "positionTask: task=" + this + " position=" + position);
+
+ final int toTop = targetPosition == mChildren.size() - 1 ? 1 : 0;
+ EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, child.mTaskId, toTop, targetPosition);
}
// TODO: We should really have users as a window container in the hierarchy so that we don't
- // have to do complicated things like we are doing in this method and also also the method to
- // just be WindowContainer#addChild
- void positionTask(Task task, int position, boolean showForAllUsers) {
+ // have to do complicated things like we are doing in this method.
+ private int findPositionForTask(Task task, int targetPosition, boolean showForAllUsers,
+ boolean addingNew) {
final boolean canShowTask =
showForAllUsers || mService.isCurrentProfileLocked(task.mUserId);
- if (mChildren.contains(task)) {
- super.removeChild(task);
- }
- int stackSize = mChildren.size();
+
+ final int stackSize = mChildren.size();
int minPosition = 0;
- int maxPosition = stackSize;
+ int maxPosition = addingNew ? stackSize : stackSize - 1;
if (canShowTask) {
minPosition = computeMinPosition(minPosition, stackSize);
@@ -540,28 +580,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
maxPosition = computeMaxPosition(maxPosition);
}
// Reset position based on minimum/maximum possible positions.
- position = Math.min(Math.max(position, minPosition), maxPosition);
-
- if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM,
- "positionTask: task=" + task + " position=" + position);
- addChild(task, position);
- task.mStack = this;
- task.updateDisplayInfo(mDisplayContent);
- boolean toTop = position == mChildren.size() - 1;
- if (toTop) {
- // TODO: Have a WidnowContainer method that moves all parents of a container to the
- // front for cases like this.
- mDisplayContent.moveStack(this, true);
- }
-
- if (StackId.windowsAreScaleable(mStackId)) {
- // We force windows out of SCALING_MODE_FREEZE so that we can continue to animate them
- // while a resize is pending.
- task.forceWindowsScaleable(true);
- } else {
- task.forceWindowsScaleable(false);
- }
- EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.mTaskId, toTop ? 1 : 0, position);
+ return Math.min(Math.max(targetPosition, minPosition), maxPosition);
}
/** Calculate the minimum possible position for a task that can be shown to the user.
@@ -591,7 +610,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
*/
private int computeMaxPosition(int maxPosition) {
while (maxPosition > 0) {
- final Task tmpTask = mChildren.get(maxPosition - 1);
+ final Task tmpTask = mChildren.get(maxPosition);
final boolean canShowTmpTask =
tmpTask.showForAllUsers()
|| mService.isCurrentProfileLocked(tmpTask.mUserId);
@@ -603,20 +622,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
return maxPosition;
}
- // TODO: Have functionality in WC to move things to the bottom or top. Also, look at the call
- // points for this methods to see if we need functionality to move something to the front/bottom
- // with its parents.
- void moveTaskToTop(Task task) {
- if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "moveTaskToTop: task=" + task + " Callers="
- + Debug.getCallers(6));
- addTask(task, true);
- }
-
- void moveTaskToBottom(Task task) {
- if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "moveTaskToBottom: task=" + task);
- addTask(task, false);
- }
-
/**
* Delete a Task from this stack. If it is the last Task in the stack, move this stack to the
* back.
@@ -627,10 +632,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "removeChild: task=" + task);
super.removeChild(task);
+ task.mStack = null;
if (mDisplayContent != null) {
if (mChildren.isEmpty()) {
- mDisplayContent.moveStack(this, false);
+ getParent().positionChildAt(POSITION_BOTTOM, this, false /* includingParents */);
}
mDisplayContent.setLayoutNeeded();
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4a1c0678a661..984cf55de0e7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -39,6 +39,9 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
*/
class WindowContainer<E extends WindowContainer> implements Comparable<WindowContainer> {
+ static final int POSITION_TOP = Integer.MAX_VALUE;
+ static final int POSITION_BOTTOM = Integer.MIN_VALUE;
+
/**
* The parent of this window container.
* For removing or setting new parent {@link #setParent} should be used, because it also
@@ -86,6 +89,16 @@ class WindowContainer<E extends WindowContainer> implements Comparable<WindowCon
// Update merged override configuration of this container and all its children.
onMergedOverrideConfigurationChanged();
}
+
+ onParentSet();
+ }
+
+ /**
+ * Callback that is triggered when @link WindowContainer#setParent(WindowContainer)} was called.
+ * Supposed to be overridden and contain actions that should be executed after parent was set.
+ */
+ void onParentSet() {
+ // Do nothing by default.
}
// Temp. holders for a chain of containers we are currently processing.
@@ -203,6 +216,57 @@ class WindowContainer<E extends WindowContainer> implements Comparable<WindowCon
}
/**
+ * Move a child from it's current place in siblings list to the specified position,
+ * with an option to move all its parents to top.
+ * @param position Target position to move the child to.
+ * @param child Child to move to selected position.
+ * @param includingParents Flag indicating whether we need to move the entire branch of the
+ * hierarchy when we're moving a child to {@link #POSITION_TOP} or
+ * {@link #POSITION_BOTTOM}. When moving to other intermediate positions
+ * this flag will do nothing.
+ */
+ @CallSuper
+ void positionChildAt(int position, E child, boolean includingParents) {
+ if ((position < 0 && position != POSITION_BOTTOM)
+ || (position >= mChildren.size() && position != POSITION_TOP)) {
+ throw new IllegalArgumentException("positionAt: invalid position=" + position
+ + ", children number=" + mChildren.size());
+ }
+
+ if (position == mChildren.size() - 1) {
+ position = POSITION_TOP;
+ } else if (position == 0) {
+ position = POSITION_BOTTOM;
+ }
+
+ switch (position) {
+ case POSITION_TOP:
+ if (mChildren.getLast() != child) {
+ mChildren.remove(child);
+ mChildren.addLast(child);
+ }
+ if (includingParents && getParent() != null) {
+ getParent().positionChildAt(POSITION_TOP, this /* child */,
+ true /* includingParents */);
+ }
+ break;
+ case POSITION_BOTTOM:
+ if (mChildren.getFirst() != child) {
+ mChildren.remove(child);
+ mChildren.addFirst(child);
+ }
+ if (includingParents && getParent() != null) {
+ getParent().positionChildAt(POSITION_BOTTOM, this /* child */,
+ true /* includingParents */);
+ }
+ break;
+ default:
+ mChildren.remove(child);
+ mChildren.add(position, child);
+ }
+ }
+
+ /**
* Returns full configuration applied to this window container.
* This method should be used for getting settings applied in each particular level of the
* hierarchy.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 577e5a0c2e20..0f1675098c18 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -209,6 +209,8 @@ import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
@@ -3311,26 +3313,17 @@ public class WindowManagerService extends IWindowManager.Stub
final long origId = Binder.clearCallingIdentity();
try {
synchronized(mWindowMap) {
- Task task = mTaskIdToTask.get(taskId);
+ final Task task = mTaskIdToTask.get(taskId);
if (task == null) {
// Normal behavior, addAppToken will be called next and task will be created.
return;
}
- final TaskStack stack = task.mStack;
- final DisplayContent displayContent = task.getDisplayContent();
- displayContent.moveStack(stack, true);
- if (displayContent.isDefaultDisplay) {
- final TaskStack homeStack = displayContent.getHomeStack();
- if (homeStack != stack) {
- // When a non-home stack moves to the top, the home stack moves to the
- // bottom.
- displayContent.moveStack(homeStack, false);
- }
- }
- stack.moveTaskToTop(task);
+ task.mStack.positionChildAt(POSITION_TOP, task, true /* includingParents */);
+
if (mAppTransition.isTransitionSet()) {
task.setSendingToBottom(false);
}
+ final DisplayContent displayContent = task.getDisplayContent();
displayContent.layoutAndAssignWindowLayersIfNeeded();
}
} finally {
@@ -3342,14 +3335,14 @@ public class WindowManagerService extends IWindowManager.Stub
final long origId = Binder.clearCallingIdentity();
try {
synchronized(mWindowMap) {
- Task task = mTaskIdToTask.get(taskId);
+ final Task task = mTaskIdToTask.get(taskId);
if (task == null) {
Slog.e(TAG_WM, "moveTaskToBottom: taskId=" + taskId
+ " not found in mTaskIdToTask");
return;
}
final TaskStack stack = task.mStack;
- stack.moveTaskToBottom(task);
+ stack.positionChildAt(POSITION_BOTTOM, task, false /* includingParents */);
if (mAppTransition.isTransitionSet()) {
task.setSendingToBottom(true);
}
@@ -3656,6 +3649,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /** @see com.android.server.am.ActivityManagerService#positionTaskInStack(int, int, int). */
public void positionTaskInStack(int taskId, int stackId, int position, Rect bounds,
Configuration overrideConfig) {
synchronized (mWindowMap) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 01808cb7462d..786c2bb06d96 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -19,12 +19,16 @@ package com.android.server.wm;
import org.junit.Test;
import org.junit.runner.RunWith;
+import android.hardware.display.DisplayManagerGlobal;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.view.Display;
+import android.view.DisplayInfo;
import java.util.ArrayList;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static org.junit.Assert.assertEquals;
@@ -203,4 +207,35 @@ public class DisplayContentTests extends WindowTestsBase {
voiceInteractionWindow.removeImmediately();
}
+
+ @Test
+ public void testMoveStackBetweenDisplays() throws Exception {
+ // Create second display.
+ final Display display = new Display(DisplayManagerGlobal.getInstance(),
+ sDisplayContent.getDisplayId() + 1, new DisplayInfo(),
+ DEFAULT_DISPLAY_ADJUSTMENTS);
+ final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
+ new WallpaperController(sWm));
+ sWm.mRoot.addChild(dc, 1);
+
+ // Add stack with activity.
+ final TaskStack stack = createTaskStackOnDisplay(dc);
+ assertEquals(dc.getDisplayId(), stack.getDisplayContent().getDisplayId());
+ assertEquals(dc, stack.getParent().getParent());
+ assertEquals(dc, stack.getDisplayContent());
+
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final TestAppWindowToken token = new TestAppWindowToken(dc);
+ task.addChild(token, 0);
+ assertEquals(dc, task.getDisplayContent());
+ assertEquals(dc, token.getDisplayContent());
+
+ // Move stack to first display.
+ sWm.moveStackToDisplay(stack.mStackId, sDisplayContent.getDisplayId());
+ assertEquals(sDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId());
+ assertEquals(sDisplayContent, stack.getParent().getParent());
+ assertEquals(sDisplayContent, stack.getDisplayContent());
+ assertEquals(sDisplayContent, task.getDisplayContent());
+ assertEquals(sDisplayContent, token.getDisplayContent());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
new file mode 100644
index 000000000000..746310217c38
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
@@ -0,0 +1,69 @@
+/*
+ * 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.wm;
+
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}.
+ *
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.wm.TaskStackContainersTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskStackContainersTests extends WindowTestsBase {
+
+ @Test
+ public void testStackPositionChildAt() throws Exception {
+ // Test that always-on-top stack can't be moved to position other than top.
+ final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
+ final TaskStack stack2 = createTaskStackOnDisplay(sDisplayContent);
+ sDisplayContent.addStackToDisplay(PINNED_STACK_ID, true);
+ final TaskStack pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID);
+
+ final WindowContainer taskStackContainer = stack1.getParent();
+
+ final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1);
+ final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2);
+ final int pinnedStackPos = taskStackContainer.mChildren.indexOf(pinnedStack);
+
+ taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, pinnedStack, false);
+ assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
+ assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
+ assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+
+ taskStackContainer.positionChildAt(1, pinnedStack, false);
+ assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
+ assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
+ assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
new file mode 100644
index 000000000000..eca2500642fe
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
@@ -0,0 +1,59 @@
+/*
+ * 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.wm;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link TaskStack} class.
+ *
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.wm.TaskStackTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskStackTests extends WindowTestsBase {
+
+ @Test
+ public void testStackPositionChildAt() throws Exception {
+ final TaskStack stack = createTaskStackOnDisplay(sDisplayContent);
+ final Task task1 = createTaskInStack(stack, 0 /* userId */);
+ final Task task2 = createTaskInStack(stack, 1 /* userId */);
+
+ // Current user task should be moved to top.
+ stack.positionChildAt(WindowContainer.POSITION_TOP, task1, false /* includingParents */);
+ assertEquals(stack.mChildren.get(0), task2);
+ assertEquals(stack.mChildren.get(1), task1);
+
+ // Non-current user won't be moved to top.
+ stack.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */);
+ assertEquals(stack.mChildren.get(0), task2);
+ assertEquals(stack.mChildren.get(1), task1);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 0ccd0ada8ac8..7277ba42c2da 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -33,6 +33,10 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCA
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -87,6 +91,14 @@ public class WindowContainerTests {
assertEquals(layer1, root.getChildAt(4));
assertEquals(secondLayer1, root.getChildAt(5));
assertEquals(layer2, root.getChildAt(6));
+
+ assertTrue(layer1.mOnParentSetCalled);
+ assertTrue(secondLayer1.mOnParentSetCalled);
+ assertTrue(layer2.mOnParentSetCalled);
+ assertTrue(layerNeg1.mOnParentSetCalled);
+ assertTrue(layerNeg2.mOnParentSetCalled);
+ assertTrue(secondLayerNeg1.mOnParentSetCalled);
+ assertTrue(layer0.mOnParentSetCalled);
}
@Test
@@ -180,6 +192,99 @@ public class WindowContainerTests {
}
@Test
+ public void testPositionChildAt() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+ final TestWindowContainer child3 = root.addChildWindow();
+
+ // Test position at top.
+ root.positionChildAt(POSITION_TOP, child1, false /* includingParents */);
+ assertEquals(child1, root.getChildAt(root.getChildrenCount() - 1));
+
+ // Test position at bottom.
+ root.positionChildAt(POSITION_BOTTOM, child1, false /* includingParents */);
+ assertEquals(child1, root.getChildAt(0));
+
+ // Test position in the middle.
+ root.positionChildAt(1, child3, false /* includingParents */);
+ assertEquals(child1, root.getChildAt(0));
+ assertEquals(child3, root.getChildAt(1));
+ assertEquals(child2, root.getChildAt(2));
+ }
+
+ @Test
+ public void testPositionChildAtIncludeParents() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child12 = child1.addChildWindow();
+ final TestWindowContainer child13 = child1.addChildWindow();
+ final TestWindowContainer child21 = child2.addChildWindow();
+ final TestWindowContainer child22 = child2.addChildWindow();
+ final TestWindowContainer child23 = child2.addChildWindow();
+
+ // Test moving to top.
+ child1.positionChildAt(POSITION_TOP, child11, true /* includingParents */);
+ assertEquals(child12, child1.getChildAt(0));
+ assertEquals(child13, child1.getChildAt(1));
+ assertEquals(child11, child1.getChildAt(2));
+ assertEquals(child2, root.getChildAt(0));
+ assertEquals(child1, root.getChildAt(1));
+
+ // Test moving to bottom.
+ child1.positionChildAt(POSITION_BOTTOM, child11, true /* includingParents */);
+ assertEquals(child11, child1.getChildAt(0));
+ assertEquals(child12, child1.getChildAt(1));
+ assertEquals(child13, child1.getChildAt(2));
+ assertEquals(child1, root.getChildAt(0));
+ assertEquals(child2, root.getChildAt(1));
+
+ // Test moving to middle, includeParents shouldn't do anything.
+ child2.positionChildAt(1, child21, true /* includingParents */);
+ assertEquals(child11, child1.getChildAt(0));
+ assertEquals(child12, child1.getChildAt(1));
+ assertEquals(child13, child1.getChildAt(2));
+ assertEquals(child22, child2.getChildAt(0));
+ assertEquals(child21, child2.getChildAt(1));
+ assertEquals(child23, child2.getChildAt(2));
+ assertEquals(child1, root.getChildAt(0));
+ assertEquals(child2, root.getChildAt(1));
+ }
+
+ @Test
+ public void testPositionChildAtInvalid() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+
+ boolean gotException = false;
+ try {
+ // Check response to negative position.
+ root.positionChildAt(-1, child1, false /* includingParents */);
+ } catch (IllegalArgumentException e) {
+ gotException = true;
+ }
+ assertTrue(gotException);
+
+ gotException = false;
+ try {
+ // Check response to position that's bigger than child number.
+ root.positionChildAt(2, child1, false /* includingParents */);
+ } catch (IllegalArgumentException e) {
+ gotException = true;
+ }
+ assertTrue(gotException);
+ }
+
+ @Test
public void testIsAnimating() throws Exception {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
final TestWindowContainer root = builder.setLayer(0).build();
@@ -548,6 +653,8 @@ public class WindowContainerTests {
private boolean mIsVisible;
private boolean mFillsParent;
+ private boolean mOnParentSetCalled;
+
/**
* Compares 2 window layers and returns -1 if the first is lesser than the second in terms
* of z-order and 1 otherwise.
@@ -598,6 +705,11 @@ public class WindowContainerTests {
}
@Override
+ void onParentSet() {
+ mOnParentSetCalled = true;
+ }
+
+ @Override
boolean isAnimating() {
return mIsAnimating || super.isAnimating();
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 41bf646a6a1d..398568771cd7 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -75,6 +75,7 @@ public class WindowTestsBase {
sLayersController = new WindowLayersController(sWm);
sDisplayContent = new DisplayContent(context.getDisplay(), sWm, sLayersController,
new WallpaperController(sWm));
+ sWm.mRoot.addChild(sDisplayContent, 0);
// Set-up some common windows.
sWallpaperWindow = createWindow(null, TYPE_WALLPAPER, sDisplayContent, "wallpaperWindow");
@@ -103,11 +104,8 @@ public class WindowTestsBase {
return new TestWindowToken(type, dc);
}
- final int stackId = sNextStackId++;
- dc.addStackToDisplay(stackId, true);
- final TaskStack stack = sWm.mStackIdToStack.get(stackId);
- final Task task = new Task(sNextTaskId++, stack, 0, sWm, null, EMPTY, false, 0, false);
- stack.addTask(task, true);
+ final TaskStack stack = createTaskStackOnDisplay(dc);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
final TestAppWindowToken token = new TestAppWindowToken(dc);
task.addChild(token, 0);
return token;
@@ -136,6 +134,21 @@ public class WindowTestsBase {
return w;
}
+ /** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */
+ TaskStack createTaskStackOnDisplay(DisplayContent dc) {
+ final int stackId = sNextStackId++;
+ dc.addStackToDisplay(stackId, true);
+ return sWm.mStackIdToStack.get(stackId);
+ }
+
+ /**Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
+ Task createTaskInStack(TaskStack stack, int userId) {
+ final Task newTask = new Task(sNextTaskId++, stack, userId, sWm, null, EMPTY, false, 0,
+ false);
+ stack.addTask(newTask, true);
+ return newTask;
+ }
+
/* Used so we can gain access to some protected members of the {@link WindowToken} class */
class TestWindowToken extends WindowToken {
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 190fc35e4873..bcc68b38dd2b 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -780,4 +780,10 @@ public class MockContext extends Context {
public IApplicationThread getIApplicationThread() {
throw new UnsupportedOperationException();
}
+
+ /** {@hide} */
+ @Override
+ public Handler getMainThreadHandler() {
+ throw new UnsupportedOperationException();
+ }
}