OSDN Git Service

release-request-53526352-7b51-4ab1-a661-632ffc55dd7c-for-git_oc-mr1-release-4371241...
authorandroid-build-team Robot <android-build-team-robot@google.com>
Mon, 2 Oct 2017 04:19:59 +0000 (04:19 +0000)
committerandroid-build-team Robot <android-build-team-robot@google.com>
Mon, 2 Oct 2017 04:19:59 +0000 (04:19 +0000)
Change-Id: I69703d6acfbdf2ae51254013175df1e1e6c1a61c

32 files changed:
config/preloaded-classes
config/preloaded-classes-extra
core/java/android/app/ActivityThread.java
core/java/android/app/assist/AssistStructure.java
core/java/android/util/Log.java
core/java/android/widget/SelectionActionModeHelper.java
data/etc/privapp-permissions-platform.xml
media/java/android/media/MediaRouter.java
packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
proto/src/wifi.proto
services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/am/ActivityStack.java
services/core/java/com/android/server/media/MediaSessionRecord.java
services/core/java/com/android/server/media/MediaSessionService.java
services/core/java/com/android/server/notification/NotificationManagerService.java
services/core/java/com/android/server/wm/WindowManagerService.java
tests/Compatibility/Android.mk
tests/Compatibility/AndroidManifest.xml
tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java
tests/Compatibility/src/com/android/compatibilitytest/AppCompatibilityRunner.java

index ff61d18..479cda4 100644 (file)
@@ -2192,6 +2192,7 @@ android.util.IntProperty
 android.util.Log
 android.util.Log$1
 android.util.Log$ImmediateLogWriter
+android.util.Log$PreloadHolder
 android.util.Log$TerribleFailureHandler
 android.util.LogPrinter
 android.util.LongArray
index 959fff5..09f393a 100644 (file)
@@ -8,6 +8,7 @@ android.media.MediaPlayer
 android.media.SoundPool
 android.text.format.Formatter
 android.text.Html$HtmlParser
+android.util.Log$PreloadHolder
 com.android.org.conscrypt.TrustedCertificateStore
 org.ccil.cowan.tagsoup.HTMLScanner
 sun.security.jca.Providers
index 2e4ce18..f01feea 100644 (file)
@@ -5267,7 +5267,7 @@ public final class ActivityThread {
                                 final ApplicationInfo aInfo =
                                         sPackageManager.getApplicationInfo(
                                                 packageName,
-                                                0 /*flags*/,
+                                                PackageManager.GET_SHARED_LIBRARY_FILES,
                                                 UserHandle.myUserId());
 
                                 if (mActivities.size() > 0) {
index 55c22de..c208f1d 100644 (file)
@@ -1260,12 +1260,12 @@ public class AssistStructure implements Parcelable {
          * <p>Typically used when the view associated with the view is a container for an HTML
          * document.
          *
-         * <strong>WARNING:</strong> a {@link android.service.autofill.AutofillService} should only
-         * use this domain for autofill purposes when it trusts the app generating it (i.e., the app
-         * defined by {@link AssistStructure#getActivityComponent()}).
+         * <p><b>Warning:</b> an autofill service cannot trust the value reported by this method
+         * without verifing its authenticity&mdash;see the "Web security" section of
+         * {@link android.service.autofill.AutofillService} for more details.
          *
          * @return domain-only part of the document. For example, if the full URL is
-         * {@code http://my.site/login?user=my_user}, it returns {@code my.site}.
+         * {@code https://my.site/login?user=my_user}, it returns {@code my.site}.
          */
         @Nullable public String getWebDomain() {
             return mWebDomain;
index 8691136..0299865 100644 (file)
@@ -392,7 +392,7 @@ public final class Log {
         // and the length of the tag.
         // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
         //       is too expensive to compute that ahead of time.
-        int bufferSize = NoPreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD  // Base.
+        int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD    // Base.
                 - 2                                                // Two terminators.
                 - (tag != null ? tag.length() : 0)                 // Tag length.
                 - 32;                                              // Some slack.
@@ -429,10 +429,10 @@ public final class Log {
     }
 
     /**
-     * NoPreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
+     * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
      * a JNI call during logging.
      */
-    static class NoPreloadHolder {
+    static class PreloadHolder {
         public final static int LOGGER_ENTRY_MAX_PAYLOAD =
                 logger_entry_max_payload_native();
     }
index 6c13e0c..513a18c 100644 (file)
@@ -51,12 +51,6 @@ final class SelectionActionModeHelper {
 
     private static final String LOG_TAG = "SelectActionModeHelper";
 
-    /**
-     * Maximum time (in milliseconds) to wait for a result before timing out.
-     */
-    // TODO: Consider making this a ViewConfiguration.
-    private static final int TIMEOUT_DURATION = 200;
-
     private final Editor mEditor;
     private final TextView mTextView;
     private final TextClassificationHelper mTextClassificationHelper;
@@ -89,7 +83,7 @@ final class SelectionActionModeHelper {
             resetTextClassificationHelper();
             mTextClassificationAsyncTask = new TextClassificationAsyncTask(
                     mTextView,
-                    TIMEOUT_DURATION,
+                    mTextClassificationHelper.getTimeoutDuration(),
                     adjustSelection
                             ? mTextClassificationHelper::suggestSelection
                             : mTextClassificationHelper::classifyText,
@@ -106,7 +100,7 @@ final class SelectionActionModeHelper {
             resetTextClassificationHelper();
             mTextClassificationAsyncTask = new TextClassificationAsyncTask(
                     mTextView,
-                    TIMEOUT_DURATION,
+                    mTextClassificationHelper.getTimeoutDuration(),
                     mTextClassificationHelper::classifyText,
                     this::invalidateActionMode)
                     .execute();
@@ -538,7 +532,7 @@ final class SelectionActionModeHelper {
     private static final class TextClassificationAsyncTask
             extends AsyncTask<Void, Void, SelectionResult> {
 
-        private final int mTimeOutDuration;
+        private final long mTimeOutDuration;
         private final Supplier<SelectionResult> mSelectionResultSupplier;
         private final Consumer<SelectionResult> mSelectionResultCallback;
         private final TextView mTextView;
@@ -551,7 +545,7 @@ final class SelectionActionModeHelper {
          * @param selectionResultCallback receives the selection results. Runs on the UiThread
          */
         TextClassificationAsyncTask(
-                @NonNull TextView textView, int timeOut,
+                @NonNull TextView textView, long timeOut,
                 @NonNull Supplier<SelectionResult> selectionResultSupplier,
                 @NonNull Consumer<SelectionResult> selectionResultCallback) {
             super(textView != null ? textView.getHandler() : null);
@@ -623,6 +617,9 @@ final class SelectionActionModeHelper {
         private LocaleList mLastClassificationLocales;
         private SelectionResult mLastClassificationResult;
 
+        /** Whether the TextClassifier has been initialized. */
+        private boolean mHot;
+
         TextClassificationHelper(TextClassifier textClassifier,
                 CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
             reset(textClassifier, text, selectionStart, selectionEnd, locales);
@@ -642,11 +639,13 @@ final class SelectionActionModeHelper {
 
         @WorkerThread
         public SelectionResult classifyText() {
-            return performClassification(null);
+            mHot = true;
+            return performClassification(null /* selection */);
         }
 
         @WorkerThread
         public SelectionResult suggestSelection() {
+            mHot = true;
             trimText();
             final TextSelection selection = mTextClassifier.suggestSelection(
                     mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
@@ -655,6 +654,22 @@ final class SelectionActionModeHelper {
             return performClassification(selection);
         }
 
+        /**
+         * Maximum time (in milliseconds) to wait for a textclassifier result before timing out.
+         */
+        // TODO: Consider making this a ViewConfiguration.
+        public long getTimeoutDuration() {
+            if (mHot) {
+                return 200;
+            } else {
+                // Return a slightly larger number than usual when the TextClassifier is first
+                // initialized. Initialization would usually take longer than subsequent calls to
+                // the TextClassifier. The impact of this on the UI is that we do not show the
+                // selection handles or toolbar until after this timeout.
+                return 500;
+            }
+        }
+
         private SelectionResult performClassification(@Nullable TextSelection selection) {
             if (!Objects.equals(mText, mLastClassificationText)
                     || mSelectionStart != mLastClassificationSelectionStart
index 7a2df85..9948f7e 100644 (file)
@@ -64,7 +64,7 @@ applications that come with the platform
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
     </privapp-permissions>
 
-    <privapp-permissions package="com.android.launcher">
+    <privapp-permissions package="com.android.launcher3">
         <permission name="android.permission.BIND_APPWIDGET"/>
         <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
     </privapp-permissions>
index 013e85e..b4fff48 100644 (file)
@@ -598,14 +598,20 @@ public class MediaRouter {
 
             @Override
             public void onRestoreRoute() {
-                // Skip restoring route if the selected route is not a system audio route, or
-                // MediaRouter is initializing.
-                if ((mSelectedRoute != mDefaultAudioVideo && mSelectedRoute != mBluetoothA2dpRoute)
-                        || mSelectedRoute == null) {
-                    return;
-                }
-                Log.v(TAG, "onRestoreRoute() : a2dp=" + isBluetoothA2dpOn());
-                mSelectedRoute.select();
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Skip restoring route if the selected route is not a system audio route,
+                        // MediaRouter is initializing, or mClient was changed.
+                        if (Client.this != mClient || mSelectedRoute == null
+                                || (mSelectedRoute != mDefaultAudioVideo
+                                        && mSelectedRoute != mBluetoothA2dpRoute)) {
+                            return;
+                        }
+                        Log.v(TAG, "onRestoreRoute() : route=" + mSelectedRoute);
+                        mSelectedRoute.select();
+                    }
+                });
             }
         }
     }
index c5eebcc..8a8bafa 100644 (file)
@@ -61,6 +61,7 @@ public class AssistManager implements ConfigurationChangedReceiver {
     private AssistOrbContainer mView;
     private final DeviceProvisionedController mDeviceProvisionedController;
     protected final AssistUtils mAssistUtils;
+    private final boolean mShouldEnableOrb;
 
     private IVoiceInteractionSessionShowCallback mShowCallback =
             new IVoiceInteractionSessionShowCallback.Stub() {
@@ -96,6 +97,7 @@ public class AssistManager implements ConfigurationChangedReceiver {
                 | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
                 | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
         onConfigurationChanged(context.getResources().getConfiguration());
+        mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic();
     }
 
     protected void registerVoiceInteractionSessionListener() {
@@ -179,7 +181,9 @@ public class AssistManager implements ConfigurationChangedReceiver {
 
     private void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
         maybeSwapSearchIcon(assistComponent, isService);
-        mView.show(true /* show */, true /* animate */);
+        if (mShouldEnableOrb) {
+            mView.show(true /* show */, true /* animate */);
+        }
     }
 
     private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
index 5c99961..debda21 100644 (file)
 
 package com.android.systemui.doze;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.format.DateUtils;
 import android.util.KeyValueListParser;
@@ -34,6 +39,10 @@ import java.util.Arrays;
 public class AlwaysOnDisplayPolicy {
     public static final String TAG = "AlwaysOnDisplayPolicy";
 
+    private static final long DEFAULT_PROX_SCREEN_OFF_DELAY_MS = 10 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_PROX_COOLDOWN_TRIGGER_MS = 2 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_PROX_COOLDOWN_PERIOD_MS = 5 * DateUtils.SECOND_IN_MILLIS;
+
     static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array";
     static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array";
     static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay";
@@ -46,7 +55,7 @@ public class AlwaysOnDisplayPolicy {
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_SCREEN_BRIGHTNESS_ARRAY
      */
-    public final int[] screenBrightnessArray;
+    public int[] screenBrightnessArray;
 
     /**
      * Integer array to map ambient brightness type to dimming scrim.
@@ -54,7 +63,7 @@ public class AlwaysOnDisplayPolicy {
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_DIMMING_SCRIM_ARRAY
      */
-    public final int[] dimmingScrimArray;
+    public int[] dimmingScrimArray;
 
     /**
      * Delay time(ms) from covering the prox to turning off the screen.
@@ -62,7 +71,7 @@ public class AlwaysOnDisplayPolicy {
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_SCREEN_OFF_DELAY_MS
      */
-    public final long proxScreenOffDelayMs;
+    public long proxScreenOffDelayMs;
 
     /**
      * The threshold time(ms) to trigger the cooldown timer, which will
@@ -71,7 +80,7 @@ public class AlwaysOnDisplayPolicy {
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_COOLDOWN_TRIGGER_MS
      */
-    public final long proxCooldownTriggerMs;
+    public long proxCooldownTriggerMs;
 
     /**
      * The period(ms) to turning off the prox sensor if
@@ -80,43 +89,78 @@ public class AlwaysOnDisplayPolicy {
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_COOLDOWN_PERIOD_MS
      */
-    public final long proxCooldownPeriodMs;
+    public long proxCooldownPeriodMs;
 
     private final KeyValueListParser mParser;
+    private final Context mContext;
+    private SettingsObserver mSettingsObserver;
 
     public AlwaysOnDisplayPolicy(Context context) {
-        final Resources resources = context.getResources();
+        mContext = context;
         mParser = new KeyValueListParser(',');
-
-        final String value = Settings.Global.getString(context.getContentResolver(),
-                Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
-
-        try {
-            mParser.setString(value);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Bad AOD constants");
-        }
-
-        proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
-                10 * DateUtils.SECOND_IN_MILLIS);
-        proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
-                2 * DateUtils.SECOND_IN_MILLIS);
-        proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
-                5 * DateUtils.SECOND_IN_MILLIS);
-        screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
-                resources.getIntArray(R.array.config_doze_brightness_sensor_to_brightness));
-        dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
-                resources.getIntArray(R.array.config_doze_brightness_sensor_to_scrim_opacity));
+        mSettingsObserver = new SettingsObserver(context.getMainThreadHandler());
+        mSettingsObserver.observe();
     }
 
     private int[] parseIntArray(final String key, final int[] defaultArray) {
         final String value = mParser.getString(key, null);
         if (value != null) {
-            return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
-                    Integer::parseInt).toArray();
+            try {
+                return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+                        Integer::parseInt).toArray();
+            } catch (NumberFormatException e) {
+                return defaultArray;
+            }
         } else {
             return defaultArray;
         }
     }
 
+    private final class SettingsObserver extends ContentObserver {
+        private final Uri ALWAYS_ON_DISPLAY_CONSTANTS_URI
+                = Settings.Global.getUriFor(Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        void observe() {
+            ContentResolver resolver = mContext.getContentResolver();
+            resolver.registerContentObserver(ALWAYS_ON_DISPLAY_CONSTANTS_URI,
+                    false, this, UserHandle.USER_ALL);
+            update(null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            update(uri);
+        }
+
+        public void update(Uri uri) {
+            if (uri == null || ALWAYS_ON_DISPLAY_CONSTANTS_URI.equals(uri)) {
+                final Resources resources = mContext.getResources();
+                final String value = Settings.Global.getString(mContext.getContentResolver(),
+                        Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+                try {
+                    mParser.setString(value);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Bad AOD constants");
+                }
+
+                proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
+                        DEFAULT_PROX_SCREEN_OFF_DELAY_MS);
+                proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
+                        DEFAULT_PROX_COOLDOWN_TRIGGER_MS);
+                proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
+                        DEFAULT_PROX_COOLDOWN_PERIOD_MS);
+                screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
+                        resources.getIntArray(
+                                R.array.config_doze_brightness_sensor_to_brightness));
+                dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
+                        resources.getIntArray(
+                                R.array.config_doze_brightness_sensor_to_scrim_opacity));
+            }
+        }
+    }
 }
index 76a1902..58f1448 100644 (file)
@@ -28,20 +28,21 @@ public class DozePauser implements DozeMachine.Part {
     public static final String TAG = DozePauser.class.getSimpleName();
     private final AlarmTimeout mPauseTimeout;
     private final DozeMachine mMachine;
-    private final long mTimeoutMs;
+    private final AlwaysOnDisplayPolicy mPolicy;
 
     public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager,
             AlwaysOnDisplayPolicy policy) {
         mMachine = machine;
         mPauseTimeout = new AlarmTimeout(alarmManager, this::onTimeout, TAG, handler);
-        mTimeoutMs = policy.proxScreenOffDelayMs;
+        mPolicy = policy;
     }
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         switch (newState) {
             case DOZE_AOD_PAUSING:
-                mPauseTimeout.schedule(mTimeoutMs, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+                mPauseTimeout.schedule(mPolicy.proxScreenOffDelayMs,
+                        AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
                 break;
             default:
                 mPauseTimeout.cancel();
index 50720e9..b5c0d53 100644 (file)
@@ -29,6 +29,8 @@ import android.os.PowerManager;
 import android.os.SystemClock;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.LinkedList;
 
 /**
@@ -57,8 +59,12 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
         }
     }
 
-    private LinkedList<Command> mCmdQueue = new LinkedList();
+    private final LinkedList<Command> mCmdQueue = new LinkedList<Command>();
 
+    private final Object mCompletionHandlingLock = new Object();
+    @GuardedBy("mCompletionHandlingLock")
+    private CreationAndCompletionThread mCompletionThread;
+    @GuardedBy("mCompletionHandlingLock")
     private Looper mLooper;
 
     /*
@@ -76,7 +82,10 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
 
         public void run() {
             Looper.prepare();
+            // ok to modify mLooper as here we are
+            // synchronized on mCompletionHandlingLock due to the Object.wait() in startSound(cmd)
             mLooper = Looper.myLooper();
+            if (DEBUG) Log.d(mTag, "in run: new looper " + mLooper);
             synchronized(this) {
                 AudioManager audioManager =
                     (AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE);
@@ -97,7 +106,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
                     if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
                             && (mCmd.uri.getEncodedPath().length() > 0)) {
                         if (!audioManager.isMusicActiveRemotely()) {
-                            synchronized(mQueueAudioFocusLock) {
+                            synchronized (mQueueAudioFocusLock) {
                                 if (mAudioManagerWithAudioFocus == null) {
                                     if (DEBUG) Log.d(mTag, "requesting AudioFocus");
                                     int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
@@ -129,7 +138,9 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
                         Log.e(mTag, "Exception while sleeping to sync notification playback"
                                 + " with ducking", e);
                     }
+                    if (DEBUG) { Log.d(mTag, "player.start"); }
                     if (mPlayer != null) {
+                        if (DEBUG) { Log.d(mTag, "mPlayer.release"); }
                         mPlayer.release();
                     }
                     mPlayer = player;
@@ -148,7 +159,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
         // is playing, let it continue until we're done, so there
         // is less of a glitch.
         try {
-            if (DEBUG) Log.d(mTag, "Starting playback");
+            if (DEBUG) { Log.d(mTag, "startSound()"); }
             //-----------------------------------
             // This is were we deviate from the AsyncPlayer implementation and create the
             // MediaPlayer in a new thread with which we're synchronized
@@ -158,10 +169,11 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
                 // matters
                 if((mLooper != null)
                         && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
+                    if (DEBUG) { Log.d(mTag, "in startSound quitting looper " + mLooper); }
                     mLooper.quit();
                 }
                 mCompletionThread = new CreationAndCompletionThread(cmd);
-                synchronized(mCompletionThread) {
+                synchronized (mCompletionThread) {
                     mCompletionThread.start();
                     mCompletionThread.wait();
                 }
@@ -209,13 +221,18 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
                         mPlayer = null;
                         synchronized(mQueueAudioFocusLock) {
                             if (mAudioManagerWithAudioFocus != null) {
+                                if (DEBUG) { Log.d(mTag, "in STOP: abandonning AudioFocus"); }
                                 mAudioManagerWithAudioFocus.abandonAudioFocus(null);
                                 mAudioManagerWithAudioFocus = null;
                             }
                         }
-                        if((mLooper != null)
-                                && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
-                            mLooper.quit();
+                        synchronized (mCompletionHandlingLock) {
+                            if ((mLooper != null) &&
+                                    (mLooper.getThread().getState() != Thread.State.TERMINATED))
+                            {
+                                if (DEBUG) { Log.d(mTag, "in STOP: quitting looper "+ mLooper); }
+                                mLooper.quit();
+                            }
                         }
                     } else {
                         Log.w(mTag, "STOP command without a player");
@@ -250,9 +267,11 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
         }
         // if there are no more sounds to play, end the Looper to listen for media completion
         synchronized (mCmdQueue) {
-            if (mCmdQueue.size() == 0) {
-                synchronized(mCompletionHandlingLock) {
-                    if(mLooper != null) {
+            synchronized(mCompletionHandlingLock) {
+                if (DEBUG) { Log.d(mTag, "onCompletion queue size=" + mCmdQueue.size()); }
+                if ((mCmdQueue.size() == 0)) {
+                    if (mLooper != null) {
+                        if (DEBUG) { Log.d(mTag, "in onCompletion quitting looper " + mLooper); }
                         mLooper.quit();
                     }
                     mCompletionThread = null;
@@ -269,13 +288,20 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
     }
 
     private String mTag;
+
+    @GuardedBy("mCmdQueue")
     private CmdThread mThread;
-    private CreationAndCompletionThread mCompletionThread;
-    private final Object mCompletionHandlingLock = new Object();
+
     private MediaPlayer mPlayer;
+
+
+    @GuardedBy("mCmdQueue")
     private PowerManager.WakeLock mWakeLock;
+
     private final Object mQueueAudioFocusLock = new Object();
-    private AudioManager mAudioManagerWithAudioFocus; // synchronized on mQueueAudioFocusLock
+    @GuardedBy("mQueueAudioFocusLock")
+    private AudioManager mAudioManagerWithAudioFocus;
+
     private int mNotificationRampTimeMs = 0;
 
     // The current state according to the caller.  Reality lags behind
@@ -311,6 +337,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
      */
     @Deprecated
     public void play(Context context, Uri uri, boolean looping, int stream) {
+        if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         PlayerBase.deprecateStreamTypeForPlayback(stream, "NotificationPlayer", "play");
         Command cmd = new Command();
         cmd.requestTime = SystemClock.uptimeMillis();
@@ -339,6 +366,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
      *          (see {@link MediaPlayer#setAudioAttributes(AudioAttributes)})
      */
     public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
+        if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         Command cmd = new Command();
         cmd.requestTime = SystemClock.uptimeMillis();
         cmd.code = PLAY;
@@ -357,6 +385,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
      * at this point.  Calling this multiple times has no ill effects.
      */
     public void stop() {
+        if (DEBUG) { Log.d(mTag, "stop"); }
         synchronized (mCmdQueue) {
             // This check allows stop to be called multiple times without starting
             // a thread that ends up doing nothing.
@@ -370,6 +399,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
         }
     }
 
+    @GuardedBy("mCmdQueue")
     private void enqueueLocked(Command cmd) {
         mCmdQueue.add(cmd);
         if (mThread == null) {
@@ -393,22 +423,26 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
      * @hide
      */
     public void setUsesWakeLock(Context context) {
-        if (mWakeLock != null || mThread != null) {
-            // if either of these has happened, we've already played something.
-            // and our releases will be out of sync.
-            throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
-                    + " mThread=" + mThread);
+        synchronized (mCmdQueue) {
+            if (mWakeLock != null || mThread != null) {
+                // if either of these has happened, we've already played something.
+                // and our releases will be out of sync.
+                throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
+                        + " mThread=" + mThread);
+            }
+            PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
         }
-        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
     }
 
+    @GuardedBy("mCmdQueue")
     private void acquireWakeLock() {
         if (mWakeLock != null) {
             mWakeLock.acquire();
         }
     }
 
+    @GuardedBy("mCmdQueue")
     private void releaseWakeLock() {
         if (mWakeLock != null) {
             mWakeLock.release();
index 8d62f2a..81b8622 100644 (file)
@@ -23,6 +23,7 @@ import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.drawable.Drawable;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
@@ -34,10 +35,8 @@ import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.R.drawable;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -135,11 +134,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
                 if (lastDevice != null) {
                     int batteryLevel = lastDevice.getBatteryLevel();
                     if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                        BluetoothDeviceLayerDrawable drawable = createLayerDrawable(mContext,
-                                R.drawable.ic_qs_bluetooth_connected, batteryLevel,
-                                mContext.getResources().getFraction(
-                                        R.fraction.bt_battery_scale_fraction, 1, 1));
-                        state.icon = new DrawableIcon(drawable);
+                        state.icon = new BluetoothBatteryDrawable(batteryLevel);
                     }
                 }
                 state.contentDescription = mContext.getString(
@@ -215,6 +210,22 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
         return new BluetoothDetailAdapter();
     }
 
+    private class BluetoothBatteryDrawable extends Icon {
+        private int mLevel;
+
+        BluetoothBatteryDrawable(int level) {
+            mLevel = level;
+        }
+
+        @Override
+        public Drawable getDrawable(Context context) {
+            return createLayerDrawable(context,
+                    R.drawable.ic_qs_bluetooth_connected, mLevel,
+                    context.getResources().getFraction(
+                            R.fraction.bt_battery_scale_fraction, 1, 1));
+        }
+    }
+
     protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
         // We probably won't ever have space in the UI for more than 20 devices, so don't
         // get info for them.
index 0bb7479..0b0f58b 100644 (file)
@@ -263,7 +263,7 @@ public class CellularTile extends QSTileImpl<SignalState> {
         }
 
         @Override
-        public void setNoSims(boolean show) {
+        public void setNoSims(boolean show, boolean simDetected) {
             mInfo.noSim = show;
             if (mInfo.noSim) {
                 // Make sure signal gets cleared out when no sims.
index bcf4f17..2d7cfb1 100644 (file)
@@ -26,8 +26,8 @@ import com.android.systemui.recents.views.TaskViewThumbnail;
 
 public class GridTaskViewThumbnail extends TaskViewThumbnail {
 
-    private Path mThumbnailOutline;
-    private Path mRestBackgroundOutline;
+    private final Path mThumbnailOutline = new Path();
+    private final Path mRestBackgroundOutline = new Path();
     // True if either this view's size or thumbnail scale has changed and mThumbnailOutline should
     // be updated.
     private boolean mUpdateThumbnailOutline = true;
@@ -77,47 +77,38 @@ public class GridTaskViewThumbnail extends TaskViewThumbnail {
             (int) (mThumbnailRect.width() * mThumbnailScale));
         final int thumbnailHeight = Math.min(viewHeight,
             (int) (mThumbnailRect.height() * mThumbnailScale));
-        // Draw the thumbnail, we only round the bottom corners:
-        //
-        // outerLeft                outerRight
-        //    <----------------------->            mRestBackgroundOutline
-        //    _________________________            (thumbnailWidth < viewWidth)
-        //    |_______________________| outerTop     A ____ B
-        //    |                       |    â†‘           |  |
-        //    |                       |    |           |  |
-        //    |                       |    |           |  |
-        //    |                       |    |           |  | C
-        //    \_______________________/    â†“           |__/
-        //  mCornerRadius             outerBottom    E    D
-        //
-        //  mRestBackgroundOutline (thumbnailHeight < viewHeight)
-        //  A _________________________ B
-        //    |                       | C
-        //  F \_______________________/
-        //    E                       D
-        final int outerLeft = 0;
-        final int outerTop = 0;
-        final int outerRight = outerLeft + thumbnailWidth;
-        final int outerBottom = outerTop + thumbnailHeight;
-        mThumbnailOutline = new Path();
-        mThumbnailOutline.moveTo(outerLeft, outerTop);
-        mThumbnailOutline.lineTo(outerRight, outerTop);
-        mThumbnailOutline.lineTo(outerRight, outerBottom - mCornerRadius);
-        mThumbnailOutline.arcTo(outerRight -  2 * mCornerRadius, outerBottom - 2 * mCornerRadius,
-                outerRight, outerBottom, 0, 90, false);
-        mThumbnailOutline.lineTo(outerLeft + mCornerRadius, outerBottom);
-        mThumbnailOutline.arcTo(outerLeft, outerBottom - 2 * mCornerRadius,
-                outerLeft + 2 * mCornerRadius, outerBottom, 90, 90, false);
-        mThumbnailOutline.lineTo(outerLeft, outerTop);
-        mThumbnailOutline.close();
 
         if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
+            // Draw the thumbnail, we only round the bottom corners:
+            //
+            // outerLeft                outerRight
+            //    <----------------------->            mRestBackgroundOutline
+            //    _________________________            (thumbnailWidth < viewWidth)
+            //    |_______________________| outerTop     A ____ B
+            //    |                       |    â†‘           |  |
+            //    |                       |    |           |  |
+            //    |                       |    |           |  |
+            //    |                       |    |           |  | C
+            //    \_______________________/    â†“           |__/
+            //  mCornerRadius             outerBottom    E    D
+            //
+            //  mRestBackgroundOutline (thumbnailHeight < viewHeight)
+            //  A _________________________ B
+            //    |                       | C
+            //  F \_______________________/
+            //    E                       D
+            final int outerLeft = 0;
+            final int outerTop = 0;
+            final int outerRight = outerLeft + thumbnailWidth;
+            final int outerBottom = outerTop + thumbnailHeight;
+            createThumbnailPath(outerLeft, outerTop, outerRight, outerBottom, mThumbnailOutline);
+
             if (thumbnailWidth < viewWidth) {
                 final int l = Math.max(0, outerRight - mCornerRadius);
                 final int r = outerRight;
                 final int t = outerTop;
                 final int b = outerBottom;
-                mRestBackgroundOutline = new Path();
+                mRestBackgroundOutline.reset();
                 mRestBackgroundOutline.moveTo(l, t); // A
                 mRestBackgroundOutline.lineTo(r, t); // B
                 mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
@@ -133,7 +124,7 @@ public class GridTaskViewThumbnail extends TaskViewThumbnail {
                 final int r = outerRight;
                 final int t = Math.max(0, thumbnailHeight - mCornerRadius);
                 final int b = outerBottom;
-                mRestBackgroundOutline = new Path();
+                mRestBackgroundOutline.reset();
                 mRestBackgroundOutline.moveTo(l, t); // A
                 mRestBackgroundOutline.lineTo(r, t); // B
                 mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
@@ -145,9 +136,26 @@ public class GridTaskViewThumbnail extends TaskViewThumbnail {
                 mRestBackgroundOutline.lineTo(l, t); // A
                 mRestBackgroundOutline.close();
             }
+        } else {
+            createThumbnailPath(0, 0, viewWidth, viewHeight, mThumbnailOutline);
         }
     }
 
+    private void createThumbnailPath(int outerLeft, int outerTop, int outerRight, int outerBottom,
+            Path outPath) {
+        outPath.reset();
+        outPath.moveTo(outerLeft, outerTop);
+        outPath.lineTo(outerRight, outerTop);
+        outPath.lineTo(outerRight, outerBottom - mCornerRadius);
+        outPath.arcTo(outerRight -  2 * mCornerRadius, outerBottom - 2 * mCornerRadius, outerRight,
+                outerBottom, 0, 90, false);
+        outPath.lineTo(outerLeft + mCornerRadius, outerBottom);
+        outPath.arcTo(outerLeft, outerBottom - 2 * mCornerRadius, outerLeft + 2 * mCornerRadius,
+                outerBottom, 90, 90, false);
+        outPath.lineTo(outerLeft, outerTop);
+        outPath.close();
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         final int titleHeight = getResources().getDimensionPixelSize(
index 759d2cf..274244e 100644 (file)
@@ -22,8 +22,6 @@ import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.telephony.SubscriptionInfo;
 import android.util.ArraySet;
@@ -53,6 +51,7 @@ import com.android.systemui.tuner.TunerService.Tunable;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 // Intimately tied to the design of res/layout/signal_cluster_view.xml
 public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback,
@@ -73,6 +72,7 @@ public class SignalClusterView extends LinearLayout implements NetworkController
 
     private boolean mNoSimsVisible = false;
     private boolean mVpnVisible = false;
+    private boolean mSimDetected;
     private int mVpnIconId = 0;
     private int mLastVpnIconId = -1;
     private boolean mEthernetVisible = false;
@@ -327,8 +327,9 @@ public class SignalClusterView extends LinearLayout implements NetworkController
     }
 
     @Override
-    public void setNoSims(boolean show) {
+    public void setNoSims(boolean show, boolean simDetected) {
         mNoSimsVisible = show && !mBlockMobile;
+        mSimDetected = simDetected;
         apply();
     }
 
@@ -548,6 +549,23 @@ public class SignalClusterView extends LinearLayout implements NetworkController
         if (mNoSimsVisible) {
             mIconLogger.onIconShown(SLOT_MOBILE);
             mNoSimsCombo.setVisibility(View.VISIBLE);
+            if (!Objects.equals(mSimDetected, mNoSimsCombo.getTag())) {
+                mNoSimsCombo.setTag(mSimDetected);
+                if (mSimDetected) {
+                    SignalDrawable d = new SignalDrawable(mNoSims.getContext());
+                    d.setDarkIntensity(0);
+                    mNoSims.setImageDrawable(d);
+                    mNoSims.setImageLevel(SignalDrawable.getEmptyState(4));
+
+                    SignalDrawable dark = new SignalDrawable(mNoSims.getContext());
+                    dark.setDarkIntensity(1);
+                    mNoSimsDark.setImageDrawable(dark);
+                    mNoSimsDark.setImageLevel(SignalDrawable.getEmptyState(4));
+                } else {
+                    mNoSims.setImageResource(R.drawable.stat_sys_no_sims);
+                    mNoSimsDark.setImageResource(R.drawable.stat_sys_no_sims);
+                }
+            }
         } else {
             mIconLogger.onIconHidden(SLOT_MOBILE);
             mNoSimsCombo.setVisibility(View.GONE);
index a456786..5159e8d 100644 (file)
@@ -71,7 +71,7 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa
                 break;
             case MSG_NO_SIM_VISIBLE_CHANGED:
                 for (SignalCallback signalCluster : mSignalCallbacks) {
-                    signalCluster.setNoSims(msg.arg1 != 0);
+                    signalCluster.setNoSims(msg.arg1 != 0, msg.arg2 != 0);
                 }
                 break;
             case MSG_ETHERNET_CHANGED:
@@ -144,8 +144,8 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa
     }
 
     @Override
-    public void setNoSims(boolean show) {
-        obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, 0).sendToTarget();
+    public void setNoSims(boolean show, boolean simDetected) {
+        obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, simDetected ? 1 : 0).sendToTarget();
     }
 
     @Override
index 2771011..9eee906 100644 (file)
@@ -52,7 +52,7 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
                 int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
                 String description, boolean isWide, int subId, boolean roaming) {}
         default void setSubs(List<SubscriptionInfo> subs) {}
-        default void setNoSims(boolean show) {}
+        default void setNoSims(boolean show, boolean simDetected) {}
 
         default void setEthernetIndicators(IconState icon) {}
 
index c217bda..bb3e09f 100644 (file)
@@ -58,10 +58,8 @@ import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 
@@ -116,7 +114,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
 
     // States that don't belong to a subcontroller.
     private boolean mAirplaneMode = false;
-    private boolean mHasNoSims;
+    private boolean mHasNoSubs;
     private Locale mLocale = null;
     // This list holds our ordering.
     private List<SubscriptionInfo> mCurrentSubscriptions = new ArrayList<>();
@@ -140,6 +138,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
     @VisibleForTesting
     ServiceState mLastServiceState;
     private boolean mUserSetup;
+    private boolean mSimDetected;
 
     /**
      * Construct this controller object and register for updates.
@@ -363,7 +362,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
         cb.setSubs(mCurrentSubscriptions);
         cb.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
-        cb.setNoSims(mHasNoSims);
+        cb.setNoSims(mHasNoSubs, mSimDetected);
         mWifiSignalController.notifyListeners(cb);
         mEthernetSignalController.notifyListeners(cb);
         for (int i = 0; i < mMobileSignalControllers.size(); i++) {
@@ -498,11 +497,25 @@ public class NetworkControllerImpl extends BroadcastReceiver
 
     @VisibleForTesting
     protected void updateNoSims() {
-        boolean hasNoSims = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
-        if (hasNoSims != mHasNoSims) {
-            mHasNoSims = hasNoSims;
-            mCallbackHandler.setNoSims(mHasNoSims);
+        boolean hasNoSubs = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
+        boolean simDetected = hasAnySim();
+        if (hasNoSubs != mHasNoSubs || simDetected != mSimDetected) {
+            mHasNoSubs = hasNoSubs;
+            mSimDetected = simDetected;
+            mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
+        }
+    }
+
+    private boolean hasAnySim() {
+        int simCount = mPhone.getSimCount();
+        for (int i = 0; i < simCount; i++) {
+            int state = mPhone.getSimState(i);
+            if (state != TelephonyManager.SIM_STATE_ABSENT
+                    && state != TelephonyManager.SIM_STATE_UNKNOWN) {
+                return true;
+            }
         }
+        return false;
     }
 
     @VisibleForTesting
@@ -631,7 +644,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
     private void notifyListeners() {
         mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
-        mCallbackHandler.setNoSims(mHasNoSims);
+        mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
     }
 
     /**
@@ -822,8 +835,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
             }
             String nosim = args.getString("nosim");
             if (nosim != null) {
-                mHasNoSims = nosim.equals("show");
-                mCallbackHandler.setNoSims(mHasNoSims);
+                mHasNoSubs = nosim.equals("show");
+                mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
             }
             String mobile = args.getString("mobile");
             if (mobile != null) {
index 51bd7bc..e3558d4 100644 (file)
@@ -161,12 +161,11 @@ public class CallbackHandlerTest extends SysuiTestCase {
     @Test
     public void testSignalCallback_setNoSims() {
         boolean noSims = true;
-        mHandler.setNoSims(noSims);
+        boolean simDetected = false;
+        mHandler.setNoSims(noSims, simDetected);
         waitForCallbacks();
 
-        ArgumentCaptor<Boolean> noSimsArg = ArgumentCaptor.forClass(Boolean.class);
-        Mockito.verify(mSignalCallback).setNoSims(noSimsArg.capture());
-        assertEquals(noSims, (boolean) noSimsArg.getValue());
+        Mockito.verify(mSignalCallback).setNoSims(eq(noSims), eq(simDetected));
     }
 
     @Test
index bb7b197..d14b23e 100644 (file)
@@ -46,8 +46,6 @@ import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -292,10 +290,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
     }
 
     protected void verifyHasNoSims(boolean hasNoSimsVisible) {
-        ArgumentCaptor<Boolean> hasNoSimsArg = ArgumentCaptor.forClass(Boolean.class);
-
-        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(hasNoSimsArg.capture());
-        assertEquals("No sims", hasNoSimsVisible, (boolean) hasNoSimsArg.getValue());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(
+                eq(hasNoSimsVisible), eq(false));
     }
 
     protected void verifyLastQsMobileDataIndicators(boolean visible, int icon, int typeIcon,
index 28bf856..54eba2b 100644 (file)
@@ -315,6 +315,27 @@ message WifiLog {
 
   // Pno scan metrics
   optional PnoScanMetrics pno_scan_metrics = 76;
+
+  // Histogram of "Connect to Network" notifications.
+  // The notification Action should be unset.
+  repeated ConnectToNetworkNotificationAndActionCount connect_to_network_notification_count = 77;
+
+  // Histogram of "Connect to Network" notification user actions.
+  repeated ConnectToNetworkNotificationAndActionCount connect_to_network_notification_action_count = 78;
+
+  // The number of SSIDs blacklisted from recommendation by the open network
+  // notification recommender
+  optional int32 open_network_recommender_blacklist_size = 79;
+
+  // Is the available network notification feature turned on
+  optional bool is_wifi_networks_available_notification_on = 80;
+
+  // Count of recommendation updates made by the open network notification
+  // recommender
+  optional int32 num_open_network_recommendation_updates = 81;
+
+  // Count of connection attempts that were initiated unsuccessfully
+  optional int32 num_open_network_connect_message_failed_to_send = 82;
 }
 
 // Information that gets logged for every WiFi connection.
@@ -950,3 +971,68 @@ message PnoScanMetrics {
   // Total number of pno scans that found any network
   optional int32 num_pno_found_network_events = 5;
 }
+
+// Number of occurrences for a particular "Connect to Network" Notification or
+// notification Action.
+message ConnectToNetworkNotificationAndActionCount {
+
+  // "Connect to Network" notifications
+  enum Notification {
+
+    // Default
+    NOTIFICATION_UNKNOWN = 0;
+
+    // Initial notification with a recommended network.
+    NOTIFICATION_RECOMMEND_NETWORK = 1;
+
+    // Notification when connecting to the recommended network.
+    NOTIFICATION_CONNECTING_TO_NETWORK = 2;
+
+    // Notification when successfully connected to the network.
+    NOTIFICATION_CONNECTED_TO_NETWORK = 3;
+
+    // Notification when failed to connect to network.
+    NOTIFICATION_FAILED_TO_CONNECT = 4;
+  }
+
+  // "Connect to Network" notification actions
+  enum Action {
+
+    // Default
+    ACTION_UNKNOWN = 0;
+
+    // User dismissed the "Connect to Network" notification.
+    ACTION_USER_DISMISSED_NOTIFICATION = 1;
+
+    // User tapped action button to connect to recommended network.
+    ACTION_CONNECT_TO_NETWORK = 2;
+
+    // User tapped action button to open Wi-Fi Settings.
+    ACTION_PICK_WIFI_NETWORK = 3;
+
+    // User tapped "Failed to connect" notification to open Wi-Fi Settings.
+    ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE = 4;
+  }
+
+  // Recommenders of the "Connect to Network" notification
+  enum Recommender {
+
+    // Default.
+    RECOMMENDER_UNKNOWN = 0;
+
+    // Open Network Available recommender.
+    RECOMMENDER_OPEN = 1;
+  }
+
+  // Notification Type.
+  optional Notification notification = 1;
+
+  // Action Type.
+  optional Action action = 2;
+
+  // Recommender Type.
+  optional Recommender recommender = 3;
+
+  // Occurrences of this action.
+  optional int32 count = 4;
+}
index bffccaf..30ecbcf 100644 (file)
@@ -24567,7 +24567,7 @@ public class ActivityManagerService extends IActivityManager.Stub
                 if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
                     try {
                         final ApplicationInfo ai = AppGlobals.getPackageManager()
-                                .getApplicationInfo(packageName, 0 /*flags*/, app.userId);
+                                .getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
                         if (ai != null) {
                             app.thread.scheduleApplicationInfoChanged(ai);
                         }
index a9ce927..148ce08 100644 (file)
@@ -2246,14 +2246,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
         try {
             // Protect against recursion.
             mStackSupervisor.inResumeTopActivity = true;
-            // The contained logic must be synchronized, since we are both changing the visibility
-            // and updating the {@link Configuration}. {@link ActivityRecord#setVisibility} will
-            // ultimately cause the client code to schedule a layout. Since layouts retrieve the
-            // current {@link Configuration}, we must ensure that the below code updates it before
-            // the layout can occur.
-            synchronized (mWindowManager.getWindowManagerLock()) {
-                result = resumeTopActivityInnerLocked(prev, options);
-            }
+            result = resumeTopActivityInnerLocked(prev, options);
         } finally {
             mStackSupervisor.inResumeTopActivity = false;
         }
@@ -2564,128 +2557,139 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
                     || (lastStack.mLastPausedActivity != null
                     && !lastStack.mLastPausedActivity.fullscreen));
 
-            // This activity is now becoming visible.
-            if (!next.visible || next.stopped || lastActivityTranslucent) {
-                next.setVisibility(true);
-            }
+            // The contained logic must be synchronized, since we are both changing the visibility
+            // and updating the {@link Configuration}. {@link ActivityRecord#setVisibility} will
+            // ultimately cause the client code to schedule a layout. Since layouts retrieve the
+            // current {@link Configuration}, we must ensure that the below code updates it before
+            // the layout can occur.
+            synchronized(mWindowManager.getWindowManagerLock()) {
+                // This activity is now becoming visible.
+                if (!next.visible || next.stopped || lastActivityTranslucent) {
+                    next.setVisibility(true);
+                }
 
-            // schedule launch ticks to collect information about slow apps.
-            next.startLaunchTickingLocked();
+                // schedule launch ticks to collect information about slow apps.
+                next.startLaunchTickingLocked();
 
-            ActivityRecord lastResumedActivity =
-                    lastStack == null ? null :lastStack.mResumedActivity;
-            ActivityState lastState = next.state;
+                ActivityRecord lastResumedActivity =
+                        lastStack == null ? null :lastStack.mResumedActivity;
+                ActivityState lastState = next.state;
 
-            mService.updateCpuStats();
+                mService.updateCpuStats();
 
-            if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next + " (in existing)");
+                if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next
+                        + " (in existing)");
 
-            setResumedActivityLocked(next, "resumeTopActivityInnerLocked");
+                setResumedActivityLocked(next, "resumeTopActivityInnerLocked");
 
-            mService.updateLruProcessLocked(next.app, true, null);
-            updateLRUListLocked(next);
-            mService.updateOomAdjLocked();
+                mService.updateLruProcessLocked(next.app, true, null);
+                updateLRUListLocked(next);
+                mService.updateOomAdjLocked();
 
-            // Have the window manager re-evaluate the orientation of
-            // the screen based on the new activity order.
-            boolean notUpdated = true;
-            if (mStackSupervisor.isFocusedStack(this)) {
-
-                // We have special rotation behavior when Keyguard is locked. Make sure all activity
-                // visibilities are set correctly as well as the transition is updated if needed to
-                // get the correct rotation behavior.
-                // TODO: Remove this once visibilities are set correctly immediately when starting
-                // an activity.
-                if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
-                    mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
-                            0 /* configChanges */, false /* preserveWindows */);
-                }
-                final Configuration config = mWindowManager.updateOrientationFromAppTokens(
-                        mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
-                        next.mayFreezeScreenLocked(next.app) ? next.appToken : null, mDisplayId);
-                if (config != null) {
-                    next.frozenBeforeDestroy = true;
-                }
-                notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
-                        false /* deferResume */, mDisplayId);
-            }
-
-            if (notUpdated) {
-                // The configuration update wasn't able to keep the existing
-                // instance of the activity, and instead started a new one.
-                // We should be all done, but let's just make sure our activity
-                // is still at the top and schedule another run if something
-                // weird happened.
-                ActivityRecord nextNext = topRunningActivityLocked();
-                if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
-                        "Activity config changed during resume: " + next
-                        + ", new next: " + nextNext);
-                if (nextNext != next) {
-                    // Do over!
-                    mStackSupervisor.scheduleResumeTopActivities();
-                }
-                if (!next.visible || next.stopped) {
-                    next.setVisibility(true);
-                }
-                next.completeResumeLocked();
-                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
-                return true;
-            }
+                // Have the window manager re-evaluate the orientation of
+                // the screen based on the new activity order.
+                boolean notUpdated = true;
 
-            try {
-                // Deliver all pending results.
-                ArrayList<ResultInfo> a = next.results;
-                if (a != null) {
-                    final int N = a.size();
-                    if (!next.finishing && N > 0) {
-                        if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
-                                "Delivering results to " + next + ": " + a);
-                        next.app.thread.scheduleSendResult(next.appToken, a);
+                if (mStackSupervisor.isFocusedStack(this)) {
+
+                    // We have special rotation behavior when Keyguard is locked. Make sure all
+                    // activity visibilities are set correctly as well as the transition is updated
+                    // if needed to get the correct rotation behavior.
+                    // TODO: Remove this once visibilities are set correctly immediately when
+                    // starting an activity.
+                    if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
+                        mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
+                                0 /* configChanges */, false /* preserveWindows */);
+                    }
+                    final Configuration config = mWindowManager.updateOrientationFromAppTokens(
+                            mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
+                            next.mayFreezeScreenLocked(next.app) ? next.appToken : null,
+                                    mDisplayId);
+                    if (config != null) {
+                        next.frozenBeforeDestroy = true;
                     }
+                    notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
+                            false /* deferResume */, mDisplayId);
                 }
 
-                if (next.newIntents != null) {
-                    next.app.thread.scheduleNewIntent(
-                            next.newIntents, next.appToken, false /* andPause */);
+                if (notUpdated) {
+                    // The configuration update wasn't able to keep the existing
+                    // instance of the activity, and instead started a new one.
+                    // We should be all done, but let's just make sure our activity
+                    // is still at the top and schedule another run if something
+                    // weird happened.
+                    ActivityRecord nextNext = topRunningActivityLocked();
+                    if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
+                            "Activity config changed during resume: " + next
+                                    + ", new next: " + nextNext);
+                    if (nextNext != next) {
+                        // Do over!
+                        mStackSupervisor.scheduleResumeTopActivities();
+                    }
+                    if (!next.visible || next.stopped) {
+                        next.setVisibility(true);
+                    }
+                    next.completeResumeLocked();
+                    if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                    return true;
                 }
 
-                // Well the app will no longer be stopped.
-                // Clear app token stopped state in window manager if needed.
-                next.notifyAppResumed(next.stopped);
-
-                EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId,
-                        System.identityHashCode(next), next.getTask().taskId,
-                        next.shortComponentName);
+                try {
+                    // Deliver all pending results.
+                    ArrayList<ResultInfo> a = next.results;
+                    if (a != null) {
+                        final int N = a.size();
+                        if (!next.finishing && N > 0) {
+                            if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
+                                    "Delivering results to " + next + ": " + a);
+                            next.app.thread.scheduleSendResult(next.appToken, a);
+                        }
+                    }
 
-                next.sleeping = false;
-                mService.showUnsupportedZoomDialogIfNeededLocked(next);
-                mService.showAskCompatModeDialogLocked(next);
-                next.app.pendingUiClean = true;
-                next.app.forceProcessStateUpTo(mService.mTopProcessState);
-                next.clearOptionsLocked();
-                next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
-                        mService.isNextTransitionForward(), resumeAnimOptions);
+                    if (next.newIntents != null) {
+                        next.app.thread.scheduleNewIntent(
+                                next.newIntents, next.appToken, false /* andPause */);
+                    }
 
-                if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " + next);
-            } catch (Exception e) {
-                // Whoops, need to restart this activity!
-                if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to "
-                        + lastState + ": " + next);
-                next.state = lastState;
-                if (lastStack != null) {
-                    lastStack.mResumedActivity = lastResumedActivity;
-                }
-                Slog.i(TAG, "Restarting because process died: " + next);
-                if (!next.hasBeenLaunched) {
-                    next.hasBeenLaunched = true;
-                } else  if (SHOW_APP_STARTING_PREVIEW && lastStack != null &&
-                        mStackSupervisor.isFrontStackOnDisplay(lastStack)) {
-                    next.showStartingWindow(null /* prev */, false /* newTask */,
-                            false /* taskSwitch */);
+                    // Well the app will no longer be stopped.
+                    // Clear app token stopped state in window manager if needed.
+                    next.notifyAppResumed(next.stopped);
+
+                    EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId,
+                            System.identityHashCode(next), next.getTask().taskId,
+                            next.shortComponentName);
+
+                    next.sleeping = false;
+                    mService.showUnsupportedZoomDialogIfNeededLocked(next);
+                    mService.showAskCompatModeDialogLocked(next);
+                    next.app.pendingUiClean = true;
+                    next.app.forceProcessStateUpTo(mService.mTopProcessState);
+                    next.clearOptionsLocked();
+                    next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
+                            mService.isNextTransitionForward(), resumeAnimOptions);
+
+                    if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
+                            + next);
+                } catch (Exception e) {
+                    // Whoops, need to restart this activity!
+                    if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to "
+                            + lastState + ": " + next);
+                    next.state = lastState;
+                    if (lastStack != null) {
+                        lastStack.mResumedActivity = lastResumedActivity;
+                    }
+                    Slog.i(TAG, "Restarting because process died: " + next);
+                    if (!next.hasBeenLaunched) {
+                        next.hasBeenLaunched = true;
+                    } else  if (SHOW_APP_STARTING_PREVIEW && lastStack != null &&
+                            mStackSupervisor.isFrontStackOnDisplay(lastStack)) {
+                        next.showStartingWindow(null /* prev */, false /* newTask */,
+                                false /* taskSwitch */);
+                    }
+                    mStackSupervisor.startSpecificActivityLocked(next, true, false);
+                    if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                    return true;
                 }
-                mStackSupervisor.startSpecificActivityLocked(next, true, false);
-                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
-                return true;
             }
 
             // From this point on, if something goes wrong there is no way
index 89e1050..0b11479 100644 (file)
@@ -462,18 +462,25 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                if (useSuggested) {
-                    if (AudioSystem.isStreamActive(stream, 0)) {
-                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction,
-                                flags, packageName, uid);
+                try {
+                    if (useSuggested) {
+                        if (AudioSystem.isStreamActive(stream, 0)) {
+                            mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream,
+                                    direction, flags, packageName, uid);
+                        } else {
+                            mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
+                                    AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
+                                    flags | previousFlagPlaySound, packageName, uid);
+                        }
                     } else {
-                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
-                                AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
-                                flags | previousFlagPlaySound, packageName, uid);
+                        mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
+                                packageName, uid);
                     }
-                } else {
-                    mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
-                            packageName, uid);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream="
+                            + stream + ", flags=" + flags + ", packageName=" + packageName
+                            + ", uid=" + uid + ", useSuggested=" + useSuggested
+                            + ", previousFlagPlaySound=" + previousFlagPlaySound, e);
                 }
             }
         });
index b77ed91..b9a2d18 100644 (file)
@@ -1363,6 +1363,10 @@ public class MediaSessionService extends SystemService implements Monitor {
                                     flags, packageName, TAG);
                         } catch (RemoteException e) {
                             Log.e(TAG, "Error adjusting default volume.", e);
+                        } catch (IllegalArgumentException e) {
+                            Log.e(TAG, "Cannot adjust volume: direction=" + direction
+                                    + ", suggestedStream=" + suggestedStream + ", flags=" + flags,
+                                    e);
                         }
                     }
                 });
index 4e92d10..c6f2e8a 100644 (file)
@@ -5838,8 +5838,8 @@ public class NotificationManagerService extends SystemService {
 
     private class ShellCmd extends ShellCommand {
         public static final String USAGE = "help\n"
-                + "allow_listener COMPONENT\n"
-                + "disallow_listener COMPONENT\n"
+                + "allow_listener COMPONENT [user_id]\n"
+                + "disallow_listener COMPONENT [user_id]\n"
                 + "set_assistant COMPONENT\n"
                 + "remove_assistant COMPONENT\n"
                 + "allow_dnd PACKAGE\n"
@@ -5870,7 +5870,13 @@ public class NotificationManagerService extends SystemService {
                             pw.println("Invalid listener - must be a ComponentName");
                             return -1;
                         }
-                        getBinderService().setNotificationListenerAccessGranted(cn, true);
+                        String userId = getNextArg();
+                        if (userId == null) {
+                            getBinderService().setNotificationListenerAccessGranted(cn, true);
+                        } else {
+                            getBinderService().setNotificationListenerAccessGrantedForUser(
+                                    cn, Integer.parseInt(userId), true);
+                        }
                     }
                     break;
                     case "disallow_listener": {
@@ -5879,7 +5885,13 @@ public class NotificationManagerService extends SystemService {
                             pw.println("Invalid listener - must be a ComponentName");
                             return -1;
                         }
-                        getBinderService().setNotificationListenerAccessGranted(cn, false);
+                        String userId = getNextArg();
+                        if (userId == null) {
+                            getBinderService().setNotificationListenerAccessGranted(cn, false);
+                        } else {
+                            getBinderService().setNotificationListenerAccessGrantedForUser(
+                                    cn, Integer.parseInt(userId), false);
+                        }
                     }
                     break;
                     case "allow_assistant": {
index f9d10ad..c8cbcd8 100644 (file)
@@ -2027,10 +2027,14 @@ public class WindowManagerService extends IWindowManager.Stub
                 Slog.i(TAG_WM, "Relayout " + win + ": oldVis=" + oldVisibility
                         + " newVis=" + viewVisibility, stack);
             }
-            if (viewVisibility == View.VISIBLE &&
-                    (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
-                            || !win.mAppToken.isClientHidden())) {
 
+            // We should only relayout if the view is visible, it is a starting window, or the
+            // associated appToken is not hidden.
+            final boolean shouldRelayout = viewVisibility == View.VISIBLE &&
+                (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
+                    || !win.mAppToken.isClientHidden());
+
+            if (shouldRelayout) {
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
 
                 // We are about to create a surface, but we didn't run a layout yet. So better run
@@ -2191,7 +2195,7 @@ public class WindowManagerService extends IWindowManager.Stub
             // to the client erroneously accepting a configuration that would have otherwise caused
             // an activity restart. We instead hand back the last reported
             // {@link MergedConfiguration}.
-            if (win.mAppToken == null || !win.mAppToken.isClientHidden()) {
+            if (shouldRelayout) {
                 win.getMergedConfiguration(mergedConfiguration);
             } else {
                 win.getLastReportedMergedConfiguration(mergedConfiguration);
index feeae02..82e2126 100644 (file)
@@ -17,9 +17,7 @@ include $(CLEAR_VARS)
 
 # We only want this apk build for tests.
 LOCAL_MODULE_TAGS := tests
-
-LOCAL_JAVA_LIBRARIES := legacy-android-test
-LOCAL_STATIC_JAVA_LIBRARIES := junit
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 # Include all test java files.
 LOCAL_SRC_FILES := \
        $(call all-java-files-under, src)
index 7017431..5d1317e 100644 (file)
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.compatibilitytest" >
+    package="com.android.compatibilitytest"
+    android:sharedUserId="android.uid.system">
     <uses-sdk android:minSdkVersion="21"
               android:targetSdkVersion="21" />
-    <application >
-        <uses-library android:name="android.test.runner" />
-    </application>
+    <application />
+    <uses-permission android:name="android.permission.READ_LOGS" />
     <uses-permission android:name="android.permission.REAL_GET_TASKS" />
     <instrumentation
         android:name=".AppCompatibilityRunner"
index f81b001..a5261d0 100644 (file)
 package com.android.compatibilitytest;
 
 import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.IActivityController;
+import android.app.IActivityManager;
+import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.app.UiModeManager;
-import android.app.ActivityManager.ProcessErrorStateInfo;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.os.Bundle;
-import android.test.InstrumentationTestCase;
+import android.os.DropBoxManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
-import junit.framework.Assert;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Application Compatibility Test that launches an application and detects
  * crashes.
  */
-public class AppCompatibility extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AppCompatibility {
 
     private static final String TAG = AppCompatibility.class.getSimpleName();
     private static final String PACKAGE_TO_LAUNCH = "package_to_launch";
     private static final String APP_LAUNCH_TIMEOUT_MSECS = "app_launch_timeout_ms";
     private static final String WORKSPACE_LAUNCH_TIMEOUT_MSECS = "workspace_launch_timeout_ms";
+    private static final Set<String> DROPBOX_TAGS = new HashSet<>();
+    static {
+        DROPBOX_TAGS.add("SYSTEM_TOMBSTONE");
+        DROPBOX_TAGS.add("system_app_anr");
+        DROPBOX_TAGS.add("system_app_native_crash");
+        DROPBOX_TAGS.add("system_app_crash");
+        DROPBOX_TAGS.add("data_app_anr");
+        DROPBOX_TAGS.add("data_app_native_crash");
+        DROPBOX_TAGS.add("data_app_crash");
+    }
 
+    // time waiting for app to launch
     private int mAppLaunchTimeout = 7000;
+    // time waiting for launcher home screen to show up
     private int mWorkspaceLaunchTimeout = 2000;
 
     private Context mContext;
     private ActivityManager mActivityManager;
     private PackageManager mPackageManager;
-    private AppCompatibilityRunner mRunner;
     private Bundle mArgs;
+    private Instrumentation mInstrumentation;
+    private String mLauncherPackageName;
+    private IActivityController mCrashSupressor = new CrashSuppressor();
+    private Map<String, List<String>> mAppErrors = new HashMap<>();
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-        mRunner = (AppCompatibilityRunner) getInstrumentation();
-        assertNotNull("Could not fetch InstrumentationTestRunner.", mRunner);
-
-        mContext = mRunner.getTargetContext();
-        Assert.assertNotNull("Could not get the Context", mContext);
-
-        mActivityManager = (ActivityManager)
-                mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        Assert.assertNotNull("Could not get Activity Manager", mActivityManager);
-
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = InstrumentationRegistry.getTargetContext();
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
         mPackageManager = mContext.getPackageManager();
-        Assert.assertNotNull("Missing Package Manager", mPackageManager);
+        mArgs = InstrumentationRegistry.getArguments();
 
-        mArgs = mRunner.getBundle();
+        // resolve launcher package name
+        Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
+        ResolveInfo resolveInfo = mPackageManager.resolveActivity(
+                intent, PackageManager.MATCH_DEFAULT_ONLY);
+        mLauncherPackageName = resolveInfo.activityInfo.packageName;
+        Assert.assertNotNull("failed to resolve package name for launcher", mLauncherPackageName);
+        Log.v(TAG, "Using launcher package name: " + mLauncherPackageName);
 
         // Parse optional inputs.
         String appLaunchTimeoutMsecs = mArgs.getString(APP_LAUNCH_TIMEOUT_MSECS);
@@ -83,13 +112,20 @@ public class AppCompatibility extends InstrumentationTestCase {
         if (workspaceLaunchTimeoutMsecs != null) {
             mWorkspaceLaunchTimeout = Integer.parseInt(workspaceLaunchTimeoutMsecs);
         }
-        getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
+        mInstrumentation.getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
+
+        // set activity controller to suppress crash dialogs and collects them by process name
+        mAppErrors.clear();
+        IActivityManager.Stub.asInterface(ServiceManager.checkService(Context.ACTIVITY_SERVICE))
+            .setActivityController(mCrashSupressor, false);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
+        // unset activity controller
+        IActivityManager.Stub.asInterface(ServiceManager.checkService(Context.ACTIVITY_SERVICE))
+            .setActivityController(null, false);
+        mInstrumentation.getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
     }
 
     /**
@@ -98,6 +134,7 @@ public class AppCompatibility extends InstrumentationTestCase {
      *
      * @throws Exception
      */
+    @Test
     public void testAppStability() throws Exception {
         String packageName = mArgs.getString(PACKAGE_TO_LAUNCH);
         if (packageName != null) {
@@ -107,13 +144,23 @@ public class AppCompatibility extends InstrumentationTestCase {
                 Log.w(TAG, String.format("Skipping %s; no launch intent", packageName));
                 return;
             }
-            ProcessErrorStateInfo err = launchActivity(packageName, intent);
-            // Make sure there are no errors when launching the application,
-            // otherwise raise an
-            // exception with the first error encountered.
-            assertNull(getStackTrace(err), err);
+            long startTime = System.currentTimeMillis();
+            launchActivity(packageName, intent);
             try {
-                assertTrue("App crashed after launch.", processStillUp(packageName));
+                checkDropbox(startTime, packageName);
+                if (mAppErrors.containsKey(packageName)) {
+                    StringBuilder message = new StringBuilder("Error detected for package: ")
+                            .append(packageName);
+                    for (String err : mAppErrors.get(packageName)) {
+                        message.append("\n\n");
+                        message.append(err);
+                    }
+                    Assert.fail(message.toString());
+                }
+                // last check: see if app process is still running
+                Assert.assertTrue("app package \"" + packageName + "\" no longer found in running "
+                    + "tasks, but no explicit crashes were detected; check logcat for details",
+                    processStillUp(packageName));
             } finally {
                 returnHome();
             }
@@ -124,31 +171,30 @@ public class AppCompatibility extends InstrumentationTestCase {
     }
 
     /**
-     * Gets the stack trace for the error.
-     *
-     * @param in {@link ProcessErrorStateInfo} to parse.
-     * @return {@link String} the long message of the error.
+     * Check dropbox for entries of interest regarding the specified process
+     * @param startTime if not 0, only check entries with timestamp later than the start time
+     * @param processName the process name to check for
      */
-    private String getStackTrace(ProcessErrorStateInfo in) {
-        if (in == null) {
-            return null;
-        } else {
-            return in.stackTrace;
-        }
-    }
-
-    /**
-     * Returns the process name that the package is going to use.
-     *
-     * @param packageName name of the package
-     * @return process name of the package
-     */
-    private String getProcessName(String packageName) {
-        try {
-            PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
-            return pi.applicationInfo.processName;
-        } catch (NameNotFoundException e) {
-            return packageName;
+    private void checkDropbox(long startTime, String processName) {
+        DropBoxManager dropbox = (DropBoxManager) mContext
+                .getSystemService(Context.DROPBOX_SERVICE);
+        DropBoxManager.Entry entry = null;
+        while (null != (entry = dropbox.getNextEntry(null, startTime))) {
+            try {
+                // only check entries with tag that's of interest
+                String tag = entry.getTag();
+                if (DROPBOX_TAGS.contains(tag)) {
+                    String content = entry.getText(4096);
+                    if (content != null) {
+                        if (content.contains(processName)) {
+                            addProcessError(processName, "dropbox:" + tag, content);
+                        }
+                    }
+                }
+                startTime = entry.getTimeMillis();
+            } finally {
+                entry.close();
+            }
         }
     }
 
@@ -166,8 +212,7 @@ public class AppCompatibility extends InstrumentationTestCase {
     }
 
     private Intent getLaunchIntentForPackage(String packageName) {
-        UiModeManager umm = (UiModeManager)
-                getInstrumentation().getContext().getSystemService(Context.UI_MODE_SERVICE);
+        UiModeManager umm = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
         boolean isLeanback = umm.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
         Intent intent = null;
         if (isLeanback) {
@@ -186,35 +231,32 @@ public class AppCompatibility extends InstrumentationTestCase {
      * @return {@link Collection} of {@link ProcessErrorStateInfo} detected
      *         during the app launch.
      */
-    private ProcessErrorStateInfo launchActivity(String packageName, Intent intent) {
+    private void launchActivity(String packageName, Intent intent) {
         Log.d(TAG, String.format("launching package \"%s\" with intent: %s",
                 packageName, intent.toString()));
 
-        String processName = getProcessName(packageName);
-
         // Launch Activity
         mContext.startActivity(intent);
 
         try {
+            // artificial delay: in case app crashes after doing some work during launch
             Thread.sleep(mAppLaunchTimeout);
         } catch (InterruptedException e) {
             // ignore
         }
+    }
 
-        // See if there are any errors. We wait until down here to give ANRs as much time as
-        // possible to occur.
-        final Collection<ProcessErrorStateInfo> postErr =
-                mActivityManager.getProcessesInErrorState();
-
-        if (postErr == null) {
-            return null;
+    private void addProcessError(String processName, String errorType, String errorInfo) {
+        // parse out the package name if necessary, for apps with multiple proceses
+        String pkgName = processName.split(":", 2)[0];
+        List<String> errors;
+        if (mAppErrors.containsKey(pkgName)) {
+            errors = mAppErrors.get(pkgName);
+        }  else {
+            errors = new ArrayList<>();
         }
-        for (ProcessErrorStateInfo error : postErr) {
-            if (error.processName.equals(processName)) {
-                return error;
-            }
-        }
-        return null;
+        errors.add(String.format("type: %s details:\n%s", errorType, errorInfo));
+        mAppErrors.put(pkgName, errors);
     }
 
     /**
@@ -233,4 +275,55 @@ public class AppCompatibility extends InstrumentationTestCase {
         }
         return false;
     }
+
+    /**
+     * An {@link IActivityController} that instructs framework to kill processes hitting crashes
+     * directly without showing crash dialogs
+     *
+     */
+    private class CrashSuppressor extends IActivityController.Stub {
+
+        @Override
+        public boolean activityStarting(Intent intent, String pkg) throws RemoteException {
+            Log.d(TAG, "activity starting: " + intent.getComponent().toShortString());
+            return true;
+        }
+
+        @Override
+        public boolean activityResuming(String pkg) throws RemoteException {
+            Log.d(TAG, "activity resuming: " + pkg);
+            return true;
+        }
+
+        @Override
+        public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
+                long timeMillis, String stackTrace) throws RemoteException {
+            Log.d(TAG, "app crash: " + processName);
+            addProcessError(processName, "crash", stackTrace);
+            // don't show dialog
+            return false;
+        }
+
+        @Override
+        public int appEarlyNotResponding(String processName, int pid, String annotation)
+                throws RemoteException {
+            // ignore
+            return 0;
+        }
+
+        @Override
+        public int appNotResponding(String processName, int pid, String processStats)
+                throws RemoteException {
+            Log.d(TAG, "app ANR: " + processName);
+            addProcessError(processName, "ANR", processStats);
+            // don't show dialog
+            return -1;
+        }
+
+        @Override
+        public int systemNotResponding(String msg) throws RemoteException {
+            // ignore
+            return -1;
+        }
+    }
 }
index 258937f..b61ec34 100644 (file)
 
 package com.android.compatibilitytest;
 
-import android.os.Bundle;
-import android.test.InstrumentationTestRunner;
+import android.support.test.runner.AndroidJUnitRunner;
 
-public class AppCompatibilityRunner extends InstrumentationTestRunner {
-
-    private Bundle mArgs;
-
-    @Override
-    public void onCreate(Bundle args) {
-        super.onCreate(args);
-        mArgs = args;
-    }
-
-    public Bundle getBundle() {
-        return mArgs;
-    }
-}
+// empty subclass to maintain backwards compatibility on host-side harness
+public class AppCompatibilityRunner extends AndroidJUnitRunner {}