summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt20
-rw-r--r--api/system-current.txt20
-rw-r--r--api/test-current.txt20
-rw-r--r--cmds/pm/src/com/android/commands/pm/Pm.java2
-rw-r--r--core/java/android/app/Activity.java11
-rw-r--r--core/java/android/app/ActivityManagerNative.java19
-rw-r--r--core/java/android/app/IActivityManager.java3
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java17
-rw-r--r--core/java/android/database/sqlite/SQLiteQueryBuilder.java13
-rw-r--r--core/java/android/view/ThreadedRenderer.java5
-rw-r--r--core/java/com/android/internal/policy/DecorView.java12
-rw-r--r--core/jni/android_view_ThreadedRenderer.cpp6
-rw-r--r--core/res/res/values/public.xml16
-rw-r--r--core/res/res/values/symbols.xml18
-rw-r--r--libs/hwui/BakedOpDispatcher.cpp30
-rw-r--r--libs/hwui/BakedOpState.h9
-rw-r--r--libs/hwui/OpReorderer.cpp30
-rw-r--r--libs/hwui/RecordedOp.h18
-rw-r--r--libs/hwui/RecordingCanvas.cpp10
-rw-r--r--libs/hwui/RecordingCanvas.h4
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp5
-rw-r--r--libs/hwui/renderthread/CanvasContext.h2
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp7
-rw-r--r--libs/hwui/renderthread/RenderProxy.h2
-rw-r--r--libs/hwui/tests/common/TestUtils.cpp21
-rw-r--r--libs/hwui/tests/common/TestUtils.h3
-rw-r--r--libs/hwui/tests/common/scenes/OvalAnimation.cpp5
-rw-r--r--libs/hwui/tests/common/scenes/TextAnimation.cpp60
-rw-r--r--libs/hwui/tests/unit/BakedOpStateTests.cpp22
-rw-r--r--libs/hwui/tests/unit/OpReordererTests.cpp32
-rw-r--r--packages/Shell/res/layout/dialog_bugreport_info.xml43
-rw-r--r--packages/Shell/res/values/strings.xml15
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java353
-rw-r--r--packages/Shell/tests/Android.mk2
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java165
-rw-r--r--packages/Shell/tests/src/com/android/shell/UiBot.java27
-rw-r--r--packages/Shell/tests/src/com/android/shell/UtilitiesTest.java40
-rw-r--r--packages/SystemUI/Android.mk11
-rw-r--r--packages/SystemUI/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/proguard.flags6
-rw-r--r--packages/SystemUI/res/values/styles.xml4
-rw-r--r--packages/SystemUI/res/xml/tuner_prefs.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java10
-rw-r--r--packages/SystemUI/tests/Android.mk12
-rw-r--r--packages/WallpaperCropper/res/values/styles.xml2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java62
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java16
-rw-r--r--services/core/java/com/android/server/notification/ZenModeConditions.java1
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java185
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java65
-rw-r--r--services/core/java/com/android/server/policy/GlobalActions.java110
-rw-r--r--services/java/com/android/server/SystemServer.java10
57 files changed, 1329 insertions, 372 deletions
diff --git a/api/current.txt b/api/current.txt
index b73214f79d32..622a8b7b203b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2114,22 +2114,6 @@ package android {
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 @@ package android {
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 @@ package android.app {
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);
@@ -10605,6 +10590,7 @@ package android.database.sqlite {
method public void setVersion(int);
method public int update(java.lang.String, android.content.ContentValues, java.lang.String, java.lang.String[]);
method public int updateWithOnConflict(java.lang.String, android.content.ContentValues, java.lang.String, java.lang.String[], int);
+ method public void validateSql(java.lang.String, android.os.CancellationSignal);
method public deprecated boolean yieldIfContended();
method public boolean yieldIfContendedSafely();
method public boolean yieldIfContendedSafely(long);
diff --git a/api/system-current.txt b/api/system-current.txt
index 84dbe6254b91..e51b7d58ac7b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2215,22 +2215,6 @@ package android {
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 @@ package android {
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 @@ package android.app {
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);
@@ -10966,6 +10951,7 @@ package android.database.sqlite {
method public void setVersion(int);
method public int update(java.lang.String, android.content.ContentValues, java.lang.String, java.lang.String[]);
method public int updateWithOnConflict(java.lang.String, android.content.ContentValues, java.lang.String, java.lang.String[], int);
+ method public void validateSql(java.lang.String, android.os.CancellationSignal);
method public deprecated boolean yieldIfContended();
method public boolean yieldIfContendedSafely();
method public boolean yieldIfContendedSafely(long);
diff --git a/api/test-current.txt b/api/test-current.txt
index 390a017bd401..2377a9b87939 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2114,22 +2114,6 @@ package android {
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 @@ package android {
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 @@ package android.app {
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);
@@ -10605,6 +10590,7 @@ package android.database.sqlite {
method public void setVersion(int);
method public int update(java.lang.String, android.content.ContentValues, java.lang.String, java.lang.String[]);
method public int updateWithOnConflict(java.lang.String, android.content.ContentValues, java.lang.String, java.lang.String[], int);
+ method public void validateSql(java.lang.String, android.os.CancellationSignal);
method public deprecated boolean yieldIfContended();
method public boolean yieldIfContendedSafely();
method public boolean yieldIfContendedSafely(long);
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index ad1b3b5013b7..24449d43b350 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -376,6 +376,7 @@ public final class Pm {
!= PackageInstaller.STATUS_SUCCESS) {
return 1;
}
+ System.out.println("Success");
return 0;
} finally {
try {
@@ -591,7 +592,6 @@ public final class Pm {
} else {
System.err.println("Failure ["
+ result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
- System.err.println("Failure details: " + result.getExtras());
}
return status;
} finally {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ef84ab05c2b2..571bc6efeac5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1782,6 +1782,17 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * 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 f11bf742c925..d724823843d7 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2759,6 +2759,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
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 @@ class ActivityManagerProxy implements IActivityManager
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 64c69af8691d..f91a0bef7833 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -550,6 +550,8 @@ public interface IActivityManager extends IInterface {
public boolean inPictureInPictureMode(IBinder token) throws RemoteException;
+ public void enterPictureInPictureMode(IBinder token) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -914,4 +916,5 @@ public interface IActivityManager extends IInterface {
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/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 433d5d1c713e..50e73564ef29 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,6 +16,8 @@
package android.database.sqlite;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseErrorHandler;
@@ -1683,6 +1685,21 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
/**
+ * Verifies that a SQL SELECT statement is valid by compiling it.
+ * If the SQL statement is not valid, this method will throw a {@link SQLiteException}.
+ *
+ * @param sql SQL to be validated
+ * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
+ * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+ * when the query is executed.
+ * @throws SQLiteException if {@code sql} is invalid
+ */
+ public void validateSql(@NonNull String sql, @Nullable CancellationSignal cancellationSignal) {
+ getThreadSession().prepare(sql,
+ getThreadDefaultConnectionFlags(/* readOnly =*/ true), cancellationSignal, null);
+ }
+
+ /**
* Returns true if the database is opened as read only.
*
* @return True if database is opened as read only.
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 91884abaf992..56cba795355e 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -386,8 +386,7 @@ public class SQLiteQueryBuilder
// in both the wrapped and original forms.
String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
having, sortOrder, limit);
- validateQuerySql(db, sqlForValidation,
- cancellationSignal); // will throw if query is invalid
+ db.validateSql(sqlForValidation, cancellationSignal); // will throw if query is invalid
}
String sql = buildQuery(
@@ -404,16 +403,6 @@ public class SQLiteQueryBuilder
}
/**
- * Verifies that a SQL SELECT statement is valid by compiling it.
- * If the SQL statement is not valid, this method will throw a {@link SQLiteException}.
- */
- private void validateQuerySql(SQLiteDatabase db, String sql,
- CancellationSignal cancellationSignal) {
- db.getThreadSession().prepare(sql,
- db.getThreadDefaultConnectionFlags(true /*readOnly*/), cancellationSignal, null);
- }
-
- /**
* Construct a SELECT statement suitable for use in a group of
* SELECT statements that will be joined through UNION operators
* in buildUnionQuery.
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index f2a4d7bb78fd..0e4bc84a4db2 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -422,9 +422,10 @@ public final class ThreadedRenderer {
* @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 @@ public final class ThreadedRenderer {
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/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 6a1e07b71037..40eaaf7bae80 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -702,11 +702,13 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
helper = mWindow.mContextMenu.showDialog(originalView, originalView.getWindowToken());
}
- // If it's a dialog, the callback needs to handle showing sub-menus.
- // Either way, the callback is required for propagating selection to
- // Context.onContextMenuItemSelected().
- callback.setShowDialogForSubmenu(!isPopup);
- helper.setPresenterCallback(callback);
+ if (helper != null) {
+ // If it's a dialog, the callback needs to handle showing
+ // sub-menus. Either way, the callback is required for propagating
+ // selection to Context.onContextMenuItemSelected().
+ callback.setShowDialogForSubmenu(!isPopup);
+ helper.setPresenterCallback(callback);
+ }
mWindow.mContextMenuHelper = helper;
return helper != null;
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 17eb876aebf3..5aa6a739751c 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -262,11 +262,11 @@ static void android_view_ThreadedRenderer_setName(JNIEnv* env, jobject clazz,
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 @@ static const JNINativeMethod gMethods[] = {
{ "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 ad36f3c107bc..09c17179b4b5 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 832984ea1813..f6326f723298 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/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 9e39797270a5..a892b1b29174 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -705,6 +705,36 @@ void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, co
renderTextOp(renderer, op, state, clip, TextRenderType::Flush);
}
+void BakedOpDispatcher::onTextOnPathOp(BakedOpRenderer& renderer, const TextOnPathOp& op, const BakedOpState& state) {
+ // Note: can't trust clipSideFlags since we record with unmappedBounds == clip.
+ // TODO: respect clipSideFlags, once we record with bounds
+ const Rect* renderTargetClip = &state.computedState.clipRect;
+
+ FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(true);
+
+ Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+
+ int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
+ SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+ TextDrawFunctor functor(&renderer, &state, renderTargetClip,
+ 0.0f, 0.0f, false, alpha, mode, op.paint);
+
+ bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
+ const Rect localSpaceClip = state.computedState.computeLocalSpaceClip();
+ if (fontRenderer.renderTextOnPath(op.paint, &localSpaceClip,
+ reinterpret_cast<const char*>(op.glyphs), op.glyphCount,
+ op.path, op.hOffset, op.vOffset,
+ mustDirtyRenderTarget ? &layerBounds : nullptr, &functor)) {
+ if (mustDirtyRenderTarget) {
+ // manually dirty render target, since TextDrawFunctor won't
+ state.computedState.transform.mapRect(layerBounds);
+ renderer.dirtyRenderTarget(layerBounds);
+ }
+ }
+}
+
void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
OffscreenBuffer* buffer = *op.layerHandle;
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 9c836a00bfea..b12c0c970352 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -124,6 +124,15 @@ public:
clipSideFlags = OpClipSideFlags::Full;
}
+ Rect computeLocalSpaceClip() const {
+ Matrix4 inverse;
+ inverse.loadInverse(transform);
+
+ Rect outClip(clipRect);
+ inverse.mapRect(outClip);
+ return outClip;
+ }
+
Matrix4 transform;
Rect clipRect;
int clipSideFlags = 0;
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 6d90a42d49c7..e1472113d25f 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -90,7 +90,9 @@ public:
}
MergingOpBatch(batchid_t batchId, BakedOpState* op)
- : BatchBase(batchId, op, true) {
+ : BatchBase(batchId, op, true)
+ , mClipSideFlags(op->computedState.clipSideFlags)
+ , mClipRect(op->computedState.clipRect) {
}
/*
@@ -202,11 +204,11 @@ public:
if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom;
}
- bool getClipSideFlags() const { return mClipSideFlags; }
+ int getClipSideFlags() const { return mClipSideFlags; }
const Rect& getClipRect() const { return mClipRect; }
private:
- int mClipSideFlags = 0;
+ int mClipSideFlags;
Rect mClipRect;
};
@@ -308,12 +310,6 @@ void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg,
mergingBatch->getClipSideFlags(),
mergingBatch->getClipRect()
};
- if (data.clipSideFlags) {
- // if right or bottom sides aren't used to clip, init them to viewport bounds
- // in the clip rect, so it can be used to scissor
- if (!(data.clipSideFlags & OpClipSideFlags::Right)) data.clip.right = width;
- if (!(data.clipSideFlags & OpClipSideFlags::Bottom)) data.clip.bottom = height;
- }
mergedReceivers[opId](arg, data);
} else {
for (const BakedOpState* op : batch->getOps()) {
@@ -850,14 +846,16 @@ void OpReorderer::deferSimpleRectsOp(const SimpleRectsOp& op) {
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
}
+static batchid_t textBatchId(const SkPaint& paint) {
+ // TODO: better handling of shader (since we won't care about color then)
+ return paint.getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText;
+}
+
void OpReorderer::deferTextOp(const TextOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
- // TODO: better handling of shader (since we won't care about color then)
- batchid_t batchId = op.paint->getColor() == SK_ColorBLACK
- ? OpBatchType::Text : OpBatchType::ColorText;
-
+ batchid_t batchId = textBatchId(*(op.paint));
if (bakedState->computedState.transform.isPureTranslate()
&& PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode) {
mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
@@ -867,6 +865,12 @@ void OpReorderer::deferTextOp(const TextOp& op) {
}
}
+void OpReorderer::deferTextOnPathOp(const TextOnPathOp& op) {
+ BakedOpState* bakedState = tryBakeOpState(op);
+ if (!bakedState) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint)));
+}
+
void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
float contentTranslateX, float contentTranslateY,
const Rect& repaintRect,
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index b58b7744c7c3..bb230054ad15 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -62,6 +62,7 @@ struct Vertex;
U_OP_FN(ShadowOp) \
U_OP_FN(SimpleRectsOp) \
M_OP_FN(TextOp) \
+ U_OP_FN(TextOnPathOp) \
U_OP_FN(BeginLayerOp) \
U_OP_FN(EndLayerOp) \
U_OP_FN(LayerOp)
@@ -327,6 +328,23 @@ struct TextOp : RecordedOp {
const float y;
};
+struct TextOnPathOp : RecordedOp {
+ TextOnPathOp(BASE_PARAMS, const glyph_t* glyphs, int glyphCount,
+ const SkPath* path, float hOffset, float vOffset)
+ : SUPER(TextOnPathOp)
+ , glyphs(glyphs)
+ , glyphCount(glyphCount)
+ , path(path)
+ , hOffset(hOffset)
+ , vOffset(vOffset) {}
+ const glyph_t* glyphs;
+ const int glyphCount;
+
+ const SkPath* path;
+ const float hOffset;
+ const float vOffset;
+};
+
////////////////////////////////////////////////////////////////////////////////////////////////////
// Layers
////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 7f035e549404..46ae7904ae78 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -503,9 +503,15 @@ void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, i
drawTextDecorations(x, y, totalAdvance, paint);
}
-void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) {
- LOG_ALWAYS_FATAL("TODO!");
+ if (!glyphs || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+ glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
+ addOp(new (alloc()) TextOnPathOp(
+ mState.getRenderTargetClipBounds(), // TODO: explicitly define bounds
+ *(mState.currentSnapshot()->transform),
+ mState.getRenderTargetClipBounds(),
+ refPaint(&paint), glyphs, glyphCount, refPath(&path), hOffset, vOffset));
}
void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 49bdba8bec9d..ce7d13507aea 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -189,10 +189,10 @@ public:
const SkPaint* paint) override;
// Text
- virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
+ virtual void drawText(const uint16_t* glyphs, const float* positions, int glyphCount,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) override;
- virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+ virtual void drawTextOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return false; }
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 0620653371a6..dfcac1d1756a 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -128,14 +128,13 @@ void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) {
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 d36ce993164e..09469008e6fa 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -73,7 +73,7 @@ public:
// 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 15ccd6ac5b6b..43282c99803a 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -140,14 +140,15 @@ void RenderProxy::setName(const char* name) {
}
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 338fab650876..1d30eb8d8efe 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -66,7 +66,7 @@ public:
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/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 6cef85203352..4c8d23dd82ea 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -41,11 +41,8 @@ void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
// drawing text requires GlyphID TextEncoding (which JNI layer would have done)
LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding,
"must use glyph encoding");
-
- SkMatrix identity;
- identity.setIdentity();
SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
- SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &identity);
+ SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &SkMatrix::I());
float totalAdvance = 0;
std::vector<glyph_t> glyphs;
@@ -89,5 +86,21 @@ void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
}
+void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
+ const SkPaint& paint, const SkPath& path) {
+ // drawing text requires GlyphID TextEncoding (which JNI layer would have done)
+ LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding,
+ "must use glyph encoding");
+ SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+ SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &SkMatrix::I());
+
+ std::vector<glyph_t> glyphs;
+ while (*text != '\0') {
+ SkUnichar unichar = SkUTF8_NextUnichar(&text);
+ glyphs.push_back(autoCache.getCache()->unicharToGlyph(unichar));
+ }
+ canvas->drawTextOnPath(glyphs.data(), glyphs.size(), path, 0, 0, paint);
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 0af99398b5f5..4f84474f9988 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -191,6 +191,9 @@ public:
static void drawTextToCanvas(TestCanvas* canvas, const char* text,
const SkPaint& paint, float x, float y);
+ static void drawTextToCanvas(TestCanvas* canvas, const char* text,
+ const SkPaint& paint, const SkPath& path);
+
private:
static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
node->syncProperties();
diff --git a/libs/hwui/tests/common/scenes/OvalAnimation.cpp b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
index 082c6287e86f..e56f2f97007e 100644
--- a/libs/hwui/tests/common/scenes/OvalAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
@@ -15,6 +15,7 @@
*/
#include "TestSceneBase.h"
+#include "utils/Color.h"
class OvalAnimation;
@@ -28,12 +29,12 @@ class OvalAnimation : public TestScene {
public:
sp<RenderNode> card;
void createContent(int width, int height, TestCanvas& canvas) override {
- canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
card = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, TestCanvas& canvas) {
SkPaint paint;
paint.setAntiAlias(true);
- paint.setColor(0xFF000000);
+ paint.setColor(Color::Black);
canvas.drawOval(0, 0, 200, 200, paint);
});
canvas.drawRenderNode(card.get());
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
new file mode 100644
index 000000000000..1823db2940aa
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#include "TestSceneBase.h"
+#include "utils/Color.h"
+
+class TextAnimation;
+
+static TestScene::Registrar _Text(TestScene::Info{
+ "text",
+ "Draws a bunch of text.",
+ TestScene::simpleCreateScene<TextAnimation>
+});
+
+class TextAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ card = TestUtils::createNode(0, 0, width, height,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ SkPaint paint;
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ paint.setAntiAlias(true);
+ paint.setTextSize(50);
+
+ paint.setColor(Color::Black);
+ for (int i = 0; i < 10; i++) {
+ TestUtils::drawTextToCanvas(&canvas, "Test string", paint, 400, i * 100);
+ }
+
+ SkPath path;
+ path.addOval(SkRect::MakeLTRB(100, 100, 300, 300));
+
+ paint.setColor(Color::Blue_500);
+ TestUtils::drawTextToCanvas(&canvas, "This is a neat circle of text!", paint, path);
+ });
+ canvas.drawRenderNode(card.get());
+ }
+
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp
index 33eff5ba2da0..f9f5316a58df 100644
--- a/libs/hwui/tests/unit/BakedOpStateTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpStateTests.cpp
@@ -56,6 +56,28 @@ TEST(ResolvedRenderState, construct) {
}
}
+TEST(ResolvedRenderState, computeLocalSpaceClip) {
+ Matrix4 translate10x20;
+ translate10x20.loadTranslate(10, 20, 0);
+
+ SkPaint paint;
+ RectOp recordedOp(Rect(1000, 1000), translate10x20, Rect(100, 200), &paint);
+ {
+ // recorded with transform, no parent transform
+ auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
+ ResolvedRenderState state(*parentSnapshot, recordedOp, false);
+ EXPECT_EQ(Rect(-10, -20, 90, 180), state.computeLocalSpaceClip())
+ << "Local clip rect should be 100x200, offset by -10,-20";
+ }
+ {
+ // recorded with transform + parent transform
+ auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
+ ResolvedRenderState state(*parentSnapshot, recordedOp, false);
+ EXPECT_EQ(Rect(-10, -20, 80, 160), state.computeLocalSpaceClip())
+ << "Local clip rect should be 90x190, offset by -10,-20";
+ }
+}
+
const float HAIRLINE = 0.0f;
// Note: bounds will be conservative, but not precise for non-hairline
diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp
index ac356a47c87c..ab0cc878178c 100644
--- a/libs/hwui/tests/unit/OpReordererTests.cpp
+++ b/libs/hwui/tests/unit/OpReordererTests.cpp
@@ -221,7 +221,35 @@ TEST(OpReorderer, simpleBatching) {
<< "Expect number of ops = 2 * loop count";
}
-TEST(OpReorderer, textStrikethroughBatching) {
+TEST(OpReorderer, textMerging) {
+ class TextMergingTestRenderer : public TestRendererBase {
+ public:
+ void onMergedTextOps(const MergedBakedOpList& opList) override {
+ EXPECT_EQ(0, mIndex);
+ mIndex += opList.count;
+ EXPECT_EQ(2u, opList.count);
+ EXPECT_EQ(OpClipSideFlags::Top, opList.clipSideFlags);
+ EXPECT_EQ(OpClipSideFlags::Top, opList.states[0]->computedState.clipSideFlags);
+ EXPECT_EQ(OpClipSideFlags::None, opList.states[1]->computedState.clipSideFlags);
+ }
+ };
+ auto node = TestUtils::createNode(0, 0, 400, 400,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ SkPaint paint;
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ paint.setAntiAlias(true);
+ paint.setTextSize(50);
+ TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped
+ TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped
+ });
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
+ createSyncedNodeList(node), sLightCenter);
+ TextMergingTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops";
+}
+
+TEST(OpReorderer, textStrikethrough) {
const int LOOPS = 5;
class TextStrikethroughTestRenderer : public TestRendererBase {
public:
@@ -250,7 +278,7 @@ TEST(OpReorderer, textStrikethroughBatching) {
TextStrikethroughTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2 * LOOPS, renderer.getIndex())
- << "Expect number of ops = 2 * loop count"; // TODO: force no merging
+ << "Expect number of ops = 2 * loop count";
}
TEST(OpReorderer, renderNode) {
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 000000000000..5d1e9f994946
--- /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 5c2557643121..a7f2df5a739a 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 e902589ddc51..82ee710f5bcc 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.InputStream;
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.PendingIntent;
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.Parcelable;
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 @@ public class BugreportProgressService extends Service {
// 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 @@ public class BugreportProgressService extends Service {
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 @@ public class BugreportProgressService extends Service {
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 @@ public class BugreportProgressService extends Service {
}
onBugreportFinished(pid, intent);
break;
+ case INTENT_BUGREPORT_INFO_LAUNCH:
+ launchBugreportInfoDialog(pid);
+ break;
case INTENT_BUGREPORT_SHARE:
shareBugreport(pid);
break;
@@ -312,6 +332,13 @@ public class BugreportProgressService extends Service {
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 @@ public class BugreportProgressService extends Service {
.setLocalOnly(true)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color))
+ .addAction(infoAction)
.addAction(cancelAction)
.build();
@@ -341,7 +369,8 @@ public class BugreportProgressService extends Service {
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 @@ public class BugreportProgressService extends Service {
}
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 @@ public class BugreportProgressService extends Service {
* 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 @@ public class BugreportProgressService extends Service {
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 @@ public class BugreportProgressService extends Service {
}
/**
+ * 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 @@ public class BugreportProgressService extends Service {
}
}
+ /**
+ * 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 @@ public class BugreportProgressService extends Service {
}
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 @@ public class BugreportProgressService extends Service {
/**
* 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 @@ public class BugreportProgressService extends Service {
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 @@ public class BugreportProgressService extends Service {
.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 @@ public class BugreportProgressService extends Service {
}
}
+ 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 @@ public class BugreportProgressService extends Service {
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 class BugreportProgressService extends Service {
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 62a37bc70f4e..1e0eaace35da 100644
--- a/packages/Shell/tests/Android.mk
+++ b/packages/Shell/tests/Android.mk
@@ -8,9 +8,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src)
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 1f4d749f3992..7f609faac83d 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 @@ public class BugreportReceiverTest extends InstrumentationTestCase {
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 @@ public class BugreportReceiverTest extends InstrumentationTestCase {
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 @@ public class BugreportReceiverTest extends InstrumentationTestCase {
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 @@ public class BugreportReceiverTest extends InstrumentationTestCase {
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 @@ public class BugreportReceiverTest extends InstrumentationTestCase {
/**
* 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 @@ public class BugreportReceiverTest extends InstrumentationTestCase {
*/
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 @@ public class BugreportReceiverTest extends InstrumentationTestCase {
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 @@ public class BugreportReceiverTest extends InstrumentationTestCase {
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 c87172720ad7..384c3daa51c9 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 @@ final class UiBot {
}
/**
+ * 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 @@ final class UiBot {
}
/**
+ * 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 @@ final class UiBot {
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 000000000000..51b7ba8529a8
--- /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 eb63e5d55f9a..61cad2fde6d8 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -8,7 +8,11 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-unde
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_PROGUARD_FLAG_FILES := proguard.flags
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 51b84f52498d..02ddae6845bc 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 75e795960737..6a10c2c62508 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/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 47ad6dc2a8ad..aad428ae4032 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 43359b32fe8a..6103216d7cab 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/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 3b6130c76bf4..df7b9a63089b 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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
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 {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index a2b062c8b710..f1de2342c9ad 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.database.ContentObserver;
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 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference
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 dcb0d8d0c498..920ec7572b8a 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 @@ package com.android.systemui.tuner;
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 @@ public class StatusBarSwitch extends SwitchPreference implements Tunable {
}
@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 c84f61865522..4173ecc2d7fc 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 b620b50bfcff..a3fe6bb61a18 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.database.ContentObserver;
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 @@ public class TunerFragment extends PreferenceFragment {
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 @@ public class TunerFragment extends PreferenceFragment {
private SwitchPreference mBatteryPct;
+ @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.tuner_prefs);
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ addPreferencesFromResource(R.xml.tuner_prefs);
- 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;
- }
- });
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 54078b0cfd91..7ad752ec74ce 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 @@ package com.android.systemui.tuner;
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 @@ public class TunerSwitch extends SwitchPreference implements Tunable {
}
@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 9cf64d347e4a..b7a41e21e7d4 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -21,7 +21,8 @@ LOCAL_PROTOC_OPTIMIZE_TYPE := nano
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 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) \
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_PACKAGE_NAME := SystemUITests
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 e438c84027db..0f9e247f1c2b 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 d8724b27e0e1..bcd8efdd61cf 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 @@ public final class ActivityManagerService extends ActivityManagerNative
@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 9117806257f0..f7d66afd148f 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 @@ public final class ActivityStackSupervisor implements DisplayListener {
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 cee9ec8a026d..9cdece525bb3 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 @@ public class ZenModeConditions implements ConditionProviders.Callback {
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 6030bab6db64..85c3cf88af19 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 @@ public class ZenModeHelper {
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 @@ public class ZenModeHelper {
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 @@ public class ZenModeHelper {
if (mAudioManager != null) {
mAudioManager.setRingerModeDelegate(mRingerModeDelegate);
}
+ mPm = mContext.getPackageManager();
mHandler.postMetricsTimer();
+ cleanUpZenRules();
evaluateZenMode("onSystemReady", true);
}
@@ -184,7 +190,10 @@ public class ZenModeHelper {
config = mDefaultConfig.copy();
config.user = user;
}
- setConfig(config, "onUserSwitched");
+ synchronized (mConfig) {
+ setConfig(config, "onUserSwitched");
+ }
+ cleanUpZenRules();
}
public void onUserRemoved(int user) {
@@ -253,14 +262,15 @@ public class ZenModeHelper {
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 @@ public class ZenModeHelper {
+ " 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 @@ public class ZenModeHelper {
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 @@ public class ZenModeHelper {
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 @@ public class ZenModeHelper {
== 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 @@ public class ZenModeHelper {
+ " 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 @@ public class ZenModeHelper {
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 class ZenModeHelper {
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 @@ public class ZenModeHelper {
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 @@ public class ZenModeHelper {
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/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index eef7d9b9f8f0..7c42ae103e8a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -46,7 +46,6 @@ import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
import android.text.TextUtils;
-
import android.util.PrintWriterPrinter;
import com.android.internal.util.SizedInputStream;
@@ -127,19 +126,30 @@ class PackageManagerShellCommand extends ShellCommand {
final InstallParams params = makeInstallParams();
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
-
- final String inPath = getNextArg();
- if (inPath == null && params.sessionParams.sizeBytes == 0) {
- pw.println("Error: must either specify a package size or an APK file");
- return 1;
- }
- if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk") != 0) {
- return 1;
- }
- if (doCommitSession(sessionId) != 0) {
- return 1;
+ boolean abandonSession = true;
+ try {
+ final String inPath = getNextArg();
+ if (inPath == null && params.sessionParams.sizeBytes == 0) {
+ pw.println("Error: must either specify a package size or an APK file");
+ return 1;
+ }
+ if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
+ false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
+ if (doCommitSession(sessionId, false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
+ abandonSession = false;
+ return 0;
+ } finally {
+ if (abandonSession) {
+ try {
+ doAbandonSession(sessionId, false /*logSuccess*/);
+ } catch (Exception ignore) {
+ }
+ }
}
- return 0;
}
private int runSuspend(boolean suspendedState) {
@@ -179,12 +189,12 @@ class PackageManagerShellCommand extends ShellCommand {
private int runInstallAbandon() throws RemoteException {
final int sessionId = Integer.parseInt(getNextArg());
- return doAbandonSession(sessionId);
+ return doAbandonSession(sessionId, true /*logSuccess*/);
}
private int runInstallCommit() throws RemoteException {
final int sessionId = Integer.parseInt(getNextArg());
- return doCommitSession(sessionId);
+ return doCommitSession(sessionId, true /*logSuccess*/);
}
private int runInstallCreate() throws RemoteException {
@@ -213,7 +223,7 @@ class PackageManagerShellCommand extends ShellCommand {
final int sessionId = Integer.parseInt(getNextArg());
final String splitName = getNextArg();
final String path = getNextArg();
- return doWriteSession(sessionId, path, sizeBytes, splitName);
+ return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
}
private int runList() throws RemoteException {
@@ -559,7 +569,7 @@ class PackageManagerShellCommand extends ShellCommand {
} else {
final PackageInfo info = mInterface.getPackageInfo(packageName, 0, userId);
if (info == null) {
- pw.println("Failure - not installed for " + userId);
+ pw.println("Failure [not installed for " + userId + "]");
return 1;
}
final boolean isSystem =
@@ -828,8 +838,8 @@ class PackageManagerShellCommand extends ShellCommand {
return sessionId;
}
- private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName)
- throws RemoteException {
+ private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName,
+ boolean logSuccess) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
if ("-".equals(inPath)) {
inPath = null;
@@ -870,7 +880,9 @@ class PackageManagerShellCommand extends ShellCommand {
}
session.fsync(out);
- pw.println("Success: streamed " + total + " bytes");
+ if (logSuccess) {
+ pw.println("Success: streamed " + total + " bytes");
+ }
return 0;
} catch (IOException e) {
pw.println("Error: failed to write; " + e.getMessage());
@@ -882,7 +894,7 @@ class PackageManagerShellCommand extends ShellCommand {
}
}
- private int doCommitSession(int sessionId) throws RemoteException {
+ private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
PackageInstaller.Session session = null;
try {
@@ -896,11 +908,12 @@ class PackageManagerShellCommand extends ShellCommand {
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
- pw.println("Success");
+ if (logSuccess) {
+ System.out.println("Success");
+ }
} else {
pw.println("Failure ["
+ result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
- pw.println("Failure details: " + result.getExtras());
}
return status;
} finally {
@@ -908,14 +921,16 @@ class PackageManagerShellCommand extends ShellCommand {
}
}
- private int doAbandonSession(int sessionId) throws RemoteException {
+ private int doAbandonSession(int sessionId, boolean logSuccess) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
PackageInstaller.Session session = null;
try {
session = new PackageInstaller.Session(
mInterface.getPackageInstaller().openSession(sessionId));
session.abandon();
- pw.println("Success");
+ if (logSuccess) {
+ pw.println("Success");
+ }
return 0;
} finally {
IoUtils.closeQuietly(session);
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index c8523c908fed..5948d3cb665d 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 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
} 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 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
}
}
- 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);
+ }
- public boolean showDuringKeyguard() {
- return true;
+ @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 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 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
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 189ed332abef..fb5f21ab9ca6 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -141,6 +141,14 @@ public final class SystemServer {
"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 @@ public final class SystemServer {
private void createSystemContext() {
ActivityThread activityThread = ActivityThread.systemMain();
mSystemContext = activityThread.getSystemContext();
- mSystemContext.setTheme(android.R.style.Theme_Material_DayNight_DarkActionBar);
+ mSystemContext.setTheme(DEFAULT_SYSTEM_THEME);
}
/**