Merge "Adding AppOps setting for entering PiP when hidden."
diff --git a/api/current.txt b/api/current.txt
index 243c78f..2fe3f44 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -36245,6 +36245,7 @@
method public abstract int getMaxBufferSize();
method public abstract boolean hasFinished();
method public abstract boolean hasStarted();
+ method public default void rangeStart(int, int, int);
method public abstract int start(int, int, int);
}
@@ -36397,6 +36398,7 @@
method public void onError(java.lang.String, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 063c3c3..7a6d87a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -39235,6 +39235,7 @@
method public abstract int getMaxBufferSize();
method public abstract boolean hasFinished();
method public abstract boolean hasStarted();
+ method public default void rangeStart(int, int, int);
method public abstract int start(int, int, int);
}
@@ -39387,6 +39388,7 @@
method public void onError(java.lang.String, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 4fcaf69..cd71166 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -36366,6 +36366,7 @@
method public abstract int getMaxBufferSize();
method public abstract boolean hasFinished();
method public abstract boolean hasStarted();
+ method public default void rangeStart(int, int, int);
method public abstract int start(int, int, int);
}
@@ -36518,6 +36519,7 @@
method public void onError(java.lang.String, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4ab815d..e3da337 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10028,6 +10028,17 @@
EPHEMERAL_SETTINGS.add(EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
}
+ /**
+ * Whether to show the high temperature warning notification.
+ * @hide
+ */
+ public static final String SHOW_TEMPERATURE_WARNING = "show_temperature_warning";
+
+ /**
+ * Temperature at which the high temperature warning notification should be shown.
+ * @hide
+ */
+ public static final String WARNING_TEMPERATURE = "warning_temperature";
}
/**
diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java
index 9920ea1..be5851c 100644
--- a/core/java/android/speech/tts/BlockingAudioTrack.java
+++ b/core/java/android/speech/tts/BlockingAudioTrack.java
@@ -164,7 +164,7 @@
// all data from the audioTrack has been sent to the mixer, so
// it's safe to release at this point.
if (DBG) Log.d(TAG, "Releasing audio track [" + track.hashCode() + "]");
- synchronized(mAudioTrackLock) {
+ synchronized (mAudioTrackLock) {
mAudioTrack = null;
}
track.release();
@@ -340,4 +340,25 @@
return value < min ? min : (value < max ? value : max);
}
+ /**
+ * @see
+ * AudioTrack#setPlaybackPositionUpdateListener(AudioTrack.OnPlaybackPositionUpdateListener).
+ */
+ public void setPlaybackPositionUpdateListener(
+ AudioTrack.OnPlaybackPositionUpdateListener listener) {
+ synchronized (mAudioTrackLock) {
+ if (mAudioTrack != null) {
+ mAudioTrack.setPlaybackPositionUpdateListener(listener);
+ }
+ }
+ }
+
+ /** @see AudioTrack#setNotificationMarkerPosition(int). */
+ public void setNotificationMarkerPosition(int frames) {
+ synchronized (mAudioTrackLock) {
+ if (mAudioTrack != null) {
+ mAudioTrack.setNotificationMarkerPosition(frames);
+ }
+ }
+ }
}
diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index 4e3acf6..edb6e48 100644
--- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -83,4 +83,19 @@
* callback.
*/
void onAudioAvailable(String utteranceId, in byte[] audio);
+
+ /**
+ * Tells the client that the engine is about to speak the specified range of the utterance.
+ *
+ * <p>
+ * Only called if the engine supplies timing information by calling
+ * {@link SynthesisCallback#rangeStart(int, int, int)} and only when the request is played back
+ * by the service, not when using {@link android.speech.tts.TextToSpeech#synthesizeToFile}.
+ * </p>
+ *
+ * @param utteranceId Unique id identifying the synthesis request.
+ * @param start The start character index of the range in the utterance text.
+ * @param end The end character index of the range (exclusive) in the utterance text.
+ */
+ void onUtteranceRangeStart(String utteranceId, int start, int end);
}
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index 778aa86..9e24b09 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -271,4 +271,12 @@
mStatusCode = errorCode;
}
}
+
+ public void rangeStart(int markerInFrames, int start, int end) {
+ if (mItem == null) {
+ Log.e(TAG, "mItem is null");
+ return;
+ }
+ mItem.rangeStart(markerInFrames, start, end);
+ }
}
diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java
index 2fd8499..8b74ed7 100644
--- a/core/java/android/speech/tts/SynthesisCallback.java
+++ b/core/java/android/speech/tts/SynthesisCallback.java
@@ -142,4 +142,26 @@
* <p>Useful for checking if a fallback from network request is possible.
*/
boolean hasFinished();
+
+ /**
+ * The service may call this method to provide timing information about the spoken text.
+ *
+ * <p>Calling this method means that at the given audio frame, the given range of the input is
+ * about to be spoken. If this method is called the client will receive a callback on the
+ * listener ({@link UtteranceProgressListener#onUtteranceRangeStart}) at the moment that frame
+ * has been reached by the playback head.
+ *
+ * <p>The markerInFrames is a frame index into the audio for this synthesis request, i.e. into
+ * the concatenation of the audio bytes sent to audioAvailable for this synthesis request. The
+ * definition of a frame depends on the format given by {@link #start}. See {@link AudioFormat}
+ * for more information.
+ *
+ * <p>This method should only be called on the synthesis thread, while in {@link
+ * TextToSpeechService#onSynthesizeText}.
+ *
+ * @param markerInFrames The position in frames in the audio where this range is spoken.
+ * @param start The start index of the range in the input text.
+ * @param end The end index (exclusive) of the range in the input text.
+ */
+ default void rangeStart(int markerInFrames, int start, int end) {}
}
diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
index 7423933..cb5f220 100644
--- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
+++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
@@ -17,18 +17,21 @@
import android.speech.tts.TextToSpeechService.AudioOutputParams;
import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
+import android.media.AudioTrack;
import android.util.Log;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.ConcurrentLinkedQueue;
/**
- * Manages the playback of a list of byte arrays representing audio data
- * that are queued by the engine to an audio track.
+ * Manages the playback of a list of byte arrays representing audio data that are queued by the
+ * engine to an audio track.
*/
-final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
+final class SynthesisPlaybackQueueItem extends PlaybackQueueItem
+ implements AudioTrack.OnPlaybackPositionUpdateListener {
private static final String TAG = "TTS.SynthQueueItem";
private static final boolean DBG = false;
@@ -63,6 +66,10 @@
private final BlockingAudioTrack mAudioTrack;
private final AbstractEventLogger mLogger;
+ // Stores a queue of markers. When the marker in front is reached the client is informed and we
+ // wait for the next one.
+ private ConcurrentLinkedQueue<ProgressMarker> markerList = new ConcurrentLinkedQueue<>();
+
SynthesisPlaybackQueueItem(AudioOutputParams audioParams, int sampleRate,
int audioFormat, int channelCount, UtteranceProgressDispatcher dispatcher,
Object callerIdentity, AbstractEventLogger logger) {
@@ -89,6 +96,8 @@
return;
}
+ mAudioTrack.setPlaybackPositionUpdateListener(this);
+
try {
byte[] buffer = null;
@@ -172,6 +181,55 @@
}
}
+ /** Convenience class for passing around TTS markers. */
+ private class ProgressMarker {
+ // The index in frames of this marker.
+ public final int frames;
+ // The start index in the text of the utterance.
+ public final int start;
+ // The end index (exclusive) in the text of the utterance.
+ public final int end;
+
+ public ProgressMarker(int frames, int start, int end) {
+ this.frames = frames;
+ this.start = start;
+ this.end = end;
+ }
+ }
+
+ /** Set a callback for the first marker in the queue. */
+ void updateMarker() {
+ ProgressMarker marker = markerList.peek();
+ if (marker != null) {
+ // Zero is used to disable the marker. The documentation recommends to use a non-zero
+ // position near zero such as 1.
+ int markerInFrames = marker.frames == 0 ? 1 : marker.frames;
+ mAudioTrack.setNotificationMarkerPosition(markerInFrames);
+ }
+ }
+
+ /** Informs us that at markerInFrames, the range between start and end is about to be spoken. */
+ void rangeStart(int markerInFrames, int start, int end) {
+ markerList.add(new ProgressMarker(markerInFrames, start, end));
+ updateMarker();
+ }
+
+ @Override
+ public void onMarkerReached(AudioTrack track) {
+ ProgressMarker marker = markerList.poll();
+ if (marker == null) {
+ Log.e(TAG, "onMarkerReached reached called but no marker in queue");
+ return;
+ }
+ // Inform the client.
+ getDispatcher().dispatchOnUtteranceRangeStart(marker.start, marker.end);
+ // Listen for the next marker.
+ // It's ok if this marker is in the past, in that case onMarkerReached will be called again.
+ updateMarker();
+ }
+
+ @Override
+ public void onPeriodicNotification(AudioTrack track) {}
void put(byte[] buffer) throws InterruptedException {
try {
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 24cad95..9a157b7 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -2103,55 +2103,69 @@
private boolean mEstablished;
- private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
- public void onStop(String utteranceId, boolean isStarted) throws RemoteException {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onStop(utteranceId, isStarted);
- }
- };
+ private final ITextToSpeechCallback.Stub mCallback =
+ new ITextToSpeechCallback.Stub() {
+ public void onStop(String utteranceId, boolean isStarted)
+ throws RemoteException {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onStop(utteranceId, isStarted);
+ }
+ };
- @Override
- public void onSuccess(String utteranceId) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onDone(utteranceId);
- }
- }
+ @Override
+ public void onSuccess(String utteranceId) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onDone(utteranceId);
+ }
+ }
- @Override
- public void onError(String utteranceId, int errorCode) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onError(utteranceId);
- }
- }
+ @Override
+ public void onError(String utteranceId, int errorCode) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onError(utteranceId);
+ }
+ }
- @Override
- public void onStart(String utteranceId) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onStart(utteranceId);
- }
- }
+ @Override
+ public void onStart(String utteranceId) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onStart(utteranceId);
+ }
+ }
- @Override
- public void onBeginSynthesis(String utteranceId, int sampleRateInHz, int audioFormat,
- int channelCount) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onBeginSynthesis(utteranceId, sampleRateInHz, audioFormat, channelCount);
- }
- }
+ @Override
+ public void onBeginSynthesis(
+ String utteranceId,
+ int sampleRateInHz,
+ int audioFormat,
+ int channelCount) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onBeginSynthesis(
+ utteranceId, sampleRateInHz, audioFormat, channelCount);
+ }
+ }
- @Override
- public void onAudioAvailable(String utteranceId, byte[] audio) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onAudioAvailable(utteranceId, audio);
- }
- }
- };
+ @Override
+ public void onAudioAvailable(String utteranceId, byte[] audio) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onAudioAvailable(utteranceId, audio);
+ }
+ }
+
+ @Override
+ public void onUtteranceRangeStart(String utteranceId, int start, int end) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onUtteranceRangeStart(utteranceId, start, end);
+ }
+ }
+ };
private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
private final ComponentName mName;
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 55da52b..80d3c8a 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -663,6 +663,8 @@
void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount);
void dispatchOnAudioAvailable(byte[] audio);
+
+ public void dispatchOnUtteranceRangeStart(int start, int end);
}
/** Set of parameters affecting audio output. */
@@ -882,6 +884,15 @@
}
}
+ @Override
+ public void dispatchOnUtteranceRangeStart(int start, int end) {
+ final String utteranceId = getUtteranceId();
+ if (utteranceId != null) {
+ mCallbacks.dispatchOnUtteranceRangeStart(
+ getCallerIdentity(), utteranceId, start, end);
+ }
+ }
+
abstract public String getUtteranceId();
String getStringParam(Bundle params, String key, String defaultValue) {
@@ -1559,6 +1570,17 @@
}
}
+ public void dispatchOnUtteranceRangeStart(
+ Object callerIdentity, String utteranceId, int start, int end) {
+ ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
+ if (cb == null) return;
+ try {
+ cb.onUtteranceRangeStart(utteranceId, start, end);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback dispatchOnUtteranceRangeStart(String, int, int) failed: " + e);
+ }
+ }
+
@Override
public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
IBinder caller = (IBinder) cookie;
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
index 72a5228..0ee3769 100644
--- a/core/java/android/speech/tts/UtteranceProgressListener.java
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -122,8 +122,24 @@
}
/**
- * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new
- * progress listener.
+ * This is called when the TTS service is about to speak the specified range of the utterance
+ * with the given utteranceId.
+ *
+ * <p>This method is called when the audio is expected to start playing on the speaker. Note
+ * that this is different from {@link #onAudioAvailable} which is called as soon as the audio is
+ * generated.
+ *
+ * <p>Only called if the engine supplies timing information by calling {@link
+ * SynthesisCallback#rangeStart(int, int, int)}.
+ *
+ * @param utteranceId Unique id identifying the synthesis request.
+ * @param start The start index of the range in the utterance text.
+ * @param end The end index of the range (exclusive) in the utterance text.
+ */
+ public void onUtteranceRangeStart(String utteranceId, int start, int end) {}
+
+ /**
+ * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new progress listener.
*
* @hide
*/
diff --git a/core/res/res/anim/app_starting_exit.xml b/core/res/res/anim/app_starting_exit.xml
index aaf7f15..dfa42e2 100644
--- a/core/res/res/anim/app_starting_exit.xml
+++ b/core/res/res/anim/app_starting_exit.xml
@@ -21,8 +21,8 @@
<alpha
xmlns:android="http://schemas.android.com/apk/res/android"
android:detachWallpaper="true"
- android:interpolator="@interpolator/decelerate_quad"
+ android:interpolator="@interpolator/linear"
android:fromAlpha="1.0"
android:toAlpha="0.0"
- android:duration="160" />
+ android:duration="150" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ac86439..d6ed9d8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -291,7 +291,7 @@
<bool name="quick_settings_show_full_alarm">false</bool>
<!-- Whether to show a warning notification when the device reaches a certain temperature. -->
- <bool name="config_showTemperatureWarning">false</bool>
+ <integer name="config_showTemperatureWarning">0</integer>
<!-- Temp at which to show a warning notification if config_showTemperatureWarning is true.
If < 0, uses the value from HardwarePropertiesManager#getDeviceTemperatures. -->
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 3103267..3df557d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -86,9 +86,12 @@
// another package than the top activity in the stack
boolean expandPipToFullscreen = true;
if (sourceComponent != null) {
- ComponentName topActivity = PipUtils.getTopPinnedActivity(mActivityManager);
- expandPipToFullscreen = topActivity != null && topActivity.getPackageName().equals(
- sourceComponent.getPackageName());
+ ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
+ mActivityManager);
+ if (topActivity != null && topActivity.getPackageName().equals(
+ sourceComponent.getPackageName())) {
+ expandPipToFullscreen = false;
+ }
}
if (expandPipToFullscreen) {
mTouchHandler.expandPinnedStackToFullscreen();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
index 2284013..d96baa6 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
@@ -148,7 +148,8 @@
*/
private void resolveActiveMediaController(List<MediaController> controllers) {
if (controllers != null) {
- final ComponentName topActivity = PipUtils.getTopPinnedActivity(mActivityManager);
+ final ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
+ mActivityManager);
if (topActivity != null) {
for (int i = 0; i < controllers.size(); i++) {
final MediaController controller = controllers.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
index 9c03830..a8cdd1b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
@@ -21,6 +21,7 @@
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
import android.content.ComponentName;
+import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
@@ -29,14 +30,23 @@
private static final String TAG = "PipUtils";
/**
- * @return the ComponentName of the top activity in the pinned stack, or null if none exists.
+ * @return the ComponentName of the top non-SystemUI activity in the pinned stack, or null if
+ * none exists.
*/
- public static ComponentName getTopPinnedActivity(IActivityManager activityManager) {
+ public static ComponentName getTopPinnedActivity(Context context,
+ IActivityManager activityManager) {
try {
- StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
+ final String sysUiPackageName = context.getPackageName();
+ final StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
pinnedStackInfo.taskIds.length > 0) {
- return pinnedStackInfo.topActivity;
+ for (int i = pinnedStackInfo.taskNames.length - 1; i >= 0; i--) {
+ ComponentName cn = ComponentName.unflattenFromString(
+ pinnedStackInfo.taskNames[i]);
+ if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) {
+ return cn;
+ }
+ }
}
} catch (RemoteException e) {
Log.w(TAG, "Unable to get pinned stack.");
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 28ca6a3..1d4a5c7 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.BatteryManager;
import android.os.Handler;
@@ -221,11 +222,15 @@
};
private void initTemperatureWarning() {
- if (!mContext.getResources().getBoolean(R.bool.config_showTemperatureWarning)) {
+ ContentResolver resolver = mContext.getContentResolver();
+ Resources resources = mContext.getResources();
+ if (Settings.Global.getInt(resolver, Settings.Global.SHOW_TEMPERATURE_WARNING,
+ resources.getInteger(R.integer.config_showTemperatureWarning)) == 0) {
return;
}
- mThrottlingTemp = mContext.getResources().getInteger(R.integer.config_warningTemperature);
+ mThrottlingTemp = Settings.Global.getFloat(resolver, Settings.Global.WARNING_TEMPERATURE,
+ resources.getInteger(R.integer.config_warningTemperature));
if (mThrottlingTemp < 0f) {
// Get the throttling temperature. No need to check if we're not throttling.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
index 0fc300d..528fefe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -91,9 +91,11 @@
@Override
public void onUserSwitched(int newUserId) {
- stopListening();
- startListening(newUserId);
- notifyUserChanged();
+ mContentResolver.unregisterContentObserver(mSettingsObserver);
+ mContentResolver.registerContentObserver(mDeviceProvisionedUri, true,
+ mSettingsObserver, 0);
+ mContentResolver.registerContentObserver(mUserSetupUri, true,
+ mSettingsObserver, newUserId);
notifyUserChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index f6b8891..266f053 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -23,6 +23,7 @@
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.Settings;
import android.support.v14.preference.PreferenceFragment;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.PreferenceCategory;
@@ -30,9 +31,9 @@
import android.support.v7.preference.PreferenceViewHolder;
import android.view.View;
+import com.android.systemui.R;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.PluginPrefs;
-import com.android.systemui.R;
import java.util.List;
import java.util.Set;
@@ -147,6 +148,12 @@
result.activityInfo.name)));
}
});
+ holder.itemView.setOnLongClickListener(v -> {
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ intent.setData(Uri.fromParts("package", mComponent.getPackageName(), null));
+ getContext().startActivity(intent);
+ return true;
+ });
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index d7b3728e..963a9dc 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1795,6 +1795,12 @@
}
}
+ void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
+ for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+ mTaskHistory.get(taskNdx).addStartingWindowsForVisibleActivities(taskSwitch);
+ }
+ }
+
/**
* @return true if the top visible activity wants to occlude the Keyguard, false otherwise
*/
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 14899b4..2c1c3a1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3229,6 +3229,17 @@
}
}
+ void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
+ for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+ final int topStackNdx = stacks.size() - 1;
+ for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = stacks.get(stackNdx);
+ stack.addStartingWindowsForVisibleActivities(taskSwitch);
+ }
+ }
+ }
+
void invalidateTaskLayers() {
mTaskLayersChanged = true;
}
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index cfe2eb0..b0a4746 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -29,6 +29,7 @@
import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY;
import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_OCCLUDE;
import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static com.android.server.wm.AppTransition.TRANSIT_NONE;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import android.os.IBinder;
@@ -120,6 +121,7 @@
// Some stack visibility might change (e.g. docked stack)
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+ mStackSupervisor.addStartingWindowsForVisibleActivities(true /* taskSwitch */);
mWindowManager.executeAppTransition();
} finally {
mWindowManager.continueSurfaceLayout();
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 1f5152a..7b4d289 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -416,7 +416,7 @@
final Configuration overrideConfig = getOverrideConfiguration();
mWindowContainerController = new TaskWindowContainerController(taskId, this, getStackId(),
userId, bounds, overrideConfig, mResizeMode, isHomeTask(), isOnTopLauncher(), onTop,
- showForAllUsers);
+ showForAllUsers, lastTaskDescription);
}
void removeWindowContainer() {
@@ -1402,6 +1402,9 @@
}
lastTaskDescription = new TaskDescription(label, null, iconFilename, colorPrimary,
colorBackground);
+ if (mWindowContainerController != null) {
+ mWindowContainerController.setTaskDescription(lastTaskDescription);
+ }
// Update the task affiliation color if we are the parent of the group
if (taskId == mAffiliatedTaskId) {
mAffiliatedTaskColor = lastTaskDescription.getPrimaryColor();
@@ -1981,6 +1984,15 @@
return rootAffinity != null && getStackId() != PINNED_STACK_ID;
}
+ void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
+ for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = mActivities.get(activityNdx);
+ if (r.visible) {
+ r.showStartingWindow(null /* prev */, false /* newTask */, taskSwitch);
+ }
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("userId="); pw.print(userId);
pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 0436139..27e0f29 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -56,7 +56,7 @@
private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2;
private final IApplicationToken mToken;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Handler mHandler;
private final Runnable mOnWindowsDrawn = () -> {
if (mListener == null) {
@@ -186,6 +186,7 @@
int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
WindowManagerService service) {
super(listener, service);
+ mHandler = new Handler(service.mH.getLooper());
mToken = token;
synchronized(mWindowMap) {
AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index ac9859d..079dc8f 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -375,6 +375,7 @@
// affected.
mService.getDefaultDisplayContentLocked().getDockedDividerController()
.notifyAppVisibilityChanged();
+ mService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible);
}
}
@@ -674,7 +675,7 @@
// well there is no point now.
if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Nulling last startingData");
startingData = null;
- } else if (mChildren.size() == 1 && startingSurface != null) {
+ } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) {
// If this is the last window except for a starting transition window,
// we need to get rid of the starting transition.
if (getController() != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3958510..914cc8d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2282,7 +2282,7 @@
final boolean foundTargetWs =
(w.mAppToken != null && w.mAppToken.token == appToken)
|| (mScreenshotApplicationState.appWin != null && wallpaperOnly);
- if (foundTargetWs && w.isDisplayedLw() && winAnim.getShown()) {
+ if (foundTargetWs && winAnim.getShown()) {
mScreenshotApplicationState.screenshotReady = true;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 680d0f2..3a3ec71 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -30,6 +30,7 @@
import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
import android.app.ActivityManager.StackId;
+import android.app.ActivityManager.TaskDescription;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -90,9 +91,11 @@
// Whether this task is an on-top launcher task, which is determined by the root activity.
private boolean mIsOnTopLauncher;
+ private TaskDescription mTaskDescription;
+
Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode, boolean homeTask,
- TaskWindowContainerController controller) {
+ TaskDescription taskDescription, TaskWindowContainerController controller) {
mTaskId = taskId;
mStack = stack;
mUserId = userId;
@@ -102,6 +105,7 @@
mHomeTask = homeTask;
setController(controller);
setBounds(bounds, overrideConfig);
+ mTaskDescription = taskDescription;
}
DisplayContent getDisplayContent() {
@@ -647,6 +651,14 @@
}
}
+ void setTaskDescription(TaskDescription taskDescription) {
+ mTaskDescription = taskDescription;
+ }
+
+ TaskDescription getTaskDescription() {
+ return mTaskDescription;
+ }
+
@Override
boolean fillsParent() {
return mFillsParent || !StackId.isTaskResizeAllowed(mStack.mStackId);
@@ -688,6 +700,5 @@
pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
wtoken.dump(pw, triplePrefix);
}
-
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 15878f6..2b74f84 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -26,6 +26,8 @@
import android.util.ArraySet;
import android.view.WindowManagerPolicy.StartingSurface;
+import com.google.android.collect.Sets;
+
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
@@ -66,10 +68,27 @@
if (!ENABLE_TASK_SNAPSHOTS) {
return;
}
+ handleClosingApps(mService.mClosingApps);
+ }
+
+
+ /**
+ * Called when the visibility of an app changes outside of the regular app transition flow.
+ */
+ void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
+ if (!ENABLE_TASK_SNAPSHOTS) {
+ return;
+ }
+ if (!visible) {
+ handleClosingApps(Sets.newArraySet(appWindowToken));
+ }
+ }
+
+ private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
// We need to take a snapshot of the task if and only if all activities of the task are
// either closing or hidden.
- getClosingTasks(mService.mClosingApps, mTmpTasks);
+ getClosingTasks(closingApps, mTmpTasks);
for (int i = mTmpTasks.size() - 1; i >= 0; i--) {
final Task task = mTmpTasks.valueAt(i);
if (!canSnapshotTask(task)) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 4a09423..cfcbbd0 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -26,12 +26,16 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.app.ActivityManager.TaskDescription;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.GraphicBuffer;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Slog;
@@ -43,6 +47,7 @@
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy.StartingSurface;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.view.BaseIWindow;
/**
@@ -61,6 +66,7 @@
private final WindowManagerService mService;
private boolean mHasDrawn;
private boolean mReportNextDraw;
+ private Paint mFillBackgroundPaint = new Paint();
static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token,
GraphicBuffer snapshot) {
@@ -73,6 +79,7 @@
final Rect tmpRect = new Rect();
final Rect tmpFrame = new Rect();
final Configuration tmpConfiguration = new Configuration();
+ int fillBackgroundColor = Color.WHITE;
synchronized (service.mWindowMap) {
layoutParams.type = TYPE_APPLICATION_STARTING;
layoutParams.format = snapshot.getFormat();
@@ -90,6 +97,12 @@
layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
layoutParams.setTitle(String.format(TITLE_FORMAT, token.mTask.mTaskId));
+ if (token.mTask != null) {
+ final TaskDescription taskDescription = token.mTask.getTaskDescription();
+ if (taskDescription != null) {
+ fillBackgroundColor = taskDescription.getBackgroundColor();
+ }
+ }
}
try {
final int res = session.addToDisplay(window, window.mSeq, layoutParams,
@@ -103,7 +116,7 @@
// Local call.
}
final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
- surface);
+ surface, fillBackgroundColor);
window.setOuter(snapshotSurface);
try {
session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame,
@@ -116,11 +129,14 @@
return snapshotSurface;
}
- private TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface) {
+ @VisibleForTesting
+ TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface,
+ int fillBackgroundColor) {
mService = service;
mSession = WindowManagerGlobal.getWindowSession();
mWindow = window;
mSurface = surface;
+ mFillBackgroundPaint.setColor(fillBackgroundColor);
}
@Override
@@ -136,7 +152,9 @@
// TODO: Just wrap the buffer here without any copying.
final Canvas c = mSurface.lockHardwareCanvas();
- c.drawBitmap(Bitmap.createHardwareBitmap(snapshot), 0, 0, null);
+ final Bitmap b = Bitmap.createHardwareBitmap(snapshot);
+ fillEmptyBackground(c, b);
+ c.drawBitmap(b, 0, 0, null);
mSurface.unlockCanvasAndPost(c);
final boolean reportNextDraw;
synchronized (mService.mWindowMap) {
@@ -149,6 +167,21 @@
mSurface.release();
}
+ @VisibleForTesting
+ void fillEmptyBackground(Canvas c, Bitmap b) {
+ final boolean fillHorizontally = c.getWidth() > b.getWidth();
+ final boolean fillVertically = c.getHeight() > b.getHeight();
+ if (fillHorizontally) {
+ c.drawRect(b.getWidth(), 0, c.getWidth(), fillVertically
+ ? b.getHeight()
+ : c.getHeight(),
+ mFillBackgroundPaint);
+ }
+ if (fillVertically) {
+ c.drawRect(0, b.getHeight(), c.getWidth(), c.getHeight(), mFillBackgroundPaint);
+ }
+ }
+
private void reportDrawn() {
synchronized (mService.mWindowMap) {
mReportNextDraw = false;
@@ -160,7 +193,7 @@
}
}
- private static Handler sHandler = new Handler() {
+ private static Handler sHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index 3c438ca..61a2cd9 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.app.ActivityManager.TaskDescription;
import android.app.ActivityManager.TaskSnapshot;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -62,7 +63,8 @@
public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
int stackId, int userId, Rect bounds, Configuration overrideConfig, int resizeMode,
- boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers) {
+ boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers,
+ TaskDescription taskDescription) {
super(listener, WindowManagerService.getInstance());
mTaskId = taskId;
@@ -79,7 +81,7 @@
}
EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId);
final Task task = createTask(taskId, stack, userId, bounds, overrideConfig, resizeMode,
- homeTask, isOnTopLauncher);
+ homeTask, isOnTopLauncher, taskDescription);
final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
stack.addTask(task, position, showForAllUsers, true /* moveParents */);
}
@@ -88,9 +90,9 @@
@VisibleForTesting
Task createTask(int taskId, TaskStack stack, int userId, Rect bounds,
Configuration overrideConfig, int resizeMode, boolean homeTask,
- boolean isOnTopLauncher) {
+ boolean isOnTopLauncher, TaskDescription taskDescription) {
return new Task(taskId, stack, userId, mService, bounds, overrideConfig, isOnTopLauncher,
- resizeMode, homeTask, this);
+ resizeMode, homeTask, taskDescription, this);
}
@Override
@@ -263,6 +265,16 @@
}
}
+ public void setTaskDescription(TaskDescription taskDescription) {
+ synchronized (mWindowMap) {
+ if (mContainer == null) {
+ Slog.w(TAG_WM, "setTaskDescription: taskId " + mTaskId + " not found.");
+ return;
+ }
+ mContainer.setTaskDescription(taskDescription);
+ }
+ }
+
void reportSnapshotChanged(TaskSnapshot snapshot) {
mHandler.obtainMessage(REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
index 26accc3..2af4163 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -18,12 +18,10 @@
import org.junit.Test;
-import android.os.Binder;
-import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.view.IApplicationToken;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -74,6 +72,67 @@
controller.removeContainer(sDisplayContent.getDisplayId());
// Assert orientation is unspecified to after container is removed.
assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, controller.getOrientation());
+
+ // Reset display frozen state
+ sWm.mDisplayFrozen = false;
+ }
+
+ private void assertHasStartingWindow(AppWindowToken atoken) {
+ assertNotNull(atoken.startingSurface);
+ assertNotNull(atoken.startingData);
+ assertNotNull(atoken.startingWindow);
+ }
+
+ private void assertNoStartingWindow(AppWindowToken atoken) {
+ assertNull(atoken.startingSurface);
+ assertNull(atoken.startingWindow);
+ assertNull(atoken.startingData);
+ }
+
+ @Test
+ public void testCreateRemoveStartingWindow() throws Exception {
+ final TestAppWindowContainerController controller = createAppWindowController();
+ controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false);
+ waitUntilHandlerIdle();
+ final AppWindowToken atoken = controller.getAppWindowToken();
+ assertHasStartingWindow(atoken);
+ controller.removeStartingWindow();
+ waitUntilHandlerIdle();
+ assertNoStartingWindow(atoken);
+ }
+
+ @Test
+ public void testTransferStartingWindow() throws Exception {
+ final TestAppWindowContainerController controller1 = createAppWindowController();
+ final TestAppWindowContainerController controller2 = createAppWindowController();
+ controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false);
+ waitUntilHandlerIdle();
+ controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
+ true, true, false);
+ waitUntilHandlerIdle();
+ assertNoStartingWindow(controller1.getAppWindowToken());
+ assertHasStartingWindow(controller2.getAppWindowToken());
+ }
+
+ @Test
+ public void testTransferStartingWindowWhileCreating() throws Exception {
+ final TestAppWindowContainerController controller1 = createAppWindowController();
+ final TestAppWindowContainerController controller2 = createAppWindowController();
+ sPolicy.setRunnableWhenAddingSplashScreen(() -> {
+
+ // Surprise, ...! Transfer window in the middle of the creation flow.
+ controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
+ true, true, false);
+ });
+ controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false);
+ waitUntilHandlerIdle();
+ assertNoStartingWindow(controller1.getAppWindowToken());
+ assertHasStartingWindow(controller2.getAppWindowToken());
}
private TestAppWindowContainerController createAppWindowController() {
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
new file mode 100644
index 0000000..aab75ee
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskSnapshotSurface}.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskSnapshotSurfaceTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskSnapshotSurfaceTest extends WindowTestsBase {
+
+ private TaskSnapshotSurface mSurface;
+
+ @Before
+ public void setUp() {
+ mSurface = new TaskSnapshotSurface(null, null, null, Color.WHITE);
+ }
+
+ @Test
+ public void fillEmptyBackground_fillHorizontally() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(200);
+ when(mockCanvas.getHeight()).thenReturn(100);
+ final Bitmap b = Bitmap.createBitmap(100, 200, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_fillVertically() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(100);
+ when(mockCanvas.getHeight()).thenReturn(200);
+ final Bitmap b = Bitmap.createBitmap(200, 100, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_fillBoth() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(200);
+ when(mockCanvas.getHeight()).thenReturn(200);
+ final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+ verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_dontFill_sameSize() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(100);
+ when(mockCanvas.getHeight()).thenReturn(100);
+ final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_dontFill_bitmapLarger() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(100);
+ when(mockCanvas.getHeight()).thenReturn(100);
+ final Bitmap b = Bitmap.createBitmap(200, 200, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 269b719..ec429a0 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -24,6 +24,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
@@ -74,6 +75,7 @@
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy;
import android.view.animation.Animation;
import android.os.PowerManagerInternal;
@@ -92,6 +94,8 @@
int rotationToReport = 0;
+ private Runnable mRunnableWhenAddingSplashScreen;
+
static synchronized WindowManagerService getWindowManagerService(Context context) {
if (sWm == null) {
// We only want to do this once for the test process as we don't want WM to try to
@@ -318,11 +322,36 @@
return false;
}
+ /**
+ * Sets a runnable to run when adding a splash screen which gets executed after the window has
+ * been added but before returning the surface.
+ */
+ void setRunnableWhenAddingSplashScreen(Runnable r) {
+ mRunnableWhenAddingSplashScreen = r;
+ }
+
@Override
public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
int logo, int windowFlags, Configuration overrideConfig, int displayId) {
- return null;
+ final com.android.server.wm.WindowState window;
+ final AppWindowToken atoken;
+ synchronized (sWm.mWindowMap) {
+ atoken = WindowTestsBase.sDisplayContent.getAppWindowToken(appToken);
+ window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, atoken,
+ "Starting window");
+ atoken.startingWindow = window;
+ }
+ if (mRunnableWhenAddingSplashScreen != null) {
+ mRunnableWhenAddingSplashScreen.run();
+ mRunnableWhenAddingSplashScreen = null;
+ }
+ return () -> {
+ synchronized (sWm.mWindowMap) {
+ atoken.removeChild(window);
+ atoken.startingWindow = null;
+ }
+ };
}
@Override
@@ -482,7 +511,7 @@
@Override
public boolean isScreenOn() {
- return false;
+ return true;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 6129198..772bfb4 100644
--- a/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -45,20 +45,18 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class UnknownAppVisibilityControllerTest {
+public class UnknownAppVisibilityControllerTest extends WindowTestsBase {
private WindowManagerService mWm;
- private @Mock ActivityManagerInternal mAm;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
+ super.setUp();
final Context context = InstrumentationRegistry.getTargetContext();
- LocalServices.addService(ActivityManagerInternal.class, mAm);
doAnswer((InvocationOnMock invocationOnMock) -> {
invocationOnMock.getArgumentAt(0, Runnable.class).run();
return null;
- }).when(mAm).notifyKeyguardFlagsChanged(any());
+ }).when(sMockAm).notifyKeyguardFlagsChanged(any());
mWm = TestWindowManagerPolicy.getWindowManagerService(context);
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 466da94..085cfd8 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -20,6 +20,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import android.app.ActivityManager.TaskDescription;
import android.content.Context;
import android.graphics.Rect;
import android.os.Binder;
@@ -76,7 +77,7 @@
final Rect mInsetBounds = new Rect();
boolean mFullscreenForTest = true;
TaskWithBounds(Rect bounds) {
- super(0, mStubStack, 0, sWm, null, null, false, 0, false, null);
+ super(0, mStubStack, 0, sWm, null, null, false, 0, false, new TaskDescription(), null);
mBounds = bounds;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 813d263..ae344dd 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -16,15 +16,18 @@
package com.android.server.wm;
+import android.app.ActivityManager.TaskDescription;
+import android.app.ActivityManagerInternal;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.view.IApplicationToken;
import org.junit.Assert;
import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
-import android.app.ActivityManager;
-import android.app.ActivityManager.TaskSnapshot;
import android.content.Context;
import android.os.IBinder;
import android.support.test.InstrumentationRegistry;
@@ -50,13 +53,17 @@
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.mockito.Mockito.mock;
+import com.android.server.AttributeCache;
+import com.android.server.LocalServices;
+
/**
* Common base class for window manager unit test classes.
*/
class WindowTestsBase {
static WindowManagerService sWm = null;
- private final IWindow mIWindow = new TestIWindow();
- private final Session mMockSession = mock(Session.class);
+ static TestWindowManagerPolicy sPolicy = null;
+ private final static IWindow sIWindow = new TestIWindow();
+ private final static Session sMockSession = mock(Session.class);
static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
private static int sNextTaskId = 0;
@@ -72,19 +79,27 @@
static WindowState sAppWindow;
static WindowState sChildAppWindowAbove;
static WindowState sChildAppWindowBelow;
+ static @Mock ActivityManagerInternal sMockAm;
@Before
public void setUp() throws Exception {
if (sOneTimeSetupDone) {
+ Mockito.reset(sMockAm);
return;
}
sOneTimeSetupDone = true;
+ MockitoAnnotations.initMocks(this);
final Context context = InstrumentationRegistry.getTargetContext();
+ LocalServices.addService(ActivityManagerInternal.class, sMockAm);
+ AttributeCache.init(context);
sWm = TestWindowManagerPolicy.getWindowManagerService(context);
+ sPolicy = (TestWindowManagerPolicy) sWm.mPolicy;
sLayersController = new WindowLayersController(sWm);
sDisplayContent = new DisplayContent(context.getDisplay(), sWm, sLayersController,
new WallpaperController(sWm));
sWm.mRoot.addChild(sDisplayContent, 0);
+ sWm.mDisplayEnabled = true;
+ sWm.mDisplayReady = true;
// Set-up some common windows.
sWallpaperWindow = createWindow(null, TYPE_WALLPAPER, sDisplayContent, "wallpaperWindow");
@@ -108,7 +123,14 @@
Assert.assertTrue("Excepted " + first + " to be greater than " + second, first > second);
}
- private WindowToken createWindowToken(DisplayContent dc, int type) {
+ /**
+ * Waits until the main handler for WM has processed all messages.
+ */
+ void waitUntilHandlerIdle() {
+ sWm.mH.runWithScissors(() -> { }, 0);
+ }
+
+ private static WindowToken createWindowToken(DisplayContent dc, int type) {
if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) {
return new TestWindowToken(type, dc);
}
@@ -120,7 +142,7 @@
return token;
}
- WindowState createWindow(WindowState parent, int type, String name) {
+ static WindowState createWindow(WindowState parent, int type, String name) {
return (parent == null)
? createWindow(parent, type, sDisplayContent, name)
: createWindow(parent, type, parent.mToken, name);
@@ -132,16 +154,16 @@
return createWindow(null, type, token, name);
}
- WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) {
+ static WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) {
final WindowToken token = createWindowToken(dc, type);
return createWindow(parent, type, token, name);
}
- WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
+ static WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
attrs.setTitle(name);
- final WindowState w = new WindowState(sWm, mMockSession, mIWindow, token, parent, OP_NONE,
+ final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE,
0, attrs, 0, 0);
// TODO: Probably better to make this call in the WindowState ctor to avoid errors with
// adding it to the token...
@@ -150,22 +172,22 @@
}
/** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */
- TaskStack createTaskStackOnDisplay(DisplayContent dc) {
+ static TaskStack createTaskStackOnDisplay(DisplayContent dc) {
final int stackId = sNextStackId++;
dc.addStackToDisplay(stackId, true);
return sWm.mStackIdToStack.get(stackId);
}
/**Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
- Task createTaskInStack(TaskStack stack, int userId) {
+ static Task createTaskInStack(TaskStack stack, int userId) {
final Task newTask = new Task(sNextTaskId++, stack, userId, sWm, null, EMPTY, false, 0,
- false, null);
+ false, new TaskDescription(), null);
stack.addTask(newTask, POSITION_TOP);
return newTask;
}
/* Used so we can gain access to some protected members of the {@link WindowToken} class */
- class TestWindowToken extends WindowToken {
+ static class TestWindowToken extends WindowToken {
TestWindowToken(int type, DisplayContent dc) {
this(type, dc, false /* persistOnEmpty */);
@@ -185,7 +207,7 @@
}
/** Used so we can gain access to some protected members of the {@link AppWindowToken} class. */
- class TestAppWindowToken extends AppWindowToken {
+ static class TestAppWindowToken extends AppWindowToken {
TestAppWindowToken(DisplayContent dc) {
super(sWm, null, false, dc);
@@ -218,7 +240,7 @@
Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode,
boolean homeTask, TaskWindowContainerController controller) {
super(taskId, stack, userId, service, bounds, overrideConfig, isOnTopLauncher,
- resizeMode, homeTask, controller);
+ resizeMode, homeTask, new TaskDescription(), controller);
}
boolean shouldDeferRemoval() {
@@ -249,13 +271,14 @@
TestTaskWindowContainerController(int stackId) {
super(sNextTaskId++, snapshot -> {}, stackId, 0 /* userId */, null /* bounds */,
EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, false /* homeTask*/,
- false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */);
+ false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */,
+ new TaskDescription());
}
@Override
TestTask createTask(int taskId, TaskStack stack, int userId, Rect bounds,
Configuration overrideConfig, int resizeMode, boolean homeTask,
- boolean isOnTopLauncher) {
+ boolean isOnTopLauncher, TaskDescription taskDescription) {
return new TestTask(taskId, stack, userId, mService, bounds, overrideConfig,
isOnTopLauncher, resizeMode, homeTask, this);
}
@@ -279,6 +302,10 @@
0 /* inputDispatchingTimeoutNanos */, sWm);
mToken = token;
}
+
+ AppWindowToken getAppWindowToken() {
+ return (AppWindowToken) sDisplayContent.getWindowToken(mToken.asBinder());
+ }
}
class TestIApplicationToken implements IApplicationToken {
@@ -295,7 +322,7 @@
boolean resizeReported;
TestWindowState(WindowManager.LayoutParams attrs, WindowToken token) {
- super(sWm, mMockSession, mIWindow, token, null, OP_NONE, 0, attrs, 0, 0);
+ super(sWm, sMockSession, sIWindow, token, null, OP_NONE, 0, attrs, 0, 0);
}
@Override