diff options
82 files changed, 2490 insertions, 724 deletions
diff --git a/Android.mk b/Android.mk index 121a38efd587..e352bc70d085 100644 --- a/Android.mk +++ b/Android.mk @@ -332,6 +332,7 @@ LOCAL_SRC_FILES += \ 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 2d3fde86de38..3d116029ad17 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4042,6 +4042,7 @@ package android.app { 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 @@ package android.telephony { 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 @@ package java.lang.annotation { 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 @@ package java.lang.reflect { 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 d0116eedaf6b..1ab4884ed736 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4172,6 +4172,7 @@ package android.app { 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 @@ package android.telephony { 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 @@ package java.lang.annotation { 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 @@ package java.lang.reflect { 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 9c744c50a6f4..6d4590def9c3 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4045,6 +4045,7 @@ package android.app { 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 @@ package android.telephony { 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 @@ package java.lang.annotation { 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 @@ package java.lang.reflect { 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 91334bd87296..3759de2e7197 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 static android.app.ActivityManager.StackId.INVALID_STACK_ID; 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.Intent; 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.Build; 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 com.android.internal.util.Preconditions; 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 @@ public class Am extends BaseCommand { // 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 @@ public class Am extends BaseCommand { " --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 @@ public class Am extends BaseCommand { 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 @@ public class Am extends BaseCommand { 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 @@ public class Am extends BaseCommand { } } - 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 32a8088e9c4e..ace4e3284930 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.Bundle; 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 libcore.io.IoUtils; 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 @@ public final class Pm { } } + 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 5a9498f88d5a..191cc4944e26 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -311,9 +311,7 @@ public class AppOpsManager { /** 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 ea8ba2f1e6df..cf77567540f0 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -23,7 +23,6 @@ import libcore.io.IoUtils; 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 @@ public class Binder implements IBinder { 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 @@ public class Binder implements IBinder { * @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 @@ public class Binder implements IBinder { * @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 @@ final class BinderProxy implements IBinder { } 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 0fa87509a77a..f762a052cb41 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -220,11 +220,13 @@ public interface IBinder { * @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 000000000000..e7fe697f9c54 --- /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 fc804e592148..831c9b27ac45 100644 --- a/core/java/android/os/ShellCommand.java +++ b/core/java/android/os/ShellCommand.java @@ -40,6 +40,7 @@ public abstract class ShellCommand { private FileDescriptor mOut; private FileDescriptor mErr; private String[] mArgs; + private ShellCallback mShellCallback; private ResultReceiver mResultReceiver; private String mCmd; @@ -55,12 +56,13 @@ public abstract class ShellCommand { 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 abstract class ShellCommand { } 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 @@ public abstract class ShellCommand { 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 @@ public abstract class ShellCommand { // 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 @@ public abstract class ShellCommand { 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 c067da7e1dec..3baccee049b0 100644 --- a/core/java/com/android/internal/os/BaseCommand.java +++ b/core/java/com/android/internal/os/BaseCommand.java @@ -36,6 +36,8 @@ public abstract class BaseCommand { 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 @@ public abstract class BaseCommand { 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 abstract class BaseCommand { 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 000000000000..57d67890d840 --- /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/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java index 1203dd2c7217..effe21902f91 100644 --- a/core/java/com/android/internal/view/FloatingActionMode.java +++ b/core/java/com/android/internal/view/FloatingActionMode.java @@ -59,15 +59,19 @@ public class FloatingActionMode extends ActionMode { private final Runnable mMovingOff = new Runnable() { public void run() { - mFloatingToolbarVisibilityHelper.setMoving(false); - mFloatingToolbarVisibilityHelper.updateToolbarVisibility(); + if (isViewStillActive()) { + mFloatingToolbarVisibilityHelper.setMoving(false); + mFloatingToolbarVisibilityHelper.updateToolbarVisibility(); + } } }; private final Runnable mHideOff = new Runnable() { public void run() { - mFloatingToolbarVisibilityHelper.setHideRequested(false); - mFloatingToolbarVisibilityHelper.updateToolbarVisibility(); + if (isViewStillActive()) { + mFloatingToolbarVisibilityHelper.setHideRequested(false); + mFloatingToolbarVisibilityHelper.updateToolbarVisibility(); + } } }; @@ -301,6 +305,11 @@ public class FloatingActionMode extends ActionMode { mOriginatingView.removeCallbacks(mHideOff); } + private boolean isViewStillActive() { + return mOriginatingView.getWindowVisibility() == View.VISIBLE + && mOriginatingView.isShown(); + } + /** * A helper for showing/hiding the floating toolbar depending on certain states. */ 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 afc0cc4011a0..000000000000 --- 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 449acc66a845..30dd76027800 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 c234b6aa314b..ee7861313428 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 abbebdae0d57..b105a0f0b785 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 ca7b5e19fcd3..8ef806290230 100644 --- a/core/tests/coretests/src/android/print/BasePrintTest.java +++ b/core/tests/coretests/src/android/print/BasePrintTest.java @@ -57,8 +57,7 @@ import java.util.concurrent.TimeoutException; * 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 @@ abstract class BasePrintTest { 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 @@ abstract class BasePrintTest { } @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 75be426e94e1..2e9c8e735fd9 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.print.mockservice.StubbablePrinterDiscoverySession; 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 @@ public class IPrintManagerParametersTest extends BasePrintTest { 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 @@ public class IPrintManagerParametersTest extends BasePrintTest { } /** - * {@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 000000000000..35cfe223a6bf --- /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 000000000000..8f1a9edeacba --- /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 08ed233afc8c..83a3f98452c8 100644 --- a/docs/html/wear/preview/downloads.jd +++ b/docs/html/wear/preview/downloads.jd @@ -626,7 +626,7 @@ This is the Android Wear SDK Preview License Agreement (the “License Agreement 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 @@ This is the Android Wear SDK Preview License Agreement (the “License Agreement </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 @@ This is the Android Wear SDK Preview License Agreement (the “License Agreement <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 @@ This is the Android Wear SDK Preview License Agreement (the “License Agreement <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 7636d863aa8c..6006627a4e16 100644 --- a/docs/html/wear/preview/support.jd +++ b/docs/html/wear/preview/support.jd @@ -319,6 +319,22 @@ page.tags="preview", "developer preview" </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/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index af1410bf7965..3cb01de7513d 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -727,7 +727,9 @@ public class RingtoneManager { String setting = getSettingForType(type); if (setting == null) return; - ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId()); + if(!isInternalRingtoneUri(ringtoneUri)) { + ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId()); + } Settings.System.putStringForUser(resolver, setting, ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId()); @@ -744,6 +746,12 @@ public class RingtoneManager { } } + private static boolean isInternalRingtoneUri(Uri uri) { + Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(uri); + return uriWithoutUserId == null ? false : uriWithoutUserId.toString() + .startsWith(MediaStore.Audio.Media.INTERNAL_CONTENT_URI.toString()); + } + /** * Try opening the given ringtone locally first, but failover to * {@link IRingtonePlayer} if we can't access it directly. Typically happens diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index f688a8e11f6b..b3cfea520767 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 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat 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 7d9cc538cfa4..1a1aa57a0148 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 000000000000..9821fb8b18fa --- /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 000000000000..a8f286d774c6 --- /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 53be0e6c116c..3fc999fb903a 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 @@ public class DashboardCategory implements Parcelable { 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 05585e53e2a2..50867eb28e0a 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.os.UserManager; 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 class SettingsDrawerActivity extends Activity { 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 @@ public class SettingsDrawerActivity extends Activity { 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 @@ public class SettingsDrawerActivity extends Activity { 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 @@ public class SettingsDrawerActivity extends Activity { 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 @@ public class SettingsDrawerActivity extends Activity { 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 @@ public class SettingsDrawerActivity extends Activity { 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 class SettingsDrawerActivity extends Activity { } } - public HashMap<Pair<String, String>, Tile> getTileCache() { - if (sTileCache == null) { - getDashboardCategories(); - } - return sTileCache; - } - public void onProfileTileOpen() { finish(); } @@ -368,7 +376,11 @@ public class SettingsDrawerActivity extends Activity { ? 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 @@ public class SettingsDrawerActivity extends Activity { 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 @@ public class SettingsDrawerActivity extends Activity { } } + 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 1d6197a9333f..e1216a1c7e18 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 @@ public class SettingsDrawerAdapter extends BaseAdapter { mActivity = activity; } + /** + * @deprecated Remove after new IA + */ + @Deprecated void updateCategories() { List<DashboardCategory> categories = mActivity.getDashboardCategories(); mItems.clear(); @@ -64,6 +68,27 @@ public class SettingsDrawerAdapter extends BaseAdapter { 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 e70cc29a7744..81f0e84903f7 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 @@ public class TileUtils { 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 @@ public class TileUtils { 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, + 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, - HashMap<Pair<String, String>, Tile> cache) { + 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 @@ public class TileUtils { 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 @@ public class TileUtils { 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 @@ public class TileUtils { 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 @@ public class TileUtils { } } - 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 000000000000..1b8efa79ad73 --- /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 000000000000..09879d895a22 --- /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 95cb672666fe..f6fe17607539 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 @@ public class ButtonDispatcher { } } - /** - * 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 0a391ebda5be..427014714040 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.EventLogTags; 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 class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } }); } else { - mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */); + mActivityStarter.startActivity(mLeftButton.getIntent(), false /* dismissShade */); } } @@ -560,7 +578,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } public KeyguardAffordanceView getRightView() { - return mCameraImageView; + return mRightAffordanceView; } public View getLeftPreview() { @@ -613,7 +631,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 06c8b685ff63..c420927d960b 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.LinearLayout; 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 class NavigationBarInflaterView extends FrameLayout implements TunerServi 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 @@ public class NavigationBarInflaterView extends FrameLayout implements TunerServi 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 @@ public class NavigationBarInflaterView extends FrameLayout implements TunerServi 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 @@ public class NavigationBarInflaterView extends FrameLayout implements TunerServi 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 @@ public class NavigationBarInflaterView extends FrameLayout implements TunerServi ((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 5d1af2f5591c..fb2c335f96bb 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 @@ public class NotificationPanelView extends PanelView implements } 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 @@ public class NotificationPanelView extends PanelView implements 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 @@ public class NotificationPanelView extends PanelView implements private void updateQsState() { mQsContainer.setExpanded(mQsExpanded); + mNotificationStackScroller.setQsExpanded(mQsExpanded); mNotificationStackScroller.setScrollingEnabled( mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded || mQsExpansionFromOverscroll)); @@ -1427,7 +1425,7 @@ public class NotificationPanelView extends PanelView implements setQsExpansion(mQsMinExpansionHeight + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight)); } - updateStackHeight(expandedHeight); + updateExpandedHeight(expandedHeight); updateHeader(); updateUnlockIcon(); updateNotificationTranslucency(); @@ -1487,7 +1485,7 @@ public class NotificationPanelView extends PanelView implements 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 @@ public class NotificationPanelView extends PanelView implements 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 @@ public class NotificationPanelView extends PanelView implements 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 e6066aaa4c31..bcc5a3fa0a1b 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.view.accessibility.AccessibilityNodeInfo; 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 50e5b886488a..81da6720828a 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 @@ public class AmbientState { private boolean mShadeExpanded; private float mMaxHeadsUpTranslation; private boolean mDismissAllInProgress; + private int mLayoutMinHeight; public int getScrollY() { return mScrollY; @@ -137,10 +138,6 @@ public class AmbientState { mStackTranslation = stackTranslation; } - public int getLayoutHeight() { - return mLayoutHeight; - } - public void setLayoutHeight(int layoutHeight) { mLayoutHeight = layoutHeight; } @@ -154,7 +151,7 @@ public class AmbientState { } public int getInnerHeight() { - return mLayoutHeight - mTopPadding; + return Math.max(mLayoutHeight - mTopPadding, mLayoutMinHeight); } public boolean isShadeExpanded() { @@ -180,4 +177,8 @@ public class AmbientState { 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 90f410079fbb..a9dee133d839 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup return object.getBackgroundFadeAmount(); } }; + private boolean mQsExpanded; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -520,6 +517,7 @@ public class NotificationStackScrollLayout extends ViewGroup clampScrollPosition(); requestChildrenUpdate(); updateFirstAndLastBackgroundViews(); + updateAlgorithmLayoutMinHeight(); } private void requestAnimationOnViewResize(ExpandableNotificationRow row) { @@ -561,9 +559,14 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup } /** - * 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 @@ public class NotificationStackScrollLayout extends ViewGroup * 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 @@ public class NotificationStackScrollLayout extends ViewGroup ? mHeadsUpManager.getTopHeadsUpPinnedHeight() + mBottomStackPeekSize + mBottomStackSlowDownHeight : getLayoutMinHeight(); - return firstItemHeight + mTopPadding + mTopPaddingOverflow; + return firstItemHeight + (onKeyguard() ? mTopPadding : mIntrinsicPadding); } /** @@ -1153,6 +1160,10 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public boolean isAntiFalsingNeeded() { + return onKeyguard(); + } + + private boolean onKeyguard() { return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD; } @@ -1262,7 +1273,7 @@ public class NotificationStackScrollLayout extends ViewGroup if (!isScrollingEnabled()) { return false; } - if (isInsideQsContainer(ev)) { + if (isInsideQsContainer(ev) && !mIsBeingDragged) { return false; } mForcedScroll = null; @@ -2158,26 +2169,22 @@ public class NotificationStackScrollLayout extends ViewGroup */ 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup } 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 000000000000..2792d8c45865 --- /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 238bf927c43e..7c2eea3f68aa 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.Process; 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 @@ public class AppOpsService extends IAppOpsService.Stub { } @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 6b517210ef31..d2cfb6d51002 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.database.ContentObserver; 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 @@ public final class BatteryService extends SystemService { 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 @@ public final class BatteryService extends SystemService { } @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 6b73fec8c920..dbc1f31a9348 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.RemoteCallbackList; 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 @@ public class DeviceIdleController extends SystemService } @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 @@ public class DeviceIdleController extends SystemService 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 e5b611ca3231..d4396a44b3e7 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.RemoteCallbackList; 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative @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 @@ public final class ActivityManagerService extends ActivityManagerNative // 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 adf6d3670af3..aed9fa4435fd 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 @@ class ActivityManagerShellCommand extends ShellCommand { 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 @@ class ActivityManagerShellCommand extends ShellCommand { 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 @@ class ActivityManagerShellCommand extends ShellCommand { 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 @@ class ActivityManagerShellCommand extends ShellCommand { 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 645d114467ec..d8206276f31a 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -5034,7 +5034,7 @@ final class ActivityStack { 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 5feaf1fb98de..028c6ac54320 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 @@ class ActivityStarter { 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 5addffbeb426..9523a1ce20c5 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 class FingerprintService extends SystemService implements IBinder.DeathRe 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 74095acca16a..87f4030a9049 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 @@ package com.android.server.input; 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.PointerIcon; 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.FileReader; 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 @@ public class InputManagerService extends IInputManager.Stub @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 9d931467d914..fe3a02d193f6 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.ContentResolver; 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.PowerManager; 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 @@ public final class JobSchedulerService extends com.android.server.SystemService @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 6381aa747022..547cc51e9a05 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.LinkProperties; 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.RemoteCallbackList; 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @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 689917cd670a..f777aaebdd14 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.Environment; 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 @@ public class OtaDexoptService extends IOtaDexopt.Stub { @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 c76302c1053c..13428b2e2c8a 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.RemoteException; 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 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); @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 13f558e3dd13..19bf751417ce 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.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.UserHandle; @@ -3366,13 +3367,14 @@ public class ShortcutService extends IShortcutService.Stub { @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 c1cb032ce97f..c6b09d190bff 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.RemoteException; 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 @@ public class UserManagerService extends IUserManager.Stub { @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 b5559382ff2a..2f25971f0f0a 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 552803f47652..7b7db0e1440b 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.IBinder; 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 @@ public class StatusBarManagerService extends IStatusBarService.Stub { @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 846169cbf9c3..43cdf5978068 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.Binder; 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 @@ public class WebViewUpdateService extends SystemService { @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 ba26e13f69c3..9c5392ec8bd2 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree + " 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 7f97c463774e..da2c6a741100 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 @@ class DimLayerController { // 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 a09c5976e2ec..34a7390665ca 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -20,6 +20,7 @@ import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.WindowManager.DOCKED_BOTTOM; @@ -37,6 +38,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; 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; @@ -45,6 +47,7 @@ import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP; 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; @@ -99,7 +102,7 @@ class DisplayContent extends WindowContainer<TaskStack> { private Rect mContentRect = new Rect(); // Accessed directly by all users. - boolean layoutNeeded; + private boolean mLayoutNeeded; int pendingLayoutChanges; final boolean isDefaultDisplay; @@ -208,18 +211,26 @@ class DisplayContent extends WindowContainer<TaskStack> { 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); } } @@ -271,7 +282,7 @@ class DisplayContent extends WindowContainer<TaskStack> { } final int orientation = super.getOrientation(); - if (orientation != SCREEN_ORIENTATION_UNSET) { + if (orientation != SCREEN_ORIENTATION_UNSET && orientation != SCREEN_ORIENTATION_BEHIND) { if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "App is requesting an orientation, return " + orientation); return orientation; @@ -366,7 +377,7 @@ class DisplayContent extends WindowContainer<TaskStack> { } } addChild(stack, addIndex); - layoutNeeded = true; + setLayoutNeeded(); } /** @@ -683,8 +694,8 @@ class DisplayContent extends WindowContainer<TaskStack> { 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:"); @@ -777,7 +788,8 @@ class DisplayContent extends WindowContainer<TaskStack> { 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; } } @@ -1089,7 +1101,7 @@ class DisplayContent extends WindowContainer<TaskStack> { 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)); @@ -1126,6 +1138,20 @@ class DisplayContent extends WindowContainer<TaskStack> { 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 ef8f492f5a6f..15a952dd385e 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 @@ public class DockedStackDividerController implements DimLayerUser { // 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 @@ public class DockedStackDividerController implements DimLayerUser { } 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 8f533fb1ac61..219fd8ed6e50 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.os.UserHandle; 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { } } - 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { } } - if (layoutNeeded()) { + if (isLayoutNeeded()) { defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT; if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("mLayoutNeeded", defaultDisplay.pendingLayoutChanges); @@ -840,13 +856,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { } if (mService.mWaitingForDrawnCallback != null || - (mOrientationChangeComplete && !defaultDisplay.layoutNeeded && - !mUpdateRotation)) { + (mOrientationChangeComplete && !defaultDisplay.isLayoutNeeded() + && !mUpdateRotation)) { mService.checkDrawnWindowsLocked(); } @@ -925,7 +941,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { for (DisplayContent displayContent : displayList) { mService.mLayersController.assignLayersLocked(displayContent.getWindowList()); - displayContent.layoutNeeded = true; + displayContent.setLayoutNeeded(); } } @@ -951,7 +967,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { } // 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { repeats++; if (repeats > 6) { Slog.w(TAG, "Animation repeat aborted after too many iterations"); - dc.layoutNeeded = false; + dc.clearLayoutNeeded(); break; } @@ -1003,20 +1019,20 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { } 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 9e8c6091909e..f6598c14da44 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 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU // 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 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU } } - 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 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU if (content != null) { content.mDimLayerController.removeDimLayerUser(this); } - mParent.removeChild(this); + getParent().removeChild(this); mService.mTaskIdToTask.delete(mTaskId); } @@ -165,7 +160,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU 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 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU if (displayContent != null) { displayContent.mDimLayerController.updateDimLayer(this); } - mOverrideConfig = mFillsParent ? Configuration.EMPTY : overrideConfig; + onOverrideConfigurationChanged(mFillsParent ? Configuration.EMPTY : overrideConfig); return boundsChange; } @@ -321,8 +316,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU */ void prepareFreezingBounds() { mPreparedFrozenBounds.set(mBounds); - mPreparedFrozenMergedConfig.setTo(mService.mGlobalConfiguration); - mPreparedFrozenMergedConfig.updateFrom(mOverrideConfig); + mPreparedFrozenMergedConfig.setTo(getConfiguration()); } /** @@ -334,9 +328,9 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU * 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 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU 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 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU 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 21db840ad255..688731256f89 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 @@ class TaskPositioner implements DimLayer.DimLayerUser { 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 e374185e7210..e98fc393d404 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 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } } alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : mBounds, insetBounds); - mDisplayContent.layoutNeeded = true; + mDisplayContent.setLayoutNeeded(); } private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) { @@ -354,11 +354,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye // 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 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye // 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 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye 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 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye di.logicalWidth, di.logicalHeight, dockDividerWidth, - mService.mGlobalConfiguration.orientation == ORIENTATION_PORTRAIT, + mDisplayContent.getConfiguration().orientation == ORIENTATION_PORTRAIT, mTmpRect2).getMiddleTarget().position; if (dockOnTopOrLeft) { @@ -1080,7 +1077,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye final Rect insetBounds = mImeGoingAway ? mBounds : mFullyAdjustedImeBounds; task.alignToAdjustedBounds(mAdjustedBounds, insetBounds, getDockSide() == DOCKED_TOP); - mDisplayContent.layoutNeeded = true; + mDisplayContent.setLayoutNeeded(); } boolean isAdjustedForMinimizedDockedStack() { @@ -1182,7 +1179,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye 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 af0fbd3ba24a..8777d88a8777 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 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; */ 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 @@ class WindowContainer<E extends WindowContainer> implements Comparable<WindowCon 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 @@ class WindowContainer<E extends WindowContainer> implements Comparable<WindowCon */ @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 @@ class WindowContainer<E extends WindowContainer> implements Comparable<WindowCon /** 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 @@ class WindowContainer<E extends WindowContainer> implements Comparable<WindowCon @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 @@ class WindowContainer<E extends WindowContainer> implements Comparable<WindowCon 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 1ed4055746b8..310ad5a46aea 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 @@ public class WindowManagerService extends IWindowManager.Stub // 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 @@ public class WindowManagerService extends IWindowManager.Stub // 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub } task.moveTaskToStack(stack, toTop); final DisplayContent displayContent = stack.getDisplayContent(); - displayContent.layoutNeeded = true; + displayContent.setLayoutNeeded(); mWindowPlacerLocked.performSurfacePlacement(); } } @@ -4044,7 +4035,7 @@ public class WindowManagerService extends IWindowManager.Stub } 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 @@ public class WindowManagerService extends IWindowManager.Stub } task.positionTaskInStack(stack, position, bounds, config); final DisplayContent displayContent = stack.getDisplayContent(); - displayContent.layoutNeeded = true; + displayContent.setLayoutNeeded(); mWindowPlacerLocked.performSurfacePlacement(); } } @@ -4101,7 +4092,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (task.resizeLocked(bounds, overrideConfig, forced) && relayout) { - task.getDisplayContent().layoutNeeded = true; + task.getDisplayContent().setLayoutNeeded(); mWindowPlacerLocked.performSurfacePlacement(); } } @@ -5459,7 +5450,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { changed = updateRotationUncheckedLocked(false); if (!changed || forceRelayout) { - getDefaultDisplayContentLocked().layoutNeeded = true; + getDefaultDisplayContentLocked().setLayoutNeeded(); mWindowPlacerLocked.performSurfacePlacement(); } } @@ -5542,7 +5533,7 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub // 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub } } 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 c3affeb9a25c..ee2da1375998 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } changed = true; if (displayContent != null) { - displayContent.layoutNeeded = true; + displayContent.setLayoutNeeded(); } } @@ -2039,7 +2039,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void setDisplayLayoutNeeded() { if (mDisplayContent != null) { - mDisplayContent.layoutNeeded = true; + mDisplayContent.setLayoutNeeded(); } } @@ -2886,14 +2886,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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 cbb5040de18a..6c7d1362071a 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 @@ class WindowStateAnimator { // 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 6d10c5aaf34e..668e1b4ec011 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.LAYOUT_REPEAT_THRESHOLD 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 @@ class WindowSurfacePlacer { mInLayout = false; - if (mService.mRoot.layoutNeeded()) { + if (mService.mRoot.isLayoutNeeded()) { if (++mLayoutRepeatCount < 6) { requestTraversal(); } else { @@ -204,10 +205,10 @@ class WindowSurfacePlacer { 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 @@ class WindowSurfacePlacer { 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 @@ class WindowSurfacePlacer { 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 @@ class WindowSurfacePlacer { // 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 @@ class WindowSurfacePlacer { 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 7ed8e7844e0b..177652c21bdc 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 @@ class WindowToken extends WindowContainer<WindowState> { 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 @@ class WindowToken extends WindowContainer<WindowState> { "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 f97e5574d5b3..004edecdc418 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 @@ public class RetailDemoModeService extends SystemService { 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 @@ public class RetailDemoModeService extends SystemService { return mContext; } - private WifiManager getWifiManager() { + WifiManager getWifiManager() { if (mWifiManager == null) { mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); } @@ -644,14 +644,6 @@ public class RetailDemoModeService extends SystemService { 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/src/com/android/server/pm/ShortcutManagerTest7.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java index 3c99174142cb..25f9100d9252 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 @@ public class ShortcutManagerTest7 extends BaseShortcutManagerTest { /* 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 56a170c5cc0e..afb11979f41e 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.pm.UserInfo; 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 @@ public class RetailDemoModeServiceTest { 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 @@ public class RetailDemoModeServiceTest { 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 @@ public class RetailDemoModeServiceTest { 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 @@ public class RetailDemoModeServiceTest { } @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 eb2372a03a96..6eb347ba7444 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 @@ package com.android.server.wm; 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 java.util.Comparator; 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 @@ public class WindowContainerTests { 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 ad5b9d195e09..deed6d4fee78 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -182,6 +182,13 @@ public class CarrierConfigManager { 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 @@ public class CarrierConfigManager { 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 cbc6c76aa934..5fdf0dd3992c 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 8d93b7f32100..a0b297701869 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.Parcel; 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 @@ public final class BridgeContext extends Context { @Override public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, - String[] args, ResultReceiver resultReceiver) { + String[] args, ShellCallback shellCallback, ResultReceiver resultReceiver) { } }; } |