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
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
final ApplicationInfo aInfo =
sPackageManager.getApplicationInfo(
packageName,
- 0 /*flags*/,
+ PackageManager.GET_SHARED_LIBRARY_FILES,
UserHandle.myUserId());
if (mActivities.size() > 0) {
* <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—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;
// 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.
}
/**
- * 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();
}
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;
resetTextClassificationHelper();
mTextClassificationAsyncTask = new TextClassificationAsyncTask(
mTextView,
- TIMEOUT_DURATION,
+ mTextClassificationHelper.getTimeoutDuration(),
adjustSelection
? mTextClassificationHelper::suggestSelection
: mTextClassificationHelper::classifyText,
resetTextClassificationHelper();
mTextClassificationAsyncTask = new TextClassificationAsyncTask(
mTextView,
- TIMEOUT_DURATION,
+ mTextClassificationHelper.getTimeoutDuration(),
mTextClassificationHelper::classifyText,
this::invalidateActionMode)
.execute();
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;
* @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);
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);
@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);
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
<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>
@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();
+ }
+ });
}
}
}
private AssistOrbContainer mView;
private final DeviceProvisionedController mDeviceProvisionedController;
protected final AssistUtils mAssistUtils;
+ private final boolean mShouldEnableOrb;
private IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
| 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() {
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,
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;
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";
* @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.
* @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.
* @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
* @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
* @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));
+ }
+ }
+ }
}
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();
import android.os.SystemClock;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.LinkedList;
/**
}
}
- 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;
/*
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);
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;
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;
// 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
// 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();
}
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");
}
// 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;
}
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
*/
@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();
* (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;
* 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.
}
}
+ @GuardedBy("mCmdQueue")
private void enqueueLocked(Command cmd) {
mCmdQueue.add(cmd);
if (mThread == null) {
* @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();
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;
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;
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(
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.
}
@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.
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;
(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
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
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(
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;
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,
private boolean mNoSimsVisible = false;
private boolean mVpnVisible = false;
+ private boolean mSimDetected;
private int mVpnIconId = 0;
private int mLastVpnIconId = -1;
private boolean mEthernetVisible = false;
}
@Override
- public void setNoSims(boolean show) {
+ public void setNoSims(boolean show, boolean simDetected) {
mNoSimsVisible = show && !mBlockMobile;
+ mSimDetected = simDetected;
apply();
}
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);
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:
}
@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
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) {}
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;
// 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<>();
@VisibleForTesting
ServiceState mLastServiceState;
private boolean mUserSetup;
+ private boolean mSimDetected;
/**
* Construct this controller object and register for updates.
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++) {
@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
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);
}
/**
}
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) {
@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
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;
}
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,
// 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.
// 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;
+}
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);
}
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;
}
|| (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
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);
}
}
});
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);
}
}
});
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"
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": {
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": {
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
// 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);
# 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)
-->
<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"
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);
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);
}
/**
*
* @throws Exception
*/
+ @Test
public void testAppStability() throws Exception {
String packageName = mArgs.getString(PACKAGE_TO_LAUNCH);
if (packageName != null) {
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();
}
}
/**
- * 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();
+ }
}
}
}
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) {
* @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);
}
/**
}
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;
+ }
+ }
}
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 {}