Merge "Remove 2nd line from failure"
diff --git a/api/current.txt b/api/current.txt
index b73214f..ccc0e5c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2114,22 +2114,6 @@
field public static final int Theme_Light_Panel = 16973914; // 0x103005a
field public static final int Theme_Light_WallpaperSettings = 16973922; // 0x1030062
field public static final int Theme_Material = 16974372; // 0x1030224
- field public static final int Theme_Material_DayNight = 16974552; // 0x10302d8
- field public static final int Theme_Material_DayNight_DarkActionBar = 16974553; // 0x10302d9
- field public static final int Theme_Material_DayNight_Dialog = 16974554; // 0x10302da
- field public static final int Theme_Material_DayNight_DialogWhenLarge = 16974560; // 0x10302e0
- field public static final int Theme_Material_DayNight_DialogWhenLarge_DarkActionBar = 16974568; // 0x10302e8
- field public static final int Theme_Material_DayNight_DialogWhenLarge_NoActionBar = 16974561; // 0x10302e1
- field public static final int Theme_Material_DayNight_Dialog_Alert = 16974555; // 0x10302db
- field public static final int Theme_Material_DayNight_Dialog_MinWidth = 16974556; // 0x10302dc
- field public static final int Theme_Material_DayNight_Dialog_NoActionBar = 16974557; // 0x10302dd
- field public static final int Theme_Material_DayNight_Dialog_NoActionBar_MinWidth = 16974558; // 0x10302de
- field public static final int Theme_Material_DayNight_Dialog_Presentation = 16974559; // 0x10302df
- field public static final int Theme_Material_DayNight_NoActionBar = 16974562; // 0x10302e2
- field public static final int Theme_Material_DayNight_NoActionBar_Fullscreen = 16974563; // 0x10302e3
- field public static final int Theme_Material_DayNight_NoActionBar_Overscan = 16974564; // 0x10302e4
- field public static final int Theme_Material_DayNight_NoActionBar_TranslucentDecor = 16974565; // 0x10302e5
- field public static final int Theme_Material_DayNight_Panel = 16974566; // 0x10302e6
field public static final int Theme_Material_Dialog = 16974373; // 0x1030225
field public static final int Theme_Material_DialogWhenLarge = 16974379; // 0x103022b
field public static final int Theme_Material_DialogWhenLarge_NoActionBar = 16974380; // 0x103022c
@@ -2143,7 +2127,7 @@
field public static final int Theme_Material_Light_DarkActionBar = 16974392; // 0x1030238
field public static final int Theme_Material_Light_Dialog = 16974393; // 0x1030239
field public static final int Theme_Material_Light_DialogWhenLarge = 16974399; // 0x103023f
- field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974567; // 0x10302e7
+ field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974552; // 0x10302d8
field public static final int Theme_Material_Light_DialogWhenLarge_NoActionBar = 16974400; // 0x1030240
field public static final int Theme_Material_Light_Dialog_Alert = 16974394; // 0x103023a
field public static final int Theme_Material_Light_Dialog_MinWidth = 16974395; // 0x103023b
@@ -3374,6 +3358,7 @@
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method public void enterPictureInPictureMode();
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 84dbe62..923e053 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2215,22 +2215,6 @@
field public static final int Theme_Light_Panel = 16973914; // 0x103005a
field public static final int Theme_Light_WallpaperSettings = 16973922; // 0x1030062
field public static final int Theme_Material = 16974372; // 0x1030224
- field public static final int Theme_Material_DayNight = 16974552; // 0x10302d8
- field public static final int Theme_Material_DayNight_DarkActionBar = 16974553; // 0x10302d9
- field public static final int Theme_Material_DayNight_Dialog = 16974554; // 0x10302da
- field public static final int Theme_Material_DayNight_DialogWhenLarge = 16974560; // 0x10302e0
- field public static final int Theme_Material_DayNight_DialogWhenLarge_DarkActionBar = 16974568; // 0x10302e8
- field public static final int Theme_Material_DayNight_DialogWhenLarge_NoActionBar = 16974561; // 0x10302e1
- field public static final int Theme_Material_DayNight_Dialog_Alert = 16974555; // 0x10302db
- field public static final int Theme_Material_DayNight_Dialog_MinWidth = 16974556; // 0x10302dc
- field public static final int Theme_Material_DayNight_Dialog_NoActionBar = 16974557; // 0x10302dd
- field public static final int Theme_Material_DayNight_Dialog_NoActionBar_MinWidth = 16974558; // 0x10302de
- field public static final int Theme_Material_DayNight_Dialog_Presentation = 16974559; // 0x10302df
- field public static final int Theme_Material_DayNight_NoActionBar = 16974562; // 0x10302e2
- field public static final int Theme_Material_DayNight_NoActionBar_Fullscreen = 16974563; // 0x10302e3
- field public static final int Theme_Material_DayNight_NoActionBar_Overscan = 16974564; // 0x10302e4
- field public static final int Theme_Material_DayNight_NoActionBar_TranslucentDecor = 16974565; // 0x10302e5
- field public static final int Theme_Material_DayNight_Panel = 16974566; // 0x10302e6
field public static final int Theme_Material_Dialog = 16974373; // 0x1030225
field public static final int Theme_Material_DialogWhenLarge = 16974379; // 0x103022b
field public static final int Theme_Material_DialogWhenLarge_NoActionBar = 16974380; // 0x103022c
@@ -2244,7 +2228,7 @@
field public static final int Theme_Material_Light_DarkActionBar = 16974392; // 0x1030238
field public static final int Theme_Material_Light_Dialog = 16974393; // 0x1030239
field public static final int Theme_Material_Light_DialogWhenLarge = 16974399; // 0x103023f
- field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974567; // 0x10302e7
+ field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974552; // 0x10302d8
field public static final int Theme_Material_Light_DialogWhenLarge_NoActionBar = 16974400; // 0x1030240
field public static final int Theme_Material_Light_Dialog_Alert = 16974394; // 0x103023a
field public static final int Theme_Material_Light_Dialog_MinWidth = 16974395; // 0x103023b
@@ -3477,6 +3461,7 @@
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method public void enterPictureInPictureMode();
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 390a017..27a7ae3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2114,22 +2114,6 @@
field public static final int Theme_Light_Panel = 16973914; // 0x103005a
field public static final int Theme_Light_WallpaperSettings = 16973922; // 0x1030062
field public static final int Theme_Material = 16974372; // 0x1030224
- field public static final int Theme_Material_DayNight = 16974552; // 0x10302d8
- field public static final int Theme_Material_DayNight_DarkActionBar = 16974553; // 0x10302d9
- field public static final int Theme_Material_DayNight_Dialog = 16974554; // 0x10302da
- field public static final int Theme_Material_DayNight_DialogWhenLarge = 16974560; // 0x10302e0
- field public static final int Theme_Material_DayNight_DialogWhenLarge_DarkActionBar = 16974568; // 0x10302e8
- field public static final int Theme_Material_DayNight_DialogWhenLarge_NoActionBar = 16974561; // 0x10302e1
- field public static final int Theme_Material_DayNight_Dialog_Alert = 16974555; // 0x10302db
- field public static final int Theme_Material_DayNight_Dialog_MinWidth = 16974556; // 0x10302dc
- field public static final int Theme_Material_DayNight_Dialog_NoActionBar = 16974557; // 0x10302dd
- field public static final int Theme_Material_DayNight_Dialog_NoActionBar_MinWidth = 16974558; // 0x10302de
- field public static final int Theme_Material_DayNight_Dialog_Presentation = 16974559; // 0x10302df
- field public static final int Theme_Material_DayNight_NoActionBar = 16974562; // 0x10302e2
- field public static final int Theme_Material_DayNight_NoActionBar_Fullscreen = 16974563; // 0x10302e3
- field public static final int Theme_Material_DayNight_NoActionBar_Overscan = 16974564; // 0x10302e4
- field public static final int Theme_Material_DayNight_NoActionBar_TranslucentDecor = 16974565; // 0x10302e5
- field public static final int Theme_Material_DayNight_Panel = 16974566; // 0x10302e6
field public static final int Theme_Material_Dialog = 16974373; // 0x1030225
field public static final int Theme_Material_DialogWhenLarge = 16974379; // 0x103022b
field public static final int Theme_Material_DialogWhenLarge_NoActionBar = 16974380; // 0x103022c
@@ -2143,7 +2127,7 @@
field public static final int Theme_Material_Light_DarkActionBar = 16974392; // 0x1030238
field public static final int Theme_Material_Light_Dialog = 16974393; // 0x1030239
field public static final int Theme_Material_Light_DialogWhenLarge = 16974399; // 0x103023f
- field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974567; // 0x10302e7
+ field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974552; // 0x10302d8
field public static final int Theme_Material_Light_DialogWhenLarge_NoActionBar = 16974400; // 0x1030240
field public static final int Theme_Material_Light_Dialog_Alert = 16974394; // 0x103023a
field public static final int Theme_Material_Light_Dialog_MinWidth = 16974395; // 0x103023b
@@ -3374,6 +3358,7 @@
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method public void enterPictureInPictureMode();
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ef84ab0..571bc6e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1782,6 +1782,17 @@
}
/**
+ * Puts the activity in picture-in-picture mode.
+ * @see android.R.attr#supportsPictureInPicture
+ */
+ public void enterPictureInPictureMode() {
+ try {
+ ActivityManagerNative.getDefault().enterPictureInPictureMode(mToken);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Called by the system when the device configuration changes while your
* activity is running. Note that this will <em>only</em> be called if
* you have selected configurations you would like to handle with the
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index f11bf742..d724823 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2759,6 +2759,13 @@
reply.writeInt(pipMode ? 1 : 0);
return true;
}
+ case ENTER_PICTURE_IN_PICTURE_MODE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final IBinder token = data.readStrongBinder();
+ enterPictureInPictureMode(token);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -6433,5 +6440,17 @@
return pipMode;
}
+ @Override
+ public void enterPictureInPictureMode(IBinder token) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ mRemote.transact(ENTER_PICTURE_IN_PICTURE_MODE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 64c69af..f91a0be 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -550,6 +550,8 @@
public boolean inPictureInPictureMode(IBinder token) throws RemoteException;
+ public void enterPictureInPictureMode(IBinder token) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -914,4 +916,5 @@
int IN_MULTI_WINDOW_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 352;
int IN_PICTURE_IN_PICTURE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 353;
int KILL_PACKAGE_DEPENDENTS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 354;
+ int ENTER_PICTURE_IN_PICTURE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 355;
}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index f2a4d7b..0e4bc84 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -422,9 +422,10 @@
* @return True if the initialization was successful, false otherwise.
*/
boolean initialize(Surface surface) throws OutOfResourcesException {
+ boolean status = !mInitialized;
mInitialized = true;
updateEnabledState(surface);
- boolean status = nInitialize(mNativeProxy, surface);
+ nInitialize(mNativeProxy, surface);
return status;
}
@@ -958,7 +959,7 @@
private static native boolean nLoadSystemProperties(long nativeProxy);
private static native void nSetName(long nativeProxy, String name);
- private static native boolean nInitialize(long nativeProxy, Surface window);
+ private static native void nInitialize(long nativeProxy, Surface window);
private static native void nUpdateSurface(long nativeProxy, Surface window);
private static native boolean nPauseSurface(long nativeProxy, Surface window);
private static native void nSetup(long nativeProxy, int width, int height,
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 17eb876..5aa6a73 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -262,11 +262,11 @@
env->ReleaseStringUTFChars(jname, name);
}
-static jboolean android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
+static void android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
sp<ANativeWindow> window = android_view_Surface_getNativeWindow(env, jsurface);
- return proxy->initialize(window);
+ proxy->initialize(window);
}
static void android_view_ThreadedRenderer_updateSurface(JNIEnv* env, jobject clazz,
@@ -492,7 +492,7 @@
{ "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
{ "nLoadSystemProperties", "(J)Z", (void*) android_view_ThreadedRenderer_loadSystemProperties },
{ "nSetName", "(JLjava/lang/String;)V", (void*) android_view_ThreadedRenderer_setName },
- { "nInitialize", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_initialize },
+ { "nInitialize", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_initialize },
{ "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
{ "nPauseSurface", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_pauseSurface },
{ "nSetup", "(JIIFII)V", (void*) android_view_ThreadedRenderer_setup },
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ad36f3c..09c1717 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2686,23 +2686,7 @@
<public type="attr" name="languageTag" />
<public type="attr" name="pointerShape" />
- <public type="style" name="Theme.Material.DayNight" />
- <public type="style" name="Theme.Material.DayNight.DarkActionBar" />
- <public type="style" name="Theme.Material.DayNight.Dialog" />
- <public type="style" name="Theme.Material.DayNight.Dialog.Alert" />
- <public type="style" name="Theme.Material.DayNight.Dialog.MinWidth" />
- <public type="style" name="Theme.Material.DayNight.Dialog.NoActionBar" />
- <public type="style" name="Theme.Material.DayNight.Dialog.NoActionBar.MinWidth" />
- <public type="style" name="Theme.Material.DayNight.Dialog.Presentation" />
- <public type="style" name="Theme.Material.DayNight.DialogWhenLarge" />
- <public type="style" name="Theme.Material.DayNight.DialogWhenLarge.NoActionBar" />
- <public type="style" name="Theme.Material.DayNight.NoActionBar" />
- <public type="style" name="Theme.Material.DayNight.NoActionBar.Fullscreen" />
- <public type="style" name="Theme.Material.DayNight.NoActionBar.Overscan" />
- <public type="style" name="Theme.Material.DayNight.NoActionBar.TranslucentDecor" />
- <public type="style" name="Theme.Material.DayNight.Panel" />
<public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
- <public type="style" name="Theme.Material.DayNight.DialogWhenLarge.DarkActionBar" />
<public type="id" name="accessibilityActionSetProgress" />
<public type="id" name="icon_frame" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 832984e..f6326f7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2389,4 +2389,22 @@
<java-symbol type="layout" name="work_widget_mask_view" />
<java-symbol type="id" name="work_widget_app_icon" />
<java-symbol type="drawable" name="work_widget_mask_view_background" />
+
+ <!-- Framework-private Material.DayNight styles. -->
+ <java-symbol type="style" name="Theme.Material.DayNight" />
+ <java-symbol type="style" name="Theme.Material.DayNight.DarkActionBar" />
+ <java-symbol type="style" name="Theme.Material.DayNight.Dialog" />
+ <java-symbol type="style" name="Theme.Material.DayNight.Dialog.Alert" />
+ <java-symbol type="style" name="Theme.Material.DayNight.Dialog.MinWidth" />
+ <java-symbol type="style" name="Theme.Material.DayNight.Dialog.NoActionBar" />
+ <java-symbol type="style" name="Theme.Material.DayNight.Dialog.NoActionBar.MinWidth" />
+ <java-symbol type="style" name="Theme.Material.DayNight.Dialog.Presentation" />
+ <java-symbol type="style" name="Theme.Material.DayNight.DialogWhenLarge" />
+ <java-symbol type="style" name="Theme.Material.DayNight.DialogWhenLarge.NoActionBar" />
+ <java-symbol type="style" name="Theme.Material.DayNight.NoActionBar" />
+ <java-symbol type="style" name="Theme.Material.DayNight.NoActionBar.Fullscreen" />
+ <java-symbol type="style" name="Theme.Material.DayNight.NoActionBar.Overscan" />
+ <java-symbol type="style" name="Theme.Material.DayNight.NoActionBar.TranslucentDecor" />
+ <java-symbol type="style" name="Theme.Material.DayNight.Panel" />
+ <java-symbol type="style" name="Theme.Material.DayNight.DialogWhenLarge.DarkActionBar" />
</resources>
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 0620653..dfcac1d 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -128,14 +128,13 @@
mSwapBehavior = swapBehavior;
}
-bool CanvasContext::initialize(ANativeWindow* window) {
+void CanvasContext::initialize(ANativeWindow* window) {
setSurface(window);
#if !HWUI_NEW_OPS
- if (mCanvas) return false;
+ if (mCanvas) return;
mCanvas = new OpenGLRenderer(mRenderThread.renderState());
mCanvas->initProperties();
#endif
- return true;
}
void CanvasContext::updateSurface(ANativeWindow* window) {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index d36ce99..0946900 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -73,7 +73,7 @@
// Won't take effect until next EGLSurface creation
void setSwapBehavior(SwapBehavior swapBehavior);
- bool initialize(ANativeWindow* window);
+ void initialize(ANativeWindow* window);
void updateSurface(ANativeWindow* window);
bool pauseSurface(ANativeWindow* window);
bool hasSurface() { return mNativeWindow.get(); }
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 15ccd6a..43282c9 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -140,14 +140,15 @@
}
CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
- return (void*) args->context->initialize(args->window);
+ args->context->initialize(args->window);
+ return nullptr;
}
-bool RenderProxy::initialize(const sp<ANativeWindow>& window) {
+void RenderProxy::initialize(const sp<ANativeWindow>& window) {
SETUP_TASK(initialize);
args->context = mContext;
args->window = window.get();
- return (bool) postAndWait(task);
+ post(task);
}
CREATE_BRIDGE2(updateSurface, CanvasContext* context, ANativeWindow* window) {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 338fab6..1d30eb8 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -66,7 +66,7 @@
ANDROID_API bool loadSystemProperties();
ANDROID_API void setName(const char* name);
- ANDROID_API bool initialize(const sp<ANativeWindow>& window);
+ ANDROID_API void initialize(const sp<ANativeWindow>& window);
ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
ANDROID_API void setup(int width, int height, float lightRadius,
diff --git a/packages/Shell/res/layout/dialog_bugreport_info.xml b/packages/Shell/res/layout/dialog_bugreport_info.xml
new file mode 100644
index 0000000..5d1e9f9
--- /dev/null
+++ b/packages/Shell/res/layout/dialog_bugreport_info.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <EditText
+ android:id="@+id/name"
+ android:maxLength="30"
+ android:singleLine="true"
+ android:inputType="textNoSuggestions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/bugreport_info_name"/>
+ <EditText
+ android:id="@+id/title"
+ android:maxLength="80"
+ android:singleLine="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/bugreport_info_title"/>
+ <EditText
+ android:id="@+id/description"
+ android:singleLine="false"
+ android:inputType="textMultiLine"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/bugreport_info_description"/>
+</LinearLayout>
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 5c25576..a7f2df5 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -42,4 +42,19 @@
<!-- Title for bug reports received from dumpstate without a name. [CHAR LIMIT=30]-->
<string name="bugreport_unnamed">unnamed</string>
+ <!-- Title of the notification action that opens the dialog for the user-defined bug report details. -->
+ <string name="bugreport_info_action">Details</string>
+
+ <!-- Title of the dialog asking for user-defined bug report details like name, title, and description. -->
+ <string name="bugreport_info_dialog_title">Bug report details</string>
+
+ <!-- Text of the hint asking for the bug report name, which when set will define a suffix in the
+ bug report file names. [CHAR LIMIT=30] -->
+ <string name="bugreport_info_name">Short name</string>
+ <!-- Text of hint asking for the bug report title, which when set will define the
+ Subject of the email message. [CHAR LIMIT=60] -->
+ <string name="bugreport_info_title">1-line summary</string>
+ <!-- Text of hint asking for the bug report description, which when set will describe
+ what the bug report is about. [CHAR LIMIT=NONE] -->
+ <string name="bugreport_info_description">Detailed description</string>
</resources>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index e902589..82ee710 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -29,16 +29,18 @@
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.ArrayList;
-import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import libcore.io.Streams;
+import com.android.internal.annotations.VisibleForTesting;
import com.google.android.collect.Lists;
import android.accounts.Account;
import android.accounts.AccountManager;
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.NotificationManager;
@@ -46,6 +48,7 @@
import android.app.Service;
import android.content.ClipData;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
@@ -59,10 +62,17 @@
import android.os.Process;
import android.os.SystemProperties;
import android.support.v4.content.FileProvider;
+import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Patterns;
import android.util.SparseArray;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.View.OnFocusChangeListener;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
import android.widget.Toast;
/**
@@ -103,19 +113,23 @@
// Internal intents used on notification actions.
static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE";
+ static final String INTENT_BUGREPORT_INFO_LAUNCH =
+ "android.intent.action.BUGREPORT_INFO_LAUNCH";
static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
static final String EXTRA_PID = "android.intent.extra.PID";
static final String EXTRA_MAX = "android.intent.extra.MAX";
static final String EXTRA_NAME = "android.intent.extra.NAME";
+ static final String EXTRA_TITLE = "android.intent.extra.TITLE";
+ static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
private static final int MSG_SERVICE_COMMAND = 1;
private static final int MSG_POLL = 2;
/** Polling frequency, in milliseconds. */
- static final long POLLING_FREQUENCY = 2000;
+ static final long POLLING_FREQUENCY = 2 * DateUtils.SECOND_IN_MILLIS;
/** How long (in ms) a dumpstate process will be monitored if it didn't show progress. */
private static final long INACTIVITY_TIMEOUT = 3 * DateUtils.MINUTE_IN_MILLIS;
@@ -124,8 +138,9 @@
private static final String DUMPSTATE_PREFIX = "dumpstate.";
private static final String PROGRESS_SUFFIX = ".progress";
private static final String MAX_SUFFIX = ".max";
+ private static final String NAME_SUFFIX = ".name";
- /** System property (and value) used for stop dumpstate. */
+ /** System property (and value) used to stop dumpstate. */
private static final String CTL_STOP = "ctl.stop";
private static final String BUGREPORT_SERVICE = "bugreportplus";
@@ -135,6 +150,8 @@
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
+ private final BugreportInfoDialog mInfoDialog = new BugreportInfoDialog();
+
@Override
public void onCreate() {
HandlerThread thread = new HandlerThread("BugreportProgressServiceThread",
@@ -242,6 +259,9 @@
}
onBugreportFinished(pid, intent);
break;
+ case INTENT_BUGREPORT_INFO_LAUNCH:
+ launchBugreportInfoDialog(pid);
+ break;
case INTENT_BUGREPORT_SHARE:
shareBugreport(pid);
break;
@@ -312,6 +332,13 @@
final String percentText = nf.format((double) info.progress / info.max);
final Action cancelAction = new Action.Builder(null, context.getString(
com.android.internal.R.string.cancel), newCancelIntent(context, info)).build();
+ final Intent infoIntent = new Intent(context, BugreportProgressService.class);
+ infoIntent.setAction(INTENT_BUGREPORT_INFO_LAUNCH);
+ infoIntent.putExtra(EXTRA_PID, info.pid);
+ final Action infoAction = new Action.Builder(null,
+ context.getString(R.string.bugreport_info_action),
+ PendingIntent.getService(context, info.pid, infoIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT)).build();
final String title = context.getString(R.string.bugreport_in_progress_title);
final String name =
@@ -328,6 +355,7 @@
.setLocalOnly(true)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color))
+ .addAction(infoAction)
.addAction(cancelAction)
.build();
@@ -341,7 +369,8 @@
final Intent intent = new Intent(INTENT_BUGREPORT_CANCEL);
intent.setClass(context, BugreportProgressService.class);
intent.putExtra(EXTRA_PID, info.pid);
- return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ return PendingIntent.getService(context, info.pid, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
}
/**
@@ -356,7 +385,7 @@
}
stopSelfWhenDone();
}
- if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): cancel notification");
+ Log.v(TAG, "stopProgress(" + pid + "): cancel notification");
NotificationManager.from(getApplicationContext()).cancel(TAG, pid);
}
@@ -364,8 +393,14 @@
* Cancels a bugreport upon user's request.
*/
private void cancel(int pid) {
- Log.i(TAG, "Cancelling PID " + pid + " on user's request");
- SystemProperties.set(CTL_STOP, BUGREPORT_SERVICE);
+ Log.v(TAG, "cancel: pid=" + pid);
+ synchronized (mProcesses) {
+ BugreportInfo info = mProcesses.get(pid);
+ if (info != null && !info.finished) {
+ Log.i(TAG, "Cancelling bugreport service (pid=" + pid + ") on user's request");
+ setSystemProperty(CTL_STOP, BUGREPORT_SERVICE);
+ }
+ }
stopProgress(pid);
}
@@ -393,7 +428,6 @@
final int progress = SystemProperties.getInt(progressKey, 0);
if (progress == 0) {
Log.v(TAG, "System property " + progressKey + " is not set yet");
- continue;
}
final int max = SystemProperties.getInt(DUMPSTATE_PREFIX + pid + MAX_SUFFIX, 0);
final boolean maxChanged = max > 0 && max != info.max;
@@ -427,6 +461,30 @@
}
/**
+ * Fetches a {@link BugreportInfo} for a given process and launches a dialog where the user can
+ * change its values.
+ */
+ private void launchBugreportInfoDialog(int pid) {
+ // Copy values so it doesn't lock mProcesses while UI is being updated
+ final String name, title, description;
+ synchronized (mProcesses) {
+ final BugreportInfo info = mProcesses.get(pid);
+ if (info == null) {
+ Log.w(TAG, "No bugreport info for PID " + pid);
+ return;
+ }
+ name = info.name;
+ title = info.title;
+ description = info.description;
+ }
+
+ // Closes the notification bar first.
+ sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+ mInfoDialog.initialize(getApplicationContext(), pid, name, title, description);
+ }
+
+ /**
* Finishes the service when it's not monitoring any more processes.
*/
private void stopSelfWhenDone() {
@@ -440,7 +498,11 @@
}
}
+ /**
+ * Handles the BUGREPORT_FINISHED intent sent by {@code dumpstate}.
+ */
private void onBugreportFinished(int pid, Intent intent) {
+ mInfoDialog.onBugreportFinished(pid);
final Context context = getApplicationContext();
BugreportInfo info;
synchronized (mProcesses) {
@@ -453,6 +515,7 @@
}
info.bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
info.screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+ info.finished = true;
}
final Configuration conf = context.getResources().getConfiguration();
@@ -494,21 +557,32 @@
/**
* Build {@link Intent} that can be used to share the given bugreport.
*/
- private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
+ private static Intent buildSendIntent(Context context, BugreportInfo info) {
+ // Files are kept on private storage, so turn into Uris that we can
+ // grant temporary permissions for.
+ final Uri bugreportUri = getUri(context, info.bugreportFile);
+ final Uri screenshotUri = getUri(context, info.screenshotFile);
+
final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
final String mimeType = "application/vnd.android.bugreport";
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setType(mimeType);
- intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+ final String subject = info.title != null ? info.title : bugreportUri.getLastPathSegment();
+ intent.putExtra(Intent.EXTRA_SUBJECT, subject);
// EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
// So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
// create the ClipData object with the attachments URIs.
- String messageBody = String.format("Build info: %s\nSerial number:%s",
- SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
- intent.putExtra(Intent.EXTRA_TEXT, messageBody);
+ StringBuilder messageBody = new StringBuilder("Build info: ")
+ .append(SystemProperties.get("ro.build.description"))
+ .append("\nSerial number: ")
+ .append(SystemProperties.get("ro.serialno"));
+ if (!TextUtils.isEmpty(info.description)) {
+ messageBody.append("\nDescription: ").append(info.description);
+ }
+ intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
final ClipData clipData = new ClipData(null, new String[] { mimeType },
new ClipData.Item(null, null, null, bugreportUri));
final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
@@ -542,12 +616,7 @@
return;
}
}
- // Files are kept on private storage, so turn into Uris that we can
- // grant temporary permissions for.
- final Uri bugreportUri = getUri(context, info.bugreportFile);
- final Uri screenshotUri = getUri(context, info.screenshotFile);
-
- final Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
+ final Intent sendIntent = buildSendIntent(context, info);
final Intent notifIntent;
// Send through warning dialog by default
@@ -580,13 +649,17 @@
.setContentTitle(title)
.setTicker(title)
.setContentText(context.getString(R.string.bugreport_finished_text))
- .setContentIntent(PendingIntent.getService(context, 0, shareIntent,
- PendingIntent.FLAG_CANCEL_CURRENT))
+ .setContentIntent(PendingIntent.getService(context, info.pid, shareIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT))
.setDeleteIntent(newCancelIntent(context, info))
.setLocalOnly(true)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color));
+ if (!TextUtils.isEmpty(info.name)) {
+ builder.setContentInfo(info.name);
+ }
+
NotificationManager.from(context).notify(TAG, info.pid, builder.build());
}
@@ -684,6 +757,231 @@
}
}
+ private static boolean setSystemProperty(String key, String value) {
+ try {
+ if (DEBUG) Log.v(TAG, "Setting system property" + key + " to " + value);
+ SystemProperties.set(key, value);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Could not set property " + key + " to " + value, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Updates the system property used by {@code dumpstate} to rename the final bugreport files.
+ */
+ private boolean setBugreportNameProperty(int pid, String name) {
+ Log.d(TAG, "Updating bugreport name to " + name);
+ final String key = DUMPSTATE_PREFIX + pid + NAME_SUFFIX;
+ return setSystemProperty(key, name);
+ }
+
+ /**
+ * Updates the user-provided details of a bugreport.
+ */
+ private void updateBugreportInfo(int pid, String name, String title, String description) {
+ synchronized (mProcesses) {
+ final BugreportInfo info = mProcesses.get(pid);
+ if (info == null) {
+ Log.w(TAG, "No bugreport info for PID " + pid);
+ return;
+ }
+ info.title = title;
+ info.description = description;
+ if (name != null && !info.name.equals(name)) {
+ info.name = name;
+ updateProgress(info);
+ }
+ }
+ }
+
+ /**
+ * Checks whether a character is valid on bugreport names.
+ */
+ @VisibleForTesting
+ static boolean isValid(char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
+ || c == '_' || c == '-';
+ }
+
+ /**
+ * Helper class encapsulating the UI elements and logic used to display a dialog where user
+ * can change the details of a bugreport.
+ */
+ private final class BugreportInfoDialog {
+ private EditText mInfoName;
+ private EditText mInfoTitle;
+ private EditText mInfoDescription;
+ private AlertDialog mDialog;
+ private Button mOkButton;
+ private int mPid;
+
+ /**
+ * Last "committed" value of the bugreport name.
+ * <p>
+ * Once initially set, it's only updated when user clicks the OK button.
+ */
+ private String mSavedName;
+
+ /**
+ * Last value of the bugreport name as entered by the user.
+ * <p>
+ * Every time it's changed the equivalent system property is changed as well, but if the
+ * user clicks CANCEL, the old value (stored on {@code mSavedName} is restored.
+ * <p>
+ * This logic handles the corner-case scenario where {@code dumpstate} finishes after the
+ * user changed the name but didn't clicked OK yet (for example, because the user is typing
+ * the description). The only drawback is that if the user changes the name while
+ * {@code dumpstate} is running but clicks CANCEL after it finishes, then the final name
+ * will be the one that has been canceled. But when {@code dumpstate} finishes the {code
+ * name} UI is disabled and the old name restored anyways, so the user will be "alerted" of
+ * such drawback.
+ */
+ private String mTempName;
+
+ /**
+ * Sets its internal state and displays the dialog.
+ */
+ private synchronized void initialize(Context context, int pid, String name, String title,
+ String description) {
+ // First initializes singleton.
+ if (mDialog == null) {
+ @SuppressLint("InflateParams")
+ // It's ok pass null ViewRoot on AlertDialogs.
+ final View view = View.inflate(context, R.layout.dialog_bugreport_info, null);
+
+ mInfoName = (EditText) view.findViewById(R.id.name);
+ mInfoTitle = (EditText) view.findViewById(R.id.title);
+ mInfoDescription = (EditText) view.findViewById(R.id.description);
+
+ mInfoName.setOnFocusChangeListener(new OnFocusChangeListener() {
+
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ return;
+ }
+ sanitizeName();
+ }
+ });
+
+ mDialog = new AlertDialog.Builder(context)
+ .setView(view)
+ .setTitle(context.getString(R.string.bugreport_info_dialog_title))
+ .setCancelable(false)
+ .setPositiveButton(context.getString(com.android.internal.R.string.ok),
+ null)
+ .setNegativeButton(context.getString(com.android.internal.R.string.cancel),
+ new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int id)
+ {
+ if (!mTempName.equals(mSavedName)) {
+ // Must restore dumpstate's name since it was changed
+ // before user clicked OK.
+ setBugreportNameProperty(mPid, mSavedName);
+ }
+ }
+ })
+ .create();
+
+ mDialog.getWindow().setAttributes(
+ new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG));
+
+ }
+
+ // Then set fields.
+ mSavedName = mTempName = name;
+ mPid = pid;
+ if (!TextUtils.isEmpty(name)) {
+ mInfoName.setText(name);
+ }
+ if (!TextUtils.isEmpty(title)) {
+ mInfoTitle.setText(title);
+ }
+ if (!TextUtils.isEmpty(description)) {
+ mInfoDescription.setText(description);
+ }
+
+ // And finally display it.
+ mDialog.show();
+
+ // TODO: in a traditional AlertDialog, when the positive button is clicked the
+ // dialog is always closed, but we need to validate the name first, so we need to
+ // get a reference to it, which is only available after it's displayed.
+ // It would be cleaner to use a regular dialog instead, but let's keep this
+ // workaround for now and change it later, when we add another button to take
+ // extra screenshots.
+ if (mOkButton == null) {
+ mOkButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ mOkButton.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View view) {
+ sanitizeName();
+ final String name = mInfoName.getText().toString();
+ final String title = mInfoTitle.getText().toString();
+ final String description = mInfoDescription.getText().toString();
+
+ updateBugreportInfo(mPid, name, title, description);
+ mDialog.dismiss();
+ }
+ });
+ }
+ }
+
+ /**
+ * Sanitizes the user-provided value for the {@code name} field, automatically replacing
+ * invalid characters if necessary.
+ */
+ private synchronized void sanitizeName() {
+ String name = mInfoName.getText().toString();
+ if (name.equals(mTempName)) {
+ if (DEBUG) Log.v(TAG, "name didn't change, no need to sanitize: " + name);
+ return;
+ }
+ final StringBuilder safeName = new StringBuilder(name.length());
+ boolean changed = false;
+ for (int i = 0; i < name.length(); i++) {
+ final char c = name.charAt(i);
+ if (isValid(c)) {
+ safeName.append(c);
+ } else {
+ changed = true;
+ safeName.append('_');
+ }
+ }
+ if (changed) {
+ Log.v(TAG, "changed invalid name '" + name + "' to '" + safeName + "'");
+ name = safeName.toString();
+ mInfoName.setText(name);
+ }
+ mTempName = name;
+
+ // Must update system property for the cases where dumpstate finishes
+ // while the user is still entering other fields (like title or
+ // description)
+ setBugreportNameProperty(mPid, name);
+ }
+
+ /**
+ * Notifies the dialog that the bugreport has finished so it disables the {@code name}
+ * field.
+ * <p>Once the bugreport is finished dumpstate has already generated the final files, so
+ * changing the name would have no effect.
+ */
+ private synchronized void onBugreportFinished(int pid) {
+ if (mInfoName != null) {
+ mInfoName.setEnabled(false);
+ mInfoName.setText(mSavedName);
+ }
+ }
+
+ }
+
/**
* Information about a bugreport process while its in progress.
*/
@@ -704,6 +1002,18 @@
String name;
/**
+ * User-provided, one-line summary of the bug; when set, will be used as the subject
+ * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
+ */
+ String title;
+
+ /**
+ * User-provided, detailed description of the bugreport; when set, will be added to the body
+ * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
+ */
+ String description;
+
+ /**
* Maximum progress of the bugreport generation.
*/
int max;
@@ -761,6 +1071,7 @@
public String toString() {
final float percent = ((float) progress * 100 / max);
return "pid: " + pid + ", name: " + name + ", finished: " + finished
+ + "\n\ttitle: " + title + "\n\tdescription: " + description
+ "\n\tfile: " + bugreportFile + "\n\tscreenshot: " + screenshotFile
+ "\n\tprogress: " + progress + "/" + max + "(" + percent + ")"
+ "\n\tlast_update: " + getFormattedLastUpdate();
diff --git a/packages/Shell/tests/Android.mk b/packages/Shell/tests/Android.mk
index 62a37bc..1e0eaac 100644
--- a/packages/Shell/tests/Android.mk
+++ b/packages/Shell/tests/Android.mk
@@ -8,9 +8,7 @@
LOCAL_JAVA_LIBRARIES := android.test.runner
-# TODO: update and/or remove
LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator
-#LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target ub-uiautomator
LOCAL_PACKAGE_NAME := ShellTests
LOCAL_INSTRUMENTATION_FOR := Shell
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 1f4d749..7f609fa 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -94,7 +94,11 @@
private static final int PID = 42;
private static final String PROGRESS_PROPERTY = "dumpstate.42.progress";
private static final String MAX_PROPERTY = "dumpstate.42.max";
+ private static final String NAME_PROPERTY = "dumpstate.42.name";
private static final String NAME = "BUG, Y U NO REPORT?";
+ private static final String NEW_NAME = "Bug_Forrest_Bug";
+ private static final String TITLE = "Wimbugdom Champion 2015";
+ private String mDescription;
private String mPlainTextPath;
private String mZipPath;
@@ -120,10 +124,17 @@
createTextFile(mScreenshotPath, SCREENSHOT_CONTENT);
createZipFile(mZipPath, BUGREPORT_FILE, BUGREPORT_CONTENT);
+ // Creates a multi-line description.
+ StringBuilder sb = new StringBuilder();
+ for (int i = 1; i <= 20; i++) {
+ sb.append("All work and no play makes Shell a dull app!\n");
+ }
+ mDescription = sb.toString();
+
BugreportPrefs.setWarningState(mContext, BugreportPrefs.STATE_HIDE);
}
- public void testFullWorkflow() throws Exception {
+ public void testProgress() throws Exception {
resetProperties();
sendBugreportStarted(1000);
@@ -145,6 +156,81 @@
assertServiceNotRunning();
}
+ public void testProgress_changeDetails() throws Exception {
+ resetProperties();
+ sendBugreportStarted(1000);
+
+ DetailsUi detailsUi = new DetailsUi(mUiBot);
+
+ // Check initial name.
+ String actualName = detailsUi.nameField.getText().toString();
+ assertEquals("Wrong value on field 'name'", NAME, actualName);
+
+ // Change name - it should have changed system property once focus is changed.
+ detailsUi.nameField.setText(NEW_NAME);
+ detailsUi.focusAwayFromName();
+ assertPropertyValue(NAME_PROPERTY, NEW_NAME);
+
+ // Cancel the dialog to make sure property was restored.
+ detailsUi.clickCancel();
+ assertPropertyValue(NAME_PROPERTY, NAME);
+
+ // Now try to set an invalid name.
+ detailsUi.reOpen();
+ detailsUi.nameField.setText("/etc/passwd");
+ detailsUi.clickOk();
+ assertPropertyValue(NAME_PROPERTY, "_etc_passwd");
+
+ // Finally, make the real changes.
+ detailsUi.reOpen();
+ detailsUi.nameField.setText(NEW_NAME);
+ detailsUi.titleField.setText(TITLE);
+ detailsUi.descField.setText(mDescription);
+
+ detailsUi.clickOk();
+
+ assertPropertyValue(NAME_PROPERTY, NEW_NAME);
+ assertProgressNotification(NEW_NAME, "0.00%");
+
+ Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath,
+ mScreenshotPath);
+ assertActionSendMultiple(extras, TITLE, mDescription, BUGREPORT_CONTENT, SCREENSHOT_CONTENT);
+
+ assertServiceNotRunning();
+ }
+
+ public void testProgress_bugreportFinishedWhileChangingDetails() throws Exception {
+ resetProperties();
+ sendBugreportStarted(1000);
+
+ DetailsUi detailsUi = new DetailsUi(mUiBot);
+
+ // Finish the bugreport while user's still typing the name.
+ detailsUi.nameField.setText(NEW_NAME);
+ sendBugreportFinished(PID, mPlainTextPath, mScreenshotPath);
+
+ // Wait until the share notifcation is received...
+ mUiBot.getNotification(mContext.getString(R.string.bugreport_finished_title));
+ // ...then close notification bar.
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+ // Make sure UI was updated properly.
+ assertFalse("didn't disable name on UI", detailsUi.nameField.isEnabled());
+ assertEquals("didn't revert name on UI", NAME, detailsUi.nameField.getText().toString());
+
+ // Finish changing other fields.
+ detailsUi.titleField.setText(TITLE);
+ detailsUi.descField.setText(mDescription);
+ detailsUi.clickOk();
+
+ // Finally, share bugreport.
+ Bundle extras = acceptBugreportAndGetSharedIntent();
+ assertActionSendMultiple(extras, TITLE, mDescription, BUGREPORT_CONTENT,
+ SCREENSHOT_CONTENT);
+
+ assertServiceNotRunning();
+ }
+
public void testBugreportFinished_withWarning() throws Exception {
// Explicitly shows the warning.
BugreportPrefs.setWarningState(mContext, BugreportPrefs.STATE_SHOW);
@@ -204,14 +290,18 @@
private void assertProgressNotification(String name, String percent) {
// TODO: it current looks for 3 distinct objects, without taking advantage of their
// relationship.
- String title = mContext.getString(R.string.bugreport_in_progress_title);
- Log.v(TAG, "Looking for progress notification title: '" + title+ "'");
- mUiBot.getNotification(title);
+ openProgressNotification();
Log.v(TAG, "Looking for progress notification details: '" + name + "-" + percent + "'");
mUiBot.getObject(name);
mUiBot.getObject(percent);
}
+ private void openProgressNotification() {
+ String title = mContext.getString(R.string.bugreport_in_progress_title);
+ Log.v(TAG, "Looking for progress notification title: '" + title + "'");
+ mUiBot.getNotification(title);
+ }
+
void resetProperties() {
// TODO: call method to remove property instead
SystemProperties.set(PROGRESS_PROPERTY, "0");
@@ -270,7 +360,6 @@
/**
* Sends a "bugreport finished" intent.
- *
*/
private void sendBugreportFinished(Integer pid, String bugreportPath, String screenshotPath) {
Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
@@ -292,13 +381,21 @@
*/
private void assertActionSendMultiple(Bundle extras, String bugreportContent,
String screenshotContent) throws IOException {
+ assertActionSendMultiple(extras, ZIP_FILE, null, bugreportContent, screenshotContent);
+ }
+
+ private void assertActionSendMultiple(Bundle extras, String subject, String description,
+ String bugreportContent, String screenshotContent) throws IOException {
String body = extras.getString(Intent.EXTRA_TEXT);
assertContainsRegex("missing build info",
SystemProperties.get("ro.build.description"), body);
assertContainsRegex("missing serial number",
SystemProperties.get("ro.serialno"), body);
+ if (description != null) {
+ assertContainsRegex("missing description", description, body);
+ }
- assertEquals("wrong subject", ZIP_FILE, extras.getString(Intent.EXTRA_SUBJECT));
+ assertEquals("wrong subject", subject, extras.getString(Intent.EXTRA_SUBJECT));
List<Uri> attachments = extras.getParcelableArrayList(Intent.EXTRA_STREAM);
int expectedSize = screenshotContent != null ? 2 : 1;
@@ -355,6 +452,11 @@
fail("Did not find entry '" + entryName + "' on file '" + uri + "'");
}
+ private void assertPropertyValue(String key, String expectedValue) {
+ String actualValue = SystemProperties.get(key);
+ assertEquals("Wrong value for property '" + key + "'", expectedValue, actualValue);
+ }
+
private void assertServiceNotRunning() {
String service = BugreportProgressService.class.getName();
assertFalse("Service '" + service + "' is still running", isServiceRunning(service));
@@ -402,4 +504,55 @@
Log.v(TAG, "Path for '" + file + "': " + path);
return path;
}
+
+ /**
+ * Helper class containing the UiObjects present in the bugreport info dialog.
+ */
+ private final class DetailsUi {
+
+ final UiObject detailsButton;
+ final UiObject nameField;
+ final UiObject titleField;
+ final UiObject descField;
+ final UiObject okButton;
+ final UiObject cancelButton;
+
+ /**
+ * Gets the UI objects by opening the progress notification and clicking DETAILS.
+ */
+ DetailsUi(UiBot uiBot) {
+ openProgressNotification();
+ detailsButton = mUiBot.getVisibleObject(
+ mContext.getString(R.string.bugreport_info_action).toUpperCase());
+ mUiBot.click(detailsButton, "details_button");
+ // TODO: unhardcode resource ids
+ nameField = mUiBot.getVisibleObjectById("com.android.shell:id/name");
+ titleField = mUiBot.getVisibleObjectById("com.android.shell:id/title");
+ descField = mUiBot.getVisibleObjectById("com.android.shell:id/description");
+ okButton = mUiBot.getObjectById("android:id/button1");
+ cancelButton = mUiBot.getObjectById("android:id/button2");
+ }
+
+ /**
+ * Takes focus away from the name field so it can be validated.
+ */
+ void focusAwayFromName() {
+ mUiBot.click(titleField, "title_field"); // Change focus.
+ mUiBot.pressBack(); // Dismiss keyboard.
+ }
+
+ void reOpen() {
+ openProgressNotification();
+ mUiBot.click(detailsButton, "details_button");
+
+ }
+
+ void clickOk() {
+ mUiBot.click(okButton, "details_ok_button");
+ }
+
+ void clickCancel() {
+ mUiBot.click(cancelButton, "details_cancel_button");
+ }
+ }
}
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
index c871727..384c3da 100644
--- a/packages/Shell/tests/src/com/android/shell/UiBot.java
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -79,6 +79,17 @@
}
/**
+ * Gets an object that might not yet be available in current UI.
+ *
+ * @param id Object's fully-qualified resource id (like {@code android:id/button1})
+ */
+ public UiObject getObjectById(String id) {
+ boolean gotIt = mDevice.wait(Until.hasObject(By.res(id)), mTimeout);
+ assertTrue("object with id '(" + id + "') not visible yet", gotIt);
+ return getVisibleObjectById(id);
+ }
+
+ /**
* Gets an object which is guaranteed to be present in the current UI.
*
* @param text Object's text as displayed by the UI.
@@ -90,6 +101,18 @@
}
/**
+ * Gets an object which is guaranteed to be present in the current UI.
+ *
+ * @param text Object's text as displayed by the UI.
+ */
+ public UiObject getVisibleObjectById(String id) {
+ UiObject uiObject = mDevice.findObject(new UiSelector().resourceId(id));
+ assertTrue("could not find object with id '" + id+ "'", uiObject.exists());
+ return uiObject;
+ }
+
+
+ /**
* Clicks on a UI element.
*
* @param uiObject UI element to be clicked.
@@ -151,4 +174,8 @@
click(activity, name);
}
}
+
+ public void pressBack() {
+ mDevice.pressBack();
+ }
}
diff --git a/packages/Shell/tests/src/com/android/shell/UtilitiesTest.java b/packages/Shell/tests/src/com/android/shell/UtilitiesTest.java
new file mode 100644
index 0000000..51b7ba8
--- /dev/null
+++ b/packages/Shell/tests/src/com/android/shell/UtilitiesTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.shell;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+import static com.android.shell.BugreportProgressService.isValid;
+
+@SmallTest
+public class UtilitiesTest extends TestCase {
+
+ public void testIsValidChar_valid() {
+ String validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+ for (int i = 0; i < validChars.length(); i++) {
+ char c = validChars.charAt(i);
+ assertTrue("char '" + c + "' should be valid", isValid(c));
+ }
+ }
+
+ public void testIsValidChar_invalid() {
+ String validChars = "/.<>;:'\'\"\\+=*&^%$#@!`~áéíóúãñÂÊÎÔÛ";
+ for (int i = 0; i < validChars.length(); i++) {
+ char c = validChars.charAt(i);
+ assertFalse("char '" + c + "' should not be valid", isValid(c));
+ }
+ }
+}
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index eb63e5d..61cad2f 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -8,7 +8,11 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
Keyguard \
- android-support-v7-recyclerview
+ android-support-v7-recyclerview \
+ android-support-v7-preference \
+ android-support-v7-appcompat \
+ android-support-v14-preference
+
LOCAL_JAVA_LIBRARIES := telephony-common
LOCAL_PACKAGE_NAME := SystemUI
@@ -22,10 +26,13 @@
LOCAL_RESOURCE_DIR := \
frameworks/base/packages/Keyguard/res \
$(LOCAL_PATH)/res \
+ frameworks/support/v7/preference/res \
+ frameworks/support/v14/preference/res \
+ frameworks/support/v7/appcompat/res \
frameworks/support/v7/recyclerview/res
LOCAL_AAPT_FLAGS := --auto-add-overlay \
- --extra-packages com.android.keyguard:android.support.v7.recyclerview
+ --extra-packages com.android.keyguard:android.support.v7.recyclerview:android.support.v7.preference:android.support.v14.preference:android.support.v7.appcompat
ifneq ($(SYSTEM_UI_INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 51b84f5..02ddae6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -186,7 +186,7 @@
<activity android:name=".tuner.TunerActivity"
android:enabled="false"
android:icon="@drawable/tuner"
- android:theme="@android:style/Theme.Material.Settings"
+ android:theme="@style/TunerSettings"
android:label="@string/system_ui_tuner"
android:process=":tuner"
android:exported="true">
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 75e7959..6a10c2c 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -27,3 +27,9 @@
public float getTaskProgress();
public void setTaskProgress(float);
}
+
+-keepclasseswithmembers class * {
+ public <init>(android.content.Context, android.util.AttributeSet);
+}
+
+-keep class ** extends android.support.v14.preference.PreferenceFragment
diff --git a/packages/SystemUI/res/layout/recents_history_task.xml b/packages/SystemUI/res/layout/recents_history_task.xml
index b9de156..ae11006 100644
--- a/packages/SystemUI/res/layout/recents_history_task.xml
+++ b/packages/SystemUI/res/layout/recents_history_task.xml
@@ -13,15 +13,30 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<TextView
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:theme="@android:style/Theme.Material"
android:layout_width="match_parent"
android:layout_height="48dp"
- android:paddingLeft="32dp"
- android:gravity="start|center_vertical"
- android:textSize="14sp"
- android:textColor="#FFFFFF"
- android:fontFamily="sans-serif-medium"
- android:background="?android:selectableItemBackground"
- android:alpha="1" />
\ No newline at end of file
+ android:orientation="horizontal"
+ android:clickable="true"
+ android:focusable="true"
+ android:background="?android:selectableItemBackground">
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_gravity="center"
+ android:layout_marginStart="16dp" />
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:layout_gravity="end"
+ android:paddingStart="16dp"
+ android:gravity="start|center_vertical"
+ android:textSize="14sp"
+ android:textColor="#FFFFFF"
+ android:fontFamily="sans-serif-medium" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 47ad6dc..aad428a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -315,4 +315,8 @@
<item name="android:layout_height">48dp</item>
</style>
+ <style name="TunerSettings" parent="@android:style/Theme.Material.Settings">
+ <item name="preferenceTheme">@android:style/Theme.Material.Settings</item>
+ </style>
+
</resources>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 43359b3..6103216 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -24,6 +24,7 @@
sysui:defValue="true" />
<PreferenceScreen
+ android:key="status_bar"
android:title="@string/status_bar" >
<com.android.systemui.tuner.StatusBarSwitch
@@ -70,6 +71,7 @@
<PreferenceScreen
+ android:key="overview"
android:title="@string/overview" >
<com.android.systemui.tuner.TunerSwitch
@@ -97,7 +99,8 @@
<Preference
android:key="demo_mode"
- android:title="@string/demo_mode" />
+ android:title="@string/demo_mode"
+ android:fragment="com.android.systemui.tuner.DemoModeFragment" />
<!-- Warning, this goes last. -->
<Preference
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index c08fb05..070b395 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -27,6 +27,7 @@
public static final int DismissSourceKeyboard = 0;
public static final int DismissSourceSwipeGesture = 1;
public static final int DismissSourceHeaderButton = 2;
+ public static final int DismissSourceHistorySwipeGesture = 3;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 9f0ac35..b3ce4a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -51,10 +51,12 @@
import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
import com.android.systemui.recents.events.activity.ShowHistoryEvent;
+import com.android.systemui.recents.events.activity.TaskStackUpdatedEvent;
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
+import com.android.systemui.recents.events.ui.DismissTaskEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
@@ -526,6 +528,22 @@
}
@Override
+ public void onMultiWindowModeChanged(boolean multiWindowMode) {
+ super.onMultiWindowModeChanged(multiWindowMode);
+ if (!multiWindowMode) {
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+ launchOpts.loadIcons = false;
+ launchOpts.loadThumbnails = false;
+ launchOpts.onlyLoadForCache = true;
+ RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
+ loader.preloadTasks(loadPlan, false);
+ loader.loadTasks(this, loadPlan, launchOpts);
+ EventBus.getDefault().send(new TaskStackUpdatedEvent(loadPlan.getTaskStack()));
+ }
+ }
+
+ @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_TAB: {
@@ -709,7 +727,7 @@
MetricsLogger.count(this, "overview_app_info", 1);
}
- public final void onBusEvent(DismissTaskViewEvent event) {
+ public final void onBusEvent(DismissTaskEvent event) {
// Remove any stored data from the loader
RecentsTaskLoader loader = Recents.getTaskLoader();
loader.deleteTaskData(event.task, false);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
new file mode 100644
index 0000000..b94ed7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.RecentsAppWidgetHost;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.model.TaskStack;
+
+/**
+ * This is sent by the activity whenever the task stach has changed.
+ */
+public class TaskStackUpdatedEvent extends EventBus.Event {
+
+ /**
+ * A new TaskStack instance representing the latest stack state.
+ */
+ public final TaskStack stack;
+
+ public TaskStackUpdatedEvent(TaskStack stack) {
+ this.stack = stack;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java
new file mode 100644
index 0000000..bcbbde8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.views.TaskView;
+
+/**
+ * This is sent when a {@link Task} has been dismissed.
+ */
+public class DismissTaskEvent extends EventBus.Event {
+
+ public final Task task;
+
+ public DismissTaskEvent(Task task) {
+ this.task = task;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
index 06265bd..76439c0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -20,14 +20,22 @@
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateFormat;
+import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.HideHistoryButtonEvent;
+import com.android.systemui.recents.events.activity.HideHistoryEvent;
+import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
import java.util.Calendar;
@@ -49,28 +57,48 @@
static final int TASK_ROW_VIEW_TYPE = 1;
/**
- * View holder implementation.
+ * View holder implementation. The {@param TaskCallbacks} are only called for TaskRow view
+ * holders.
*/
- public static class ViewHolder extends RecyclerView.ViewHolder {
- public View mContent;
+ public static class ViewHolder extends RecyclerView.ViewHolder implements Task.TaskCallbacks {
+ public final View content;
public ViewHolder(View v) {
super(v);
- mContent = v;
+ content = v;
+ }
+
+ @Override
+ public void onTaskDataLoaded(Task task) {
+ // This callback is only made for TaskRow view holders
+ ImageView iv = (ImageView) content.findViewById(R.id.icon);
+ iv.setImageDrawable(task.applicationIcon);
+ }
+
+ @Override
+ public void onTaskDataUnloaded() {
+ // This callback is only made for TaskRow view holders
+ ImageView iv = (ImageView) content.findViewById(R.id.icon);
+ iv.setImageBitmap(null);
+ }
+
+ @Override
+ public void onTaskStackIdChanged() {
+ // Do nothing, this callback is only made for TaskRow view holders
}
}
/**
* A single row of content.
*/
- private interface Row {
+ interface Row {
int getViewType();
}
/**
* A date row.
*/
- private static class DateRow implements Row {
+ static class DateRow implements Row {
public final String date;
@@ -87,20 +115,20 @@
/**
* A task row.
*/
- private static class TaskRow implements Row, View.OnClickListener {
+ static class TaskRow implements Row, View.OnClickListener {
- public final String description;
- private final int mTaskId;
+ public final Task task;
+ public final int dateKey;
- public TaskRow(Task task) {
- mTaskId = task.key.id;
- description = task.activityLabel;
+ public TaskRow(Task task, int dateKey) {
+ this.task = task;
+ this.dateKey = dateKey;
}
@Override
public void onClick(View v) {
SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.startActivityFromRecents(v.getContext(), mTaskId, description,
+ ssp.startActivityFromRecents(v.getContext(), task.key.id, task.activityLabel,
ActivityOptions.makeBasic());
}
@@ -114,6 +142,8 @@
private LayoutInflater mLayoutInflater;
private final List<Task> mTasks = new ArrayList<>();
private final List<Row> mRows = new ArrayList<>();
+ private final SparseIntArray mTaskRowCount = new SparseIntArray();
+ private TaskStack mStack;
public RecentsHistoryAdapter(Context context) {
mLayoutInflater = LayoutInflater.from(context);
@@ -122,17 +152,17 @@
/**
* Updates this adapter with the given tasks.
*/
- public void updateTasks(Context context, List<Task> tasks) {
+ public void updateTasks(Context context, TaskStack stack) {
mContext = context;
- mTasks.clear();
- mTasks.addAll(tasks);
+ mStack = stack;
final Locale l = context.getResources().getConfiguration().locale;
final String dateFormatStr = DateFormat.getBestDateTimePattern(l, "EEEEMMMMd");
- final List<Task> tasksMostRecent = new ArrayList<>(tasks);
+ final List<Task> tasksMostRecent = new ArrayList<>(stack.getHistoricalTasks());
Collections.reverse(tasksMostRecent);
- int prevDayKey = -1;
+ int prevDateKey = -1;
mRows.clear();
+ mTaskRowCount.clear();
for (Task task : tasksMostRecent) {
if (task.isFreeformTask()) {
continue;
@@ -140,34 +170,46 @@
Calendar cal = Calendar.getInstance(l);
cal.setTimeInMillis(task.key.lastActiveTime);
- int dayKey = Objects.hash(cal.get(Calendar.YEAR), cal.get(Calendar.DAY_OF_YEAR));
- if (dayKey != prevDayKey) {
- prevDayKey = dayKey;
+ int dateKey = Objects.hash(cal.get(Calendar.YEAR), cal.get(Calendar.DAY_OF_YEAR));
+ if (dateKey != prevDateKey) {
+ prevDateKey = dateKey;
mRows.add(new DateRow(DateFormat.format(dateFormatStr, cal).toString()));
}
- mRows.add(new TaskRow(task));
+ mRows.add(new TaskRow(task, dateKey));
+ mTaskRowCount.put(dateKey, mTaskRowCount.get(dateKey, 0) + 1);
}
notifyDataSetChanged();
}
/**
- * Removes historical tasks beloning to the specified package and user.
+ * Removes historical tasks belonging to the specified package and user. We do not need to
+ * remove the task from the TaskStack since the TaskStackView will also receive this event.
*/
public void removeTasks(String packageName, int userId) {
boolean packagesRemoved = false;
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- Task task = mTasks.get(i);
- String taskPackage = task.key.getComponent().getPackageName();
- if (task.key.userId == userId && taskPackage.equals(packageName)) {
- mTasks.remove(i);
- packagesRemoved = true;
+ for (int i = mRows.size() - 1; i >= 0; i--) {
+ Row row = mRows.get(i);
+ if (row.getViewType() == TASK_ROW_VIEW_TYPE) {
+ TaskRow taskRow = (TaskRow) row;
+ Task task = taskRow.task;
+ String taskPackage = task.key.getComponent().getPackageName();
+ if (task.key.userId == userId && taskPackage.equals(packageName)) {
+ i = removeTaskRow(i);
+ }
}
}
- if (packagesRemoved) {
- updateTasks(mContext, new ArrayList<Task>(mTasks));
+ if (mRows.isEmpty()) {
+ dismissHistory();
}
}
+ /**
+ * Returns the row at the given {@param position}.
+ */
+ public Row getRow(int position) {
+ return mRows.get(position);
+ }
+
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
@@ -184,25 +226,55 @@
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+
Row row = mRows.get(position);
- int viewType = mRows.get(position).getViewType();
+ int viewType = row.getViewType();
switch (viewType) {
case DATE_ROW_VIEW_TYPE: {
- TextView tv = (TextView) holder.mContent;
+ TextView tv = (TextView) holder.content;
tv.setText(((DateRow) row).date);
break;
}
case TASK_ROW_VIEW_TYPE: {
- TextView tv = (TextView) holder.mContent;
TaskRow taskRow = (TaskRow) row;
- tv.setText(taskRow.description);
- tv.setOnClickListener(taskRow);
+ taskRow.task.addCallback(holder);
+ TextView tv = (TextView) holder.content.findViewById(R.id.description);
+ tv.setText(taskRow.task.activityLabel);
+ holder.content.setOnClickListener(taskRow);
+ loader.loadTaskData(taskRow.task);
break;
}
}
}
@Override
+ public void onViewRecycled(ViewHolder holder) {
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+
+ int position = holder.getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ Row row = mRows.get(position);
+ int viewType = row.getViewType();
+ if (viewType == TASK_ROW_VIEW_TYPE) {
+ TaskRow taskRow = (TaskRow) row;
+ taskRow.task.removeCallback(holder);
+ loader.unloadTaskData(taskRow.task);
+ }
+ }
+ }
+
+ public void onTaskRemoved(Task task, int position) {
+ // Since this is removed from the history, we need to update the stack as well to ensure
+ // that the model is correct
+ mStack.removeTask(task);
+ removeTaskRow(position);
+ if (mRows.isEmpty()) {
+ dismissHistory();
+ }
+ }
+
+ @Override
public int getItemCount() {
return mRows.size();
}
@@ -211,4 +283,40 @@
public int getItemViewType(int position) {
return mRows.get(position).getViewType();
}
+
+ /**
+ * Removes a task row, also removing the associated {@link DateRow} if there are no more tasks
+ * in that date group.
+ *
+ * @param position an adapter position of a task row such that 0 < position < num rows.
+ * @return the index of the last removed row
+ */
+ private int removeTaskRow(int position) {
+ // Remove the task at that row
+ TaskRow taskRow = (TaskRow) mRows.remove(position);
+ int numTasks = mTaskRowCount.get(taskRow.dateKey) - 1;
+ mTaskRowCount.put(taskRow.dateKey, numTasks);
+ notifyItemRemoved(position);
+
+ if (numTasks == 0) {
+ // If that was the last task row in the group, then remove the date as well
+ mRows.remove(position - 1);
+ mTaskRowCount.removeAt(mTaskRowCount.indexOfKey(taskRow.dateKey));
+ notifyItemRemoved(position - 1);
+ return position - 1;
+ } else {
+ return position;
+ }
+ }
+
+ /**
+ * Dismisses history back to the stack view.
+ */
+ private void dismissHistory() {
+ ReferenceCountedTrigger t = new ReferenceCountedTrigger(mContext);
+ t.increment();
+ EventBus.getDefault().send(new HideHistoryEvent(true /* animate */, t));
+ t.decrement();
+ EventBus.getDefault().send(new HideHistoryButtonEvent());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
new file mode 100644
index 0000000..e0a2730
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.history;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.ui.DismissTaskEvent;
+
+
+/**
+ * An item touch handler for items in the history view.
+ */
+public class RecentsHistoryItemTouchCallbacks extends ItemTouchHelper.SimpleCallback {
+
+ private Context mContext;
+ private RecentsHistoryAdapter mAdapter;
+
+ public RecentsHistoryItemTouchCallbacks(Context context, RecentsHistoryAdapter adapter) {
+ super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
+ mContext = context;
+ mAdapter = adapter;
+ }
+
+ @Override
+ public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
+ RecyclerView.ViewHolder target) {
+ return false;
+ }
+
+ @Override
+ public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ int viewType = mAdapter.getItemViewType(viewHolder.getAdapterPosition());
+ switch (viewType) {
+ case RecentsHistoryAdapter.DATE_ROW_VIEW_TYPE:
+ // Disallow swiping
+ return 0;
+ default:
+ return super.getSwipeDirs(recyclerView, viewHolder);
+ }
+ }
+
+ @Override
+ public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
+ int position = viewHolder.getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ RecentsHistoryAdapter.Row row = mAdapter.getRow(position);
+ RecentsHistoryAdapter.TaskRow taskRow = (RecentsHistoryAdapter.TaskRow) row;
+
+ // Remove the task from the system
+ EventBus.getDefault().send(new DismissTaskEvent(taskRow.task));
+ mAdapter.onTaskRemoved(taskRow.task, position);
+
+ // Keep track of deletions by swiping within history
+ MetricsLogger.histogram(mContext, "overview_task_dismissed_source",
+ Constants.Metrics.DismissSourceHistorySwipeGesture);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
index 1163f14..9524da5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
@@ -16,12 +16,12 @@
package com.android.systemui.recents.history;
-import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsets;
@@ -36,12 +36,7 @@
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskView;
-
-import java.util.ArrayList;
-import java.util.HashSet;
/**
* A list of the recent tasks that are not in the stack.
@@ -53,6 +48,7 @@
private RecyclerView mRecyclerView;
private RecentsHistoryAdapter mAdapter;
+ private RecentsHistoryItemTouchCallbacks mItemTouchHandler;
private boolean mIsVisible;
private Rect mSystemInsets = new Rect();
@@ -77,6 +73,7 @@
super(context, attrs, defStyleAttr, defStyleRes);
Resources res = context.getResources();
mAdapter = new RecentsHistoryAdapter(context);
+ mItemTouchHandler = new RecentsHistoryItemTouchCallbacks(context, mAdapter);
mHistoryTransitionDuration = res.getInteger(R.integer.recents_history_transition_duration);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_slow_in);
@@ -101,7 +98,7 @@
.start();
}
});
- mAdapter.updateTasks(getContext(), stack.getHistoricalTasks());
+ mAdapter.updateTasks(getContext(), stack);
mIsVisible = true;
}
@@ -156,6 +153,8 @@
mRecyclerView = (RecyclerView) findViewById(R.id.list);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+ ItemTouchHelper touchHelper = new ItemTouchHelper(mItemTouchHandler);
+ touchHelper.attachToRecyclerView(mRecyclerView);
}
@Override
@@ -180,7 +179,6 @@
@Override
protected void onAttachedToWindow() {
- SystemServicesProxy ssp = Recents.getSystemServices();
EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
super.onAttachedToWindow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index dae6e94..0884695d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -200,7 +200,7 @@
allTasks.addAll(stackTasks);
allTasks.addAll(freeformTasks);
mStack = new TaskStack();
- mStack.setTasks(allTasks);
+ mStack.setTasks(allTasks, false /* notifyStackChanges */);
mStack.createAffiliatedGroupings(mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 3aed3f3..73c0adb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -26,6 +26,7 @@
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
+import java.util.ArrayList;
import java.util.Objects;
@@ -36,7 +37,7 @@
/* Task callbacks */
public interface TaskCallbacks {
/* Notifies when a task has been bound */
- public void onTaskDataLoaded();
+ public void onTaskDataLoaded(Task task);
/* Notifies when a task has been unbound */
public void onTaskDataUnloaded();
/* Notifies when a task's stack id has changed. */
@@ -110,7 +111,7 @@
public String iconFilename;
public Rect bounds;
- private TaskCallbacks mCb;
+ private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
public Task() {
// Do nothing
@@ -157,16 +158,24 @@
this.bounds = o.bounds;
}
- /** Set the callbacks */
- public void setCallbacks(TaskCallbacks cb) {
- mCb = cb;
+ /**
+ * Add a callback.
+ */
+ public void addCallback(TaskCallbacks cb) {
+ if (!mCallbacks.contains(cb)) {
+ mCallbacks.add(cb);
+ }
+ }
+
+ /**
+ * Remove a callback.
+ */
+ public void removeCallback(TaskCallbacks cb) {
+ mCallbacks.remove(cb);
}
/** Set the grouping */
public void setGroup(TaskGrouping group) {
- if (group != null && this.group != null) {
- throw new RuntimeException("This task is already assigned to a group.");
- }
this.group = group;
}
@@ -175,8 +184,9 @@
*/
public void setStackId(int stackId) {
key.stackId = stackId;
- if (mCb != null) {
- mCb.onTaskStackIdChanged();
+ int callbackCount = mCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ mCallbacks.get(i).onTaskStackIdChanged();
}
}
@@ -192,8 +202,9 @@
public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
this.applicationIcon = applicationIcon;
this.thumbnail = thumbnail;
- if (mCb != null) {
- mCb.onTaskDataLoaded();
+ int callbackCount = mCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ mCallbacks.get(i).onTaskDataLoaded(this);
}
}
@@ -201,8 +212,9 @@
public void notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultApplicationIcon) {
applicationIcon = defaultApplicationIcon;
thumbnail = defaultThumbnail;
- if (mCb != null) {
- mCb.onTaskDataUnloaded();
+ int callbackCount = mCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ mCallbacks.get(i).onTaskDataUnloaded();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 5d07b10..c4a71b3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -206,11 +206,20 @@
/** Task stack callbacks */
public interface TaskStackCallbacks {
- /* Notifies when a task has been removed from the stack */
+ /**
+ * Notifies when a new task has been added to the stack.
+ */
+ void onStackTaskAdded(TaskStack stack, Task newTask);
+
+ /**
+ * Notifies when a task has been removed from the stack.
+ */
void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
Task newFrontMostTask);
- /* Notifies when a task has been removed from the history */
+ /**
+ * Notifies when a task has been removed from the history.
+ */
void onHistoryTaskRemoved(TaskStack stack, Task removedTask);
}
@@ -315,6 +324,7 @@
// The task offset to apply to a task id as a group affiliation
static final int IndividualTaskIdOffset = 1 << 16;
+ ArrayList<Task> mRawTaskList = new ArrayList<>();
FilteredTaskList mStackTaskList = new FilteredTaskList();
FilteredTaskList mHistoryTaskList = new FilteredTaskList();
TaskStackCallbacks mCb;
@@ -397,9 +407,11 @@
taskList.remove(t);
// Remove it from the group as well, and if it is empty, remove the group
TaskGrouping group = t.group;
- group.removeTask(t);
- if (group.getTaskCount() == 0) {
- removeGroup(group);
+ if (group != null) {
+ group.removeTask(t);
+ if (group.getTaskCount() == 0) {
+ removeGroup(group);
+ }
}
// Update the lock-to-app state
t.lockToThisTask = false;
@@ -409,7 +421,6 @@
public void removeTask(Task t) {
if (mStackTaskList.contains(t)) {
boolean wasFrontMostTask = (getStackFrontMostTask() == t);
- int removedTaskIndex = indexOfStackTask(t);
removeTaskImpl(mStackTaskList, t);
Task newFrontMostTask = getStackFrontMostTask();
if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) {
@@ -430,19 +441,73 @@
/**
* Sets a few tasks in one go, without calling any callbacks.
+ *
+ * @param tasks the new set of tasks to replace the current set.
+ * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
*/
- public void setTasks(List<Task> tasks) {
+ public void setTasks(List<Task> tasks, boolean notifyStackChanges) {
+ // Compute a has set for each of the tasks
+ HashMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
+ HashMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
+
+ ArrayList<Task> newTasks = new ArrayList<>();
+
+ // Disable notifications if there are no callbacks
+ if (mCb == null) {
+ notifyStackChanges = false;
+ }
+
+ // Remove any tasks that no longer exist
+ int taskCount = mRawTaskList.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = mRawTaskList.get(i);
+ if (!newTasksMap.containsKey(task.key)) {
+ if (notifyStackChanges) {
+ mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null);
+ }
+ }
+ task.setGroup(null);
+ }
+
+ // Add any new tasks
+ taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ if (!currentTasksMap.containsKey(task.key)) {
+ if (notifyStackChanges) {
+ mCb.onStackTaskAdded(this, task);
+ }
+ newTasks.add(task);
+ } else {
+ newTasks.add(currentTasksMap.get(task.key));
+ }
+ }
+
+ // Sort all the tasks to ensure they are ordered correctly
+ Collections.sort(newTasks, LAST_ACTIVE_TIME_COMPARATOR);
+
+ // TODO: Update screen pinning for the new front-most task post refactoring lockToTask out
+ // of the Task
+
+ // Filter out the historical tasks from this new list
ArrayList<Task> stackTasks = new ArrayList<>();
ArrayList<Task> historyTasks = new ArrayList<>();
- for (Task task : tasks) {
+ int newTaskCount = newTasks.size();
+ for (int i = 0; i < newTaskCount; i++) {
+ Task task = newTasks.get(i);
if (task.isHistorical) {
historyTasks.add(task);
} else {
stackTasks.add(task);
}
}
+
mStackTaskList.set(stackTasks);
mHistoryTaskList.set(historyTasks);
+ mRawTaskList.clear();
+ mRawTaskList.addAll(newTasks);
+ mGroups.clear();
+ mAffinitiesGroups.clear();
}
/** Gets the front task */
@@ -714,4 +779,17 @@
}
return str;
}
-}
\ No newline at end of file
+
+ /**
+ * Given a list of tasks, returns a map of each task's key to the task.
+ */
+ private HashMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
+ HashMap<Task.TaskKey, Task> map = new HashMap<>();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ map.put(task.key, task);
+ }
+ return map;
+ }
+}
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 21c08d1..53c02cb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -50,6 +50,7 @@
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent;
import com.android.systemui.recents.events.activity.ShowHistoryEvent;
+import com.android.systemui.recents.events.activity.TaskStackUpdatedEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
@@ -599,6 +600,11 @@
hideHistoryButton(100);
}
+ public final void onBusEvent(TaskStackUpdatedEvent event) {
+ mStack.setTasks(event.stack.computeAllTasksList(), true /* notifyStackChanges */);
+ mStack.createAffiliatedGroupings(getContext());
+ }
+
/**
* Shows the history button.
*/
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 4e75d5a..b9ca9fd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -57,6 +57,7 @@
import com.android.systemui.recents.events.activity.ShowHistoryEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
+import com.android.systemui.recents.events.ui.DismissTaskEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
@@ -103,6 +104,8 @@
private static final float SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
+ private static final int DEFAULT_SYNC_STACK_DURATION = 200;
+
public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
new IntProperty<Drawable>("drawableAlpha") {
@Override
@@ -1199,6 +1202,15 @@
/**** TaskStackCallbacks Implementation ****/
@Override
+ public void onStackTaskAdded(TaskStack stack, Task newTask) {
+ // Update the min/max scroll and animate other task views into their new positions
+ updateLayout(true);
+
+ // Animate all the tasks into place
+ requestSynchronizeStackViewsWithModel(DEFAULT_SYNC_STACK_DURATION);
+ }
+
+ @Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
Task newFrontMostTask) {
if (mFocusedTask == removedTask) {
@@ -1244,7 +1256,7 @@
}
// Animate all the tasks into place
- requestSynchronizeStackViewsWithModel(200);
+ requestSynchronizeStackViewsWithModel(DEFAULT_SYNC_STACK_DURATION);
} else {
// Remove the view associated with this task, we can't rely on updateTransforms
// to work here because the task is no longer in the list
@@ -1257,7 +1269,7 @@
updateLayout(true);
// Animate all the tasks into place
- requestSynchronizeStackViewsWithModel(200);
+ requestSynchronizeStackViewsWithModel(DEFAULT_SYNC_STACK_DURATION);
}
// Update the new front most task
@@ -1419,6 +1431,7 @@
public final void onBusEvent(DismissTaskViewEvent event) {
removeTaskViewFromStack(event.taskView);
+ EventBus.getDefault().send(new DismissTaskEvent(event.task));
}
public final void onBusEvent(FocusNextTaskViewEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index ab51d5f..df7b9a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -341,11 +341,10 @@
if (launchState.launchedFromAppWithThumbnail) {
if (mTask.isLaunchTarget) {
+ ctx.postAnimationTrigger.increment();
// Immediately start the dim animation
animateDimToProgress(taskViewEnterFromAppDuration,
ctx.postAnimationTrigger.decrementOnAnimationEnd());
- ctx.postAnimationTrigger.increment();
-
// Animate the action button in
fadeInActionButton(taskViewEnterFromAppDuration);
} else {
@@ -678,7 +677,7 @@
/** Binds this task view to the task */
public void onTaskBound(Task t) {
mTask = t;
- mTask.setCallbacks(this);
+ mTask.addCallback(this);
// Hide the action button if lock to app is disabled for this view
int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE;
@@ -689,7 +688,7 @@
}
@Override
- public void onTaskDataLoaded() {
+ public void onTaskDataLoaded(Task task) {
if (mThumbnailView != null && mHeaderView != null) {
// Bind each of the views to the new task data
mThumbnailView.rebindToTask(mTask);
@@ -706,7 +705,7 @@
public void onTaskDataUnloaded() {
if (mThumbnailView != null && mHeaderView != null) {
// Unbind each of the views from the task data and remove the task callback
- mTask.setCallbacks(null);
+ mTask.removeCallback(this);
mThumbnailView.unbindFromTask();
mHeaderView.unbindFromTask();
// Unbind any listeners
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 37d8cd6..8edfae0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -129,6 +129,7 @@
mBitmapShader = null;
mDrawPaint.setShader(null);
}
+ invalidate();
}
/** Updates the paint to draw the thumbnail. */
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index a2b062c..f1de234 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -22,12 +22,12 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.preference.SwitchPreference;
import android.provider.Settings;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.PreferenceScreen;
import android.view.MenuItem;
import com.android.internal.logging.MetricsLogger;
@@ -56,9 +56,7 @@
private SwitchPreference mOnSwitch;
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
Context context = getContext();
mEnabledSwitch = new SwitchPreference(context);
mEnabledSwitch.setTitle(R.string.enable_demo_mode);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
index dcb0d8d..920ec75 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
@@ -18,8 +18,8 @@
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
-import android.preference.SwitchPreference;
import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -38,15 +38,15 @@
}
@Override
- protected void onAttachedToActivity() {
- super.onAttachedToActivity();
+ public void onAttached() {
+ super.onAttached();
TunerService.get(getContext()).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
}
@Override
- protected void onDetachedFromActivity() {
+ public void onDetached() {
TunerService.get(getContext()).removeTunable(this);
- super.onDetachedFromActivity();
+ super.onDetached();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index c84f618..4173ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -15,16 +15,64 @@
*/
package com.android.systemui.tuner;
-import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
import android.os.Bundle;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.Log;
-public class TunerActivity extends Activity {
+import com.android.settingslib.drawer.SettingsDrawerActivity;
+import com.android.systemui.R;
+
+public class TunerActivity extends SettingsDrawerActivity implements
+ PreferenceFragment.OnPreferenceStartFragmentCallback,
+ PreferenceFragment.OnPreferenceStartScreenCallback {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- getFragmentManager().beginTransaction().replace(android.R.id.content, new TunerFragment())
+ getFragmentManager().beginTransaction().replace(R.id.content_frame, new TunerFragment())
.commit();
}
+ @Override
+ public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
+ try {
+ Class<?> cls = Class.forName(pref.getFragment());
+ Fragment fragment = (Fragment) cls.newInstance();
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.replace(R.id.content_frame, fragment);
+ transaction.addToBackStack("PreferenceFragment");
+ transaction.commit();
+ return true;
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
+ Log.d("TunerActivity", "Problem launching fragment", e);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) {
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ SubSettingsFragment fragment = new SubSettingsFragment();
+ final Bundle b = new Bundle(1);
+ b.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
+ fragment.setArguments(b);
+ fragment.setTargetFragment(caller, 0);
+ transaction.replace(R.id.content_frame, fragment);
+ transaction.addToBackStack("PreferenceFragment");
+ transaction.commit();
+ return true;
+ }
+
+ public static class SubSettingsFragment extends PreferenceFragment {
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setPreferenceScreen((PreferenceScreen) ((PreferenceFragment) getTargetFragment())
+ .getPreferenceScreen().findPreference(rootKey));
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index b620b50b..a3fe6bb 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -24,20 +24,19 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceFragment;
-import android.preference.SwitchPreference;
import android.provider.Settings;
import android.provider.Settings.System;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.Preference.OnPreferenceClickListener;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.tuner.TunerService.Tunable;
import static com.android.systemui.BatteryMeterDrawable.SHOW_PERCENT_SETTING;
@@ -45,8 +44,6 @@
private static final String TAG = "TunerFragment";
- private static final String KEY_QS_TUNER = "qs_tuner";
- private static final String KEY_DEMO_MODE = "demo_mode";
private static final String KEY_BATTERY_PCT = "battery_pct";
public static final String SETTING_SEEN_TUNER_WARNING = "seen_tuner_warning";
@@ -59,23 +56,18 @@
private SwitchPreference mBatteryPct;
+ @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.tuner_prefs);
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
setHasOptionsMenu(true);
+ }
- findPreference(KEY_DEMO_MODE).setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.replace(android.R.id.content, new DemoModeFragment(), "DemoMode");
- ft.addToBackStack(null);
- ft.commit();
- return true;
- }
- });
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ addPreferencesFromResource(R.xml.tuner_prefs);
+
mBatteryPct = (SwitchPreference) findPreference(KEY_BATTERY_PCT);
if (Settings.Secure.getInt(getContext().getContentResolver(), SETTING_SEEN_TUNER_WARNING,
0) == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
index 54078b0..7ad752e 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
@@ -2,8 +2,8 @@
import android.content.Context;
import android.content.res.TypedArray;
-import android.preference.SwitchPreference;
import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
import android.util.AttributeSet;
import com.android.systemui.R;
@@ -21,15 +21,15 @@
}
@Override
- protected void onAttachedToActivity() {
- super.onAttachedToActivity();
+ public void onAttached() {
+ super.onAttached();
TunerService.get(getContext()).addTunable(this, getKey());
}
@Override
- protected void onDetachedFromActivity() {
+ public void onDetached() {
TunerService.get(getContext()).removeTunable(this);
- super.onDetachedFromActivity();
+ super.onDetached();
}
@Override
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 9cf64d3..b7a41e2 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -21,7 +21,8 @@
LOCAL_PROTOC_FLAGS := -I$(LOCAL_PATH)/..
LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
-LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages com.android.systemui:com.android.keyguard
+LOCAL_AAPT_FLAGS := --auto-add-overlay \
+ --extra-packages com.android.systemui:com.android.keyguard:android.support.v14.preference:android.support.v7.preference:android.support.v7.appcompat:android.support.v7.recyclerview
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-Iaidl-files-under, src) \
@@ -30,6 +31,10 @@
src/com/android/systemui/EventLogTags.logtags
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
+ frameworks/support/v7/preference/res \
+ frameworks/support/v14/preference/res \
+ frameworks/support/v7/appcompat/res \
+ frameworks/support/v7/recyclerview/res \
frameworks/base/packages/SystemUI/res \
frameworks/base/packages/Keyguard/res
@@ -40,7 +45,10 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
mockito-target \
Keyguard \
- android-support-v7-recyclerview
+ android-support-v7-recyclerview \
+ android-support-v7-preference \
+ android-support-v7-appcompat \
+ android-support-v14-preference
# sign this with platform cert, so this test is allowed to inject key events into
# UI it doesn't own. This is necessary to allow screenshots to be taken
diff --git a/packages/WallpaperCropper/res/values/styles.xml b/packages/WallpaperCropper/res/values/styles.xml
index e438c84..0f9e247 100644
--- a/packages/WallpaperCropper/res/values/styles.xml
+++ b/packages/WallpaperCropper/res/values/styles.xml
@@ -15,7 +15,7 @@
-->
<resources>
- <style name="Theme.WallpaperCropper" parent="@android:style/Theme.Material.DayNight">
+ <style name="Theme.WallpaperCropper" parent="@*android:style/Theme.Material.DayNight">
<item name="android:actionBarStyle">@style/WallpaperCropperActionBar</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowActionBarOverlay">true</item>
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d8724b27..bcd8efd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7171,24 +7171,64 @@
@Override
public boolean inMultiWindowMode(IBinder token) {
- synchronized(this) {
- final ActivityRecord r = ActivityRecord.isInStackLocked(token);
- if (r == null) {
- return false;
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized(this) {
+ final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ if (r == null) {
+ return false;
+ }
+ // An activity is consider to be in multi-window mode if its task isn't fullscreen.
+ return !r.task.mFullscreen;
}
- // An activity is consider to be in multi-window mode if its task isn't fullscreen.
- return !r.task.mFullscreen;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
}
@Override
public boolean inPictureInPictureMode(IBinder token) {
- synchronized(this) {
- final ActivityStack stack = ActivityRecord.getStackLocked(token);
- if (stack == null) {
- return false;
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized(this) {
+ final ActivityStack stack = ActivityRecord.getStackLocked(token);
+ if (stack == null) {
+ return false;
+ }
+ return stack.mStackId == PINNED_STACK_ID;
}
- return stack.mStackId == PINNED_STACK_ID;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
+ public void enterPictureInPictureMode(IBinder token) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized(this) {
+ if (!mSupportsPictureInPicture) {
+ throw new IllegalStateException("enterPictureInPictureMode: "
+ + "Device doesn't support picture-in-picture mode.");
+ }
+
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+
+ if (r == null) {
+ throw new IllegalStateException("enterPictureInPictureMode: "
+ + "Can't find activity for token=" + token);
+ }
+
+ if (!r.info.supportsPip) {
+ throw new IllegalArgumentException("enterPictureInPictureMode: "
+ + "Picture-In-Picture not supported for r=" + r);
+ }
+
+ mStackSupervisor.moveActivityToStackLocked(
+ r, PINNED_STACK_ID, "enterPictureInPictureMode", null);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 9117806..f7d66af 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3623,24 +3623,30 @@
return false;
}
+ moveActivityToStackLocked(r, PINNED_STACK_ID, "moveTopActivityToPinnedStack", bounds);
+ return true;
+ }
+
+ void moveActivityToStackLocked(ActivityRecord r, int stackId, String reason, Rect bounds) {
final TaskRecord task = r.task;
if (task.mActivities.size() == 1) {
// There is only one activity in the task. So, we can just move the task over to the
- // pinned stack without re-parenting the activity in a different task.
- moveTaskToStackLocked(task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS,
- "moveTopActivityToPinnedStack", true /* animate */);
+ // stack without re-parenting the activity in a different task.
+ moveTaskToStackLocked(
+ task.taskId, stackId, ON_TOP, FORCE_FOCUS, reason, true /* animate */);
} else {
final ActivityStack pinnedStack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
pinnedStack.moveActivityToStack(r);
}
- resizeStackLocked(PINNED_STACK_ID, bounds, !PRESERVE_WINDOWS, true);
+ if (bounds != null) {
+ resizeStackLocked(PINNED_STACK_ID, bounds, !PRESERVE_WINDOWS, true);
+ }
// The task might have already been running and its visibility needs to be synchronized with
// the visibility of the stack / windows.
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeTopActivitiesLocked();
- return true;
}
void positionTaskInStackLocked(int taskId, int stackId, int position) {
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index cee9ec8..9cdece5 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -109,7 +109,6 @@
if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition);
ZenModeConfig config = mHelper.getConfig();
if (config == null) return;
- config = config.copy();
boolean updated = updateCondition(id, condition, config.manualRule);
for (ZenRule automaticRule : config.automaticRules.values()) {
updated |= updateCondition(id, condition, automaticRule);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 6030bab..85c3cf8 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -77,6 +77,9 @@
static final String TAG = "ZenModeHelper";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ // The amount of time rules instances can exist without their owning app being installed.
+ private static final int RULE_INSTANCE_GRACE_PERIOD = 1000 * 60 * 60 * 72;
+
private final Context mContext;
private final H mHandler;
private final SettingsObserver mSettingsObserver;
@@ -93,6 +96,7 @@
private int mUser = UserHandle.USER_SYSTEM;
private ZenModeConfig mConfig;
private AudioManagerInternal mAudioManager;
+ private PackageManager mPm;
private boolean mEffectsSuppressed;
public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) {
@@ -170,7 +174,9 @@
if (mAudioManager != null) {
mAudioManager.setRingerModeDelegate(mRingerModeDelegate);
}
+ mPm = mContext.getPackageManager();
mHandler.postMetricsTimer();
+ cleanUpZenRules();
evaluateZenMode("onSystemReady", true);
}
@@ -184,7 +190,10 @@
config = mDefaultConfig.copy();
config.user = user;
}
- setConfig(config, "onUserSwitched");
+ synchronized (mConfig) {
+ setConfig(config, "onUserSwitched");
+ }
+ cleanUpZenRules();
}
public void onUserRemoved(int user) {
@@ -253,14 +262,15 @@
throw new IllegalArgumentException("Rule already exists");
}
newConfig = mConfig.copy();
- }
- ZenRule rule = new ZenRule();
- populateZenRule(automaticZenRule, rule, true);
- newConfig.automaticRules.put(rule.id, rule);
- if (setConfig(newConfig, reason, true)) {
- return createAutomaticZenRule(rule);
- } else {
- return null;
+
+ ZenRule rule = new ZenRule();
+ populateZenRule(automaticZenRule, rule, true);
+ newConfig.automaticRules.put(rule.id, rule);
+ if (setConfig(newConfig, reason, true)) {
+ return createAutomaticZenRule(rule);
+ } else {
+ return null;
+ }
}
}
@@ -273,21 +283,21 @@
+ " reason=" + reason);
}
newConfig = mConfig.copy();
- }
- final String ruleId = automaticZenRule.getId();
- ZenModeConfig.ZenRule rule;
- if (ruleId == null) {
- throw new IllegalArgumentException("Rule doesn't exist");
- } else {
- rule = newConfig.automaticRules.get(ruleId);
- if (rule == null || !canManageAutomaticZenRule(rule)) {
- throw new SecurityException(
- "Cannot update rules not owned by your condition provider");
+ final String ruleId = automaticZenRule.getId();
+ ZenModeConfig.ZenRule rule;
+ if (ruleId == null) {
+ throw new IllegalArgumentException("Rule doesn't exist");
+ } else {
+ rule = newConfig.automaticRules.get(ruleId);
+ if (rule == null || !canManageAutomaticZenRule(rule)) {
+ throw new SecurityException(
+ "Cannot update rules not owned by your condition provider");
+ }
}
+ populateZenRule(automaticZenRule, rule, false);
+ newConfig.automaticRules.put(ruleId, rule);
+ return setConfig(newConfig, reason, true);
}
- populateZenRule(automaticZenRule, rule, false);
- newConfig.automaticRules.put(ruleId, rule);
- return setConfig(newConfig, reason, true);
}
public boolean removeAutomaticZenRule(String id, String reason) {
@@ -295,17 +305,17 @@
synchronized (mConfig) {
if (mConfig == null) return false;
newConfig = mConfig.copy();
+ ZenRule rule = newConfig.automaticRules.get(id);
+ if (rule == null) return false;
+ if (canManageAutomaticZenRule(rule)) {
+ newConfig.automaticRules.remove(id);
+ if (DEBUG) Log.d(TAG, "removeZenRule zenRule=" + id + " reason=" + reason);
+ } else {
+ throw new SecurityException(
+ "Cannot delete rules not owned by your condition provider");
+ }
+ return setConfig(newConfig, reason, true);
}
- ZenRule rule = newConfig.automaticRules.get(id);
- if (rule == null) return false;
- if (canManageAutomaticZenRule(rule)) {
- newConfig.automaticRules.remove(id);
- if (DEBUG) Log.d(TAG, "removeZenRule zenRule=" + id + " reason=" + reason);
- } else {
- throw new SecurityException(
- "Cannot delete rules not owned by your condition provider");
- }
- return setConfig(newConfig, reason, true);
}
public boolean removeAutomaticZenRules(String packageName, String reason) {
@@ -313,15 +323,15 @@
synchronized (mConfig) {
if (mConfig == null) return false;
newConfig = mConfig.copy();
- }
- for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
- ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
- if (rule.component.getPackageName().equals(packageName)
- && canManageAutomaticZenRule(rule)) {
- newConfig.automaticRules.removeAt(i);
+ for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
+ ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
+ if (rule.component.getPackageName().equals(packageName)
+ && canManageAutomaticZenRule(rule)) {
+ newConfig.automaticRules.removeAt(i);
+ }
}
+ return setConfig(newConfig, reason, true);
}
- return setConfig(newConfig, reason, true);
}
public boolean canManageAutomaticZenRule(ZenRule rule) {
@@ -332,8 +342,7 @@
== PackageManager.PERMISSION_GRANTED) {
return true;
} else {
- String[] packages =
- mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid());
+ String[] packages = mPm.getPackagesForUid(Binder.getCallingUid());
if (packages != null) {
final int packageCount = packages.length;
for (int i = 0; i < packageCount; i++) {
@@ -384,22 +393,22 @@
+ " conditionId=" + conditionId + " reason=" + reason
+ " setRingerMode=" + setRingerMode);
newConfig = mConfig.copy();
- }
- if (zenMode == Global.ZEN_MODE_OFF) {
- newConfig.manualRule = null;
- for (ZenRule automaticRule : newConfig.automaticRules.values()) {
- if (automaticRule.isAutomaticActive()) {
- automaticRule.snoozing = true;
+ if (zenMode == Global.ZEN_MODE_OFF) {
+ newConfig.manualRule = null;
+ for (ZenRule automaticRule : newConfig.automaticRules.values()) {
+ if (automaticRule.isAutomaticActive()) {
+ automaticRule.snoozing = true;
+ }
}
+ } else {
+ final ZenRule newRule = new ZenRule();
+ newRule.enabled = true;
+ newRule.zenMode = zenMode;
+ newRule.conditionId = conditionId;
+ newConfig.manualRule = newRule;
}
- } else {
- final ZenRule newRule = new ZenRule();
- newRule.enabled = true;
- newRule.zenMode = zenMode;
- newRule.conditionId = conditionId;
- newConfig.manualRule = newRule;
+ setConfig(newConfig, reason, setRingerMode);
}
- setConfig(newConfig, reason, setRingerMode);
}
public void dump(PrintWriter pw, String prefix) {
@@ -450,16 +459,20 @@
return;
}
config.manualRule = null; // don't restore the manual rule
+ long time = System.currentTimeMillis();
if (config.automaticRules != null) {
for (ZenRule automaticRule : config.automaticRules.values()) {
// don't restore transient state from restored automatic rules
automaticRule.snoozing = false;
automaticRule.condition = null;
+ automaticRule.creationTime = time;
}
}
}
if (DEBUG) Log.d(TAG, "readXml");
- setConfig(config, "readXml");
+ synchronized (mConfig) {
+ setConfig(config, "readXml");
+ }
}
}
@@ -484,11 +497,39 @@
public void setNotificationPolicy(Policy policy) {
if (policy == null || mConfig == null) return;
- final ZenModeConfig newConfig = mConfig.copy();
- newConfig.applyNotificationPolicy(policy);
- setConfig(newConfig, "setNotificationPolicy");
+ synchronized (mConfig) {
+ final ZenModeConfig newConfig = mConfig.copy();
+ newConfig.applyNotificationPolicy(policy);
+ setConfig(newConfig, "setNotificationPolicy");
+ }
}
+ /**
+ * Removes old rule instances whose owner is not installed.
+ */
+ private void cleanUpZenRules() {
+ long currentTime = System.currentTimeMillis();
+ synchronized (mConfig) {
+ final ZenModeConfig newConfig = mConfig.copy();
+ if (newConfig.automaticRules != null) {
+ for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
+ ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
+ if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
+ try {
+ mPm.getPackageInfo(rule.component.getPackageName(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ newConfig.automaticRules.removeAt(i);
+ }
+ }
+ }
+ }
+ setConfig(newConfig, "cleanUpZenRules");
+ }
+ }
+
+ /**
+ * @return a copy of the zen mode configuration
+ */
public ZenModeConfig getConfig() {
synchronized (mConfig) {
return mConfig.copy();
@@ -517,19 +558,17 @@
return true;
}
mConditions.evaluateConfig(config, false /*processSubscriptions*/); // may modify config
- synchronized (mConfig) {
- mConfigs.put(config.user, config);
- if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
- ZenLog.traceConfig(reason, mConfig, config);
- final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
- getNotificationPolicy(config));
- mConfig = config;
- if (config.equals(mConfig)) {
- dispatchOnConfigChanged();
- }
- if (policyChanged) {
- dispatchOnPolicyChanged();
- }
+ mConfigs.put(config.user, config);
+ if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
+ ZenLog.traceConfig(reason, mConfig, config);
+ final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
+ getNotificationPolicy(config));
+ mConfig = config;
+ if (config.equals(mConfig)) {
+ dispatchOnConfigChanged();
+ }
+ if (policyChanged) {
+ dispatchOnPolicyChanged();
}
final String val = Integer.toString(config.hashCode());
Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
@@ -994,7 +1033,9 @@
break;
case MSG_SET_CONFIG:
ConfigMessageData configData = (ConfigMessageData)msg.obj;
- setConfig(configData.config, configData.reason);
+ synchronized (mConfig) {
+ setConfig(configData.config, configData.reason);
+ }
break;
}
}
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index c8523c9..5948d3c 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -278,7 +278,7 @@
} else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
- mItems.add(getBugReportAction());
+ mItems.add(new BugReportAction());
}
} else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
if (mShowSilentToggle) {
@@ -367,60 +367,67 @@
}
}
- private Action getBugReportAction() {
- return new SinglePressAction(com.android.internal.R.drawable.ic_lock_bugreport,
- R.string.bugreport_title) {
+ private class BugReportAction extends SinglePressAction implements LongPressAction {
- public void onPress() {
- AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
- builder.setTitle(com.android.internal.R.string.bugreport_title);
- builder.setMessage(com.android.internal.R.string.bugreport_message);
- builder.setNegativeButton(com.android.internal.R.string.cancel, null);
- builder.setPositiveButton(com.android.internal.R.string.report,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // don't actually trigger the bugreport if we are running stability
- // tests via monkey
- if (ActivityManager.isUserAMonkey()) {
- return;
- }
- // Add a little delay before executing, to give the
- // dialog a chance to go away before it takes a
- // screenshot.
- mHandler.postDelayed(new Runnable() {
- @Override public void run() {
- // TODO: select 'progress' flag according to menu choice
- try {
- ActivityManagerNative.getDefault()
- .requestBugReport(true);
- } catch (RemoteException e) {
- }
- }
- }, 500);
- }
- });
- AlertDialog dialog = builder.create();
- dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- dialog.show();
+ public BugReportAction() {
+ super(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title);
+ }
+
+ @Override
+ public void onPress() {
+ // don't actually trigger the bugreport if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return;
}
+ // Add a little delay before executing, to give the
+ // dialog a chance to go away before it takes a
+ // screenshot.
+ // TODO: remove once screenshots are handled by Shell (instead of dumpstate)
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // Take a "heavy" bugreport: it's more user friendly, but causes more
+ // interference.
+ ActivityManagerNative.getDefault().requestBugReport(true);
+ } catch (RemoteException e) {
+ }
+ }
+ }, 500);
+ }
- public boolean showDuringKeyguard() {
- return true;
- }
-
- public boolean showBeforeProvisioning() {
+ @Override
+ public boolean onLongPress() {
+ // don't actually trigger the bugreport if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
return false;
}
-
- @Override
- public String getStatus() {
- return mContext.getString(
- com.android.internal.R.string.bugreport_status,
- Build.VERSION.RELEASE,
- Build.ID);
+ try {
+ // Take a "light" bugreport, with less interference.
+ ActivityManagerNative.getDefault().requestBugReport(false);
+ } catch (RemoteException e) {
}
- };
+ return true;
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ @Override
+ public String getStatus() {
+ return mContext.getString(
+ com.android.internal.R.string.bugreport_status,
+ Build.VERSION.RELEASE,
+ Build.ID);
+ }
}
private Action getSettingsAction() {
@@ -742,13 +749,6 @@
mIcon = icon;
}
- protected SinglePressAction(int iconResId, CharSequence message) {
- mIconResId = iconResId;
- mMessageResId = 0;
- mMessage = message;
- mIcon = null;
- }
-
public boolean isEnabled() {
return true;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 189ed33..fb5f21a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -141,6 +141,14 @@
"com.android.server.MountService$Lifecycle";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
+ /**
+ * Default theme used by the system context. This is used to style
+ * system-provided dialogs, such as the Power Off dialog, and other
+ * visual content.
+ */
+ private static final int DEFAULT_SYSTEM_THEME =
+ com.android.internal.R.style.Theme_Material_DayNight_DarkActionBar;
+
private final int mFactoryTestMode;
private Timer mProfilerSnapshotTimer;
@@ -320,7 +328,7 @@
private void createSystemContext() {
ActivityThread activityThread = ActivityThread.systemMain();
mSystemContext = activityThread.getSystemContext();
- mSystemContext.setTheme(android.R.style.Theme_Material_DayNight_DarkActionBar);
+ mSystemContext.setTheme(DEFAULT_SYSTEM_THEME);
}
/**