diff options
37 files changed, 660 insertions, 111 deletions
diff --git a/api/current.txt b/api/current.txt index 243c78f80bad..2fe3f44533e2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -36245,6 +36245,7 @@ package android.speech.tts { 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 @@ package android.speech.tts { 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 063c3c3a0e34..7a6d87a3c9f1 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -39235,6 +39235,7 @@ package android.speech.tts { 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 @@ package android.speech.tts { 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 4fcaf6926cae..cd711664bd65 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -36366,6 +36366,7 @@ package android.speech.tts { 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 @@ package android.speech.tts { 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 4ab815d2d07e..e3da337d9e6a 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10028,6 +10028,17 @@ public final class Settings { 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 9920ea11e35d..be5851c3e64f 100644 --- a/core/java/android/speech/tts/BlockingAudioTrack.java +++ b/core/java/android/speech/tts/BlockingAudioTrack.java @@ -164,7 +164,7 @@ class BlockingAudioTrack { // 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 @@ class BlockingAudioTrack { 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 4e3acf6a1993..edb6e482f3d0 100644 --- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl +++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl @@ -83,4 +83,19 @@ oneway interface ITextToSpeechCallback { * 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 778aa86bcee5..9e24b09e94ad 100644 --- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java +++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java @@ -271,4 +271,12 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { 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 2fd84996ece0..8b74ed763c32 100644 --- a/core/java/android/speech/tts/SynthesisCallback.java +++ b/core/java/android/speech/tts/SynthesisCallback.java @@ -142,4 +142,26 @@ public interface SynthesisCallback { * <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 742393346288..cb5f2209fa37 100644 --- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java +++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java @@ -17,18 +17,21 @@ package android.speech.tts; 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 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { 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 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { return; } + mAudioTrack.setPlaybackPositionUpdateListener(this); + try { byte[] buffer = null; @@ -172,6 +181,55 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { } } + /** 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 24cad950f648..9a157b7c9b5d 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -2103,55 +2103,69 @@ public class TextToSpeech { 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 55da52b028d7..80d3c8a252e3 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -663,6 +663,8 @@ public abstract class TextToSpeechService extends Service { 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 @@ public abstract class TextToSpeechService extends Service { } } + @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 abstract class TextToSpeechService extends Service { } } + 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 72a522800d65..0ee376948538 100644 --- a/core/java/android/speech/tts/UtteranceProgressListener.java +++ b/core/java/android/speech/tts/UtteranceProgressListener.java @@ -122,8 +122,24 @@ public abstract class UtteranceProgressListener { } /** - * 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 aaf7f156883b..dfa42e206c28 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 ac86439665b3..d6ed9d8092ca 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 3103267344a5..3df557d6227c 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 @@ public class PipManager { // 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 22840138e152..d96baa6b3620 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 @@ public class PipMediaController { */ 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 9c03830810aa..a8cdd1bdb802 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 static android.app.ActivityManager.StackId.PINNED_STACK_ID; 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 @@ public class PipUtils { 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 28ca6a3a347b..1d4a5c71c2b4 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.ContentResolver; 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 @@ public class PowerUI extends SystemUI { }; 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 0fc300d1aa07..528fefe49653 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 @@ public class DeviceProvisionedControllerImpl extends CurrentUserTracker implemen @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 f6b8891863d4..266f05372813 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.PackageManager; 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.PreferenceScreen; 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 @@ public class PluginFragment extends PreferenceFragment { 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 d7b3728e9803..963a9dcb1b01 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 @@ final class ActivityStack extends ConfigurationContainer { } } + 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 14899b4d5256..2c1c3a191d11 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } + 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 cfe2eb075530..b0a4746b8ff1 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_FLAG_KEYGUARD_GOING_AW 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 @@ class KeyguardController { // 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 1f5152a1606d..7b4d289ac82a 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 class TaskRecord extends ConfigurationContainer implements TaskWindowConta 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 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta } 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 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta 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 0436139f4fda..27e0f292fb65 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 @@ public class AppWindowContainerController 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 @@ public class AppWindowContainerController 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 ac9859d42fcc..079dc8f2b0ad 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // affected. mService.getDefaultDisplayContentLocked().getDockedDividerController() .notifyAppVisibilityChanged(); + mService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible); } } @@ -674,7 +675,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // 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 3958510f5957..914cc8de37ed 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 680d0f2881a6..3a3ec71ff86e 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.WindowManagerDebugConfig.TAG_WM; 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 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU // 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 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU mHomeTask = homeTask; setController(controller); setBounds(bounds, overrideConfig); + mTaskDescription = taskDescription; } DisplayContent getDisplayContent() { @@ -647,6 +651,14 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU } } + void setTaskDescription(TaskDescription taskDescription) { + mTaskDescription = taskDescription; + } + + TaskDescription getTaskDescription() { + return mTaskDescription; + } + @Override boolean fillsParent() { return mFillsParent || !StackId.isTaskResizeAllowed(mStack.mStackId); @@ -688,6 +700,5 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU 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 15878f6e414a..2b74f84701b4 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.os.Environment; 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 @@ class TaskSnapshotController { 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 4a094237b037..cfcbbd0358b5 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 android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 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.WindowManager; 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 @@ class TaskSnapshotSurface implements StartingSurface { 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 @@ class TaskSnapshotSurface implements StartingSurface { 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 @@ class TaskSnapshotSurface implements StartingSurface { 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 @@ class TaskSnapshotSurface implements StartingSurface { // 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 @@ class TaskSnapshotSurface implements StartingSurface { 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 @@ class TaskSnapshotSurface implements StartingSurface { // 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 @@ class TaskSnapshotSurface implements StartingSurface { 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 @@ class TaskSnapshotSurface implements StartingSurface { } } - 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 3c438ca3195f..61a2cd96f6c5 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 class TaskWindowContainerController 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 @@ public class TaskWindowContainerController } 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 @@ public class TaskWindowContainerController @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 class TaskWindowContainerController } } + 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 26accc3bcd41..2af4163770ae 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 @@ package com.android.server.wm; 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 @@ public class AppWindowContainerControllerTests extends WindowTestsBase { 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 000000000000..aab75ee1699b --- /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 269b71986e72..ec429a05e3ca 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_ATTACHED_ 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.Display; 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 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { 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 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { 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 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { @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 612919845f92..772bfb49ed4f 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 @@ import org.mockito.invocation.InvocationOnMock; @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 466da942edbc..085cfd8b1ef0 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.Before; 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 @@ public class WindowFrameTests { 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 813d2638ac94..ae344ddb38f3 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 android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; 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 @@ class WindowTestsBase { 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 @@ class WindowTestsBase { 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 @@ class WindowTestsBase { 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 @@ class WindowTestsBase { 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 @@ class WindowTestsBase { } /** 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 @@ class WindowTestsBase { } /** 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 @@ class WindowTestsBase { 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 @@ class WindowTestsBase { 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 @@ class WindowTestsBase { 0 /* inputDispatchingTimeoutNanos */, sWm); mToken = token; } + + AppWindowToken getAppWindowToken() { + return (AppWindowToken) sDisplayContent.getWindowToken(mToken.asBinder()); + } } class TestIApplicationToken implements IApplicationToken { @@ -295,7 +322,7 @@ class WindowTestsBase { 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 |