Merge "MediaCodec refactoring part 1-a: buffers become separate class"
diff --git a/Android.mk b/Android.mk
index 121a38e..e352bc7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -332,6 +332,7 @@
core/java/com/android/internal/os/IDropBoxManagerService.aidl \
core/java/com/android/internal/os/IParcelFileDescriptorFactory.aidl \
core/java/com/android/internal/os/IResultReceiver.aidl \
+ core/java/com/android/internal/os/IShellCallback.aidl \
core/java/com/android/internal/statusbar/IStatusBar.aidl \
core/java/com/android/internal/statusbar/IStatusBarService.aidl \
core/java/com/android/internal/textservice/ISpellCheckerService.aidl \
diff --git a/api/current.txt b/api/current.txt
index 2d3fde8..3d11602 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4042,6 +4042,7 @@
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
field public static final java.lang.String OPSTR_READ_CELL_BROADCASTS = "android:read_cell_broadcasts";
@@ -36915,6 +36916,7 @@
field public static final java.lang.String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
+ field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
@@ -52070,6 +52072,8 @@
enum_constant public static final java.lang.annotation.ElementType PACKAGE;
enum_constant public static final java.lang.annotation.ElementType PARAMETER;
enum_constant public static final java.lang.annotation.ElementType TYPE;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_PARAMETER;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_USE;
}
public class IncompleteAnnotationException extends java.lang.RuntimeException {
@@ -52151,7 +52155,7 @@
method public abstract <T extends java.lang.annotation.Annotation> T getAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getAnnotationsByType(java.lang.Class<T>);
- method public default <T extends java.lang.annotation.Annotation> java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
+ method public default <T extends java.lang.annotation.Annotation> T getDeclaredAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public default boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
diff --git a/api/system-current.txt b/api/system-current.txt
index d0116ee..1ab4884 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4172,6 +4172,7 @@
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
field public static final java.lang.String OPSTR_READ_CELL_BROADCASTS = "android:read_cell_broadcasts";
@@ -40006,6 +40007,7 @@
field public static final java.lang.String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
+ field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
@@ -55607,6 +55609,8 @@
enum_constant public static final java.lang.annotation.ElementType PACKAGE;
enum_constant public static final java.lang.annotation.ElementType PARAMETER;
enum_constant public static final java.lang.annotation.ElementType TYPE;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_PARAMETER;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_USE;
}
public class IncompleteAnnotationException extends java.lang.RuntimeException {
@@ -55688,7 +55692,7 @@
method public abstract <T extends java.lang.annotation.Annotation> T getAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getAnnotationsByType(java.lang.Class<T>);
- method public default <T extends java.lang.annotation.Annotation> java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
+ method public default <T extends java.lang.annotation.Annotation> T getDeclaredAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public default boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
diff --git a/api/test-current.txt b/api/test-current.txt
index 9c744c5..6d4590d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4045,6 +4045,7 @@
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
field public static final java.lang.String OPSTR_READ_CELL_BROADCASTS = "android:read_cell_broadcasts";
@@ -36996,6 +36997,7 @@
field public static final java.lang.String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
+ field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
@@ -52172,6 +52174,8 @@
enum_constant public static final java.lang.annotation.ElementType PACKAGE;
enum_constant public static final java.lang.annotation.ElementType PARAMETER;
enum_constant public static final java.lang.annotation.ElementType TYPE;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_PARAMETER;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_USE;
}
public class IncompleteAnnotationException extends java.lang.RuntimeException {
@@ -52253,7 +52257,7 @@
method public abstract <T extends java.lang.annotation.Annotation> T getAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getAnnotationsByType(java.lang.Class<T>);
- method public default <T extends java.lang.annotation.Annotation> java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
+ method public default <T extends java.lang.annotation.Annotation> T getDeclaredAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public default boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 91334bd..3759de2 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -26,7 +26,6 @@
import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityManagerNative;
-import android.app.ActivityOptions;
import android.app.IActivityContainer;
import android.app.IActivityController;
import android.app.IActivityManager;
@@ -46,7 +45,6 @@
import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.content.pm.ParceledListSlice;
-import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -55,10 +53,11 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.ShellCommand;
-import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -72,6 +71,7 @@
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -96,6 +96,7 @@
// Amount we reduce the stack size by when testing a task re-size.
private static final int STACK_BOUNDS_INSET = 10;
+ public static final String NO_CLASS_ERROR_CODE = "Error type 3";
private IActivityManager mAm;
private IPackageManager mPm;
@@ -198,7 +199,7 @@
" --track-allocation: enable tracking of object allocations\n" +
" --user <USER_ID> | current: Specify which user to run as; if not\n" +
" specified then run as the current user.\n" +
- " --stack <STACK_ID>: Specify into which stack should the activity be put." +
+ " --stack <STACK_ID>: Specify into which stack should the activity be put.\n" +
"\n" +
"am startservice: start a Service. Options are:\n" +
" --user <USER_ID> | current: Specify which user to run as; if not\n" +
@@ -385,17 +386,13 @@
String op = nextArgRequired();
if (op.equals("start")) {
- runStart();
+ runAmCmd(getRawArgs());
} else if (op.equals("startservice")) {
runStartService();
} else if (op.equals("stopservice")) {
runStopService();
- } else if (op.equals("force-stop")) {
- runForceStop();
- } else if (op.equals("kill")) {
- runKill();
- } else if (op.equals("kill-all")) {
- runKillAll();
+ } else if (op.equals("force-stop") || op.equals("kill") || op.equals("kill-all")) {
+ runAmCmd(getRawArgs());
} else if (op.equals("instrument")) {
runInstrument();
} else if (op.equals("trace-ipc")) {
@@ -475,6 +472,49 @@
return userId;
}
+ static final class MyShellCallback extends ShellCallback {
+ @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ File file = new File(path);
+ //System.err.println("Opening file: " + file.getAbsolutePath());
+ //Log.i("Am", "Opening file: " + file.getAbsolutePath());
+ final ParcelFileDescriptor fd;
+ try {
+ fd = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE |
+ ParcelFileDescriptor.MODE_WRITE_ONLY);
+ } catch (FileNotFoundException e) {
+ String msg = "Unable to open file " + path + ": " + e;
+ System.err.println(msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (seLinuxContext != null) {
+ final String tcon = SELinux.getFileContext(file.getAbsolutePath());
+ if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ }
+ String msg = "System server has no access to file context " + tcon;
+ System.err.println(msg + " (from path " + file.getAbsolutePath()
+ + ", context " + seLinuxContext + ")");
+ throw new IllegalArgumentException(msg);
+ }
+ }
+ return fd;
+ }
+ }
+
+ void runAmCmd(String[] args) throws AndroidException {
+ try {
+ mAm.asBinder().shellCommand(FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ args, new MyShellCallback(), new ResultReceiver(null) { });
+ } catch (RemoteException e) {
+ System.err.println(NO_SYSTEM_ERROR_CODE);
+ throw new AndroidException("Can't call activity manager; is the system running?");
+ }
+ }
+
private Intent makeIntent(int defUser) throws URISyntaxException {
mStartFlags = 0;
mWaitOption = false;
@@ -558,211 +598,6 @@
}
}
- private void runStart() throws Exception {
- Intent intent = makeIntent(UserHandle.USER_CURRENT);
-
- if (mUserId == UserHandle.USER_ALL) {
- System.err.println("Error: Can't start service with user 'all'");
- return;
- }
-
- String mimeType = intent.getType();
- if (mimeType == null && intent.getData() != null
- && "content".equals(intent.getData().getScheme())) {
- mimeType = mAm.getProviderMimeType(intent.getData(), mUserId);
- }
-
-
- do {
- if (mStopOption) {
- String packageName;
- if (intent.getComponent() != null) {
- packageName = intent.getComponent().getPackageName();
- } else {
- List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType, 0,
- mUserId).getList();
- if (activities == null || activities.size() <= 0) {
- System.err.println("Error: Intent does not match any activities: "
- + intent);
- return;
- } else if (activities.size() > 1) {
- System.err.println("Error: Intent matches multiple activities; can't stop: "
- + intent);
- return;
- }
- packageName = activities.get(0).activityInfo.packageName;
- }
- System.out.println("Stopping: " + packageName);
- mAm.forceStopPackage(packageName, mUserId);
- Thread.sleep(250);
- }
-
- System.out.println("Starting: " + intent);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- ParcelFileDescriptor fd = null;
- ProfilerInfo profilerInfo = null;
-
- if (mProfileFile != null) {
- try {
- fd = openForSystemServer(
- new File(mProfileFile),
- ParcelFileDescriptor.MODE_CREATE |
- ParcelFileDescriptor.MODE_TRUNCATE |
- ParcelFileDescriptor.MODE_WRITE_ONLY);
- } catch (FileNotFoundException e) {
- System.err.println("Error: Unable to open file: " + mProfileFile);
- System.err.println("Consider using a file under /data/local/tmp/");
- return;
- }
- profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop);
- }
-
- IActivityManager.WaitResult result = null;
- int res;
- final long startTime = SystemClock.uptimeMillis();
- ActivityOptions options = null;
- if (mStackId != INVALID_STACK_ID) {
- options = ActivityOptions.makeBasic();
- options.setLaunchStackId(mStackId);
- }
- if (mWaitOption) {
- result = mAm.startActivityAndWait(null, null, intent, mimeType,
- null, null, 0, mStartFlags, profilerInfo,
- options != null ? options.toBundle() : null, mUserId);
- res = result.result;
- } else {
- res = mAm.startActivityAsUser(null, null, intent, mimeType,
- null, null, 0, mStartFlags, profilerInfo,
- options != null ? options.toBundle() : null, mUserId);
- }
- final long endTime = SystemClock.uptimeMillis();
- PrintStream out = mWaitOption ? System.out : System.err;
- boolean launched = false;
- switch (res) {
- case ActivityManager.START_SUCCESS:
- launched = true;
- break;
- case ActivityManager.START_SWITCHES_CANCELED:
- launched = true;
- out.println(
- "Warning: Activity not started because the "
- + " current activity is being kept for the user.");
- break;
- case ActivityManager.START_DELIVERED_TO_TOP:
- launched = true;
- out.println(
- "Warning: Activity not started, intent has "
- + "been delivered to currently running "
- + "top-most instance.");
- break;
- case ActivityManager.START_RETURN_INTENT_TO_CALLER:
- launched = true;
- out.println(
- "Warning: Activity not started because intent "
- + "should be handled by the caller");
- break;
- case ActivityManager.START_TASK_TO_FRONT:
- launched = true;
- out.println(
- "Warning: Activity not started, its current "
- + "task has been brought to the front");
- break;
- case ActivityManager.START_INTENT_NOT_RESOLVED:
- out.println(
- "Error: Activity not started, unable to "
- + "resolve " + intent.toString());
- break;
- case ActivityManager.START_CLASS_NOT_FOUND:
- out.println(NO_CLASS_ERROR_CODE);
- out.println("Error: Activity class " +
- intent.getComponent().toShortString()
- + " does not exist.");
- break;
- case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
- out.println(
- "Error: Activity not started, you requested to "
- + "both forward and receive its result");
- break;
- case ActivityManager.START_PERMISSION_DENIED:
- out.println(
- "Error: Activity not started, you do not "
- + "have permission to access it.");
- break;
- case ActivityManager.START_NOT_VOICE_COMPATIBLE:
- out.println(
- "Error: Activity not started, voice control not allowed for: "
- + intent);
- break;
- case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY:
- out.println(
- "Error: Not allowed to start background user activity"
- + " that shouldn't be displayed for all users.");
- break;
- default:
- out.println(
- "Error: Activity not started, unknown error code " + res);
- break;
- }
- if (mWaitOption && launched) {
- if (result == null) {
- result = new IActivityManager.WaitResult();
- result.who = intent.getComponent();
- }
- System.out.println("Status: " + (result.timeout ? "timeout" : "ok"));
- if (result.who != null) {
- System.out.println("Activity: " + result.who.flattenToShortString());
- }
- if (result.thisTime >= 0) {
- System.out.println("ThisTime: " + result.thisTime);
- }
- if (result.totalTime >= 0) {
- System.out.println("TotalTime: " + result.totalTime);
- }
- System.out.println("WaitTime: " + (endTime-startTime));
- System.out.println("Complete");
- }
- mRepeat--;
- if (mRepeat > 0) {
- mAm.unhandledBack();
- }
- } while (mRepeat > 0);
- }
-
- private void runForceStop() throws Exception {
- int userId = UserHandle.USER_ALL;
-
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
- mAm.forceStopPackage(nextArgRequired(), userId);
- }
-
- private void runKill() throws Exception {
- int userId = UserHandle.USER_ALL;
-
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
- mAm.killBackgroundProcesses(nextArgRequired(), userId);
- }
-
- private void runKillAll() throws Exception {
- mAm.killAllBackgroundProcesses();
- }
-
private void sendBroadcast() throws Exception {
Intent intent = makeIntent(UserHandle.USER_CURRENT);
IntentReceiver receiver = new IntentReceiver();
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 32a8088..ace4e32 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -49,9 +49,12 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IUserManager;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -68,6 +71,7 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -284,13 +288,45 @@
}
}
+ static final class MyShellCallback extends ShellCallback {
+ @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ File file = new File(path);
+ final ParcelFileDescriptor fd;
+ try {
+ fd = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE |
+ ParcelFileDescriptor.MODE_WRITE_ONLY);
+ } catch (FileNotFoundException e) {
+ String msg = "Unable to open file " + path + ": " + e;
+ System.err.println(msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (seLinuxContext != null) {
+ final String tcon = SELinux.getFileContext(file.getAbsolutePath());
+ if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ }
+ String msg = "System server has no access to file context " + tcon;
+ System.err.println(msg + " (from path " + file.getAbsolutePath()
+ + ", context " + seLinuxContext + ")");
+ throw new IllegalArgumentException(msg);
+ }
+ }
+ return fd;
+ }
+ }
+
private int runShellCommand(String serviceName, String[] args) {
final HandlerThread handlerThread = new HandlerThread("results");
handlerThread.start();
try {
ServiceManager.getService(serviceName).shellCommand(
FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- args, new ResultReceiver(new Handler(handlerThread.getLooper())));
+ args, new MyShellCallback(),
+ new ResultReceiver(new Handler(handlerThread.getLooper())));
return 0;
} catch (RemoteException e) {
e.printStackTrace();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 5a9498f..191cc49 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -311,9 +311,7 @@
/** Access APIs for SIP calling over VOIP or WiFi */
public static final String OPSTR_USE_SIP
= "android:use_sip";
- /** Access APIs for diverting outgoing calls
- * @hide
- */
+ /** Access APIs for diverting outgoing calls */
public static final String OPSTR_PROCESS_OUTGOING_CALLS
= "android:process_outgoing_calls";
/** Use the fingerprint API. */
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index ea8ba2f..cf77567 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -23,7 +23,6 @@
import java.io.FileDescriptor;
import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
@@ -361,13 +360,14 @@
ParcelFileDescriptor out = data.readFileDescriptor();
ParcelFileDescriptor err = data.readFileDescriptor();
String[] args = data.readStringArray();
+ ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);
ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);
try {
if (out != null) {
shellCommand(in != null ? in.getFileDescriptor() : null,
out.getFileDescriptor(),
err != null ? err.getFileDescriptor() : out.getFileDescriptor(),
- args, resultReceiver);
+ args, shellCallback, resultReceiver);
}
} finally {
IoUtils.closeQuietly(in);
@@ -459,13 +459,15 @@
* @param out The raw file descriptor that normal command messages should be written to.
* @param err The raw file descriptor that command error messages should be written to.
* @param args Command-line arguments.
+ * @param callback Callback through which to interact with the invoking shell.
* @param resultReceiver Called when the command has finished executing, with the result code.
* @throws RemoteException
* @hide
*/
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
- onShellCommand(in, out, err, args, resultReceiver);
+ String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) throws RemoteException {
+ onShellCommand(in, out, err, args, callback, resultReceiver);
}
/**
@@ -477,7 +479,7 @@
* @hide
*/
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException {
FileOutputStream fout = new FileOutputStream(err != null ? err : out);
PrintWriter pw = new FastPrintWriter(fout);
pw.println("No shell command implementation.");
@@ -650,13 +652,15 @@
}
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeFileDescriptor(in);
data.writeFileDescriptor(out);
data.writeFileDescriptor(err);
data.writeStringArray(args);
+ ShellCallback.writeToParcel(callback, data);
resultReceiver.writeToParcel(data, 0);
try {
transact(SHELL_COMMAND_TRANSACTION, data, reply, 0);
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 0fa8750..f762a05 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -220,11 +220,13 @@
* @param out The raw file descriptor that normal command messages should be written to.
* @param err The raw file descriptor that command error messages should be written to.
* @param args Command-line arguments.
+ * @param shellCallback Optional callback to the caller's shell to perform operations in it.
* @param resultReceiver Called when the command has finished executing, with the result code.
* @hide
*/
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException;
+ String[] args, ShellCallback shellCallback,
+ ResultReceiver resultReceiver) throws RemoteException;
/**
* Perform a generic operation with the object.
diff --git a/core/java/android/os/ShellCallback.java b/core/java/android/os/ShellCallback.java
new file mode 100644
index 0000000..e7fe697
--- /dev/null
+++ b/core/java/android/os/ShellCallback.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.Log;
+
+import com.android.internal.os.IShellCallback;
+
+/**
+ * Special-purpose API for use with {@link IBinder#shellCommand IBinder.shellCommand} for
+ * performing operations back on the invoking shell.
+ * @hide
+ */
+public class ShellCallback implements Parcelable {
+ final static String TAG = "ShellCallback";
+
+ final static boolean DEBUG = false;
+
+ final boolean mLocal;
+
+ IShellCallback mShellCallback;
+
+ class MyShellCallback extends IShellCallback.Stub {
+ public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
+ return onOpenOutputFile(path, seLinuxContext);
+ }
+ }
+
+ /**
+ * Create a new ShellCallback to receive requests.
+ */
+ public ShellCallback() {
+ mLocal = true;
+ }
+
+ /**
+ * Ask the shell to open a file for writing. This will truncate the file if it
+ * already exists. It will create the file if it doesn't exist.
+ * @param path Path of the file to be opened/created.
+ * @param seLinuxContext Optional SELinux context that must be allowed to have
+ * access to the file; if null, nothing is required.
+ */
+ public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
+ if (DEBUG) Log.d(TAG, "openOutputFile " + this + ": mLocal=" + mLocal
+ + " mShellCallback=" + mShellCallback);
+
+ if (mLocal) {
+ return onOpenOutputFile(path, seLinuxContext);
+ }
+
+ if (mShellCallback != null) {
+ try {
+ return mShellCallback.openOutputFile(path, seLinuxContext);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure opening " + path, e);
+ }
+ }
+ return null;
+ }
+
+ public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ return null;
+ }
+
+ public static void writeToParcel(ShellCallback callback, Parcel out) {
+ if (callback == null) {
+ out.writeStrongBinder(null);
+ } else {
+ callback.writeToParcel(out, 0);
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ synchronized (this) {
+ if (mShellCallback == null) {
+ mShellCallback = new MyShellCallback();
+ }
+ out.writeStrongBinder(mShellCallback.asBinder());
+ }
+ }
+
+ ShellCallback(Parcel in) {
+ mLocal = false;
+ mShellCallback = IShellCallback.Stub.asInterface(in.readStrongBinder());
+ }
+
+ public static final Parcelable.Creator<ShellCallback> CREATOR
+ = new Parcelable.Creator<ShellCallback>() {
+ public ShellCallback createFromParcel(Parcel in) {
+ return new ShellCallback(in);
+ }
+ public ShellCallback[] newArray(int size) {
+ return new ShellCallback[size];
+ }
+ };
+}
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index fc804e5..831c9b2 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -40,6 +40,7 @@
private FileDescriptor mOut;
private FileDescriptor mErr;
private String[] mArgs;
+ private ShellCallback mShellCallback;
private ResultReceiver mResultReceiver;
private String mCmd;
@@ -55,12 +56,13 @@
private InputStream mInputStream;
public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, int firstArgPos) {
+ String[] args, ShellCallback callback, int firstArgPos) {
mTarget = target;
mIn = in;
mOut = out;
mErr = err;
mArgs = args;
+ mShellCallback = callback;
mResultReceiver = null;
mCmd = null;
mArgPos = firstArgPos;
@@ -74,7 +76,7 @@
}
public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
String cmd;
int start;
if (args != null && args.length > 0) {
@@ -84,7 +86,7 @@
cmd = null;
start = 0;
}
- init(target, in, out, err, args, start);
+ init(target, in, out, err, args, callback, start);
mCmd = cmd;
mResultReceiver = resultReceiver;
@@ -105,7 +107,7 @@
// go.
PrintWriter eout = getErrPrintWriter();
eout.println();
- eout.println("Exception occurred while dumping:");
+ eout.println("Exception occurred while executing:");
e.printStackTrace(eout);
} finally {
if (DEBUG) Slog.d(TAG, "Flushing output streams on " + mTarget);
@@ -257,6 +259,13 @@
return arg;
}
+ /**
+ * Return the {@link ShellCallback} for communicating back with the calling shell.
+ */
+ public ShellCallback getShellCallback() {
+ return mShellCallback;
+ }
+
public int handleDefaultCommands(String cmd) {
if ("dump".equals(cmd)) {
String[] newArgs = new String[mArgs.length-1];
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index c067da7..3baccee 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -36,6 +36,8 @@
public static final String NO_SYSTEM_ERROR_CODE = "Error type 2";
public static final String NO_CLASS_ERROR_CODE = "Error type 3";
+ private String[] mRawArgs;
+
/**
* Call to run the command.
*/
@@ -45,7 +47,8 @@
return;
}
- mArgs.init(null, null, null, null, args, 0);
+ mRawArgs = args;
+ mArgs.init(null, null, null, null, args, null, 0);
try {
onRun();
@@ -109,4 +112,11 @@
public String nextArgRequired() {
return mArgs.getNextArgRequired();
}
+
+ /**
+ * Return the original raw argument list supplied to the command.
+ */
+ public String[] getRawArgs() {
+ return mRawArgs;
+ }
}
diff --git a/core/java/com/android/internal/os/IShellCallback.aidl b/core/java/com/android/internal/os/IShellCallback.aidl
new file mode 100644
index 0000000..57d6789
--- /dev/null
+++ b/core/java/com/android/internal/os/IShellCallback.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.ParcelFileDescriptor;
+
+/** @hide */
+interface IShellCallback {
+ ParcelFileDescriptor openOutputFile(String path, String seLinuxContext);
+}
diff --git a/core/res/res/values-mcc238-mnc06/config.xml b/core/res/res/values-mcc238-mnc06/config.xml
deleted file mode 100644
index afc0cc4..0000000
--- a/core/res/res/values-mcc238-mnc06/config.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- SIM does not save, but the voice mail number to be changed. -->
- <bool name="editable_voicemailnumber">true</bool>
-</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 449acc6..30dd760 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -347,8 +347,6 @@
<java-symbol type="integer" name="config_wifi_operating_voltage_mv" />
<java-symbol type="string" name="config_wifi_framework_sap_2G_channel_list" />
- <java-symbol type="bool" name="editable_voicemailnumber" />
-
<java-symbol type="bool" name="config_wifi_framework_cellular_handover_enable_user_triggered_adjustment" />
<java-symbol type="integer" name="config_wifi_framework_associated_full_scan_tx_packet_threshold" />
<java-symbol type="integer" name="config_wifi_framework_associated_full_scan_rx_packet_threshold" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index c234b6a..ee78613 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1329,6 +1329,11 @@
android:exported="true">
</activity>
+ <activity
+ android:name="android.print.mockservice.AddPrintersActivity"
+ android:exported="true">
+ </activity>
+
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/core/tests/coretests/res/xml/printservice.xml b/core/tests/coretests/res/xml/printservice.xml
index abbebda..b105a0f 100644
--- a/core/tests/coretests/res/xml/printservice.xml
+++ b/core/tests/coretests/res/xml/printservice.xml
@@ -17,4 +17,5 @@
-->
<print-service xmlns:android="http://schemas.android.com/apk/res/android"
- android:settingsActivity="android.print.mockservice.SettingsActivity"/>
+ android:settingsActivity="android.print.mockservice.SettingsActivity"
+ android:addPrintersActivity="android.print.mockservice.AddPrintersActivity" />
diff --git a/core/tests/coretests/src/android/print/BasePrintTest.java b/core/tests/coretests/src/android/print/BasePrintTest.java
index ca7b5e1..8ef8062 100644
--- a/core/tests/coretests/src/android/print/BasePrintTest.java
+++ b/core/tests/coretests/src/android/print/BasePrintTest.java
@@ -57,8 +57,7 @@
* This is the base class for print tests.
*/
abstract class BasePrintTest {
-
- private static final long OPERATION_TIMEOUT = 30000;
+ protected static final long OPERATION_TIMEOUT = 30000;
private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
@@ -75,6 +74,39 @@
new ActivityTestRule<>(PrintTestActivity.class, false, true);
/**
+ * {@link Runnable} that can throw and {@link Exception}
+ */
+ interface Invokable {
+ /**
+ * Execute the invokable
+ *
+ * @throws Exception
+ */
+ void run() throws Exception;
+ }
+
+ /**
+ * Assert that the invokable throws an expectedException
+ *
+ * @param invokable The {@link Invokable} to run
+ * @param expectedClass The {@link Exception} that is supposed to be thrown
+ */
+ void assertException(Invokable invokable, Class<? extends Exception> expectedClass)
+ throws Exception {
+ try {
+ invokable.run();
+ } catch (Exception e) {
+ if (e.getClass().isAssignableFrom(expectedClass)) {
+ return;
+ } else {
+ throw e;
+ }
+ }
+
+ throw new AssertionError("No exception thrown");
+ }
+
+ /**
* Return the UI device
*
* @return the UI device
@@ -105,14 +137,14 @@
}
@Before
- public void setUp() throws Exception {
+ public void initCounters() throws Exception {
// Initialize the latches.
mStartCallCounter = new CallCounter();
mStartSessionCallCounter = new CallCounter();
}
@After
- public void tearDown() throws Exception {
+ public void exitActivities() throws Exception {
// Exit print spooler
getUiDevice().pressBack();
getUiDevice().pressBack();
diff --git a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
index 75be426..2e9c8e7 100644
--- a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
+++ b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
@@ -41,6 +41,7 @@
import android.support.test.filters.LargeTest;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -219,10 +220,8 @@
return new PrinterId(getActivity().getComponentName(), "dummy printer");
}
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
+ @Before
+ public void setUpMockService() throws Exception {
MockPrintService.setCallbacks(createMockCallbacks());
mIPrintManager = IPrintManager.Stub
@@ -230,40 +229,6 @@
}
/**
- * {@link Runnable} that can throw and {@link Exception}
- */
- private interface Invokable {
- /**
- * Execute the invokable
- *
- * @throws Exception
- */
- void run() throws Exception;
- }
-
- /**
- * Assert that the invokable throws an expectedException
- *
- * @param invokable The {@link Invokable} to run
- * @param expectedClass The {@link Exception} that is supposed to be thrown
- */
- public void assertException(Invokable invokable, Class<? extends Exception> expectedClass)
- throws Exception {
- try {
- invokable.run();
- } catch (Exception e) {
- if (e.getClass().isAssignableFrom(expectedClass)) {
- return;
- } else {
- throw new AssertionError("Expected: " + expectedClass.getName() + ", got: "
- + e.getClass().getName());
- }
- }
-
- throw new AssertionError("No exception thrown");
- }
-
- /**
* test IPrintManager.getPrintJobInfo
*/
@LargeTest
diff --git a/core/tests/coretests/src/android/print/WorkflowTest.java b/core/tests/coretests/src/android/print/WorkflowTest.java
new file mode 100644
index 0000000..35cfe22
--- /dev/null
+++ b/core/tests/coretests/src/android/print/WorkflowTest.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.print;
+
+import android.graphics.pdf.PdfDocument;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.print.mockservice.AddPrintersActivity;
+import android.print.mockservice.MockPrintService;
+
+import android.print.mockservice.PrinterDiscoverySessionCallbacks;
+import android.print.mockservice.StubbablePrinterDiscoverySession;
+import android.print.pdf.PrintedPdfDocument;
+import android.support.test.filters.LargeTest;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.util.Log;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for the basic printing workflows
+ */
+public class WorkflowTest extends BasePrintTest {
+ private static final String LOG_TAG = WorkflowTest.class.getSimpleName();
+
+ private static float sWindowAnimationScaleBefore;
+ private static float sTransitionAnimationScaleBefore;
+ private static float sAnimatiorDurationScaleBefore;
+
+ interface InterruptableConsumer<T> {
+ void accept(T t) throws InterruptedException;
+ }
+
+ /**
+ * Execute {@code waiter} until {@code condition} is met.
+ *
+ * @param condition Conditions to wait for
+ * @param waiter Code to execute while waiting
+ */
+ private void waitWithTimeout(Supplier<Boolean> condition, InterruptableConsumer<Long> waiter)
+ throws TimeoutException, InterruptedException {
+ long startTime = System.currentTimeMillis();
+ while (condition.get()) {
+ long timeLeft = OPERATION_TIMEOUT - (System.currentTimeMillis() - startTime);
+ if (timeLeft < 0) {
+ throw new TimeoutException();
+ }
+
+ waiter.accept(timeLeft);
+ }
+ }
+
+ /**
+ * Executes a shell command using shell user identity, and return the standard output in
+ * string.
+ *
+ * @param cmd the command to run
+ *
+ * @return the standard output of the command
+ */
+ private static String runShellCommand(String cmd) throws IOException {
+ try (FileInputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
+ getInstrumentation().getUiAutomation().executeShellCommand(cmd))) {
+ byte[] buf = new byte[64];
+ int bytesRead;
+
+ StringBuilder stdout = new StringBuilder();
+ while ((bytesRead = is.read(buf)) != -1) {
+ stdout.append(new String(buf, 0, bytesRead));
+ }
+
+ return stdout.toString();
+ }
+ }
+
+ @BeforeClass
+ public static void disableAnimations() throws Exception {
+ try {
+ sWindowAnimationScaleBefore = Float.parseFloat(runShellCommand(
+ "settings get global window_animation_scale"));
+
+ runShellCommand("settings put global window_animation_scale 0");
+ } catch (NumberFormatException e) {
+ sWindowAnimationScaleBefore = Float.NaN;
+ }
+ try {
+ sTransitionAnimationScaleBefore = Float.parseFloat(runShellCommand(
+ "settings get global transition_animation_scale"));
+
+ runShellCommand("settings put global transition_animation_scale 0");
+ } catch (NumberFormatException e) {
+ sTransitionAnimationScaleBefore = Float.NaN;
+ }
+ try {
+ sAnimatiorDurationScaleBefore = Float.parseFloat(runShellCommand(
+ "settings get global animator_duration_scale"));
+
+ runShellCommand("settings put global animator_duration_scale 0");
+ } catch (NumberFormatException e) {
+ sAnimatiorDurationScaleBefore = Float.NaN;
+ }
+ }
+
+ @AfterClass
+ public static void enableAnimations() throws Exception {
+ if (sWindowAnimationScaleBefore != Float.NaN) {
+ runShellCommand(
+ "settings put global window_animation_scale " + sWindowAnimationScaleBefore);
+ }
+ if (sTransitionAnimationScaleBefore != Float.NaN) {
+ runShellCommand(
+ "settings put global transition_animation_scale " +
+ sTransitionAnimationScaleBefore);
+ }
+ if (sAnimatiorDurationScaleBefore != Float.NaN) {
+ runShellCommand(
+ "settings put global animator_duration_scale " + sAnimatiorDurationScaleBefore);
+ }
+ }
+
+ /** Add a printer with a given name and supported mediasize to a session */
+ private void addPrinter(StubbablePrinterDiscoverySession session,
+ String name, PrintAttributes.MediaSize mediaSize) {
+ PrinterId printerId = session.getService().generatePrinterId(name);
+ List<PrinterInfo> printers = new ArrayList<>(1);
+
+ PrinterCapabilitiesInfo.Builder builder =
+ new PrinterCapabilitiesInfo.Builder(printerId);
+
+ builder.setMinMargins(new PrintAttributes.Margins(0, 0, 0, 0))
+ .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
+ PrintAttributes.COLOR_MODE_COLOR)
+ .addMediaSize(mediaSize, true)
+ .addResolution(new PrintAttributes.Resolution("300x300", "300x300", 300, 300),
+ true);
+
+ printers.add(new PrinterInfo.Builder(printerId, name,
+ PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build());
+
+ session.addPrinters(printers);
+ }
+
+ /** Find a certain element in the UI and click on it */
+ private void clickOn(UiSelector selector) throws UiObjectNotFoundException {
+ Log.i(LOG_TAG, "Click on " + selector);
+ UiObject view = getUiDevice().findObject(selector);
+ view.click();
+ getUiDevice().waitForIdle();
+ }
+
+ /** Find a certain text in the UI and click on it */
+ private void clickOnText(String text) throws UiObjectNotFoundException {
+ clickOn(new UiSelector().text(text));
+ }
+
+ /** Set the printer in the print activity */
+ private void setPrinter(String printerName) throws UiObjectNotFoundException {
+ clickOn(new UiSelector().resourceId("com.android.printspooler:id/destination_spinner"));
+
+ clickOnText(printerName);
+ }
+
+ /**
+ * Init mock print servic that returns a single printer by default.
+ *
+ * @param sessionRef Where to store the reference to the session once started
+ */
+ private void setMockPrintServiceCallbacks(StubbablePrinterDiscoverySession[] sessionRef) {
+ MockPrintService.setCallbacks(createMockPrintServiceCallbacks(
+ inv -> createMockPrinterDiscoverySessionCallbacks(inv2 -> {
+ synchronized (sessionRef) {
+ sessionRef[0] = ((PrinterDiscoverySessionCallbacks) inv2.getMock())
+ .getSession();
+
+ addPrinter(sessionRef[0], "1st printer",
+ PrintAttributes.MediaSize.ISO_A0);
+
+ sessionRef.notifyAll();
+ }
+ return null;
+ },
+ null, null, null, null, null, inv2 -> {
+ synchronized (sessionRef) {
+ sessionRef[0] = null;
+ sessionRef.notifyAll();
+ }
+ return null;
+ }
+ ), null, null));
+ }
+
+ /**
+ * Start print operation that just prints a single empty page
+ *
+ * @param printAttributesRef Where to store the reference to the print attributes once started
+ */
+ private void print(PrintAttributes[] printAttributesRef) {
+ print(new PrintDocumentAdapter() {
+ @Override
+ public void onStart() {
+ }
+
+ @Override
+ public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
+ CancellationSignal cancellationSignal, LayoutResultCallback callback,
+ Bundle extras) {
+ callback.onLayoutFinished((new PrintDocumentInfo.Builder("doc")).build(),
+ !newAttributes.equals(printAttributesRef[0]));
+
+ synchronized (printAttributesRef) {
+ printAttributesRef[0] = newAttributes;
+ printAttributesRef.notifyAll();
+ }
+ }
+
+ @Override
+ public void onWrite(PageRange[] pages, ParcelFileDescriptor destination,
+ CancellationSignal cancellationSignal, WriteResultCallback callback) {
+ try {
+ try {
+ PrintedPdfDocument document = new PrintedPdfDocument(getActivity(),
+ printAttributesRef[0]);
+ try {
+ PdfDocument.Page page = document.startPage(0);
+ document.finishPage(page);
+ try (FileOutputStream os = new FileOutputStream(
+ destination.getFileDescriptor())) {
+ document.writeTo(os);
+ os.flush();
+ }
+ } finally {
+ document.close();
+ }
+ } finally {
+ destination.close();
+ }
+
+ callback.onWriteFinished(pages);
+ } catch (IOException e) {
+ callback.onWriteFailed(e.getMessage());
+ }
+ }
+ }, null);
+ }
+
+ @Test
+ @LargeTest
+ public void addAndSelectPrinter() throws Exception {
+ final StubbablePrinterDiscoverySession session[] = new StubbablePrinterDiscoverySession[1];
+ final PrintAttributes printAttributes[] = new PrintAttributes[1];
+
+ setMockPrintServiceCallbacks(session);
+ print(printAttributes);
+
+ // We are now in the PrintActivity
+ Log.i(LOG_TAG, "Waiting for session");
+ synchronized (session) {
+ waitWithTimeout(() -> session[0] == null, session::wait);
+ }
+
+ setPrinter("1st printer");
+
+ Log.i(LOG_TAG, "Waiting for print attributes to change");
+ synchronized (printAttributes) {
+ waitWithTimeout(
+ () -> printAttributes[0] == null || !printAttributes[0].getMediaSize().equals(
+ PrintAttributes.MediaSize.ISO_A0), printAttributes::wait);
+ }
+
+ setPrinter("All printers\u2026");
+
+ // We are now in the SelectPrinterActivity
+ clickOnText("Add printer");
+
+ // We are now in the AddPrinterActivity
+ AddPrintersActivity.addObserver(
+ () -> addPrinter(session[0], "2nd printer", PrintAttributes.MediaSize.ISO_A1));
+
+ // This executes the observer registered above
+ clickOn(new UiSelector().text(MockPrintService.class.getCanonicalName())
+ .resourceId("com.android.printspooler:id/title"));
+
+ getUiDevice().pressBack();
+ AddPrintersActivity.clearObservers();
+
+ // We are now in the SelectPrinterActivity
+ clickOnText("2nd printer");
+
+ // We are now in the PrintActivity
+ Log.i(LOG_TAG, "Waiting for print attributes to change");
+ synchronized (printAttributes) {
+ waitWithTimeout(
+ () -> printAttributes[0] == null || !printAttributes[0].getMediaSize().equals(
+ PrintAttributes.MediaSize.ISO_A1), printAttributes::wait);
+ }
+
+ getUiDevice().pressBack();
+
+ // We are back in the test activity
+ Log.i(LOG_TAG, "Waiting for session to end");
+ synchronized (session) {
+ waitWithTimeout(() -> session[0] != null, session::wait);
+ }
+ }
+
+ @Test
+ @LargeTest
+ public void abortSelectingPrinter() throws Exception {
+ final StubbablePrinterDiscoverySession session[] = new StubbablePrinterDiscoverySession[1];
+ final PrintAttributes printAttributes[] = new PrintAttributes[1];
+
+ setMockPrintServiceCallbacks(session);
+ print(printAttributes);
+
+ // We are now in the PrintActivity
+ Log.i(LOG_TAG, "Waiting for session");
+ synchronized (session) {
+ waitWithTimeout(() -> session[0] == null, session::wait);
+ }
+
+ setPrinter("1st printer");
+
+ Log.i(LOG_TAG, "Waiting for print attributes to change");
+ synchronized (printAttributes) {
+ waitWithTimeout(
+ () -> printAttributes[0] == null || !printAttributes[0].getMediaSize().equals(
+ PrintAttributes.MediaSize.ISO_A0), printAttributes::wait);
+ }
+
+ setPrinter("All printers\u2026");
+
+ // We are now in the SelectPrinterActivity
+ clickOnText("Add printer");
+
+ // We are now in the AddPrinterActivity
+ AddPrintersActivity.addObserver(
+ () -> addPrinter(session[0], "2nd printer", PrintAttributes.MediaSize.ISO_A1));
+
+ // This executes the observer registered above
+ clickOn(new UiSelector().text(MockPrintService.class.getCanonicalName())
+ .resourceId("com.android.printspooler:id/title"));
+
+ getUiDevice().pressBack();
+ AddPrintersActivity.clearObservers();
+
+ // Do not select a new printer, just press back
+ getUiDevice().pressBack();
+
+ // We are now in the PrintActivity
+ // The media size should not change
+ Log.i(LOG_TAG, "Make sure print attributes did not change");
+ Thread.sleep(100);
+ assertEquals(PrintAttributes.MediaSize.ISO_A0, printAttributes[0].getMediaSize());
+
+ getUiDevice().pressBack();
+
+ // We are back in the test activity
+ Log.i(LOG_TAG, "Waiting for session to end");
+ synchronized (session) {
+ waitWithTimeout(() -> session[0] != null, session::wait);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/print/mockservice/AddPrintersActivity.java b/core/tests/coretests/src/android/print/mockservice/AddPrintersActivity.java
new file mode 100644
index 0000000..8f1a9ed
--- /dev/null
+++ b/core/tests/coretests/src/android/print/mockservice/AddPrintersActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.print.mockservice;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+
+public class AddPrintersActivity extends Activity {
+ private static final ArrayList<Runnable> sObservers = new ArrayList<>();
+
+ public static void addObserver(@NonNull Runnable observer) {
+ synchronized (sObservers) {
+ sObservers.add(observer);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ synchronized (sObservers) {
+ for (Runnable sObserver : sObservers) {
+ sObserver.run();
+ }
+ }
+
+ finish();
+ }
+
+ public static void clearObservers() {
+ synchronized (sObservers) {
+ sObservers.clear();
+ }
+ }
+}
diff --git a/docs/html/wear/preview/downloads.jd b/docs/html/wear/preview/downloads.jd
index 08ed233..83a3f98 100644
--- a/docs/html/wear/preview/downloads.jd
+++ b/docs/html/wear/preview/downloads.jd
@@ -626,7 +626,7 @@
the accounts on the phone.
</li>
- <li>Choose a Google account to add and sync to your watch.
+ <li>Choose a Google Account to add and sync to your watch.
</li>
<li>Confirm the screen lock and enter the password to start the copying of
@@ -647,8 +647,13 @@
</h2>
<p>
- To test with the Android Emulator, create a virtual device in Android
- Studio as follows:
+ To test with the Android Emulator,
+ confirm that you have the latest version of the <strong>Android SDK
+ Platform-tools</strong> from the <a href=
+ "{@docRoot}studio/intro/update.html#sdk-manager">SDK Manager</a>.
+ </p>
+
+ <p>Create a new virtual device in Android Studio as follows:
</p>
<ol>
@@ -659,8 +664,8 @@
<li>Click <strong>Create Virtual Device</strong>.
</li>
- <li>In the <strong>Category</strong> pane, select Wear and
- choose a hardware profile.
+ <li>In the <strong>Category</strong> pane, select <strong>Wear</strong>
+ and choose a hardware profile.
The Android Wear 2.0 Developer Preview
is only optimized for round devices currently, so we recommend not
using the square or chin profiles for now.
@@ -679,16 +684,66 @@
<li>Verify the configuration of the Android Virtual Device (AVD) and
click <strong>Finish</strong>.
</li>
+
+ <li>Start the emulator by selecting the new virtual device, clicking the
+ <strong>Play</strong> button, and waiting until
+ the emulator initializes and shows the Android Wear home screen.
+ </li>
</ol>
<p>
- You can now test an application with a virtual preview device
+ Pair the phone with the emulator, and sync a Google Account, as follows:
+ </p>
+
+ <ol>
+ <li>On the phone, install the Android Wear app from Google Play.
+ </li>
+
+ <li>On the phone, enable Developer Options and USB Debugging.
+ </li>
+
+ <li>Connect the phone to your computer through USB.
+ </li>
+
+ <li>Forward the AVD's communication port to the connected handheld device
+ (each time the phone is connected):<br>
+ <code>adb -d forward tcp:5601 tcp:5601</code>
+ </li>
+
+ <li>On the phone, in the Android Wear app, begin the standard pairing
+ process. For example, on the Welcome screen, tap the
+ <strong>Set It Up</strong> button.
+ Alternatively, if an existing watch already is paired, in the upper-left
+ drop-down, tap <strong>Add a New Watch</strong>.
+ </li>
+
+ <li>On the phone, in the Android Wear app, tap the
+ Overflow button, and then tap
+ <strong>Pair with Emulator</strong>.
+ </li>
+
+ <li>Tap the Settings icon.
+ </li>
+
+ <li>Under Device Settings, tap <strong>Emulator</strong>.
+ </li>
+
+ <li>Tap <strong>Accounts</strong> and select a Google Account,
+ and follow the steps in the wizard to
+ sync the account with the emulator. If necessary, type the screen-lock
+ device password, and Google Account password, to start the account sync.
+ </li>
+ </ol>
+
+ <p>
+ You can now test an app with a virtual preview device
in the <a href=
"{@docRoot}tools/devices/emulator.html">Android Emulator</a>. For more
information about using virtual devices, see <a href=
- "{@docRoot}tools/devices/managing-avds.html">Managing AVDs with the AVD
- Manager</a>.
+ "{@docRoot}tools/devices/managing-avds.html">
+ Create and Manage Virtual Devices</a>.
</p>
+
</div><!-- landing -->
</div><!-- relative wrapper -->
diff --git a/docs/html/wear/preview/support.jd b/docs/html/wear/preview/support.jd
index 7636d86..6006627 100644
--- a/docs/html/wear/preview/support.jd
+++ b/docs/html/wear/preview/support.jd
@@ -319,6 +319,22 @@
</li>
</ul>
+ <h4 id="account">
+ Account sync
+ </h4>
+
+ <ul>
+ <li>Account sync initiated from watch settings may not work reliably.
+ Instead, add accounts from the setup flow of the Android Wear app, or using
+ the Accounts settings for a device from the Android Wear app.
+ </li>
+
+ <li>The list of accounts that can be synced is the same as the list of accounts
+ on the phone. So to add a new account, use the Android settings on the phone,
+ and then proceed to Android Wear app to sync that account.
+ </li>
+ </ul>
+
<h4 id="devices">
Devices
</h4>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index f688a8e..b3cfea5 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -760,6 +760,10 @@
mPrintJob.setPrinterId(printerInfo.getId());
mPrintJob.setPrinterName(printerInfo.getName());
+ if (printerInfo.getCapabilities() != null) {
+ updatePrintAttributesFromCapabilities(printerInfo.getCapabilities());
+ }
+
mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerInfo);
MetricsLogger.action(this, MetricsEvent.ACTION_PRINTER_SELECT_ALL,
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 7d9cc53..1a1aa57 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -65,9 +65,9 @@
<!-- Summary for the remembered network but currently not in range. -->
<string name="wifi_not_in_range">Not in range</string>
<!-- Summary for the network but no internet connection was detected. -->
- <string name="wifi_no_internet_no_reconnect">No Internet Access Detected, won\'t automatically reconnect.</string>
+ <string name="wifi_no_internet_no_reconnect">Won\'t automatically connect</string>
<!-- Summary for the remembered network but no internet connection was detected. -->
- <string name="wifi_no_internet">No Internet Access.</string>
+ <string name="wifi_no_internet">No Internet access</string>
<!-- Summary for saved networks -->
<string name="saved_network">Saved by <xliff:g id="name">%1$s</xliff:g></string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
new file mode 100644
index 0000000..9821fb8
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.drawer;
+
+public final class CategoryKey {
+
+ // Activities in this category shows up in Settings homepage.
+ public static final String CATEGORY_HOMEPAGE = "com.android.settings.category.homepage";
+
+ // Top level categor.
+ public static final String CATEGORY_NETWORK = "com.android.settings.category.wireless";
+ public static final String CATEGORY_DEVICE = "com.android.settings.category.device";
+ public static final String CATEGORY_APPS = "com.android.settings.category.apps";
+ public static final String CATEGORY_BATTERY = "com.android.settings.category.battery";
+ public static final String CATEGORY_DISPLAY = "com.android.settings.category.display";
+ public static final String CATEGORY_SOUND = "com.android.settings.category.sound";
+ public static final String CATEGORY_STORAGE = "com.android.settings.category.storage";
+ public static final String CATEGORY_SECURITY = "com.android.settings.category.security";
+ public static final String CATEGORY_ACCOUNT = "com.android.settings.category.accounts";
+ public static final String CATEGORY_SYSTEM = "com.android.settings.category.system";
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
new file mode 100644
index 0000000..a8f286d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
@@ -0,0 +1,108 @@
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.drawer;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.settingslib.applications.InterestingConfigChanges;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class CategoryManager {
+
+ private static final String TAG = "CategoryManager";
+
+ private static CategoryManager sInstance;
+
+ private final InterestingConfigChanges mInterestingConfigChanges;
+
+ // Tile cache (key: <packageName, activityName>, value: tile)
+ private final Map<Pair<String, String>, Tile> mTileByComponentCache;
+
+ // Tile cache (key: category key, value: category)
+ private final Map<String, DashboardCategory> mCategoryByKeyMap;
+
+ private List<DashboardCategory> mCategories;
+
+ public static CategoryManager get() {
+ if (sInstance == null) {
+ sInstance = new CategoryManager();
+ }
+ return sInstance;
+ }
+
+ CategoryManager() {
+ mInterestingConfigChanges = new InterestingConfigChanges();
+ mTileByComponentCache = new ArrayMap<>();
+ mCategoryByKeyMap = new ArrayMap<>();
+ }
+
+ public DashboardCategory getTilesByCategory(Context context, String categoryKey) {
+ tryInitCategories(context);
+
+ final DashboardCategory category = mCategoryByKeyMap.get(categoryKey);
+ if (category == null) {
+ throw new IllegalStateException("Can't find category with key " + categoryKey);
+ }
+ return category;
+ }
+
+ public List<DashboardCategory> getCategories(Context context) {
+ tryInitCategories(context);
+ return mCategories;
+ }
+
+ public void reloadAllCategoriesForConfigChange(Context context) {
+ if (mInterestingConfigChanges.applyNewConfig(context.getResources())) {
+ mCategories = null;
+ tryInitCategories(context);
+ }
+ }
+
+ public void updateCategoryFromBlacklist(Set<ComponentName> tileBlacklist) {
+ if (mCategories == null) {
+ Log.w(TAG, "Category is null, skipping blacklist update");
+ }
+ for (int i = 0; i < mCategories.size(); i++) {
+ DashboardCategory category = mCategories.get(i);
+ for (int j = 0; j < category.tiles.size(); j++) {
+ Tile tile = category.tiles.get(j);
+ if (tileBlacklist.contains(tile.intent.getComponent())) {
+ category.tiles.remove(j--);
+ }
+ }
+ }
+ }
+
+ private void tryInitCategories(Context context) {
+ if (mCategories == null) {
+ mTileByComponentCache.clear();
+ mCategoryByKeyMap.clear();
+ mCategories = TileUtils.getCategories(context, mTileByComponentCache,
+ false /* categoryDefinedInManifest */);
+ for (DashboardCategory category : mCategories) {
+ mCategoryByKeyMap.put(category.key, category);
+ }
+ }
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
index 53be0e6..3fc999f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
@@ -16,15 +16,20 @@
package com.android.settingslib.drawer;
+import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class DashboardCategory implements Parcelable {
+ private static final String TAG = "DashboardCategory";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
/**
* Title of the category that is shown to the user.
*/
@@ -74,6 +79,22 @@
return tiles.get(n);
}
+ public boolean containsComponent(ComponentName component) {
+ for (Tile tile : tiles) {
+ if (TextUtils.equals(tile.intent.getComponent().getClassName(),
+ component.getClassName())) {
+ if (DEBUG) {
+ Log.d(TAG, "category " + key + "contains component" + component);
+ }
+ return true;
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "category " + key + " does not contain component" + component);
+ }
+ return false;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 05585e53e..50867eb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -33,7 +33,6 @@
import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.support.v4.widget.DrawerLayout;
-import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -64,12 +63,9 @@
public static final String EXTRA_SHOW_MENU = "show_drawer_menu";
- private static List<DashboardCategory> sDashboardCategories;
- private static HashMap<Pair<String, String>, Tile> sTileCache;
// Serves as a temporary list of tiles to ignore until we heard back from the PM that they
// are disabled.
private static ArraySet<ComponentName> sTileBlacklist = new ArraySet<>();
- private static InterestingConfigChanges sConfigTracker;
private final PackageReceiver mPackageReceiver = new PackageReceiver();
private final List<CategoryListener> mCategoryListeners = new ArrayList<>();
@@ -80,6 +76,15 @@
private boolean mShowingMenu;
private UserManager mUserManager;
+ // Remove below after new IA
+ @Deprecated
+ private static List<DashboardCategory> sDashboardCategories;
+ @Deprecated
+ private static HashMap<Pair<String, String>, Tile> sTileCache;
+ @Deprecated
+ private static InterestingConfigChanges sConfigTracker;
+ // Remove above after new IA
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -105,7 +110,9 @@
mDrawerLayout = null;
return;
}
- getDashboardCategories();
+ if (!isDashboardFeatureEnabled()) {
+ getDashboardCategories();
+ }
setActionBar(toolbar);
mDrawerAdapter = new SettingsDrawerAdapter(this);
ListView listView = (ListView) findViewById(R.id.left_drawer);
@@ -144,7 +151,11 @@
filter.addDataScheme("package");
registerReceiver(mPackageReceiver, filter);
- new CategoriesUpdater().execute();
+ if (isDashboardFeatureEnabled()) {
+ new CategoriesUpdateTask().execute();
+ } else {
+ new CategoriesUpdater().execute();
+ }
}
final Intent intent = getIntent();
if (intent != null) {
@@ -173,23 +184,23 @@
if (componentName == null) {
return false;
}
- // Look for a tile that has the same component as incoming intent
- final List<DashboardCategory> categories = getDashboardCategories();
- for (DashboardCategory category : categories) {
- for (Tile tile : category.tiles) {
- if (TextUtils.equals(tile.intent.getComponent().getClassName(),
- componentName.getClassName())) {
- if (DEBUG) {
- Log.d(TAG, "intent is for top level tile: " + tile.title);
- }
+ if (isDashboardFeatureEnabled()) {
+ final DashboardCategory homepageCategories = CategoryManager.get()
+ .getTilesByCategory(this, CategoryKey.CATEGORY_HOMEPAGE);
+ return homepageCategories.containsComponent(componentName);
+ } else {
+ // Look for a tile that has the same component as incoming intent
+ final List<DashboardCategory> categories = getDashboardCategories();
+ for (DashboardCategory category : categories) {
+ if (category.containsComponent(componentName)) {
return true;
}
}
+ if (DEBUG) {
+ Log.d(TAG, "Intent is not for top level settings " + intent);
+ }
+ return false;
}
- if (DEBUG) {
- Log.d(TAG, "Intent is not for top level settings " + intent);
- }
- return false;
}
public void addCategoryListener(CategoryListener listener) {
@@ -255,7 +266,11 @@
return;
}
// TODO: Do this in the background with some loading.
- mDrawerAdapter.updateCategories();
+ if (isDashboardFeatureEnabled()) {
+ mDrawerAdapter.updateHomepageCategories();
+ } else {
+ mDrawerAdapter.updateCategories();
+ }
if (mDrawerAdapter.getCount() != 0) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
} else {
@@ -343,13 +358,6 @@
}
}
- public HashMap<Pair<String, String>, Tile> getTileCache() {
- if (sTileCache == null) {
- getDashboardCategories();
- }
- return sTileCache;
- }
-
public void onProfileTileOpen() {
finish();
}
@@ -368,7 +376,11 @@
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
- new CategoriesUpdater().execute();
+ if (isDashboardFeatureEnabled()) {
+ new CategoriesUpdateTask().execute();
+ } else {
+ new CategoriesUpdater().execute();
+ }
}
}
@@ -376,6 +388,10 @@
void onCategoriesChanged();
}
+ /**
+ * @deprecated remove after new IA
+ */
+ @Deprecated
private class CategoriesUpdater extends AsyncTask<Void, Void, List<DashboardCategory>> {
@Override
protected List<DashboardCategory> doInBackground(Void... params) {
@@ -408,10 +424,39 @@
}
}
+ private class CategoriesUpdateTask extends AsyncTask<Void, Void, Void> {
+
+ private final CategoryManager mCategoryManager;
+
+ public CategoriesUpdateTask() {
+ mCategoryManager = CategoryManager.get();
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ mCategoryManager.reloadAllCategoriesForConfigChange(SettingsDrawerActivity.this);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ mCategoryManager.updateCategoryFromBlacklist(sTileBlacklist);
+ onCategoriesChanged();
+ }
+ }
+
+ protected boolean isDashboardFeatureEnabled() {
+ return false;
+ }
+
private class PackageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- new CategoriesUpdater().execute();
+ if (isDashboardFeatureEnabled()) {
+ new CategoriesUpdateTask().execute();
+ } else {
+ new CategoriesUpdater().execute();
+ }
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
index 1d6197a..e1216a1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
@@ -37,6 +37,10 @@
mActivity = activity;
}
+ /**
+ * @deprecated Remove after new IA
+ */
+ @Deprecated
void updateCategories() {
List<DashboardCategory> categories = mActivity.getDashboardCategories();
mItems.clear();
@@ -64,6 +68,27 @@
notifyDataSetChanged();
}
+ public void updateHomepageCategories() {
+ DashboardCategory category =
+ CategoryManager.get().getTilesByCategory(mActivity, CategoryKey.CATEGORY_HOMEPAGE);
+ mItems.clear();
+ // Spacer.
+ mItems.add(null);
+ Item tile = new Item();
+ tile.label = mActivity.getString(R.string.home);
+ tile.icon = Icon.createWithResource(mActivity, R.drawable.home);
+ mItems.add(tile);
+ for (int j = 0; j < category.tiles.size(); j++) {
+ tile = new Item();
+ Tile dashboardTile = category.tiles.get(j);
+ tile.label = dashboardTile.title;
+ tile.icon = dashboardTile.icon;
+ tile.tile = dashboardTile;
+ mItems.add(tile);
+ }
+ notifyDataSetChanged();
+ }
+
public Tile getTile(int position) {
return mItems.get(position) != null ? mItems.get(position).tile : null;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index e70cc29..81f0e84 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -94,6 +94,13 @@
private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
/**
+ * The key used to get the category from metadata of activities of action
+ * {@link #EXTRA_SETTINGS_ACTION}
+ * The value must be one of constants defined in {@code CategoryKey}.
+ */
+ private static final String EXTRA_IA_CATEGORY_KEY = "com.android.settings.iacategory";
+
+ /**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the icon that should be displayed for the preference.
*/
@@ -113,8 +120,24 @@
private static final String SETTING_PKG = "com.android.settings";
+ /**
+ * Build a list of DashboardCategory. Each category must be defined in manifest.
+ * eg: .Settings$DeviceSettings
+ * @deprecated
+ */
+ @Deprecated
public static List<DashboardCategory> getCategories(Context context,
- HashMap<Pair<String, String>, Tile> cache) {
+ Map<Pair<String, String>, Tile> cache) {
+ return getCategories(context, cache, true /*categoryDefinedInManifest*/);
+ }
+
+ /**
+ * Build a list of DashboardCategory.
+ * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
+ * represent this category (eg: .Settings$DeviceSettings)
+ */
+ public static List<DashboardCategory> getCategories(Context context,
+ Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest) {
final long startTime = System.currentTimeMillis();
boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)
!= 0;
@@ -134,11 +157,12 @@
getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
}
}
+
HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
for (Tile tile : tiles) {
DashboardCategory category = categoryMap.get(tile.category);
if (category == null) {
- category = createCategory(context, tile.category);
+ category = createCategory(context, tile.category, categoryDefinedInManifest);
if (category == null) {
Log.w(LOG_TAG, "Couldn't find category " + tile.category);
continue;
@@ -157,9 +181,21 @@
return categories;
}
- private static DashboardCategory createCategory(Context context, String categoryKey) {
+ /**
+ * Create a new DashboardCategory from key.
+ *
+ * @param context Context to query intent
+ * @param categoryKey The category key
+ * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
+ * represent this category (eg: .Settings$DeviceSettings)
+ */
+ private static DashboardCategory createCategory(Context context, String categoryKey,
+ boolean categoryDefinedInManifest) {
DashboardCategory category = new DashboardCategory();
category.key = categoryKey;
+ if (!categoryDefinedInManifest) {
+ return category;
+ }
PackageManager pm = context.getPackageManager();
List<ResolveInfo> results = pm.queryIntentActivities(new Intent(categoryKey), 0);
if (results.size() == 0) {
@@ -204,14 +240,19 @@
ActivityInfo activityInfo = resolved.activityInfo;
Bundle metaData = activityInfo.metaData;
String categoryKey = defaultCategory;
- if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
- && categoryKey == null) {
+ if (metaData != null && categoryKey == null) {
+ // categoryKey is null, try to get it from metadata.
+ if (metaData.containsKey(EXTRA_IA_CATEGORY_KEY)) {
+ categoryKey = metaData.getString(EXTRA_IA_CATEGORY_KEY);
+ } else if (metaData.containsKey(EXTRA_CATEGORY_KEY)) {
+ categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
+ }
+ }
+ if (checkCategory && categoryKey == null) {
Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
+ intent + " missing metadata "
+ (metaData == null ? "" : EXTRA_CATEGORY_KEY));
continue;
- } else {
- categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
}
Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
activityInfo.name);
@@ -238,16 +279,6 @@
}
}
- private static DashboardCategory getCategory(List<DashboardCategory> target,
- String categoryKey) {
- for (DashboardCategory category : target) {
- if (categoryKey.equals(category.key)) {
- return category;
- }
- }
- return null;
- }
-
private static boolean updateTileData(Context context, Tile tile,
ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) {
if (applicationInfo.isSystemApp()) {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java
new file mode 100644
index 0000000..1b8efa7
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+
+/**
+ * An Intent Button represents a triggerable element in SysUI that consists of an
+ * Icon and an intent to trigger when it is activated (clicked, swiped, etc.).
+ */
+public interface IntentButtonProvider extends Plugin {
+
+ public static final int VERSION = 1;
+
+ public IntentButton getIntentButton();
+
+ public interface IntentButton {
+ public static class IconState {
+ public boolean isVisible = true;
+ public CharSequence contentDescription = null;
+ public Drawable drawable;
+ }
+
+ public IconState getIcon();
+
+ public Intent getIntent();
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
new file mode 100644
index 0000000..09879d8
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugins.statusbar.phone;
+
+import android.annotation.DrawableRes;
+import android.annotation.Nullable;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.Plugin;
+
+public interface NavBarButtonProvider extends Plugin {
+
+ public static final String ACTION = "com.android.systemui.action.PLUGIN_NAV_BUTTON";
+
+ public static final int VERSION = 1;
+
+ /**
+ * Returns a view in the nav bar. If the id is set "back", "home", "recent_apps", "menu",
+ * or "ime_switcher", it is expected to implement ButtonInterface.
+ */
+ public View createView(String spec, ViewGroup parent);
+
+ /**
+ * Interface for button actions.
+ */
+ interface ButtonInterface {
+ void setImageResource(@DrawableRes int resId);
+
+ void setImageDrawable(@Nullable Drawable drawable);
+
+ void abortCurrentGesture();
+
+ void setLandscape(boolean landscape);
+
+ void setCarMode(boolean carMode);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
index 95cb672..f6fe176 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -14,11 +14,11 @@
package com.android.systemui.statusbar.phone;
-import android.annotation.DrawableRes;
-import android.annotation.Nullable;
import android.graphics.drawable.Drawable;
import android.view.View;
+import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
+
import java.util.ArrayList;
/**
@@ -186,18 +186,4 @@
}
}
- /**
- * Interface for button actions.
- */
- public interface ButtonInterface {
- void setImageResource(@DrawableRes int resId);
-
- void setImageDrawable(@Nullable Drawable drawable);
-
- void abortCurrentGesture();
-
- void setLandscape(boolean landscape);
-
- void setCarMode(boolean carMode);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 0a391eb..4270147 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -61,6 +61,11 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.plugins.IntentButtonProvider;
+import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
+import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSContainer.ActivityStarter;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -86,6 +91,11 @@
public static final String EXTRA_CAMERA_LAUNCH_SOURCE
= "com.android.systemui.camera_launch_source";
+ private static final String LEFT_BUTTON_PLUGIN
+ = "com.android.systemui.action.PLUGIN_LOCKSCREEN_LEFT_BUTTON";
+ private static final String RIGHT_BUTTON_PLUGIN
+ = "com.android.systemui.action.PLUGIN_LOCKSCREEN_RIGHT_BUTTON";
+
private static final Intent SECURE_CAMERA_INTENT =
new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
@@ -95,7 +105,7 @@
private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
- private KeyguardAffordanceView mCameraImageView;
+ private KeyguardAffordanceView mRightAffordanceView;
private KeyguardAffordanceView mLeftAffordanceView;
private LockIcon mLockIcon;
private TextView mIndicationText;
@@ -132,6 +142,9 @@
private boolean mLeftIsVoiceAssist;
private AssistManager mAssistManager;
+ private IntentButton mRightButton = new DefaultRightButton();
+ private IntentButton mLeftButton = new DefaultLeftButton();
+
public KeyguardBottomAreaView(Context context) {
this(context, null);
}
@@ -156,7 +169,7 @@
String label = null;
if (host == mLockIcon) {
label = getResources().getString(R.string.unlock_label);
- } else if (host == mCameraImageView) {
+ } else if (host == mRightAffordanceView) {
label = getResources().getString(R.string.camera_label);
} else if (host == mLeftAffordanceView) {
if (mLeftIsVoiceAssist) {
@@ -175,7 +188,7 @@
mPhoneStatusBar.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
return true;
- } else if (host == mCameraImageView) {
+ } else if (host == mRightAffordanceView) {
launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
return true;
} else if (host == mLeftAffordanceView) {
@@ -192,7 +205,7 @@
super.onFinishInflate();
mLockPatternUtils = new LockPatternUtils(mContext);
mPreviewContainer = (ViewGroup) findViewById(R.id.preview_container);
- mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
+ mRightAffordanceView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
mLeftAffordanceView = (KeyguardAffordanceView) findViewById(R.id.left_button);
mLockIcon = (LockIcon) findViewById(R.id.lock_icon);
mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text);
@@ -207,15 +220,31 @@
inflateCameraPreview();
mLockIcon.setOnClickListener(this);
mLockIcon.setOnLongClickListener(this);
- mCameraImageView.setOnClickListener(this);
+ mRightAffordanceView.setOnClickListener(this);
mLeftAffordanceView.setOnClickListener(this);
initAccessibility();
}
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ PluginManager.getInstance(getContext()).addPluginListener(RIGHT_BUTTON_PLUGIN,
+ mRightListener, IntentButtonProvider.VERSION, false /* Only allow one */);
+ PluginManager.getInstance(getContext()).addPluginListener(LEFT_BUTTON_PLUGIN,
+ mLeftListener, IntentButtonProvider.VERSION, false /* Only allow one */);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ PluginManager.getInstance(getContext()).removePluginListener(mRightListener);
+ PluginManager.getInstance(getContext()).removePluginListener(mLeftListener);
+ }
+
private void initAccessibility() {
mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
mLeftAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
- mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate);
+ mRightAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
}
@Override
@@ -234,11 +263,11 @@
getResources().getDimensionPixelSize(
com.android.internal.R.dimen.text_size_small_material));
- ViewGroup.LayoutParams lp = mCameraImageView.getLayoutParams();
+ ViewGroup.LayoutParams lp = mRightAffordanceView.getLayoutParams();
lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
- mCameraImageView.setLayoutParams(lp);
- mCameraImageView.setImageDrawable(mContext.getDrawable(R.drawable.ic_camera_alt_24dp));
+ mRightAffordanceView.setLayoutParams(lp);
+ updateRightAffordanceIcon();
lp = mLockIcon.getLayoutParams();
lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
@@ -253,6 +282,13 @@
updateLeftAffordanceIcon();
}
+ private void updateRightAffordanceIcon() {
+ IconState state = mRightButton.getIcon();
+ mRightAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE);
+ mRightAffordanceView.setImageDrawable(state.drawable);
+ mRightAffordanceView.setContentDescription(state.contentDescription);
+ }
+
public void setActivityStarter(ActivityStarter activityStarter) {
mActivityStarter = activityStarter;
}
@@ -279,11 +315,7 @@
}
private Intent getCameraIntent() {
- KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
- boolean canSkipBouncer = updateMonitor.getUserCanSkipBouncer(
- KeyguardUpdateMonitor.getCurrentUser());
- boolean secure = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
- return (secure && !canSkipBouncer) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
+ return mRightButton.getIntent();
}
/**
@@ -296,33 +328,19 @@
}
private void updateCameraVisibility() {
- if (mCameraImageView == null) {
+ if (mRightAffordanceView == null) {
// Things are not set up yet; reply hazy, ask again later
return;
}
- ResolveInfo resolved = resolveCameraIntent();
- boolean visible = !isCameraDisabledByDpm() && resolved != null
- && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance)
- && mUserSetupComplete;
- mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
+ mRightAffordanceView.setVisibility(mRightButton.getIcon().isVisible
+ ? View.VISIBLE : View.GONE);
}
private void updateLeftAffordanceIcon() {
- mLeftIsVoiceAssist = canLaunchVoiceAssist();
- int drawableId;
- int contentDescription;
- boolean visible = mUserSetupComplete;
- if (mLeftIsVoiceAssist) {
- drawableId = R.drawable.ic_mic_26dp;
- contentDescription = R.string.accessibility_voice_assist_button;
- } else {
- visible &= isPhoneVisible();
- drawableId = R.drawable.ic_phone_24dp;
- contentDescription = R.string.accessibility_phone_button;
- }
- mLeftAffordanceView.setVisibility(visible ? View.VISIBLE : View.GONE);
- mLeftAffordanceView.setImageDrawable(mContext.getDrawable(drawableId));
- mLeftAffordanceView.setContentDescription(mContext.getString(contentDescription));
+ IconState state = mLeftButton.getIcon();
+ mLeftAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE);
+ mLeftAffordanceView.setImageDrawable(state.drawable);
+ mLeftAffordanceView.setContentDescription(state.contentDescription);
}
public boolean isLeftVoiceAssist() {
@@ -363,16 +381,16 @@
@Override
public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
- mCameraImageView.setClickable(touchExplorationEnabled);
+ mRightAffordanceView.setClickable(touchExplorationEnabled);
mLeftAffordanceView.setClickable(touchExplorationEnabled);
- mCameraImageView.setFocusable(accessibilityEnabled);
+ mRightAffordanceView.setFocusable(accessibilityEnabled);
mLeftAffordanceView.setFocusable(accessibilityEnabled);
mLockIcon.update();
}
@Override
public void onClick(View v) {
- if (v == mCameraImageView) {
+ if (v == mRightAffordanceView) {
launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
} else if (v == mLeftAffordanceView) {
launchLeftAffordance();
@@ -541,7 +559,7 @@
}
});
} else {
- mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */);
+ mActivityStarter.startActivity(mLeftButton.getIntent(), false /* dismissShade */);
}
}
@@ -560,7 +578,7 @@
}
public KeyguardAffordanceView getRightView() {
- return mCameraImageView;
+ return mRightAffordanceView;
}
public View getLeftPreview() {
@@ -613,7 +631,7 @@
mLeftPreview = mPreviewInflater.inflatePreviewFromService(
mAssistManager.getVoiceInteractorComponentName());
} else {
- mLeftPreview = mPreviewInflater.inflatePreview(PHONE_INTENT);
+ mLeftPreview = mPreviewInflater.inflatePreview(mLeftButton.getIntent());
}
if (mLeftPreview != null) {
mPreviewContainer.addView(mLeftPreview);
@@ -629,8 +647,8 @@
}
startFinishDozeAnimationElement(mLockIcon, delay);
delay += DOZE_ANIMATION_STAGGER_DELAY;
- if (mCameraImageView.getVisibility() == View.VISIBLE) {
- startFinishDozeAnimationElement(mCameraImageView, delay);
+ if (mRightAffordanceView.getVisibility() == View.VISIBLE) {
+ startFinishDozeAnimationElement(mRightAffordanceView, delay);
}
mIndicationText.setAlpha(0f);
mIndicationText.animate()
@@ -664,46 +682,46 @@
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
- @Override
- public void onUserSwitchComplete(int userId) {
- updateCameraVisibility();
- }
+ @Override
+ public void onUserSwitchComplete(int userId) {
+ updateCameraVisibility();
+ }
- @Override
- public void onStartedWakingUp() {
- mLockIcon.setDeviceInteractive(true);
- }
+ @Override
+ public void onStartedWakingUp() {
+ mLockIcon.setDeviceInteractive(true);
+ }
- @Override
- public void onFinishedGoingToSleep(int why) {
- mLockIcon.setDeviceInteractive(false);
- }
+ @Override
+ public void onFinishedGoingToSleep(int why) {
+ mLockIcon.setDeviceInteractive(false);
+ }
- @Override
- public void onScreenTurnedOn() {
- mLockIcon.setScreenOn(true);
- }
+ @Override
+ public void onScreenTurnedOn() {
+ mLockIcon.setScreenOn(true);
+ }
- @Override
- public void onScreenTurnedOff() {
- mLockIcon.setScreenOn(false);
- }
+ @Override
+ public void onScreenTurnedOff() {
+ mLockIcon.setScreenOn(false);
+ }
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mLockIcon.update();
- }
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mLockIcon.update();
+ }
- @Override
- public void onFingerprintRunningStateChanged(boolean running) {
- mLockIcon.update();
- }
+ @Override
+ public void onFingerprintRunningStateChanged(boolean running) {
+ mLockIcon.update();
+ }
- @Override
- public void onStrongAuthStateChanged(int userId) {
- mLockIcon.update();
- }
- };
+ @Override
+ public void onStrongAuthStateChanged(int userId) {
+ mLockIcon.update();
+ }
+ };
public void setKeyguardIndicationController(
KeyguardIndicationController keyguardIndicationController) {
@@ -724,4 +742,96 @@
updateLeftAffordance();
inflateCameraPreview();
}
+
+ private void setRightButton(IntentButton button) {
+ mRightButton = button;
+ updateRightAffordanceIcon();
+ updateCameraVisibility();
+ inflateCameraPreview();
+ }
+
+ private void setLeftButton(IntentButton button) {
+ mLeftButton = button;
+ mLeftIsVoiceAssist = false;
+ updateLeftAffordance();
+ }
+
+ private final PluginListener<IntentButtonProvider> mRightListener =
+ new PluginListener<IntentButtonProvider>() {
+ @Override
+ public void onPluginConnected(IntentButtonProvider plugin) {
+ setRightButton(plugin.getIntentButton());
+ }
+
+ @Override
+ public void onPluginDisconnected(IntentButtonProvider plugin) {
+ setRightButton(new DefaultRightButton());
+ }
+ };
+
+ private final PluginListener<IntentButtonProvider> mLeftListener =
+ new PluginListener<IntentButtonProvider>() {
+ @Override
+ public void onPluginConnected(IntentButtonProvider plugin) {
+ setLeftButton(plugin.getIntentButton());
+ }
+
+ @Override
+ public void onPluginDisconnected(IntentButtonProvider plugin) {
+ setLeftButton(new DefaultLeftButton());
+ }
+ };
+
+ private class DefaultLeftButton implements IntentButton {
+
+ private IconState mIconState = new IconState();
+
+ @Override
+ public IconState getIcon() {
+ mLeftIsVoiceAssist = canLaunchVoiceAssist();
+ if (mLeftIsVoiceAssist) {
+ mIconState.isVisible = mUserSetupComplete;
+ mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp);
+ mIconState.contentDescription = mContext.getString(
+ R.string.accessibility_voice_assist_button);
+ } else {
+ mIconState.isVisible = mUserSetupComplete && isPhoneVisible();
+ mIconState.drawable = mContext.getDrawable(R.drawable.ic_phone_24dp);
+ mIconState.contentDescription = mContext.getString(
+ R.string.accessibility_phone_button);
+ }
+ return mIconState;
+ }
+
+ @Override
+ public Intent getIntent() {
+ return PHONE_INTENT;
+ }
+ }
+
+ private class DefaultRightButton implements IntentButton {
+
+ private IconState mIconState = new IconState();
+
+ @Override
+ public IconState getIcon() {
+ ResolveInfo resolved = resolveCameraIntent();
+ mIconState.isVisible = !isCameraDisabledByDpm() && resolved != null
+ && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance)
+ && mUserSetupComplete;
+ mIconState.drawable = mContext.getDrawable(R.drawable.ic_camera_alt_24dp);
+ mIconState.contentDescription =
+ mContext.getString(R.string.accessibility_camera_button);
+ return mIconState;
+ }
+
+ @Override
+ public Intent getIntent() {
+ KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ boolean canSkipBouncer = updateMonitor.getUserCanSkipBouncer(
+ KeyguardUpdateMonitor.getCurrentUser());
+ boolean secure = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
+ return (secure && !canSkipBouncer) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 06c8b68..c420927 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -28,12 +28,19 @@
import android.widget.Space;
import com.android.systemui.R;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider;
import com.android.systemui.statusbar.policy.KeyButtonView;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
-public class NavigationBarInflaterView extends FrameLayout implements TunerService.Tunable {
+public class NavigationBarInflaterView extends FrameLayout
+ implements Tunable, PluginListener<NavBarButtonProvider> {
private static final String TAG = "NavBarInflater";
@@ -57,6 +64,8 @@
public static final String KEY_IMAGE_DELIM = ":";
public static final String KEY_CODE_END = ")";
+ private final List<NavBarButtonProvider> mPlugins = new ArrayList<>();
+
protected LayoutInflater mLayoutInflater;
protected LayoutInflater mLandscapeInflater;
private int mDensity;
@@ -129,6 +138,8 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
+ PluginManager.getInstance(getContext()).addPluginListener(NavBarButtonProvider.ACTION, this,
+ NavBarButtonProvider.VERSION, true /* Allow multiple */);
}
@Override
@@ -240,8 +251,36 @@
int indexInParent) {
LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
float size = extractSize(buttonSpec);
- String button = extractButton(buttonSpec);
+ View v = createView(buttonSpec, parent, inflater, landscape);
+ if (v == null) return null;
+
+ if (size != 0) {
+ ViewGroup.LayoutParams params = v.getLayoutParams();
+ params.width = (int) (params.width * size);
+ }
+ parent.addView(v);
+ addToDispatchers(v, landscape);
+ View lastView = landscape ? mLastRot90 : mLastRot0;
+ if (lastView != null) {
+ v.setAccessibilityTraversalAfter(lastView.getId());
+ }
+ if (landscape) {
+ mLastRot90 = v;
+ } else {
+ mLastRot0 = v;
+ }
+ return v;
+ }
+
+ private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater,
+ boolean landscape) {
View v = null;
+ String button = extractButton(buttonSpec);
+ // Let plugins go first so they can override a standard view if they want.
+ for (NavBarButtonProvider provider : mPlugins) {
+ v = provider.createView(buttonSpec, parent);
+ if (v != null) return v;
+ }
if (HOME.equals(button)) {
v = inflater.inflate(R.layout.home, parent, false);
if (landscape && isSw600Dp()) {
@@ -271,24 +310,6 @@
if (uri != null) {
((KeyButtonView) v).loadAsync(uri);
}
- } else {
- return null;
- }
-
- if (size != 0) {
- ViewGroup.LayoutParams params = v.getLayoutParams();
- params.width = (int) (params.width * size);
- }
- parent.addView(v);
- addToDispatchers(v, landscape);
- View lastView = landscape ? mLastRot90 : mLastRot0;
- if (lastView != null) {
- v.setAccessibilityTraversalAfter(lastView.getId());
- }
- if (landscape) {
- mLastRot90 = v;
- } else {
- mLastRot0 = v;
}
return v;
}
@@ -374,4 +395,18 @@
((ViewGroup) group.getChildAt(i)).removeAllViews();
}
}
+
+ @Override
+ public void onPluginConnected(NavBarButtonProvider plugin) {
+ mPlugins.add(plugin);
+ clearViews();
+ inflateLayout(mCurrentLayout);
+ }
+
+ @Override
+ public void onPluginDisconnected(NavBarButtonProvider plugin) {
+ mPlugins.remove(plugin);
+ clearViews();
+ inflateLayout(mCurrentLayout);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 5d1af2f..fb2c335 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -328,7 +328,7 @@
} else if (!mQsExpanded) {
setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
}
- updateStackHeight(getExpandedHeight());
+ updateExpandedHeight(getExpandedHeight());
updateHeader();
// If we are running a size change animation, the animation takes care of the height of
@@ -376,10 +376,7 @@
boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
int stackScrollerPadding;
if (mStatusBarState != StatusBarState.KEYGUARD) {
- int bottom = mQsContainer.getHeader().getHeight();
- stackScrollerPadding = mStatusBarState == StatusBarState.SHADE
- ? bottom + mQsPeekHeight
- : mKeyguardStatusBar.getHeight();
+ stackScrollerPadding = mQsContainer.getHeader().getHeight() + mQsPeekHeight;
mTopPaddingAdjustment = 0;
} else {
mClockPositionAlgorithm.setup(
@@ -1166,6 +1163,7 @@
private void updateQsState() {
mQsContainer.setExpanded(mQsExpanded);
+ mNotificationStackScroller.setQsExpanded(mQsExpanded);
mNotificationStackScroller.setScrollingEnabled(
mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
|| mQsExpansionFromOverscroll));
@@ -1427,7 +1425,7 @@
setQsExpansion(mQsMinExpansionHeight
+ t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
}
- updateStackHeight(expandedHeight);
+ updateExpandedHeight(expandedHeight);
updateHeader();
updateUnlockIcon();
updateNotificationTranslucency();
@@ -1487,7 +1485,7 @@
maxQsHeight, mStatusBarState == StatusBarState.KEYGUARD
? mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment
: 0)
- + notificationHeight;
+ + notificationHeight + mNotificationStackScroller.getTopPaddingOverflow();
if (totalHeight > mNotificationStackScroller.getHeight()) {
float fullyCollapsedHeight = maxQsHeight
+ mNotificationStackScroller.getLayoutMinHeight();
@@ -1730,6 +1728,14 @@
if (view == null && mQsExpanded) {
return;
}
+ ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
+ ExpandableNotificationRow firstRow = firstChildNotGone instanceof ExpandableNotificationRow
+ ? (ExpandableNotificationRow) firstChildNotGone
+ : null;
+ if (firstRow != null
+ && (view == firstRow || (firstRow.getNotificationParent() == firstRow))) {
+ requestScrollerTopPaddingUpdate(false);
+ }
requestPanelHeightUpdate();
}
@@ -2249,8 +2255,8 @@
mQsAutoReinflateContainer.setTranslationX(translation);
}
- protected void updateStackHeight(float stackHeight) {
- mNotificationStackScroller.setStackHeight(stackHeight);
+ protected void updateExpandedHeight(float expandedHeight) {
+ mNotificationStackScroller.setExpandedHeight(expandedHeight);
updateKeyguardBottomAreaAlpha();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index e6066aa..bcc5a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -44,12 +44,12 @@
import android.widget.ImageView;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ButtonDispatcher;
+import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
-public class KeyButtonView extends ImageView implements ButtonDispatcher.ButtonInterface {
+public class KeyButtonView extends ImageView implements ButtonInterface {
private int mContentDescriptionRes;
private long mDownTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 50e5b88..81da672 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -43,6 +43,7 @@
private boolean mShadeExpanded;
private float mMaxHeadsUpTranslation;
private boolean mDismissAllInProgress;
+ private int mLayoutMinHeight;
public int getScrollY() {
return mScrollY;
@@ -137,10 +138,6 @@
mStackTranslation = stackTranslation;
}
- public int getLayoutHeight() {
- return mLayoutHeight;
- }
-
public void setLayoutHeight(int layoutHeight) {
mLayoutHeight = layoutHeight;
}
@@ -154,7 +151,7 @@
}
public int getInnerHeight() {
- return mLayoutHeight - mTopPadding;
+ return Math.max(mLayoutHeight - mTopPadding, mLayoutMinHeight);
}
public boolean isShadeExpanded() {
@@ -180,4 +177,8 @@
public boolean isDismissAllInProgress() {
return mDismissAllInProgress;
}
+
+ public void setLayoutMinHeight(int layoutMinHeight) {
+ mLayoutMinHeight = layoutMinHeight;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 90f4100..a9dee13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -112,11 +112,7 @@
private int mCurrentStackHeight = Integer.MAX_VALUE;
private final Paint mBackgroundPaint = new Paint();
- /**
- * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
- * externally from {@link #setStackHeight}
- */
- private float mLastSetStackHeight;
+ private float mExpandedHeight;
private int mOwnScrollY;
private int mMaxLayoutHeight;
@@ -355,6 +351,7 @@
return object.getBackgroundFadeAmount();
}
};
+ private boolean mQsExpanded;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -520,6 +517,7 @@
clampScrollPosition();
requestChildrenUpdate();
updateFirstAndLastBackgroundViews();
+ updateAlgorithmLayoutMinHeight();
}
private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
@@ -561,9 +559,14 @@
private void updateAlgorithmHeightAndPadding() {
mAmbientState.setLayoutHeight(getLayoutHeight());
+ updateAlgorithmLayoutMinHeight();
mAmbientState.setTopPadding(mTopPadding);
}
+ private void updateAlgorithmLayoutMinHeight() {
+ mAmbientState.setLayoutMinHeight(mQsExpanded && !onKeyguard() ? getLayoutMinHeight() : 0);
+ }
+
/**
* Updates the children views according to the stack scroll algorithm. Call this whenever
* modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
@@ -660,19 +663,19 @@
}
/**
- * Update the height of the stack to a new height.
+ * Update the height of the panel.
*
- * @param height the new height of the stack
+ * @param height the expanded height of the panel
*/
- public void setStackHeight(float height) {
- mLastSetStackHeight = height;
+ public void setExpandedHeight(float height) {
+ mExpandedHeight = height;
setIsExpanded(height > 0.0f);
int stackHeight;
float translationY;
float appearEndPosition = getAppearEndPosition();
float appearStartPosition = getAppearStartPosition();
if (height >= appearEndPosition) {
- translationY = mTopPaddingOverflow;
+ translationY = 0;
stackHeight = (int) height;
} else {
float appearFraction = getAppearFraction(height);
@@ -699,8 +702,12 @@
* Measured relative to the resting position.
*/
private float getExpandTranslationStart() {
- int startPosition = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()
- ? 0 : -getFirstChildIntrinsicHeight();
+ int startPosition = 0;
+ if (!mTrackingHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
+ startPosition = - Math.min(getFirstChildIntrinsicHeight(),
+ mMaxLayoutHeight - mIntrinsicPadding - mBottomStackSlowDownHeight
+ - mBottomStackPeekSize);
+ }
return startPosition - mTopPadding;
}
@@ -723,7 +730,7 @@
? mHeadsUpManager.getTopHeadsUpPinnedHeight() + mBottomStackPeekSize
+ mBottomStackSlowDownHeight
: getLayoutMinHeight();
- return firstItemHeight + mTopPadding + mTopPaddingOverflow;
+ return firstItemHeight + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
}
/**
@@ -1153,6 +1160,10 @@
@Override
public boolean isAntiFalsingNeeded() {
+ return onKeyguard();
+ }
+
+ private boolean onKeyguard() {
return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
}
@@ -1262,7 +1273,7 @@
if (!isScrollingEnabled()) {
return false;
}
- if (isInsideQsContainer(ev)) {
+ if (isInsideQsContainer(ev) && !mIsBeingDragged) {
return false;
}
mForcedScroll = null;
@@ -2158,26 +2169,22 @@
*/
public void updateTopPadding(float qsHeight, boolean animate,
boolean ignoreIntrinsicPadding) {
- float start = qsHeight;
- float stackHeight = getHeight() - start;
+ int topPadding = (int) qsHeight;
int minStackHeight = getLayoutMinHeight();
- if (stackHeight <= minStackHeight) {
- float overflow = minStackHeight - stackHeight;
- stackHeight = minStackHeight;
- start = getHeight() - stackHeight;
- mTopPaddingOverflow = overflow;
+ if (topPadding + minStackHeight > getHeight()) {
+ mTopPaddingOverflow = topPadding + minStackHeight - getHeight();
} else {
mTopPaddingOverflow = 0;
}
- setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
+ setTopPadding(ignoreIntrinsicPadding ? topPadding : clampPadding(topPadding),
animate);
- setStackHeight(mLastSetStackHeight);
+ setExpandedHeight(mExpandedHeight);
}
public int getLayoutMinHeight() {
int firstChildMinHeight = getFirstChildIntrinsicHeight();
return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight,
- mMaxLayoutHeight - mTopPadding);
+ mMaxLayoutHeight - mIntrinsicPadding);
}
public int getFirstChildIntrinsicHeight() {
@@ -3128,10 +3135,14 @@
updateScrollPositionOnExpandInBottom(view);
clampScrollPosition();
notifyHeightChangeListener(view);
+ ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
+ ? (ExpandableNotificationRow) view
+ : null;
+ if (row != null && (row == mFirstVisibleBackgroundChild
+ || row.getNotificationParent() == mFirstVisibleBackgroundChild)) {
+ updateAlgorithmLayoutMinHeight();
+ }
if (needsAnimation) {
- ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
- ? (ExpandableNotificationRow) view
- : null;
requestAnimationOnViewResize(row);
}
requestChildrenUpdate();
@@ -3414,7 +3425,7 @@
}
private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
- if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
+ if (screenLocation == null || screenLocation.y < mTopPadding) {
return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
}
if (screenLocation.y > getBottomMostNotificationBottom()) {
@@ -3898,6 +3909,11 @@
mCurrentStackScrollState.removeViewStateForView(view);
}
+ public void setQsExpanded(boolean qsExpanded) {
+ mQsExpanded = qsExpanded;
+ updateAlgorithmLayoutMinHeight();
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
@@ -4121,7 +4137,7 @@
onDragCancelled(animView);
// If we're on the lockscreen we want to false this.
- if (mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ if (isAntiFalsingNeeded()) {
mHandler.removeCallbacks(mFalsingCheck);
mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java
new file mode 100644
index 0000000..2792d8c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java
@@ -0,0 +1,85 @@
+package com.android.systemui.statusbar;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+import static org.mockito.Mockito.mock;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.StatusBarIconList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StatusBarIconListTest extends SysuiTestCase {
+
+ private final static String[] STATUS_BAR_SLOTS = {"aaa", "bbb", "ccc"};
+
+ @Test
+ public void testGetExistingSlot() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ assertEquals(1, statusBarIconList.getSlotIndex("bbb"));
+ assertEquals(2, statusBarIconList.getSlotIndex("ccc"));
+ }
+
+ @Test
+ public void testGetNonexistingSlot() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ assertEquals(0, statusBarIconList.getSlotIndex("aaa"));
+ assertEquals(3, statusBarIconList.size());
+ assertEquals(0, statusBarIconList.getSlotIndex("zzz")); // new content added in front
+ assertEquals(1, statusBarIconList.getSlotIndex("aaa")); // slid back
+ assertEquals(4, statusBarIconList.size());
+ }
+
+ @Test
+ public void testAddSlotSlidesIcons() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ StatusBarIcon sbIcon = mock(StatusBarIcon.class);
+ statusBarIconList.setIcon(0, sbIcon);
+ statusBarIconList.getSlotIndex("zzz"); // new content added in front
+ assertNull(statusBarIconList.getIcon(0));
+ assertEquals(sbIcon, statusBarIconList.getIcon(1));
+ }
+
+ @Test
+ public void testGetAndSetIcon() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ StatusBarIcon sbIconA = mock(StatusBarIcon.class);
+ StatusBarIcon sbIconB = mock(StatusBarIcon.class);
+ statusBarIconList.setIcon(0, sbIconA);
+ statusBarIconList.setIcon(1, sbIconB);
+ assertEquals(sbIconA, statusBarIconList.getIcon(0));
+ assertEquals(sbIconB, statusBarIconList.getIcon(1));
+ assertNull(statusBarIconList.getIcon(2)); // icon not set
+ }
+
+ @Test
+ public void testRemoveIcon() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ StatusBarIcon sbIconA = mock(StatusBarIcon.class);
+ StatusBarIcon sbIconB = mock(StatusBarIcon.class);
+ statusBarIconList.setIcon(0, sbIconA);
+ statusBarIconList.setIcon(1, sbIconB);
+ statusBarIconList.removeIcon(0);
+ assertNull(statusBarIconList.getIcon(0)); // icon not set
+ }
+
+ @Test
+ public void testGetViewIndex() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ StatusBarIcon sbIcon = mock(StatusBarIcon.class);
+ statusBarIconList.setIcon(2, sbIcon);
+ assertEquals(0, statusBarIconList.getViewIndex(2)); // Icon for item 2 is 0th child view.
+ statusBarIconList.setIcon(0, sbIcon);
+ assertEquals(0, statusBarIconList.getViewIndex(0)); // Icon for item 0 is 0th child view,
+ assertEquals(1, statusBarIconList.getViewIndex(2)); // and item 2 is now 1st child view.
+ }
+
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 238bf92..7c2eea3f 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -51,6 +51,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.UserHandle;
import android.os.storage.MountServiceInternal;
@@ -1787,8 +1788,9 @@
}
@Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
- (new Shell(this, this)).exec(this, in, out, err, args, resultReceiver);
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new Shell(this, this)).exec(this, in, out, err, args, callback, resultReceiver);
}
static void dumpCommandHelp(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 6b51721..d2cfb6d 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -20,6 +20,7 @@
import android.os.BatteryStats;
import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.ShellCommand;
import com.android.internal.app.IBatteryStats;
import com.android.server.am.BatteryStatsService;
@@ -789,7 +790,7 @@
pw.println(" technology: " + mBatteryProps.batteryTechnology);
} else {
Shell shell = new Shell();
- shell.exec(mBinderService, null, fd, null, args, new ResultReceiver(null));
+ shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
}
}
}
@@ -875,8 +876,9 @@
}
@Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
- (new Shell()).exec(this, in, out, err, args, resultReceiver);
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
}
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 6b73fec..dbc1f31 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -60,6 +60,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -1232,8 +1233,8 @@
}
@Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
- (new Shell()).exec(this, in, out, err, args, resultReceiver);
+ FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
}
@@ -2838,7 +2839,8 @@
shell.userId = userId;
String[] newArgs = new String[args.length-i];
System.arraycopy(args, i, newArgs, 0, args.length-i);
- shell.exec(mBinderService, null, fd, null, newArgs, new ResultReceiver(null));
+ shell.exec(mBinderService, null, fd, null, newArgs, null,
+ new ResultReceiver(null));
return;
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e5b611c..d4396a4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -182,6 +182,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -6126,6 +6127,8 @@
ProcessList.INVALID_ADJ, callerWillRestart, true, doit, evenPersistent,
packageName == null ? ("stop user " + userId) : ("stop " + packageName));
+ didSomething |= mActivityStarter.clearPendingActivityLaunchesLocked(packageName);
+
if (mStackSupervisor.finishDisabledPackageActivitiesLocked(
packageName, null, doit, evenPersistent, userId)) {
if (!doit) {
@@ -14024,9 +14027,10 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
(new ActivityManagerShellCommand(this, false)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
@Override
@@ -14249,7 +14253,8 @@
// Dumping a single activity?
if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll, dumpVisibleStacks)) {
ActivityManagerShellCommand shell = new ActivityManagerShellCommand(this, true);
- int res = shell.exec(this, null, fd, null, args, new ResultReceiver(null));
+ int res = shell.exec(this, null, fd, null, args, null,
+ new ResultReceiver(null));
if (res < 0) {
pw.println("Bad activity command, or no activities match: " + cmd);
pw.println("Use -h for help.");
@@ -20679,8 +20684,11 @@
final long now = SystemClock.elapsedRealtime();
Long lastReported = userState.mProviderLastReportedFg.get(authority);
if (lastReported == null || lastReported < now - 60 * 1000L) {
- mUsageStatsService.reportContentProviderUsage(
- authority, providerPkgName, app.userId);
+ if (mSystemReady) {
+ // Cannot touch the user stats if not system ready
+ mUsageStatsService.reportContentProviderUsage(
+ authority, providerPkgName, app.userId);
+ }
userState.mProviderLastReportedFg.put(authority, now);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index adf6d36..aed9fa4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -17,26 +17,57 @@
package com.android.server.am;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.AppGlobals;
import android.app.IActivityManager;
+import android.app.ProfilerInfo;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ShellCommand;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.DebugUtils;
import java.io.PrintWriter;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
class ActivityManagerShellCommand extends ShellCommand {
+ public static final String NO_CLASS_ERROR_CODE = "Error type 3";
+
// IPC interface to activity manager -- don't need to do additional security checks.
final IActivityManager mInterface;
// Internal service impl -- must perform security checks before touching.
final ActivityManagerService mInternal;
+ // Convenience for interacting with package manager.
+ final IPackageManager mPm;
+
+ private int mStartFlags = 0;
+ private boolean mWaitOption = false;
+ private boolean mStopOption = false;
+
+ private int mRepeat = 0;
+ private int mUserId;
+ private String mReceiverPermission;
+
+ private String mProfileFile;
+ private int mSamplingInterval;
+ private boolean mAutoStop;
+ private int mStackId;
+
final boolean mDumping;
ActivityManagerShellCommand(ActivityManagerService service, boolean dumping) {
mInterface = service;
mInternal = service;
+ mPm = AppGlobals.getPackageManager();
mDumping = dumping;
}
@@ -48,6 +79,15 @@
PrintWriter pw = getOutPrintWriter();
try {
switch (cmd) {
+ case "start":
+ case "start-activity":
+ return runStartActivity(pw);
+ case "startservice":
+ case "start-service":
+ return 1; //runStartService(pw);
+ case "stopservice":
+ case "stop-service":
+ return 1; //runStopService(pw);
case "force-stop":
return runForceStop(pw);
case "kill":
@@ -75,6 +115,241 @@
return -1;
}
+ private Intent makeIntent(int defUser) throws URISyntaxException {
+ mStartFlags = 0;
+ mWaitOption = false;
+ mStopOption = false;
+ mRepeat = 0;
+ mProfileFile = null;
+ mSamplingInterval = 0;
+ mAutoStop = false;
+ mUserId = defUser;
+ mStackId = INVALID_STACK_ID;
+
+ return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {
+ @Override
+ public boolean handleOption(String opt, ShellCommand cmd) {
+ if (opt.equals("-D")) {
+ mStartFlags |= ActivityManager.START_FLAG_DEBUG;
+ } else if (opt.equals("-N")) {
+ mStartFlags |= ActivityManager.START_FLAG_NATIVE_DEBUGGING;
+ } else if (opt.equals("-W")) {
+ mWaitOption = true;
+ } else if (opt.equals("-P")) {
+ mProfileFile = getNextArgRequired();
+ mAutoStop = true;
+ } else if (opt.equals("--start-profiler")) {
+ mProfileFile = getNextArgRequired();
+ mAutoStop = false;
+ } else if (opt.equals("--sampling")) {
+ mSamplingInterval = Integer.parseInt(getNextArgRequired());
+ } else if (opt.equals("-R")) {
+ mRepeat = Integer.parseInt(getNextArgRequired());
+ } else if (opt.equals("-S")) {
+ mStopOption = true;
+ } else if (opt.equals("--track-allocation")) {
+ mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION;
+ } else if (opt.equals("--user")) {
+ mUserId = UserHandle.parseUserArg(getNextArgRequired());
+ } else if (opt.equals("--receiver-permission")) {
+ mReceiverPermission = getNextArgRequired();
+ } else if (opt.equals("--stack")) {
+ mStackId = Integer.parseInt(getNextArgRequired());
+ } else {
+ return false;
+ }
+ return true;
+ }
+ });
+ }
+
+ ParcelFileDescriptor openOutputFile(String path) {
+ try {
+ ParcelFileDescriptor pfd = getShellCallback().openOutputFile(path,
+ "u:r:system_server:s0");
+ if (pfd != null) {
+ return pfd;
+ }
+ } catch (RuntimeException e) {
+ getErrPrintWriter().println("Failure opening file: " + e.getMessage());
+ }
+ getErrPrintWriter().println("Error: Unable to open file: " + path);
+ getErrPrintWriter().println("Consider using a file under /data/local/tmp/");
+ return null;
+ }
+
+ int runStartActivity(PrintWriter pw) throws RemoteException {
+ Intent intent;
+ try {
+ intent = makeIntent(UserHandle.USER_CURRENT);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+
+ if (mUserId == UserHandle.USER_ALL) {
+ getErrPrintWriter().println("Error: Can't start service with user 'all'");
+ return 1;
+ }
+
+ String mimeType = intent.getType();
+ if (mimeType == null && intent.getData() != null
+ && "content".equals(intent.getData().getScheme())) {
+ mimeType = mInterface.getProviderMimeType(intent.getData(), mUserId);
+ }
+
+ do {
+ if (mStopOption) {
+ String packageName;
+ if (intent.getComponent() != null) {
+ packageName = intent.getComponent().getPackageName();
+ } else {
+ List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType, 0,
+ mUserId).getList();
+ if (activities == null || activities.size() <= 0) {
+ getErrPrintWriter().println("Error: Intent does not match any activities: "
+ + intent);
+ return 1;
+ } else if (activities.size() > 1) {
+ getErrPrintWriter().println(
+ "Error: Intent matches multiple activities; can't stop: "
+ + intent);
+ return 1;
+ }
+ packageName = activities.get(0).activityInfo.packageName;
+ }
+ pw.println("Stopping: " + packageName);
+ mInterface.forceStopPackage(packageName, mUserId);
+ try {
+ Thread.sleep(250);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ ProfilerInfo profilerInfo = null;
+
+ if (mProfileFile != null) {
+ ParcelFileDescriptor fd = openOutputFile(mProfileFile);
+ if (fd == null) {
+ return 1;
+ }
+ profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop);
+ }
+
+ pw.println("Starting: " + intent);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ IActivityManager.WaitResult result = null;
+ int res;
+ final long startTime = SystemClock.uptimeMillis();
+ ActivityOptions options = null;
+ if (mStackId != INVALID_STACK_ID) {
+ options = ActivityOptions.makeBasic();
+ options.setLaunchStackId(mStackId);
+ }
+ if (mWaitOption) {
+ result = mInterface.startActivityAndWait(null, null, intent, mimeType,
+ null, null, 0, mStartFlags, profilerInfo,
+ options != null ? options.toBundle() : null, mUserId);
+ res = result.result;
+ } else {
+ res = mInterface.startActivityAsUser(null, null, intent, mimeType,
+ null, null, 0, mStartFlags, profilerInfo,
+ options != null ? options.toBundle() : null, mUserId);
+ }
+ final long endTime = SystemClock.uptimeMillis();
+ PrintWriter out = mWaitOption ? pw : getErrPrintWriter();
+ boolean launched = false;
+ switch (res) {
+ case ActivityManager.START_SUCCESS:
+ launched = true;
+ break;
+ case ActivityManager.START_SWITCHES_CANCELED:
+ launched = true;
+ out.println(
+ "Warning: Activity not started because the "
+ + " current activity is being kept for the user.");
+ break;
+ case ActivityManager.START_DELIVERED_TO_TOP:
+ launched = true;
+ out.println(
+ "Warning: Activity not started, intent has "
+ + "been delivered to currently running "
+ + "top-most instance.");
+ break;
+ case ActivityManager.START_RETURN_INTENT_TO_CALLER:
+ launched = true;
+ out.println(
+ "Warning: Activity not started because intent "
+ + "should be handled by the caller");
+ break;
+ case ActivityManager.START_TASK_TO_FRONT:
+ launched = true;
+ out.println(
+ "Warning: Activity not started, its current "
+ + "task has been brought to the front");
+ break;
+ case ActivityManager.START_INTENT_NOT_RESOLVED:
+ out.println(
+ "Error: Activity not started, unable to "
+ + "resolve " + intent.toString());
+ break;
+ case ActivityManager.START_CLASS_NOT_FOUND:
+ out.println(NO_CLASS_ERROR_CODE);
+ out.println("Error: Activity class " +
+ intent.getComponent().toShortString()
+ + " does not exist.");
+ break;
+ case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
+ out.println(
+ "Error: Activity not started, you requested to "
+ + "both forward and receive its result");
+ break;
+ case ActivityManager.START_PERMISSION_DENIED:
+ out.println(
+ "Error: Activity not started, you do not "
+ + "have permission to access it.");
+ break;
+ case ActivityManager.START_NOT_VOICE_COMPATIBLE:
+ out.println(
+ "Error: Activity not started, voice control not allowed for: "
+ + intent);
+ break;
+ case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY:
+ out.println(
+ "Error: Not allowed to start background user activity"
+ + " that shouldn't be displayed for all users.");
+ break;
+ default:
+ out.println(
+ "Error: Activity not started, unknown error code " + res);
+ break;
+ }
+ if (mWaitOption && launched) {
+ if (result == null) {
+ result = new IActivityManager.WaitResult();
+ result.who = intent.getComponent();
+ }
+ pw.println("Status: " + (result.timeout ? "timeout" : "ok"));
+ if (result.who != null) {
+ pw.println("Activity: " + result.who.flattenToShortString());
+ }
+ if (result.thisTime >= 0) {
+ pw.println("ThisTime: " + result.thisTime);
+ }
+ if (result.totalTime >= 0) {
+ pw.println("TotalTime: " + result.totalTime);
+ }
+ pw.println("WaitTime: " + (endTime-startTime));
+ pw.println("Complete");
+ }
+ mRepeat--;
+ if (mRepeat > 0) {
+ mInterface.unhandledBack();
+ }
+ } while (mRepeat > 0);
+ return 0;
+ }
+
int runIsUserStopped(PrintWriter pw) {
int userId = UserHandle.parseUserArg(getNextArgRequired());
boolean stopped = mInternal.isUserStopped(userId);
@@ -223,6 +498,24 @@
pw.println("Activity manager (activity) commands:");
pw.println(" help");
pw.println(" Print this help text.");
+ pw.println(" start-activity [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]");
+ pw.println(" [--sampling INTERVAL] [-R COUNT] [-S]");
+ pw.println(" [--track-allocation] [--user <USER_ID> | current] <INTENT>");
+ pw.println(" Start an Activity. Options are:");
+ pw.println(" -D: enable debugging");
+ pw.println(" -N: enable native debugging");
+ pw.println(" -W: wait for launch to complete");
+ pw.println(" --start-profiler <FILE>: start profiler and send results to <FILE>");
+ pw.println(" --sampling INTERVAL: use sample profiling with INTERVAL microseconds");
+ pw.println(" between samples (use with --start-profiler)");
+ pw.println(" -P <FILE>: like above, but profiling stops when app goes idle");
+ pw.println(" -R: repeat the activity launch <COUNT> times. Prior to each repeat,");
+ pw.println(" the top activity will be finished.");
+ pw.println(" -S: force stop the target app before starting the activity");
+ pw.println(" --track-allocation: enable tracking of object allocations");
+ pw.println(" --user <USER_ID> | current: Specify which user to run as; if not");
+ pw.println(" specified then run as the current user.");
+ pw.println(" --stack <STACK_ID>: Specify into which stack should the activity be put.");
pw.println(" force-stop [--user <USER_ID> | all | current] <PACKAGE>");
pw.println(" Completely stop the given application package.");
pw.println(" kill [--user <USER_ID> | all | current] <PACKAGE>");
@@ -241,6 +534,8 @@
pw.println(" Optionally controls lenient background check mode, returns current mode.");
pw.println(" get-uid-state <UID>");
pw.println(" Gets the process state of an app given its <UID>.");
+ pw.println();
+ Intent.printIntentArgsHelp(pw, "");
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 7708b02..d820627 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1414,7 +1414,12 @@
* this function updates the rest of our state to match that fact.
*/
private void completeResumeLocked(ActivityRecord next) {
+ boolean wasVisible = next.visible;
next.visible = true;
+ if (!wasVisible) {
+ // Visibility has changed, so take a note of it so we call the TaskStackChangedListener
+ mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
+ }
next.idle = false;
next.results = null;
next.newIntents = null;
@@ -5029,7 +5034,7 @@
if (top >= 0) {
final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities;
int activityTop = activities.size() - 1;
- if (activityTop > 0) {
+ if (activityTop >= 0) {
finishActivityLocked(activities.get(activityTop), Activity.RESULT_CANCELED, null,
"unhandled-back", true);
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 5feaf1f..028c6ac 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -2091,4 +2091,18 @@
return (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 &&
(flags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0;
}
+
+ boolean clearPendingActivityLaunchesLocked(String packageName) {
+ boolean didSomething = false;
+
+ for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) {
+ PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
+ ActivityRecord r = pal.r;
+ if (r != null && r.packageName.equals(packageName)) {
+ mPendingActivityLaunches.remove(palNdx);
+ didSomething = true;
+ }
+ }
+ return didSomething;
+ }
}
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 5addffb..9523a1c 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -195,6 +195,7 @@
public void binderDied() {
Slog.v(TAG, "fingerprintd died");
mDaemon = null;
+ mCurrentUserId = UserHandle.USER_CURRENT;
handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE);
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 74095ac..87f4030 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.Build;
import android.os.LocaleList;
+import android.os.ShellCallback;
import android.util.Log;
import android.view.Display;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
@@ -91,10 +91,8 @@
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
-import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
-import android.widget.Toast;
import java.io.File;
import java.io.FileDescriptor;
@@ -103,11 +101,8 @@
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -1739,8 +1734,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
- (new Shell()).exec(this, in, out, err, args, resultReceiver);
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
public int onShellCommand(Shell shell, String cmd) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 9d93146..fe3a02d 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -44,9 +44,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -62,6 +60,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -1718,9 +1717,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
};
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 6381aa7..547cc51 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -123,7 +123,6 @@
import android.net.NetworkIdentity;
import android.net.NetworkInfo;
import android.net.NetworkPolicy;
-import android.net.NetworkPolicyManager;
import android.net.NetworkQuotaInfo;
import android.net.NetworkState;
import android.net.NetworkTemplate;
@@ -144,6 +143,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -2386,9 +2386,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new NetworkPolicyManagerShellCommand(mContext, this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
@Override
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 689917c..f777aae 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -28,6 +28,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.storage.StorageManager;
import android.util.Log;
import android.util.Slog;
@@ -107,9 +108,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new OtaDexoptShellCommand(this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c76302c..13428b2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -185,6 +185,7 @@
import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
@@ -18397,9 +18398,10 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
(new PackageManagerShellCommand(this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
@Override
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 13f558e..19bf751 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -67,6 +67,7 @@
import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -3366,13 +3367,14 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
enforceShell();
final long token = injectClearCallingIdentity();
try {
- final int status = (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
+ final int status = (new MyShellCommand()).exec(this, in, out, err, args, callback,
+ resultReceiver);
resultReceiver.send(status, null);
} finally {
injectRestoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c1cb032..c6b09d1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -62,6 +62,7 @@
import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.UserHandle;
import android.os.UserManager;
@@ -3209,8 +3210,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
- (new Shell()).exec(this, in, out, err, args, resultReceiver);
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
int onShellCommand(Shell shell, String cmd) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b555938..2f25971 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1373,8 +1373,13 @@
case LONG_PRESS_BACK_NOTHING:
break;
case LONG_PRESS_BACK_GO_TO_VOICE_ASSIST:
- Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
- startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ final boolean keyguardActive = mKeyguardDelegate == null
+ ? false
+ : mKeyguardDelegate.isShowing();
+ if (!keyguardActive) {
+ Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+ startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ }
break;
}
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 552803f..7b7db0e 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -28,23 +28,21 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
-import android.view.KeyEvent;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.FastPrintWriter;
import com.android.server.LocalServices;
import com.android.server.notification.NotificationDelegate;
import com.android.server.wm.WindowManagerService;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -849,9 +847,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new StatusBarShellCommand(this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
// ================================================================================
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 846169c..43cdf59 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -25,10 +25,10 @@
import android.os.PatternMatcher;
import android.os.Process;
import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.util.Slog;
import android.webkit.IWebViewUpdateService;
-import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
@@ -140,9 +140,10 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
(new WebViewUpdateServiceShellCommand(this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index ba26e13..9c5392e 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -429,7 +429,7 @@
for (int i = 0; i < displayList.size(); i++) {
final DisplayContent displayContent = displayList.get(i);
mService.mLayersController.assignLayersLocked(displayContent.getWindowList());
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
}
@@ -729,9 +729,7 @@
if (mTask.mPreparedFrozenMergedConfig.equals(Configuration.EMPTY)) {
// We didn't call prepareFreezingBounds on the task, so use the current value.
- final Configuration config = new Configuration(mService.mGlobalConfiguration);
- config.updateFrom(mTask.mOverrideConfig);
- mFrozenMergedConfig.offer(config);
+ mFrozenMergedConfig.offer(new Configuration(mTask.getConfiguration()));
} else {
mFrozenMergedConfig.offer(new Configuration(mTask.mPreparedFrozenMergedConfig));
}
@@ -957,7 +955,7 @@
mService.updateFocusedWindowLocked(
UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
- mService.getDefaultDisplayContentLocked().layoutNeeded = true;
+ mService.getDefaultDisplayContentLocked().setLayoutNeeded();
mService.mWindowPlacerLocked.performSurfacePlacement();
Binder.restoreCallingIdentity(origId);
return true;
@@ -1065,7 +1063,7 @@
allDrawn = true;
// Force an additional layout pass where
// WindowStateAnimator#commitFinishDrawingLocked() will call performShowLocked().
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
mService.mH.obtainMessage(NOTIFY_ACTIVITY_DRAWN, token).sendToTarget();
}
}
@@ -1076,7 +1074,7 @@
+ " interesting=" + numInteresting
+ " drawn=" + numDrawnWindowsExcludingSaved);
allDrawnExcludingSaved = true;
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
if (isAnimatingInvisibleWithSavedSurface()
&& !mService.mFinishedEarlyAnim.contains(this)) {
mService.mFinishedEarlyAnim.add(this);
diff --git a/services/core/java/com/android/server/wm/DimLayerController.java b/services/core/java/com/android/server/wm/DimLayerController.java
index 7f97c46..da2c6a7 100644
--- a/services/core/java/com/android/server/wm/DimLayerController.java
+++ b/services/core/java/com/android/server/wm/DimLayerController.java
@@ -256,7 +256,7 @@
// on whether a dim layer is showing or not.
if (targetAlpha == 0) {
mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
- mDisplayContent.layoutNeeded = true;
+ mDisplayContent.setLayoutNeeded();
}
}
} else if (state.dimLayer.getLayer() != dimLayer) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 66682d8..34a7390 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -38,6 +38,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -46,6 +47,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager.StackId;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Region.Op;
@@ -100,7 +102,7 @@
private Rect mContentRect = new Rect();
// Accessed directly by all users.
- boolean layoutNeeded;
+ private boolean mLayoutNeeded;
int pendingLayoutChanges;
final boolean isDefaultDisplay;
@@ -209,18 +211,26 @@
return null;
}
- /** Callback used to notify about configuration changes. */
- void onConfigurationChanged(@NonNull List<Integer> changedStackList) {
+ @Override
+ void onConfigurationChanged(Configuration newParentConfig) {
+ super.onConfigurationChanged(newParentConfig);
+
// The display size information is heavily dependent on the resources in the current
// configuration, so we need to reconfigure it every time the configuration changes.
// See {@link PhoneWindowManager#setInitialDisplaySize}...sigh...
mService.reconfigureDisplayLocked(this);
getDockedDividerController().onConfigurationChanged();
+ }
- for (int i = 0; i < mChildren.size(); i++) {
+ /**
+ * Callback used to trigger bounds update after configuration change and get ids of stacks whose
+ * bounds were updated.
+ */
+ void updateStackBoundsAfterConfigChange(@NonNull List<Integer> changedStackList) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
final TaskStack stack = mChildren.get(i);
- if (stack.onConfigurationChanged()) {
+ if (stack.updateBoundsAfterConfigChange()) {
changedStackList.add(stack.mStackId);
}
}
@@ -367,7 +377,7 @@
}
}
addChild(stack, addIndex);
- layoutNeeded = true;
+ setLayoutNeeded();
}
/**
@@ -684,8 +694,8 @@
pw.print("x"); pw.print(mDisplayInfo.smallestNominalAppHeight);
pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth);
pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
- pw.print(subPrefix); pw.print("deferred="); pw.print(mDeferredRemoval);
- pw.print(" layoutNeeded="); pw.println(layoutNeeded);
+ pw.println(subPrefix + "deferred=" + mDeferredRemoval
+ + " mLayoutNeeded=" + mLayoutNeeded);
pw.println();
pw.println(" Application tokens in top down Z order:");
@@ -778,7 +788,8 @@
for (int i = 0; i < windowCount; i++) {
WindowState window = windows.get(i);
if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == uid
- && !window.mPermanentlyHidden && !window.mAnimatingExit) {
+ && !window.mPermanentlyHidden && !window.mAnimatingExit
+ && !window.mRemoveOnExit) {
return false;
}
}
@@ -1090,7 +1101,7 @@
i -= lastBelow;
if (i != numRemoved) {
- layoutNeeded = true;
+ setLayoutNeeded();
Slog.w(TAG_WM, "On display=" + mDisplayId + " Rebuild removed " + numRemoved
+ " windows but added " + i + " rebuildAppWindowListLocked() "
+ " callers=" + Debug.getCallers(10));
@@ -1127,6 +1138,20 @@
return windowList;
}
+ void setLayoutNeeded() {
+ if (DEBUG_LAYOUT) Slog.w(TAG_WM, "setLayoutNeeded: callers=" + Debug.getCallers(3));
+ mLayoutNeeded = true;
+ }
+
+ void clearLayoutNeeded() {
+ if (DEBUG_LAYOUT) Slog.w(TAG_WM, "clearLayoutNeeded: callers=" + Debug.getCallers(3));
+ mLayoutNeeded = false;
+ }
+
+ boolean isLayoutNeeded() {
+ return mLayoutNeeded;
+ }
+
private int addAppWindowExisting(WindowState win, WindowList tokenWindowList) {
int tokenWindowsPos;
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index ef8f492..15a952d 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -153,7 +153,7 @@
// If the bounds are fullscreen, return the value of the fullscreen configuration
if (bounds == null || (bounds.left == 0 && bounds.top == 0
&& bounds.right == di.logicalWidth && bounds.bottom == di.logicalHeight)) {
- return mService.mGlobalConfiguration.smallestScreenWidthDp;
+ return mDisplayContent.getConfiguration().smallestScreenWidthDp;
}
final int baseDisplayWidth = mDisplayContent.mBaseDisplayWidth;
final int baseDisplayHeight = mDisplayContent.mBaseDisplayHeight;
@@ -190,7 +190,7 @@
}
private void initSnapAlgorithmForRotations() {
- final Configuration baseConfig = mService.mGlobalConfiguration;
+ final Configuration baseConfig = mDisplayContent.getConfiguration();
// Initialize the snap algorithms for all 4 screen orientations.
final Configuration config = new Configuration();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 8f533fb1..219fd8e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -29,7 +29,6 @@
import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Display;
import android.view.DisplayInfo;
@@ -265,11 +264,11 @@
return bounds;
}
- boolean layoutNeeded() {
+ boolean isLayoutNeeded() {
final int numDisplays = mChildren.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
- if (displayContent.layoutNeeded) {
+ if (displayContent.isLayoutNeeded()) {
return true;
}
}
@@ -347,18 +346,35 @@
}
}
- int[] onConfigurationChanged(Configuration config) {
+ /** Set new config and return array of ids of stacks that were changed during update. */
+ int[] setGlobalConfigurationIfNeeded(Configuration newConfiguration) {
+ final boolean configChanged = getConfiguration().diff(newConfiguration) != 0;
+ if (!configChanged) {
+ return null;
+ }
+ onConfigurationChanged(newConfiguration);
+ return updateStackBoundsAfterConfigChange();
+ }
+
+ @Override
+ void onConfigurationChanged(Configuration newParentConfig) {
prepareFreezingTaskBounds();
- mService.mGlobalConfiguration = new Configuration(config);
+ super.onConfigurationChanged(newParentConfig);
mService.mPolicy.onConfigurationChanged();
+ }
+ /**
+ * Callback used to trigger bounds update after configuration change and get ids of stacks whose
+ * bounds were updated.
+ */
+ int[] updateStackBoundsAfterConfigChange() {
mChangedStackList.clear();
final int numDisplays = mChildren.size();
for (int i = 0; i < numDisplays; ++i) {
final DisplayContent dc = mChildren.get(i);
- dc.onConfigurationChanged(mChangedStackList);
+ dc.updateStackBoundsAfterConfigChange(mChangedStackList);
}
return mChangedStackList.isEmpty() ? null : ArrayUtils.convertToIntArray(mChangedStackList);
@@ -753,7 +769,7 @@
}
}
- if (layoutNeeded()) {
+ if (isLayoutNeeded()) {
defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("mLayoutNeeded",
defaultDisplay.pendingLayoutChanges);
@@ -840,13 +856,13 @@
if (wallpaperDestroyed) {
defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- defaultDisplay.layoutNeeded = true;
+ defaultDisplay.setLayoutNeeded();
}
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
if (displayContent.pendingLayoutChanges != 0) {
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
}
@@ -901,8 +917,8 @@
}
if (mService.mWaitingForDrawnCallback != null ||
- (mOrientationChangeComplete && !defaultDisplay.layoutNeeded &&
- !mUpdateRotation)) {
+ (mOrientationChangeComplete && !defaultDisplay.isLayoutNeeded()
+ && !mUpdateRotation)) {
mService.checkDrawnWindowsLocked();
}
@@ -925,7 +941,7 @@
for (DisplayContent displayContent : displayList) {
mService.mLayersController.assignLayersLocked(displayContent.getWindowList());
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
}
@@ -951,7 +967,7 @@
}
// TODO: Super crazy long method that should be broken down...
- void applySurfaceChangesTransaction(boolean recoveringMemory, int defaultDw, int defaultDh) {
+ private void applySurfaceChangesTransaction(boolean recoveringMemory, int defaultDw, int defaultDh) {
mHoldScreenWindow = null;
mObsuringWindow = null;
@@ -993,7 +1009,7 @@
repeats++;
if (repeats > 6) {
Slog.w(TAG, "Animation repeat aborted after too many iterations");
- dc.layoutNeeded = false;
+ dc.clearLayoutNeeded();
break;
}
@@ -1003,20 +1019,20 @@
if ((dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0
&& mService.mWallpaperControllerLocked.adjustWallpaperWindows()) {
mService.mLayersController.assignLayersLocked(windows);
- dc.layoutNeeded = true;
+ dc.setLayoutNeeded();
}
if (isDefaultDisplay
&& (dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
if (mService.updateOrientationFromAppTokensLocked(true)) {
- dc.layoutNeeded = true;
+ dc.setLayoutNeeded();
mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
}
}
if ((dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
- dc.layoutNeeded = true;
+ dc.setLayoutNeeded();
}
// FIRST LOOP: Perform a layout, if needed.
@@ -1407,14 +1423,14 @@
}
void dumpLayoutNeededDisplayIds(PrintWriter pw) {
- if (!layoutNeeded()) {
+ if (!isLayoutNeeded()) {
return;
}
- pw.print(" layoutNeeded on displays=");
+ pw.print(" mLayoutNeeded on displays=");
final int count = mChildren.size();
for (int displayNdx = 0; displayNdx < count; ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
- if (displayContent.layoutNeeded) {
+ if (displayContent.isLayoutNeeded()) {
pw.print(displayContent.getDisplayId());
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9e8c609..f6598c1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -70,12 +70,6 @@
// Whether mBounds is fullscreen
private boolean mFillsParent = true;
- /**
- * Contains configurations settings that are different from the parent configuration due to
- * stack specific operations. E.g. {@link #setBounds}.
- */
- Configuration mOverrideConfig = Configuration.EMPTY;
-
// For comparison with DisplayContent bounds.
private Rect mTmpRect = new Rect();
// For handling display rotations.
@@ -120,8 +114,9 @@
}
}
- if (wtoken.mParent != null) {
- wtoken.mParent.removeChild(wtoken);
+ final WindowContainer parent = wtoken.getParent();
+ if (parent != null) {
+ parent.removeChild(wtoken);
}
addChild(wtoken, addPos);
wtoken.mTask = this;
@@ -153,7 +148,7 @@
if (content != null) {
content.mDimLayerController.removeDimLayerUser(this);
}
- mParent.removeChild(this);
+ getParent().removeChild(this);
mService.mTaskIdToTask.delete(mTaskId);
}
@@ -165,7 +160,7 @@
if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: removing taskId=" + mTaskId
+ " from stack=" + mStack);
EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
- mParent.removeChild(this);
+ getParent().removeChild(this);
stack.addTask(this, toTop);
}
@@ -254,7 +249,7 @@
if (displayContent != null) {
displayContent.mDimLayerController.updateDimLayer(this);
}
- mOverrideConfig = mFillsParent ? Configuration.EMPTY : overrideConfig;
+ onOverrideConfigurationChanged(mFillsParent ? Configuration.EMPTY : overrideConfig);
return boundsChange;
}
@@ -321,8 +316,7 @@
*/
void prepareFreezingBounds() {
mPreparedFrozenBounds.set(mBounds);
- mPreparedFrozenMergedConfig.setTo(mService.mGlobalConfiguration);
- mPreparedFrozenMergedConfig.updateFrom(mOverrideConfig);
+ mPreparedFrozenMergedConfig.setTo(getConfiguration());
}
/**
@@ -334,9 +328,9 @@
* bounds's bottom; false if the task's top should be aligned
* the adjusted bounds's top.
*/
- void alignToAdjustedBounds(
- Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
- if (!isResizeable() || mOverrideConfig == Configuration.EMPTY) {
+ void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
+ final Configuration overrideConfig = getOverrideConfiguration();
+ if (!isResizeable() || Configuration.EMPTY.equals(overrideConfig)) {
return;
}
@@ -348,7 +342,7 @@
mTmpRect2.offsetTo(adjustedBounds.left, adjustedBounds.top);
}
setTempInsetBounds(tempInsetBounds);
- resizeLocked(mTmpRect2, mOverrideConfig, false /* forced */);
+ resizeLocked(mTmpRect2, overrideConfig, false /* forced */);
}
/** Return true if the current bound can get outputted to the rest of the system as-is. */
@@ -500,12 +494,12 @@
mTmpRect2.set(mBounds);
if (!StackId.isTaskResizeAllowed(mStack.mStackId)) {
- setBounds(mTmpRect2, mOverrideConfig);
+ setBounds(mTmpRect2, getOverrideConfiguration());
return;
}
displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
- if (setBounds(mTmpRect2, mOverrideConfig) != BOUNDS_CHANGE_NONE) {
+ if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) {
// Post message to inform activity manager of the bounds change simulating a one-way
// call. We do this to prevent a deadlock between window manager lock and activity
// manager lock been held.
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 21db840..6887312 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -478,7 +478,7 @@
private int getDimSide(int x) {
if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
|| !mTask.mStack.fillsParent()
- || mService.mGlobalConfiguration.orientation != ORIENTATION_LANDSCAPE) {
+ || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
return CTRL_NONE;
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index e374185e..e98fc39 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -230,7 +230,7 @@
}
}
alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : mBounds, insetBounds);
- mDisplayContent.layoutNeeded = true;
+ mDisplayContent.setLayoutNeeded();
}
private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
@@ -354,11 +354,8 @@
// If the rotation or density didn't match, we'll update it in onConfigurationChanged.
}
- boolean onConfigurationChanged() {
- return updateBoundsAfterConfigChange();
- }
-
- private boolean updateBoundsAfterConfigChange() {
+ /** @return true if bounds were updated to some non-empty value. */
+ boolean updateBoundsAfterConfigChange() {
if (mDisplayContent == null) {
// If the stack is already detached we're not updating anything,
// as it's going away soon anyway.
@@ -459,7 +456,7 @@
// Snap the position to a target.
final int rotation = displayInfo.rotation;
- final int orientation = mService.mGlobalConfiguration.orientation;
+ final int orientation = mDisplayContent.getConfiguration().orientation;
mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds);
final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
mService.mContext.getResources(), displayWidth, displayHeight,
@@ -598,7 +595,7 @@
if (mChildren.isEmpty()) {
mDisplayContent.moveStack(this, false);
}
- mDisplayContent.layoutNeeded = true;
+ mDisplayContent.setLayoutNeeded();
}
for (int appNdx = mExitingAppTokens.size() - 1; appNdx >= 0; --appNdx) {
final AppWindowToken wtoken = mExitingAppTokens.get(appNdx);
@@ -713,7 +710,7 @@
di.logicalWidth,
di.logicalHeight,
dockDividerWidth,
- mService.mGlobalConfiguration.orientation == ORIENTATION_PORTRAIT,
+ mDisplayContent.getConfiguration().orientation == ORIENTATION_PORTRAIT,
mTmpRect2).getMiddleTarget().position;
if (dockOnTopOrLeft) {
@@ -1080,7 +1077,7 @@
final Rect insetBounds = mImeGoingAway ? mBounds : mFullyAdjustedImeBounds;
task.alignToAdjustedBounds(mAdjustedBounds, insetBounds, getDockSide() == DOCKED_TOP);
- mDisplayContent.layoutNeeded = true;
+ mDisplayContent.setLayoutNeeded();
}
boolean isAdjustedForMinimizedDockedStack() {
@@ -1182,7 +1179,7 @@
return DOCKED_INVALID;
}
mDisplayContent.getLogicalDisplayRect(mTmpRect);
- final int orientation = mService.mGlobalConfiguration.orientation;
+ final int orientation = mDisplayContent.getConfiguration().orientation;
return getDockSideUnchecked(bounds, mTmpRect, orientation);
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index af0fbd3..8777d88 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import android.annotation.CallSuper;
+import android.content.res.Configuration;
import android.view.animation.Animation;
import java.util.Comparator;
@@ -34,13 +35,33 @@
*/
class WindowContainer<E extends WindowContainer> implements Comparable<WindowContainer> {
- // The parent of this window container.
- protected WindowContainer mParent = null;
+ /**
+ * The parent of this window container.
+ * For removing or setting new parent {@link #setParent} should be used, because it also
+ * performs configuration updates based on new parent's settings.
+ */
+ private WindowContainer mParent = null;
// List of children for this window container. List is in z-order as the children appear on
// screen with the top-most window container at the tail of the list.
protected final LinkedList<E> mChildren = new LinkedList();
+ /** Contains override configuration settings applied to this window container. */
+ private Configuration mOverrideConfiguration = new Configuration();
+
+ /**
+ * Contains full configuration applied to this window container. Corresponds to full parent's
+ * config with applied {@link #mOverrideConfiguration}.
+ */
+ private Configuration mFullConfiguration = new Configuration();
+
+ /**
+ * Contains merged override configuration settings from the top of the hierarchy down to this
+ * particular instance. It is different from {@link #mFullConfiguration} because it starts from
+ * topmost container's override config instead of global config.
+ */
+ private Configuration mMergedOverrideConfiguration = new Configuration();
+
// The specified orientation for this window container.
protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
@@ -48,6 +69,14 @@
return mParent;
}
+ final protected void setParent(WindowContainer parent) {
+ mParent = parent;
+ // Update full configuration of this container and all its children.
+ onConfigurationChanged(mParent != null ? mParent.mFullConfiguration : Configuration.EMPTY);
+ // Update merged override configuration of this container and all its children.
+ onMergedOverrideConfigurationChanged();
+ }
+
// Temp. holders for a chain of containers we are currently processing.
private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList();
private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList();
@@ -61,12 +90,12 @@
*/
@CallSuper
protected void addChild(E child, Comparator<E> comparator) {
- if (child.mParent != null) {
+ if (child.getParent() != null) {
throw new IllegalArgumentException("addChild: container=" + child.getName()
- + " is already a child of container=" + child.mParent.getName()
+ + " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
- child.mParent = this;
+ child.setParent(this);
if (mChildren.isEmpty() || comparator == null) {
mChildren.add(child);
@@ -87,12 +116,12 @@
/** Adds the input window container has a child of this container at the input index. */
@CallSuper
protected void addChild(E child, int index) {
- if (child.mParent != null) {
+ if (child.getParent() != null) {
throw new IllegalArgumentException("addChild: container=" + child.getName()
- + " is already a child of container=" + child.mParent.getName()
+ + " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
- child.mParent = this;
+ child.setParent(this);
mChildren.add(index, child);
}
@@ -104,7 +133,7 @@
@CallSuper
void removeChild(E child) {
if (mChildren.remove(child)) {
- child.mParent = null;
+ child.setParent(null);
} else {
throw new IllegalArgumentException("removeChild: container=" + child.getName()
+ " is not a child of container=" + getName());
@@ -158,6 +187,73 @@
return false;
}
+ /**
+ * Returns full configuration applied to this window container.
+ * This method should be used for getting settings applied in each particular level of the
+ * hierarchy.
+ */
+ Configuration getConfiguration() {
+ return mFullConfiguration;
+ }
+
+ /**
+ * Notify that parent config changed and we need to update full configuration.
+ * @see #mFullConfiguration
+ */
+ void onConfigurationChanged(Configuration newParentConfig) {
+ mFullConfiguration.setTo(newParentConfig);
+ mFullConfiguration.updateFrom(mOverrideConfiguration);
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mChildren.get(i);
+ child.onConfigurationChanged(mFullConfiguration);
+ }
+ }
+
+ /** Returns override configuration applied to this window container. */
+ Configuration getOverrideConfiguration() {
+ return mOverrideConfiguration;
+ }
+
+ /**
+ * Update override configuration and recalculate full config.
+ * @see #mOverrideConfiguration
+ * @see #mFullConfiguration
+ */
+ void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ mOverrideConfiguration.setTo(overrideConfiguration);
+ // Update full configuration of this container and all its children.
+ onConfigurationChanged(mParent != null ? mParent.getConfiguration() : Configuration.EMPTY);
+ // Update merged override config of this container and all its children.
+ onMergedOverrideConfigurationChanged();
+ }
+
+ /**
+ * Get merged override configuration from the top of the hierarchy down to this
+ * particular instance. This should be reported to client as override config.
+ */
+ Configuration getMergedOverrideConfiguration() {
+ return mMergedOverrideConfiguration;
+ }
+
+ /**
+ * Update merged override configuration based on corresponding parent's config and notify all
+ * its children. If there is no parent, merged override configuration will set equal to current
+ * override config.
+ * @see #mMergedOverrideConfiguration
+ */
+ private void onMergedOverrideConfigurationChanged() {
+ if (mParent != null) {
+ mMergedOverrideConfiguration.setTo(mParent.getMergedOverrideConfiguration());
+ mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration);
+ } else {
+ mMergedOverrideConfiguration.setTo(mOverrideConfiguration);
+ }
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mChildren.get(i);
+ child.onMergedOverrideConfigurationChanged();
+ }
+ }
+
void setWaitingForDrawnIfResizingChanged() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1ed4055..310ad5a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -592,12 +592,6 @@
// State while inside of layoutAndPlaceSurfacesLocked().
boolean mFocusMayChange;
- /**
- * Current global configuration information. Contains general settings for the entire system,
- * corresponds to the configuration of the default display.
- */
- Configuration mGlobalConfiguration = new Configuration();
-
// This is held as long as we have the screen frozen, to give us time to
// perform a rotation animation when turning off shows the lock screen which
// changes the orientation.
@@ -2674,7 +2668,8 @@
// is running.
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked");
if (okToDisplay()) {
- DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
+ final DisplayContent displayContent = atoken.mTask.getDisplayContent();
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final int width = displayInfo.appWidth;
final int height = displayInfo.appHeight;
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM,
@@ -2711,10 +2706,10 @@
if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
+ " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+ " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
- Animation a = mAppTransition.loadAnimation(lp, transit, enter,
- mGlobalConfiguration.uiMode, mGlobalConfiguration.orientation, frame,
- displayFrame, insets, surfaceInsets, isVoiceInteraction, freeform,
- atoken.mTask.mTaskId);
+ final Configuration displayConfig = displayContent.getConfiguration();
+ Animation a = mAppTransition.loadAnimation(lp, transit, enter, displayConfig.uiMode,
+ displayConfig.orientation, frame, displayFrame, insets, surfaceInsets,
+ isVoiceInteraction, freeform, atoken.mTask.mTaskId);
if (a != null) {
if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken);
final int containingWidth = frame.width();
@@ -3029,7 +3024,7 @@
if (currentConfig.diff(mTempConfiguration) != 0) {
mWaitingForConfig = true;
final DisplayContent displayContent = getDefaultDisplayContentLocked();
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
int anim[] = new int[2];
if (displayContent.isDimming()) {
anim[0] = anim[1] = 0;
@@ -3104,11 +3099,7 @@
mWaitingForConfig = false;
mLastFinishedFreezeSource = "new-config";
}
- final boolean configChanged = mGlobalConfiguration.diff(config) != 0;
- if (!configChanged) {
- return null;
- }
- return mRoot.onConfigurationChanged(config);
+ return mRoot.setGlobalConfigurationIfNeeded(config);
}
}
@@ -3804,7 +3795,7 @@
displayContent.rebuildAppWindowList();
- // Set displayContent.layoutNeeded if window order changed.
+ // Set displayContent.mLayoutNeeded if window order changed.
final int tmpSize = mTmpWindows.size();
final int winSize = windows.size();
int tmpNdx = 0, winNdx = 0;
@@ -3822,13 +3813,13 @@
if (tmp != win) {
// Window order changed.
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
break;
}
}
if (tmpNdx != winNdx) {
// One list was different from the other.
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
mTmpWindows.clear();
@@ -3992,7 +3983,7 @@
}
task.moveTaskToStack(stack, toTop);
final DisplayContent displayContent = stack.getDisplayContent();
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
}
}
@@ -4044,7 +4035,7 @@
}
if (stack.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
&& stack.isVisible()) {
- stack.getDisplayContent().layoutNeeded = true;
+ stack.getDisplayContent().setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
}
return stack.getRawFullscreen();
@@ -4081,7 +4072,7 @@
}
task.positionTaskInStack(stack, position, bounds, config);
final DisplayContent displayContent = stack.getDisplayContent();
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
}
}
@@ -4101,7 +4092,7 @@
}
if (task.resizeLocked(bounds, overrideConfig, forced) && relayout) {
- task.getDisplayContent().layoutNeeded = true;
+ task.getDisplayContent().setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
}
}
@@ -5459,7 +5450,7 @@
synchronized(mWindowMap) {
changed = updateRotationUncheckedLocked(false);
if (!changed || forceRelayout) {
- getDefaultDisplayContentLocked().layoutNeeded = true;
+ getDefaultDisplayContentLocked().setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
}
}
@@ -5542,7 +5533,7 @@
mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION);
mWaitingForConfig = true;
final DisplayContent displayContent = getDefaultDisplayContentLocked();
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
final int[] anim = new int[2];
if (displayContent.isDimming()) {
anim[0] = anim[1] = 0;
@@ -5595,7 +5586,7 @@
// the top of the method, the caller is obligated to call computeNewConfigurationLocked().
// By updating the Display info here it will be available to
// computeScreenConfigurationLocked later.
- updateDisplayAndOrientationLocked(mGlobalConfiguration.uiMode);
+ updateDisplayAndOrientationLocked(mRoot.getConfiguration().uiMode);
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
if (!inTransaction) {
@@ -6961,8 +6952,8 @@
View view = null;
try {
- final Configuration overrideConfig = wtoken != null && wtoken.mTask != null
- ? wtoken.mTask.mOverrideConfig : null;
+ final Configuration overrideConfig =
+ wtoken != null ? wtoken.getMergedOverrideConfiguration() : null;
view = mPolicy.addStartingWindow(wtoken.token, sd.pkg, sd.theme,
sd.compatInfo, sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo,
sd.windowFlags, overrideConfig);
@@ -7908,12 +7899,13 @@
return;
}
configureDisplayPolicyLocked(displayContent);
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
boolean configChanged = updateOrientationFromAppTokensLocked(false);
- mTempConfiguration.setTo(mGlobalConfiguration);
+ final Configuration globalConfig = mRoot.getConfiguration();
+ mTempConfiguration.setTo(globalConfig);
computeScreenConfigurationLocked(mTempConfiguration);
- configChanged |= mGlobalConfiguration.diff(mTempConfiguration) != 0;
+ configChanged |= globalConfig.diff(mTempConfiguration) != 0;
if (configChanged) {
mWaitingForConfig = true;
@@ -8071,7 +8063,7 @@
w.setReportResizeHints();
boolean configChanged = w.isConfigChanged();
if (DEBUG_CONFIGURATION && configChanged) {
- Slog.v(TAG_WM, "Win " + w + " config changed: " + mGlobalConfiguration);
+ Slog.v(TAG_WM, "Win " + w + " config changed: " + w.getConfiguration());
}
final boolean dragResizingChanged = w.isDragResizeChanged()
&& !w.isDragResizingChangeReported();
@@ -8259,7 +8251,7 @@
mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
&& mode != UPDATE_FOCUS_WILL_PLACE_SURFACES);
if (imWindowChanged) {
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
newFocus = mRoot.computeFocusedWindow();
}
@@ -8286,7 +8278,7 @@
if ((focusChanged & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) {
// The change in focus caused us to need to do a layout. Okay.
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
mWindowPlacerLocked.performLayoutLockedInner(displayContent, true /*initial*/,
updateInputWindows);
@@ -8959,7 +8951,7 @@
}
}
pw.println();
- pw.print(" mGlobalConfiguration="); pw.println(mGlobalConfiguration);
+ pw.print(" mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad);
pw.print(" mCurrentFocus="); pw.println(mCurrentFocus);
if (mLastFocus != mCurrentFocus) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c3affeb..ee2da13 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1493,7 +1493,7 @@
}
changed = true;
if (displayContent != null) {
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
}
@@ -2039,7 +2039,7 @@
void setDisplayLayoutNeeded() {
if (mDisplayContent != null) {
- mDisplayContent.layoutNeeded = true;
+ mDisplayContent.setLayoutNeeded();
}
}
@@ -2886,14 +2886,8 @@
outConfig.setTo(mAppToken.mFrozenMergedConfig.peek());
return;
}
- final Task task = getTask();
- final Configuration overrideConfig = task != null
- ? task.mOverrideConfig
- : Configuration.EMPTY;
- outConfig.setTo(mService.mGlobalConfiguration);
- if (overrideConfig != Configuration.EMPTY) {
- outConfig.updateFrom(overrideConfig);
- }
+ outConfig.setTo(
+ mAppToken != null ? getConfiguration() : mDisplayContent.getConfiguration());
}
void reportResized() {
@@ -3528,7 +3522,7 @@
void requestUpdateWallpaperIfNeeded() {
if (mDisplayContent != null && (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- mDisplayContent.layoutNeeded = true;
+ mDisplayContent.setLayoutNeeded();
mService.mWindowPlacerLocked.requestTraversal();
}
@@ -3646,7 +3640,7 @@
// want to make sure to do a layout. If called from within the transaction
// loop, this will cause it to restart with a new layout.
if (displayContent != null) {
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index cbb5040..6c7d136 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -482,7 +482,7 @@
// Upon completion of a not-visible to visible status bar animation a relayout is
// required.
if (displayContent != null) {
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 6d10c5a..668e1b4 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -25,6 +25,7 @@
import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
@@ -168,7 +169,7 @@
mInLayout = false;
- if (mService.mRoot.layoutNeeded()) {
+ if (mService.mRoot.isLayoutNeeded()) {
if (++mLayoutRepeatCount < 6) {
requestTraversal();
} else {
@@ -204,10 +205,10 @@
final void performLayoutLockedInner(final DisplayContent displayContent,
boolean initial, boolean updateInputWindows) {
- if (!displayContent.layoutNeeded) {
+ if (!displayContent.isLayoutNeeded()) {
return;
}
- displayContent.layoutNeeded = false;
+ displayContent.clearLayoutNeeded();
WindowList windows = displayContent.getWindowList();
boolean isDefaultDisplay = displayContent.isDefaultDisplay;
@@ -228,12 +229,12 @@
if (DEBUG_LAYOUT) {
Slog.v(TAG, "-------------------------------------");
- Slog.v(TAG, "performLayout: needed="
- + displayContent.layoutNeeded + " dw=" + dw + " dh=" + dh);
+ Slog.v(TAG, "performLayout: needed=" + displayContent.isLayoutNeeded()
+ + " dw=" + dw + " dh=" + dh);
}
mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mService.mRotation,
- mService.mGlobalConfiguration.uiMode);
+ displayContent.getConfiguration().uiMode);
if (isDefaultDisplay) {
// Not needed on non-default displays.
mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
@@ -427,7 +428,7 @@
if ((displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0 &&
mWallpaperControllerLocked.adjustWallpaperWindows()) {
mService.mLayersController.assignLayersLocked(windows);
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
final WindowState lowerWallpaperTarget =
@@ -532,7 +533,7 @@
// This has changed the visibility of windows, so perform
// a new layout to get them all up-to-date.
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
// TODO(multidisplay): IMEs are only supported on the default display.
if (windows == mService.getDefaultWindowListLocked()
@@ -840,13 +841,14 @@
Rect appRect = win != null ? win.getContentFrameLw() :
new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
Rect insets = win != null ? win.mContentInsets : null;
+ final Configuration displayConfig = displayContent.getConfiguration();
// For the new aspect-scaled transition, we want it to always show
// above the animating opening/closing window, and we want to
// synchronize its thumbnail surface with the surface for the
// open/close animation (only on the way down)
anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
- insets, thumbnailHeader, taskId, mService.mGlobalConfiguration.uiMode,
- mService.mGlobalConfiguration.orientation);
+ insets, thumbnailHeader, taskId, displayConfig.uiMode,
+ displayConfig.orientation);
openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
openingAppAnimator.deferThumbnailDestruction =
!mService.mAppTransition.isNextThumbnailTransitionScaleUp();
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 7ed8e78..177652c 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import android.annotation.CallSuper;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
@@ -258,7 +257,7 @@
if (hidden == visible) {
hidden = !visible;
// Need to do a layout to ensure the wallpaper now has the correct size.
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
@@ -281,7 +280,7 @@
"Wallpaper token " + token + " hidden=" + !visible);
hidden = !visible;
// Need to do a layout to ensure the wallpaper now has the correct size.
- mService.getDefaultDisplayContentLocked().layoutNeeded = true;
+ mService.getDefaultDisplayContentLocked().setLayoutNeeded();
}
final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index f97e557..004edec 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
@@ -466,8 +466,8 @@
mInjector.getSystemUsersConfiguration(), userId);
mInjector.turnOffAllFlashLights(mCameraIdsWithFlash);
muteVolumeStreams();
- if (!mInjector.isWifiEnabled()) {
- mInjector.enableWifi();
+ if (!mInjector.getWifiManager().isWifiEnabled()) {
+ mInjector.getWifiManager().setWifiEnabled(true);
}
// Disable lock screen for demo users.
mInjector.getLockPatternUtils().setLockScreenDisabled(true, userId);
@@ -535,7 +535,7 @@
return mContext;
}
- private WifiManager getWifiManager() {
+ WifiManager getWifiManager() {
if (mWifiManager == null) {
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
}
@@ -644,14 +644,6 @@
mWakeLock.release();
}
- boolean isWifiEnabled() {
- return getWifiManager().isWifiEnabled();
- }
-
- void enableWifi() {
- getWifiManager().setWifiEnabled(true);
- }
-
void logSessionDuration(int duration) {
MetricsLogger.histogram(getContext(), DEMO_SESSION_DURATION, duration);
}
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index b76392c..3f5b96e 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -53,6 +53,8 @@
LOCAL_JACK_FLAGS := --multi-dex native
endif # EMMA_INSTRUMENT_STATIC
+LOCAL_STATIC_JAVA_LIBRARIES += ub-uiautomator
+
include $(BUILD_PACKAGE)
#########################################################################
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index b8ace28..514f095 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -43,6 +43,7 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
<application>
<uses-library android:name="android.test.runner" />
@@ -155,6 +156,9 @@
</intent-filter>
</activity-alias>
+ <activity android:name="com.android.server.am.TaskStackChangedListenerTest$ActivityA" />
+ <activity android:name="com.android.server.am.TaskStackChangedListenerTest$ActivityB" />
+
</application>
<instrumentation
diff --git a/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java b/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java
new file mode 100644
index 0000000..b47d17e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.ITaskStackListener;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.internal.annotations.GuardedBy;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class TaskStackChangedListenerTest extends ITaskStackListener.Stub {
+
+ private IActivityManager mService;
+
+ private static final Object sLock = new Object();
+ @GuardedBy("sLock")
+ private static boolean sTaskStackChangedCalled;
+ private static boolean sActivityBResumed;
+
+ @Before
+ public void setUp() throws Exception {
+ mService = ActivityManagerNative.getDefault();
+ mService.registerTaskStackListener(this);
+ }
+
+ @Test
+ public void testTaskStackChanged_afterFinish() throws Exception {
+ Context ctx = InstrumentationRegistry.getContext();
+ ctx.startActivity(new Intent(ctx, ActivityA.class));
+ UiDevice.getInstance(getInstrumentation()).waitForIdle();
+ synchronized (sLock) {
+ Assert.assertTrue(sTaskStackChangedCalled);
+ }
+ Assert.assertTrue(sActivityBResumed);
+ }
+
+ @Override
+ public void onTaskStackChanged() throws RemoteException {
+ synchronized (sLock) {
+ sTaskStackChangedCalled = true;
+ }
+ }
+
+ @Override
+ public void onActivityPinned() throws RemoteException {
+ }
+
+ @Override
+ public void onPinnedActivityRestartAttempt() throws RemoteException {
+ }
+
+ @Override
+ public void onPinnedStackAnimationEnded() throws RemoteException {
+ }
+
+ @Override
+ public void onActivityForcedResizable(String packageName, int taskId) throws RemoteException {
+ }
+
+ @Override
+ public void onActivityDismissingDockedStack() throws RemoteException {
+ }
+
+ public static class ActivityA extends Activity {
+
+ private boolean mActivityBLaunched = false;
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ if (mActivityBLaunched) {
+ return;
+ }
+ mActivityBLaunched = true;
+ finish();
+ startActivity(new Intent(this, ActivityB.class));
+ }
+ }
+
+ public static class ActivityB extends Activity {
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ synchronized (sLock) {
+ sTaskStackChangedCalled = false;
+ }
+ sActivityBResumed = true;
+ finish();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
index 3c99174..25f9100 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
@@ -68,7 +68,7 @@
/* fdin*/ null,
/* fdout*/ fd.getFileDescriptor(),
/* fderr*/ fd.getFileDescriptor(),
- args, rr);
+ args, null, rr);
}
return readAll(out);
} finally {
diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
index 56a170c..afb1197 100644
--- a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
@@ -46,6 +46,7 @@
import android.content.res.Configuration;
import android.media.AudioManager;
import android.net.Uri;
+import android.net.wifi.WifiManager;
import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
@@ -97,6 +98,7 @@
private @Mock NotificationManager mNm;
private @Mock ActivityManagerInternal mAmi;
private @Mock AudioManager mAudioManager;
+ private @Mock WifiManager mWifiManager;
private @Mock LockPatternUtils mLockPatternUtils;
private MockPreloadAppsInstaller mPreloadAppsInstaller;
private MockContentResolver mContentResolver;
@@ -227,6 +229,7 @@
final UserInfo userInfo = new UserInfo(TEST_DEMO_USER, "demo_user",
UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
when(mUm.getUserInfo(TEST_DEMO_USER)).thenReturn(userInfo);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
final int minVolume = -111;
for (int stream : RetailDemoModeService.VOLUME_STREAMS_TO_MUTE) {
when(mAudioManager.getStreamMinVolume(stream)).thenReturn(minVolume);
@@ -238,6 +241,7 @@
verify(mAudioManager).setStreamVolume(stream, minVolume, 0);
}
verify(mLockPatternUtils).setLockScreenDisabled(true, TEST_DEMO_USER);
+ verify(mWifiManager).setWifiEnabled(true);
}
private void setCameraPackage(String pkgName) {
@@ -321,6 +325,11 @@
}
@Override
+ WifiManager getWifiManager() {
+ return mWifiManager;
+ }
+
+ @Override
void switchUser(int userId) {
if (mLatch != null) {
mLatch.countDown();
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index eb2372a..6eb347b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -19,6 +19,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import android.content.res.Configuration;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -28,6 +29,8 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static org.junit.Assert.assertEquals;
@@ -377,6 +380,160 @@
assertEquals(1, child2223.compareTo(child21));
}
+ @Test
+ public void testConfigurationInit() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+
+ // Check root container initial config.
+ final TestWindowContainer root = builder.setLayer(0).build();
+ assertEquals(Configuration.EMPTY, root.getOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, root.getMergedOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, root.getConfiguration());
+
+ // Check child initial config.
+ final TestWindowContainer child1 = root.addChildWindow();
+ assertEquals(Configuration.EMPTY, child1.getOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, child1.getMergedOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, child1.getConfiguration());
+
+ // Check child initial config if root has overrides.
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+ final TestWindowContainer child2 = root.addChildWindow();
+ assertEquals(Configuration.EMPTY, child2.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child2.getMergedOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child2.getConfiguration());
+
+ // Check child initial config if root has parent config set.
+ final Configuration rootParentConfig = new Configuration();
+ rootParentConfig.fontScale = 0.8f;
+ rootParentConfig.orientation = SCREEN_ORIENTATION_LANDSCAPE;
+ root.onConfigurationChanged(rootParentConfig);
+ final Configuration rootFullConfig = new Configuration(rootParentConfig);
+ rootFullConfig.updateFrom(rootOverrideConfig);
+
+ final TestWindowContainer child3 = root.addChildWindow();
+ assertEquals(Configuration.EMPTY, child3.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child3.getMergedOverrideConfiguration());
+ assertEquals(rootFullConfig, child3.getConfiguration());
+ }
+
+ @Test
+ public void testConfigurationChangeOnAddRemove() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+
+ // Init root's config.
+ final TestWindowContainer root = builder.setLayer(0).build();
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ // Init child's config.
+ final TestWindowContainer child = root.addChildWindow();
+ final Configuration childOverrideConfig = new Configuration();
+ childOverrideConfig.densityDpi = 320;
+ child.onOverrideConfigurationChanged(childOverrideConfig);
+
+ // Check configuration update when child is removed from parent.
+ root.removeChild(child);
+ assertEquals(childOverrideConfig, child.getOverrideConfiguration());
+ assertEquals(childOverrideConfig, child.getMergedOverrideConfiguration());
+ assertEquals(childOverrideConfig, child.getConfiguration());
+
+ // It may be paranoia... but let's check if parent's config didn't change after removal.
+ assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getConfiguration());
+
+ // Check configuration update when child is added to parent.
+ final Configuration mergedOverrideConfig = new Configuration(root.getConfiguration());
+ mergedOverrideConfig.updateFrom(childOverrideConfig);
+ root.addChildWindow(child);
+ assertEquals(childOverrideConfig, child.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
+ assertEquals(mergedOverrideConfig, child.getConfiguration());
+ }
+
+ @Test
+ public void testConfigurationChangePropagation() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+
+ // Builds 3-level vertical hierarchy with one window container on each level.
+ // In addition to different overrides on each level, everyone in hierarchy will have one
+ // common overridden value - orientation;
+
+ // Init root's config.
+ final TestWindowContainer root = builder.setLayer(0).build();
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ rootOverrideConfig.orientation = SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ // Init children.
+ final TestWindowContainer child1 = root.addChildWindow();
+ final Configuration childOverrideConfig1 = new Configuration();
+ childOverrideConfig1.densityDpi = 320;
+ childOverrideConfig1.orientation = SCREEN_ORIENTATION_LANDSCAPE;
+ child1.onOverrideConfigurationChanged(childOverrideConfig1);
+
+ final TestWindowContainer child2 = child1.addChildWindow();
+ final Configuration childOverrideConfig2 = new Configuration();
+ childOverrideConfig2.screenWidthDp = 150;
+ childOverrideConfig2.orientation = SCREEN_ORIENTATION_PORTRAIT;
+ child2.onOverrideConfigurationChanged(childOverrideConfig2);
+
+ // Check configuration on all levels when root override is updated.
+ rootOverrideConfig.smallestScreenWidthDp = 200;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ final Configuration mergedOverrideConfig1 = new Configuration(rootOverrideConfig);
+ mergedOverrideConfig1.updateFrom(childOverrideConfig1);
+ final Configuration mergedConfig1 = new Configuration(mergedOverrideConfig1);
+
+ final Configuration mergedOverrideConfig2 = new Configuration(mergedOverrideConfig1);
+ mergedOverrideConfig2.updateFrom(childOverrideConfig2);
+ final Configuration mergedConfig2 = new Configuration(mergedOverrideConfig2);
+
+ assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getConfiguration());
+
+ assertEquals(childOverrideConfig1, child1.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig1, child1.getConfiguration());
+
+ assertEquals(childOverrideConfig2, child2.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig2, child2.getConfiguration());
+
+ // Check configuration on all levels when root parent config is updated.
+ final Configuration rootParentConfig = new Configuration();
+ rootParentConfig.screenHeightDp = 100;
+ rootParentConfig.orientation = SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ root.onConfigurationChanged(rootParentConfig);
+ final Configuration mergedRootConfig = new Configuration(rootParentConfig);
+ mergedRootConfig.updateFrom(rootOverrideConfig);
+
+ mergedConfig1.setTo(mergedRootConfig);
+ mergedConfig1.updateFrom(mergedOverrideConfig1);
+
+ mergedConfig2.setTo(mergedConfig1);
+ mergedConfig2.updateFrom(mergedOverrideConfig2);
+
+ assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+ assertEquals(mergedRootConfig, root.getConfiguration());
+
+ assertEquals(childOverrideConfig1, child1.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig1, child1.getConfiguration());
+
+ assertEquals(childOverrideConfig2, child2.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig2, child2.getConfiguration());
+ }
+
/* Used so we can gain access to some protected members of the {@link WindowContainer} class */
private class TestWindowContainer extends WindowContainer<TestWindowContainer> {
private final int mLayer;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ad5b9d1..deed6d4 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -182,6 +182,13 @@
public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
/**
+ * Since the default voicemail number is empty, if a SIM card does not have a voicemail number
+ * available the user cannot use voicemail. This flag allows the user to edit the voicemail
+ * number in such cases, and is false by default.
+ */
+ public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL= "editable_voicemail_number_bool";
+
+ /**
* Determine whether the voicemail notification is persistent in the notification bar. If true,
* the voicemail notifications cannot be dismissed from the notification bar.
*/
@@ -1084,6 +1091,7 @@
sDefaults.putBoolean(KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL, true);
sDefaults.putBoolean(KEY_USE_HFA_FOR_PROVISIONING_BOOL, false);
+ sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL, false);
sDefaults.putBoolean(KEY_USE_OTASP_FOR_PROVISIONING_BOOL, false);
sDefaults.putBoolean(KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL, false);
sDefaults.putBoolean(KEY_VOICE_PRIVACY_DISABLE_UI_BOOL, false);
diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml
index cbc6c76..5fdf0dd 100644
--- a/tests/VoiceInteraction/AndroidManifest.xml
+++ b/tests/VoiceInteraction/AndroidManifest.xml
@@ -1,6 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test.voiceinteraction">
+ <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="25" />
+
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.READ_LOGS" />
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 8d93b7f..a0b2977 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -76,6 +76,7 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -1180,7 +1181,7 @@
@Override
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) {
+ String[] args, ShellCallback shellCallback, ResultReceiver resultReceiver) {
}
};
}