Merge "VolumeShaper: Enable xOffset from Java" into oc-dev
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 97da588..17f00c2 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -167,4 +167,12 @@
* Remove user's running state
*/
public abstract void removeUserState(int userId);
+
+ /**
+ * Returns an array of user ids. This array is cached in UserManagerService and passed as a
+ * reference, so do not modify the returned array.
+ *
+ * @return the array of user ids.
+ */
+ public abstract int[] getUserIds();
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 9cea4ed..2c33b60 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -856,7 +856,7 @@
*/
@Deprecated
public void setWindowType(int type) {
- if (getContext().getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.N_MR1) {
+ if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
throw new UnsupportedOperationException(
"SurfaceView#setWindowType() has never been a public API.");
}
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index e4d4e7b..0973d0a 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -334,7 +334,7 @@
*/
ViewTreeObserver(Context context) {
sIllegalOnDrawModificationIsFatal =
- context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.N_MR1;
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O;
}
/**
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index f53b5d6..8920089 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -83,8 +83,7 @@
* supported by the current framework version.
*/
public static boolean hasCorrectTargetSdkVersion(PackageInfo packageInfo) {
- // TODO(gsennton) use Build.VERSION_CODES.O when that has been updated.
- return packageInfo.applicationInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1;
+ return packageInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O;
}
public UserInfo getUserInfo() {
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index be0967f..cfd1445 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -16,7 +16,7 @@
package android.widget;
-import static android.os.Build.VERSION_CODES.N_MR1;
+import static android.os.Build.VERSION_CODES.O;
import android.annotation.Nullable;
import android.content.Context;
@@ -302,7 +302,7 @@
getTempCalendarForTime(timeInMillis);
final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
- if (targetSdkVersion > N_MR1) {
+ if (targetSdkVersion >= O) {
if (mTempCalendar.before(mMinDate) || mTempCalendar.after(mMaxDate)) {
throw new IllegalArgumentException("timeInMillis must be between the values of "
+ "getMinDate() and getMaxDate()");
diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml
index c96e280..c1d26e3 100644
--- a/core/res/res/anim/lock_screen_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_behind_enter.xml
@@ -17,8 +17,7 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:detachWallpaper="true"
- android:shareInterpolator="false"
- android:startOffset="100">
+ android:shareInterpolator="false">
<translate android:fromYDelta="110%p" android:toYDelta="0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
diff --git a/core/res/res/anim/lock_screen_behind_enter_fade_in.xml b/core/res/res/anim/lock_screen_behind_enter_fade_in.xml
index 94e40a8..e9475f5 100644
--- a/core/res/res/anim/lock_screen_behind_enter_fade_in.xml
+++ b/core/res/res/anim/lock_screen_behind_enter_fade_in.xml
@@ -22,6 +22,5 @@
android:interpolator="@interpolator/linear"
android:fromAlpha="0" android:toAlpha="1"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
- android:duration="250"
- android:startOffset="100">
+ android:duration="250">
</alpha>
\ No newline at end of file
diff --git a/core/res/res/anim/lock_screen_behind_enter_wallpaper.xml b/core/res/res/anim/lock_screen_behind_enter_wallpaper.xml
index 660b662..50156fb 100644
--- a/core/res/res/anim/lock_screen_behind_enter_wallpaper.xml
+++ b/core/res/res/anim/lock_screen_behind_enter_wallpaper.xml
@@ -16,12 +16,12 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="100">
+ android:detachWallpaper="true" android:shareInterpolator="false" >
<alpha
android:fromAlpha="0.0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true"
android:interpolator="@interpolator/decelerate_quint"
- android:duration="400"/>
+ android:duration="300"/>
<translate android:fromYDelta="11%p" android:toYDelta="0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
diff --git a/core/res/res/anim/lock_screen_wallpaper_exit.xml b/core/res/res/anim/lock_screen_wallpaper_exit.xml
index 49d0327..ba8741a 100644
--- a/core/res/res/anim/lock_screen_wallpaper_exit.xml
+++ b/core/res/res/anim/lock_screen_wallpaper_exit.xml
@@ -16,12 +16,12 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false" android:startOffset="100">
+ android:shareInterpolator="false">
<alpha
android:fromAlpha="1.0" android:toAlpha="0.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:interpolator="@interpolator/fast_out_linear_in"
- android:duration="150"/>
+ android:duration="200"/>
<!-- Empty animation so the animation has same duration as lock_screen_behind_enter animation
-->
diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml
index 8b95f9f..863efef 100644
--- a/core/res/res/layout-land/time_picker_material.xml
+++ b/core/res/res/layout-land/time_picker_material.xml
@@ -20,11 +20,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
- <LinearLayout
+ <RelativeLayout
android:id="@+id/time_header"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:orientation="vertical"
android:gravity="center"
android:paddingStart="?attr/dialogPreferredPadding"
android:paddingEnd="?attr/dialogPreferredPadding">
@@ -112,7 +111,7 @@
android:includeFontPadding="false"
android:button="@null" />
</RadioGroup>
- </LinearLayout>
+ </RelativeLayout>
<TextView
android:visibility="gone"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6640102..8e6c402 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2849,6 +2849,11 @@
<!-- Default data warning level in mb -->
<integer name="default_data_warning_level_mb">2048</integer>
+ <!-- When true, indicates that the vendor's IMS implementation requires a workaround when
+ sending a request to enable or disable the camera while the video session is also
+ paused. -->
+ <bool name="config_useVideoPauseWorkaround">false</bool>
+
<!-- Whether to send a custom package name with the PSD.-->
<bool name="config_sendPackageName">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 51d5b1e..8d2666e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3005,7 +3005,7 @@
<java-symbol type="bool" name="enable_pbap_pce_profile" />
<java-symbol type="integer" name="default_data_warning_level_mb" />
-
+ <java-symbol type="bool" name="config_useVideoPauseWorkaround" />
<java-symbol type="bool" name="config_sendPackageName" />
<java-symbol type="string" name="config_helpPackageNameKey" />
<java-symbol type="string" name="config_helpPackageNameValue" />
diff --git a/media/java/android/media/AudioFocusRequest.java b/media/java/android/media/AudioFocusRequest.java
index b1dc3ad..9841815 100644
--- a/media/java/android/media/AudioFocusRequest.java
+++ b/media/java/android/media/AudioFocusRequest.java
@@ -40,7 +40,7 @@
* <p>When an application requests audio focus, it expresses its intention to “own” audio focus to
* play audio. Let’s review the different types of focus requests, the return value after a request,
* and the responses to a loss.
- * <br><b>Note:<b> applications should not play anything until granted focus.
+ * <p class="note">Note: applications should not play anything until granted focus.</p>
*
* <h3>The different types of focus requests</h3>
* <p>There are four focus request types. A successful focus request with each will yield different
@@ -77,9 +77,10 @@
*
* <p>An {@code AudioFocusRequest} instance always contains one of the four types of requests
* explained above. It is passed when building an {@code AudioFocusRequest} instance with its
- * builder in the {@link Builder} constructor {@link Builder#Builder(int)}, or with
- * {@link Builder#setFocusGain(int)} after copying an existing instance with
- * {@link Builder#Builder(AudioFocusRequest)}.
+ * builder in the {@link Builder} constructor
+ * {@link AudioFocusRequest.Builder#AudioFocusRequest.Builder(int)}, or
+ * with {@link AudioFocusRequest.Builder#setFocusGain(int)} after copying an existing instance with
+ * {@link AudioFocusRequest.Builder#AudioFocusRequest.Builder(AudioFocusRequest)}.
*
* <h3>Qualifying your focus request</h3>
* <h4>Use case requiring a focus request</h4>
@@ -105,10 +106,11 @@
* <h4>Pausing vs ducking</h4>
* <p>When an application requested audio focus with
* {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, the system will duck the current focus
- * owner. Note that this behavior is <b>new for Android O<b>, whereas applications targeting SDK
- * up to API 25, applications had to implement the ducking themselves when they received a focus
+ * owner.
+ * <p class="note">Note: this behavior is <b>new for Android O</b>, whereas applications targeting
+ * SDK level up to API 25 had to implement the ducking themselves when they received a focus
* loss of {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
- * <br>But ducking is not always the behavior expected by the user. A typical example is when the
+ * <p>But ducking is not always the behavior expected by the user. A typical example is when the
* device plays driving directions while the user is listening to an audio book or podcast, and
* expects the audio playback to pause, instead of duck, as it is hard to understand a navigation
* prompt and spoken content at the same time. Therefore the system will not automatically duck
@@ -126,7 +128,92 @@
* speech, you can also declare so with {@link Builder#setWillPauseWhenDucked(boolean)}, which will
* cause the system to call your focus listener instead of automatically ducking.
*
+ * <h4>Example</h4>
+ * <p>The example below covers the following steps to be found in any application that would play
+ * audio, and use audio focus. Here we play an audio book, and our application is intended to pause
+ * rather than duck when it loses focus. These steps consist in:
+ * <ul>
+ * <li>Creating {@code AudioAttributes} to be used for the playback and the focus request.</li>
+ * <li>Configuring and creating the {@code AudioFocusRequest} instance that defines the intended
+ * focus behaviors.</li>
+ * <li>Requesting audio focus and checking the return code to see if playback can happen right
+ * away, or is delayed.</li>
+ * <li>Implementing a focus change listener to respond to focus gains and losses.</li>
+ * </ul>
+ * <p>
+ * <pre class="prettyprint">
+ * // initialization of the audio attributes and focus request
+ * mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
+ * mPlaybackAttributes = new AudioAttributes.Builder()
+ * .setUsage(AudioAttributes.USAGE_MEDIA)
+ * .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+ * .build();
+ * mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+ * .setAudioAttributes(mPlaybackAttributes)
+ * .setAcceptsDelayedFocusGain(true)
+ * .setWillPauseWhenDucked(true)
+ * .setOnAudioFocusChangeListener(this, mMyHandler)
+ * .build();
+ * mMediaPlayer = new MediaPlayer();
+ * mMediaPlayer.setAudioAttributes(mPlaybackAttributes);
+ * final Object mFocusLock = new Object();
+ *
+ * boolean mPlaybackDelayed = false;
+ *
+ * // requesting audio focus
+ * int res = mAudioManager.requestAudioFocus(mFocusRequest);
+ * synchronized (mFocusLock) {
+ * if (res == AUDIOFOCUS_REQUEST_FAILED) {
+ * mPlaybackDelayed = false;
+ * } else if (res == AUDIOFOCUS_REQUEST_GRANTED) {
+ * mPlaybackDelayed = false;
+ * playbackNow();
+ * } else if (res == AUDIOFOCUS_REQUEST_DELAYED) {
+ * mPlaybackDelayed = true;
+ * }
+ * }
+ *
+ * // implementation of the OnAudioFocusChangeListener
+ * @Override
+ * public void onAudioFocusChange(int focusChange) {
+ * switch (focusChange) {
+ * case AudioManager.AUDIOFOCUS_GAIN:
+ * if (mPlaybackDelayed || mResumeOnFocusGain) {
+ * synchronized (mFocusLock) {
+ * mPlaybackDelayed = false;
+ * mResumeOnFocusGain = false;
+ * }
+ * playbackNow();
+ * }
+ * break;
+ * case AudioManager.AUDIOFOCUS_LOSS:
+ * synchronized (mFocusLock) {
+ * // this is not a transient loss, we shouldn't automatically resume for now
+ * mResumeOnFocusGain = false;
+ * mPlaybackDelayed = false;
+ * }
+ * pausePlayback();
+ * break;
+ * case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ * case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ * // we handle all transient losses the same way because we never duck audio books
+ * synchronized (mFocusLock) {
+ * // we should only resume if playback was interrupted
+ * mResumeOnFocusGain = mMediaPlayer.isPlaying();
+ * mPlaybackDelayed = false;
+ * }
+ * pausePlayback();
+ * break;
+ * }
+ * }
+ *
+ * // Important:
+ * // Also set "mResumeOnFocusGain" to false when the user pauses or stops playback: this way your
+ * // application doesn't automatically restart when it gains focus, even though the user had
+ * // stopped it.
+ * </pre>
*/
+
public final class AudioFocusRequest {
// default attributes for the request when not specified
@@ -244,36 +331,15 @@
/**
* Builder class for {@link AudioFocusRequest} objects.
- * <p> Here is an example where {@code Builder} is used to define the
- * {@link AudioFocusRequest} for requesting audio focus:
- *
- * <pre class="prettyprint">
- * mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
- * mPlaybackAttributes = new AudioAttributes.Builder()
- * .setUsage(AudioAttributes.USAGE_GAME)
- * .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
- * .build();
- * mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
- * .setAudioAttributes(mPlaybackAttributes)
- * .setAcceptsDelayedFocusGain(true)
- * .setOnAudioFocusChangeListener(mMyFocusListener, mMyHandler)
- * .build();
- * mMediaPlayer = new MediaPlayer();
- * ...
- * mMediaPlayer.setAudioAttributes(mPlaybackAttributes);
- * ...
- * boolean mPlaybackAuthorized = true;;
- * int res = mAudioManager.requestAudioFocus(mFocusRequest);
- * if (res == AUDIOFOCUS_REQUEST_FAILED) {
- * mPlaybackAuthorized = false;
- * cancelPlayback();
- * } else if (res == AUDIOFOCUS_REQUEST_DELAYED) {
- * playbackDelayed();
- * } else { // res == AUDIOFOCUS_REQUEST_GRANTED
- * playbackNow();
- * }
- * </pre>
- *
+ * <p>See {@link AudioFocusRequest} for an example of building an instance with this builder.
+ * <br>The default values for the instance to be built are:
+ * <table>
+ * <tr><td>focus listener and handler</td><td>none</td></tr>
+ * <tr><td>{@code AudioAttributes}</td><td>attributes with usage set to
+ * {@link AudioAttributes#USAGE_MEDIA}</td></tr>
+ * <tr><td>pauses on duck</td><td>false</td></tr>
+ * <tr><td>supports delayed focus grant</td><td>false</td></tr>
+ * </table>
*/
public static final class Builder {
private OnAudioFocusChangeListener mFocusListener;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 24ede16..db9f7b8 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -19,6 +19,8 @@
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
@@ -26,16 +28,22 @@
import android.os.ParcelUuid;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.R;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
-public final class A2dpProfile implements LocalBluetoothProfile {
+public class A2dpProfile implements LocalBluetoothProfile {
private static final String TAG = "A2dpProfile";
private static boolean V = false;
+ private Context mContext;
+
private BluetoothA2dp mService;
+ BluetoothA2dpWrapper.Factory mWrapperFactory;
+ private BluetoothA2dpWrapper mServiceWrapper;
private boolean mIsProfileReady;
private final LocalBluetoothAdapter mLocalAdapter;
@@ -59,6 +67,7 @@
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothA2dp) proxy;
+ mServiceWrapper = mWrapperFactory.getInstance(mService);
// We just bound to the service, so refresh the UI for any connected A2DP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
while (!deviceList.isEmpty()) {
@@ -88,11 +97,18 @@
A2dpProfile(Context context, LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
+ mContext = context;
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(),
BluetoothProfile.A2DP);
+ mWrapperFactory = new BluetoothA2dpWrapperImpl.Factory();
+ }
+
+ @VisibleForTesting
+ void setWrapperFactory(BluetoothA2dpWrapper.Factory factory) {
+ mWrapperFactory = factory;
}
public boolean isConnectable() {
@@ -173,6 +189,72 @@
return false;
}
+ public boolean supportsHighQualityAudio(BluetoothDevice device) {
+ int support = mServiceWrapper.supportsOptionalCodecs(device);
+ return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED;
+ }
+
+ public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
+ int enabled = mServiceWrapper.getOptionalCodecsEnabled(device);
+ if (enabled != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN) {
+ return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED;
+ } else if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED &&
+ supportsHighQualityAudio(device)) {
+ // Since we don't have a stored preference and the device isn't connected, just return
+ // true since the default behavior when the device gets connected in the future would be
+ // to have optional codecs enabled.
+ return true;
+ }
+ BluetoothCodecConfig codecConfig = null;
+ if (mServiceWrapper.getCodecStatus() != null) {
+ codecConfig = mServiceWrapper.getCodecStatus().getCodecConfig();
+ }
+ if (codecConfig != null) {
+ return !codecConfig.isMandatoryCodec();
+ } else {
+ return false;
+ }
+ }
+
+ public void setHighQualityAudioEnabled(BluetoothDevice device, boolean enabled) {
+ int prefValue = enabled
+ ? BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED
+ : BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED;
+ mServiceWrapper.setOptionalCodecsEnabled(device, prefValue);
+ if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
+ return;
+ }
+ if (enabled) {
+ mService.enableOptionalCodecs();
+ } else {
+ mService.disableOptionalCodecs();
+ }
+ }
+
+ public String getHighQualityAudioOptionLabel(BluetoothDevice device) {
+ int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec;
+ if (!supportsHighQualityAudio(device) ||
+ getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
+ return mContext.getString(unknownCodecId);
+ }
+ // We want to get the highest priority codec, since that's the one that will be used with
+ // this device, and see if it is high-quality (ie non-mandatory).
+ BluetoothCodecConfig[] selectable = null;
+ if (mServiceWrapper.getCodecStatus() != null) {
+ selectable = mServiceWrapper.getCodecStatus().getCodecsSelectableCapabilities();
+ // To get the highest priority, we sort in reverse.
+ Arrays.sort(selectable,
+ (a, b) -> {
+ return b.getCodecPriority() - a.getCodecPriority();
+ });
+ }
+ if (selectable == null || selectable.length < 1 || selectable[0].isMandatoryCodec()) {
+ return mContext.getString(unknownCodecId);
+ }
+ return mContext.getString(R.string.bluetooth_profile_a2dp_high_quality,
+ selectable[0].getCodecName());
+ }
+
public String toString() {
return NAME;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java
new file mode 100644
index 0000000..aa3e835
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java
@@ -0,0 +1,58 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * This interface replicates some methods of android.bluetooth.BluetoothA2dp that are new and not
+ * yet available in our current version of Robolectric. It provides a thin wrapper to call the real
+ * methods in production and a mock in tests.
+ */
+public interface BluetoothA2dpWrapper {
+
+ static interface Factory {
+ BluetoothA2dpWrapper getInstance(BluetoothA2dp service);
+ }
+
+ /**
+ * @return the real {@code BluetoothA2dp} object
+ */
+ BluetoothA2dp getService();
+
+ /**
+ * Wraps {@code BluetoothA2dp.getCodecStatus}
+ */
+ public BluetoothCodecStatus getCodecStatus();
+
+ /**
+ * Wraps {@code BluetoothA2dp.supportsOptionalCodecs}
+ */
+ int supportsOptionalCodecs(BluetoothDevice device);
+
+ /**
+ * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled}
+ */
+ int getOptionalCodecsEnabled(BluetoothDevice device);
+
+ /**
+ * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled}
+ */
+ void setOptionalCodecsEnabled(BluetoothDevice device, int value);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java
new file mode 100644
index 0000000..14fa796
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java
@@ -0,0 +1,62 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+
+public class BluetoothA2dpWrapperImpl implements BluetoothA2dpWrapper {
+
+ public static class Factory implements BluetoothA2dpWrapper.Factory {
+ @Override
+ public BluetoothA2dpWrapper getInstance(BluetoothA2dp service) {
+ return new BluetoothA2dpWrapperImpl(service);
+ }
+ }
+
+ private BluetoothA2dp mService;
+
+ public BluetoothA2dpWrapperImpl(BluetoothA2dp service) {
+ mService = service;
+ }
+
+ @Override
+ public BluetoothA2dp getService() {
+ return mService;
+ }
+
+ @Override
+ public BluetoothCodecStatus getCodecStatus() {
+ return mService.getCodecStatus();
+ }
+
+ @Override
+ public int supportsOptionalCodecs(BluetoothDevice device) {
+ return mService.supportsOptionalCodecs(device);
+ }
+
+ @Override
+ public int getOptionalCodecsEnabled(BluetoothDevice device) {
+ return mService.getOptionalCodecsEnabled(device);
+ }
+
+ @Override
+ public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
+ mService.setOptionalCodecsEnabled(device, value);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 5a9a749..0750dc7 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -42,7 +42,7 @@
* LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
* objects for the available Bluetooth profiles.
*/
-public final class LocalBluetoothProfileManager {
+public class LocalBluetoothProfileManager {
private static final String TAG = "LocalBluetoothProfileManager";
private static final boolean DEBUG = Utils.D;
/** Singleton instance. */
diff --git a/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecConfig.java b/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecConfig.java
new file mode 100644
index 0000000..656ab86
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecConfig.java
@@ -0,0 +1,25 @@
+/*
+ * 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 android.bluetooth;
+
+/**
+ * A placeholder class to prevent ClassNotFound exceptions caused by lack of visibility.
+ */
+public class BluetoothCodecConfig {
+ public boolean isMandatoryCodec() { return true; }
+ public String getCodecName() { return null;}
+}
diff --git a/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecStatus.java b/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecStatus.java
new file mode 100644
index 0000000..919ec3f
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecStatus.java
@@ -0,0 +1,25 @@
+/*
+ * 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 android.bluetooth;
+
+/**
+ * A placeholder class to prevent ClassNotFound exceptions caused by lack of visibility.
+ */
+public class BluetoothCodecStatus {
+ public BluetoothCodecConfig getCodecConfig() { return null; }
+ public BluetoothCodecConfig[] getCodecsSelectableCapabilities() { return null; }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
new file mode 100644
index 0000000..07a0e11
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settingslib.R;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class A2dpProfileTest {
+
+ @Mock Context mContext;
+ @Mock LocalBluetoothAdapter mAdapter;
+ @Mock CachedBluetoothDeviceManager mDeviceManager;
+ @Mock LocalBluetoothProfileManager mProfileManager;
+ @Mock BluetoothDevice mDevice;
+ @Mock BluetoothA2dp mBluetoothA2dp;
+ @Mock BluetoothA2dpWrapper mBluetoothA2dpWrapper;
+ BluetoothProfile.ServiceListener mServiceListener;
+
+ A2dpProfile mProfile;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ // Capture the A2dpServiceListener our A2dpProfile will pass during its constructor, so that
+ // we can call its onServiceConnected method and get it to use our mock BluetoothA2dp
+ // object.
+ doAnswer((invocation) -> {
+ mServiceListener = (BluetoothProfile.ServiceListener) invocation.getArguments()[1];
+ return null;
+ }).when(mAdapter).getProfileProxy(any(Context.class), any(), eq(BluetoothProfile.A2DP));
+
+ mProfile = new A2dpProfile(mContext, mAdapter, mDeviceManager, mProfileManager);
+ mProfile.setWrapperFactory((service) -> { return mBluetoothA2dpWrapper; });
+ mServiceListener.onServiceConnected(BluetoothProfile.A2DP, mBluetoothA2dp);
+ }
+
+ @Test
+ public void supportsHighQualityAudio() {
+ when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
+ assertThat(mProfile.supportsHighQualityAudio(mDevice)).isTrue();
+
+ when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
+ assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse();
+
+ when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+ assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse();
+ }
+
+ @Test
+ public void isHighQualityAudioEnabled() {
+ when(mBluetoothA2dpWrapper.getOptionalCodecsEnabled(any())).thenReturn(
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
+ assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
+
+ when(mBluetoothA2dpWrapper.getOptionalCodecsEnabled(any())).thenReturn(
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED);
+ assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
+
+ // If we don't have a stored pref for whether optional codecs should be enabled or not,
+ // then isHighQualityAudioEnabled() should return true or false based on whether optional
+ // codecs are supported. If the device is connected then we should ask it directly, but if
+ // the device isn't connected then rely on the stored pref about such support.
+ when(mBluetoothA2dpWrapper.getOptionalCodecsEnabled(any())).thenReturn(
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+ when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
+ BluetoothProfile.STATE_DISCONNECTED);
+
+ when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
+ assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
+
+ when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
+ assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
+
+ when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
+ BluetoothProfile.STATE_CONNECTED);
+ BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
+ when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+ BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
+ when(status.getCodecConfig()).thenReturn(config);
+ when(config.isMandatoryCodec()).thenReturn(false);
+ assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
+ when(config.isMandatoryCodec()).thenReturn(true);
+ assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
+ }
+
+ // Strings to use in fake resource lookups.
+ private static String KNOWN_CODEC_LABEL = "Use high quality audio: %1$s";
+ private static String UNKNOWN_CODEC_LABEL = "Use high quality audio";
+
+ /**
+ * Helper for setting up several tests of getHighQualityAudioOptionLabel
+ */
+ private void setupLabelTest() {
+ // SettingsLib doesn't have string resource lookup working for robotests, so fake our own
+ // string loading.
+ when(mContext.getString(eq(R.string.bluetooth_profile_a2dp_high_quality),
+ any(String.class))).thenAnswer((invocation) -> {
+ return String.format(KNOWN_CODEC_LABEL, invocation.getArguments()[1]);
+ });
+ when(mContext.getString(eq(R.string.bluetooth_profile_a2dp_high_quality_unknown_codec)))
+ .thenReturn(UNKNOWN_CODEC_LABEL);
+
+ // Most tests want to simulate optional codecs being supported by the device, so do that
+ // by default here.
+ when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
+ }
+
+ @Test
+ public void getLableCodecsNotSupported() {
+ setupLabelTest();
+ when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
+ assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL);
+ }
+
+ @Test
+ public void getLabelDeviceDisconnected() {
+ setupLabelTest();
+ when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
+ BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL);
+ }
+
+ @Test
+ public void getLabelDeviceConnectedButNotHighQualityCodec() {
+ setupLabelTest();
+ when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
+ BluetoothProfile.STATE_CONNECTED);
+ BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
+ BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
+ BluetoothCodecConfig[] configs = {config};
+ when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+ when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
+
+ when(config.isMandatoryCodec()).thenReturn(true);
+ assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL);
+ }
+
+ @Test
+ public void getLabelDeviceConnectedWithHighQualityCodec() {
+ setupLabelTest();
+ when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
+ BluetoothProfile.STATE_CONNECTED);
+ BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
+ BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
+ BluetoothCodecConfig[] configs = {config};
+ when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+ when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
+
+ when(config.isMandatoryCodec()).thenReturn(false);
+ when(config.getCodecName()).thenReturn("PiedPiper");
+ assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(
+ String.format(KNOWN_CODEC_LABEL, config.getCodecName()));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index f4d4a9f..0cf8ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -264,6 +264,8 @@
mProviders.put(AccessibilityManagerWrapper.class,
() -> new AccessibilityManagerWrapper(mContext));
+ mProviders.put(UiOffloadThread.class, UiOffloadThread::new);
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
new file mode 100644
index 0000000..82fd9b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
@@ -0,0 +1,34 @@
+/*
+ * 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.systemui;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Thread that offloads work from the UI thread but that is still perceptible to the user, so the
+ * priority is the same as the main thread.
+ */
+public class UiOffloadThread {
+
+ private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
+
+ public Future<?> submit(Runnable runnable) {
+ return mExecutorService.submit(runnable);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index 1abea37..a129ace 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -30,7 +30,10 @@
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityManager;
+import com.android.systemui.Dependency;
+import com.android.systemui.UiOffloadThread;
import com.android.systemui.analytics.DataCollector;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.statusbar.StatusBarState;
import java.io.PrintWriter;
@@ -63,6 +66,7 @@
private final DataCollector mDataCollector;
private final HumanInteractionClassifier mHumanInteractionClassifier;
private final AccessibilityManager mAccessibilityManager;
+ private final UiOffloadThread mUiOffloadThread;
private static FalsingManager sInstance = null;
@@ -86,6 +90,7 @@
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mDataCollector = DataCollector.getInstance(mContext);
mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
+ mUiOffloadThread = Dependency.get(UiOffloadThread.class);
mScreenOn = context.getSystemService(PowerManager.class).isInteractive();
mContext.getContentResolver().registerContentObserver(
@@ -130,7 +135,11 @@
private void sessionExitpoint(boolean force) {
if (mSessionActive && (force || !shouldSessionBeActive())) {
mSessionActive = false;
- mSensorManager.unregisterListener(this);
+
+ // This can be expensive, and doesn't need to happen on the main thread.
+ mUiOffloadThread.submit(() -> {
+ mSensorManager.unregisterListener(this);
+ });
}
}
@@ -154,7 +163,11 @@
for (int sensorType : sensors) {
Sensor s = mSensorManager.getDefaultSensor(sensorType);
if (s != null) {
- mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_GAME);
+
+ // This can be expensive, and doesn't need to happen on the main thread.
+ mUiOffloadThread.submit(() -> {
+ mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_GAME);
+ });
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
index 262d29d..745f312 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
@@ -17,6 +17,10 @@
package com.android.systemui.keyguard;
import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.systemui.Dependency;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import java.util.ArrayList;
@@ -26,6 +30,7 @@
public class DismissCallbackRegistry {
private final ArrayList<DismissCallbackWrapper> mDismissCallbacks = new ArrayList<>();
+ private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
public void addCallback(IKeyguardDismissCallback callback) {
mDismissCallbacks.add(new DismissCallbackWrapper(callback));
@@ -33,14 +38,16 @@
public void notifyDismissCancelled() {
for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) {
- mDismissCallbacks.get(i).notifyDismissCancelled();
+ DismissCallbackWrapper callback = mDismissCallbacks.get(i);
+ mUiOffloadThread.submit(callback::notifyDismissCancelled);
}
mDismissCallbacks.clear();
}
public void notifyDismissSucceeded() {
for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) {
- mDismissCallbacks.get(i).notifyDismissSucceeded();
+ DismissCallbackWrapper callback = mDismissCallbacks.get(i);
+ mUiOffloadThread.submit(callback::notifyDismissSucceeded);
}
mDismissCallbacks.clear();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f618a2c..2d7521d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -51,6 +51,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.provider.Settings.System;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.EventLog;
@@ -76,9 +77,13 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.LatencyTracker;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
+import com.android.systemui.UiOffloadThread;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.statusbar.phone.FingerprintUnlockController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -195,6 +200,7 @@
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
+ private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
private boolean mSystemReady;
private boolean mBootCompleted;
@@ -1199,18 +1205,20 @@
updateInputRestrictedLocked();
}
}
+
private void updateInputRestrictedLocked() {
boolean inputRestricted = isInputRestricted();
if (mInputRestricted != inputRestricted) {
mInputRestricted = inputRestricted;
int size = mKeyguardStateCallbacks.size();
for (int i = size - 1; i >= 0; i--) {
+ final IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
try {
- mKeyguardStateCallbacks.get(i).onInputRestrictedStateChanged(inputRestricted);
+ callback.onInputRestrictedStateChanged(inputRestricted);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call onDeviceProvisioned", e);
if (e instanceof DeadObjectException) {
- mKeyguardStateCallbacks.remove(i);
+ mKeyguardStateCallbacks.remove(callback);
}
}
}
@@ -1569,9 +1577,11 @@
private void handleKeyguardDone() {
Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");
final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
- if (mLockPatternUtils.isSecure(currentUser)) {
- mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
- }
+ mUiOffloadThread.submit(() -> {
+ if (mLockPatternUtils.isSecure(currentUser)) {
+ mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
+ }
+ });
if (DEBUG) Log.d(TAG, "handleKeyguardDone");
synchronized (this) {
resetKeyguardDonePendingLocked();
@@ -1611,10 +1621,12 @@
final UserHandle currentUser = new UserHandle(currentUserId);
final UserManager um = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
- for (int profileId : um.getProfileIdsWithDisabled(currentUser.getIdentifier())) {
- mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, UserHandle.of(profileId));
- }
- getLockPatternUtils().userPresent(currentUserId);
+ mUiOffloadThread.submit(() -> {
+ for (int profileId : um.getProfileIdsWithDisabled(currentUser.getIdentifier())) {
+ mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, UserHandle.of(profileId));
+ }
+ getLockPatternUtils().userPresent(currentUserId);
+ });
} else {
mBootSendUserPresent = true;
}
@@ -1659,11 +1671,18 @@
if (mAudioManager == null) return;
mUiSoundsStreamType = mAudioManager.getUiSoundsStreamType();
}
- // If the stream is muted, don't play the sound
- if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
- mLockSoundStreamId = mLockSounds.play(soundId,
- mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
+ mUiOffloadThread.submit(() -> {
+ // If the stream is muted, don't play the sound
+ if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
+
+ int id = mLockSounds.play(soundId,
+ mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
+ synchronized (this) {
+ mLockSoundStreamId = id;
+ }
+ });
+
}
}
@@ -1671,13 +1690,13 @@
playSound(mTrustedSoundId);
}
- private void updateActivityLockScreenState() {
- Trace.beginSection("KeyguardViewMediator#updateActivityLockScreenState");
- try {
- ActivityManager.getService().setLockScreenShown(mShowing);
- } catch (RemoteException e) {
- }
- Trace.endSection();
+ private void updateActivityLockScreenState(boolean showing) {
+ mUiOffloadThread.submit(() -> {
+ try {
+ ActivityManager.getService().setLockScreenShown(showing);
+ } catch (RemoteException e) {
+ }
+ });
}
/**
@@ -1846,7 +1865,10 @@
}
if (!(mContext instanceof Activity)) {
- mStatusBarManager.disable(flags);
+ final int finalFlags = flags;
+ mUiOffloadThread.submit(() -> {
+ mStatusBarManager.disable(finalFlags);
+ });
}
}
}
@@ -2044,18 +2066,21 @@
mShowing = showing;
int size = mKeyguardStateCallbacks.size();
for (int i = size - 1; i >= 0; i--) {
+ IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
try {
- mKeyguardStateCallbacks.get(i).onShowingStateChanged(showing);
+ callback.onShowingStateChanged(showing);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call onShowingStateChanged", e);
if (e instanceof DeadObjectException) {
- mKeyguardStateCallbacks.remove(i);
+ mKeyguardStateCallbacks.remove(callback);
}
}
}
updateInputRestrictedLocked();
- mTrustManager.reportKeyguardShowingChanged();
- updateActivityLockScreenState();
+ mUiOffloadThread.submit(() -> {
+ mTrustManager.reportKeyguardShowingChanged();
+ });
+ updateActivityLockScreenState(showing);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 4eb0748..dbf0724 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -204,8 +204,8 @@
Recents.getSystemServices().removeTask(task.persistentId);
}
}
- Settings.Secure.putLongForUser(RecentsActivity.this.getContentResolver(),
- Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, currentTime, currentUser);
+ Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
+ currentTime, currentUser);
// Clear the last PiP task time, it's an edge case and we'd rather it
// not relaunch the PiP task if the user double taps
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index cde865e..e229c90 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -35,15 +35,18 @@
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.util.Log;
import android.util.MutableBoolean;
+import android.util.Pair;
import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.widget.Toast;
+
+import com.google.android.collect.Lists;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.policy.DockedDividerUtils;
import com.android.systemui.R;
@@ -74,6 +77,8 @@
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskGrouping;
import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.RecentsTransitionHelper;
+import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
import com.android.systemui.recents.views.TaskStackView;
@@ -671,7 +676,7 @@
windowRect.bottom -= systemInsets.bottom;
systemInsets.bottom = 0;
}
- calculateWindowStableInsets(systemInsets, windowRect);
+ calculateWindowStableInsets(systemInsets, windowRect, displayRect);
windowRect.offsetTo(0, 0);
synchronized (mDummyStackView) {
@@ -761,8 +766,7 @@
* Given the stable insets and the rect for our window, calculates the insets that affect our
* window.
*/
- private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
- Rect displayRect = Recents.getSystemServices().getDisplayRect();
+ private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect, Rect displayRect) {
// Display rect without insets - available app space
Rect appRect = new Rect(displayRect);
@@ -812,8 +816,9 @@
/**
* Creates the activity options for an app->recents transition.
*/
- private ActivityOptions getThumbnailTransitionActivityOptions(
- ActivityManager.RunningTaskInfo runningTask, Rect windowOverrideRect) {
+ private Pair<ActivityOptions, AppTransitionAnimationSpecsFuture>
+ getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask,
+ Rect windowOverrideRect) {
if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
ArrayList<Task> tasks;
@@ -844,23 +849,27 @@
}
AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
specs.toArray(specsArray);
- return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
- specsArray, mHandler, null, this);
+ return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+ specsArray, mHandler, null, this), null);
} else {
// Update the destination rect
Task toTask = new Task();
TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
windowOverrideRect);
- Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform,
- mThumbTransitionBitmapCache);
- if (thumbnail != null) {
- RectF toTaskRect = toTransform.rect;
- return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
- thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
- (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
- }
- // If both the screenshot and thumbnail fails, then just fall back to the default transition
- return getUnknownTransitionActivityOptions();
+
+ RectF toTaskRect = toTransform.rect;
+ AppTransitionAnimationSpecsFuture future =
+ new RecentsTransitionHelper(mContext).getAppTransitionFuture(
+ () -> {
+ Rect rect = new Rect();
+ toTaskRect.round(rect);
+ Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform,
+ mThumbTransitionBitmapCache);
+ return Lists.newArrayList(new AppTransitionAnimationSpec(
+ toTask.key.id, thumbnail, rect));
+ });
+ return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
+ mHandler, future.getFuture(), null, false /* scaleUp */), future);
}
}
@@ -986,30 +995,31 @@
launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
if (!animate) {
- startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
+ startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1),
+ null /* future */);
return;
}
- ActivityOptions opts;
+ Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair;
if (isBlacklisted) {
- opts = getUnknownTransitionActivityOptions();
+ pair = new Pair<>(getUnknownTransitionActivityOptions(), null);
} else if (useThumbnailTransition) {
// Try starting with a thumbnail transition
- opts = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
+ pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
} else {
// If there is no thumbnail transition, but is launching from home into recents, then
// use a quick home transition
- opts = hasRecentTasks
- ? getHomeTransitionActivityOptions()
- : getUnknownTransitionActivityOptions();
+ pair = new Pair<>(hasRecentTasks
+ ? getHomeTransitionActivityOptions()
+ : getUnknownTransitionActivityOptions(), null);
}
- startRecentsActivity(opts);
+ startRecentsActivity(pair.first, pair.second);
mLastToggleTime = SystemClock.elapsedRealtime();
}
private Rect getWindowRectOverride(int growTarget) {
if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) {
- return null;
+ return SystemServicesProxy.getInstance(mContext).getWindowRect();
}
Rect result = new Rect();
Rect displayRect = Recents.getSystemServices().getDisplayRect();
@@ -1022,21 +1032,21 @@
/**
* Starts the recents activity.
*/
- private void startRecentsActivity(ActivityOptions opts) {
+ private void startRecentsActivity(ActivityOptions opts,
+ final AppTransitionAnimationSpecsFuture future) {
Intent intent = new Intent();
intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-
+ Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent();
hideMenuEvent.addPostAnimationCallback(() -> {
- if (opts != null) {
- mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
- } else {
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- }
+ Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
EventBus.getDefault().send(new RecentsActivityStartingEvent());
+ if (future != null) {
+ future.precacheSpecs();
+ }
});
EventBus.getDefault().send(hideMenuEvent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index f85fb34..a155a71 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -58,20 +58,22 @@
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.IRemoteCallback;
-import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
import android.util.ArraySet;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.MutableBoolean;
-import android.view.AppTransitionAnimationSpec;
import android.view.Display;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IDockedStackListener;
@@ -84,14 +86,15 @@
import com.android.internal.app.AssistUtils;
import com.android.internal.os.BackgroundThread;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.UiOffloadThread;
import com.android.systemui.pip.tv.PipMenuActivity;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.RecentsImpl;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.ThumbnailData;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
import java.io.IOException;
import java.util.ArrayList;
@@ -130,6 +133,8 @@
PackageManager mPm;
IconDrawableFactory mDrawableFactory;
IPackageManager mIpm;
+ private final IDreamManager mDreamManager;
+ private final Context mContext;
AssistUtils mAssistUtils;
WindowManager mWm;
IWindowManager mIwm;
@@ -149,7 +154,8 @@
Canvas mBgProtectionCanvas;
private final Handler mHandler = new H();
- private final ExecutorService mOnewayExecutor = Executors.newSingleThreadExecutor();
+
+ private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
/**
* An abstract class to track task stack changes.
@@ -285,6 +291,7 @@
/** Private constructor */
private SystemServicesProxy(Context context) {
+ mContext = context.getApplicationContext();
mAccm = AccessibilityManager.getInstance(context);
mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mIam = ActivityManager.getService();
@@ -296,6 +303,8 @@
mIwm = WindowManagerGlobal.getWindowManagerService();
mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
mUm = UserManager.get(context);
+ mDreamManager = IDreamManager.Stub.asInterface(
+ ServiceManager.checkService(DreamService.DREAM_SERVICE));
mDisplay = mWm.getDefaultDisplay();
mRecentsPackage = context.getPackageName();
mHasFreeformWorkspaceSupport =
@@ -778,7 +787,7 @@
* Sends a message to close other system windows.
*/
public void sendCloseSystemWindows(String reason) {
- mOnewayExecutor.submit(() -> {
+ mUiOffloadThread.submit(() -> {
try {
mIam.closeSystemDialogs(reason);
} catch (RemoteException e) {
@@ -1023,7 +1032,12 @@
* Returns the current user id.
*/
public int getCurrentUser() {
- return KeyguardUpdateMonitor.getCurrentUser();
+ if (mAm == null) return 0;
+
+ // This must call through ActivityManager, as the SystemServicesProxy can be called in a
+ // secondary user's SystemUI process, and KeyguardUpdateMonitor is only updated in the
+ // primary user's SystemUI process
+ return mAm.getCurrentUser();
}
/**
@@ -1127,6 +1141,11 @@
}
}
+ public void startActivityAsUserAsync(Intent intent, ActivityOptions opts) {
+ mUiOffloadThread.submit(() -> mContext.startActivityAsUser(intent,
+ opts != null ? opts.toBundle() : null, UserHandle.CURRENT));
+ }
+
/** Starts an activity from recents. */
public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
ActivityOptions options, int stackId,
@@ -1151,7 +1170,7 @@
// Execute this from another thread such that we can do other things (like caching the
// bitmap for the thumbnail) while AM is busy starting our activity.
- mOnewayExecutor.submit(() -> {
+ mUiOffloadThread.submit(() -> {
try {
mIam.startActivityFromRecents(
taskKey.id, finalOptions == null ? null : finalOptions.toBundle());
@@ -1283,6 +1302,33 @@
}
}
+ public boolean isDreaming() {
+ try {
+ return mDreamManager.isDreaming();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query dream manager.", e);
+ }
+ return false;
+ }
+
+ public void awakenDreamsAsync() {
+ mUiOffloadThread.submit(() -> {
+ try {
+ mDreamManager.awaken();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ public void updateOverviewLastStackActiveTimeAsync(long newLastStackActiveTime,
+ int currentUserId) {
+ mUiOffloadThread.submit(() -> {
+ Settings.Secure.putLongForUser(mContext.getContentResolver(),
+ Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId);
+ });
+ }
+
public interface StartActivityFromRecentsResultListener {
void onStartActivityResult(boolean succeeded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 7ee0906..ed09640 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -87,7 +87,7 @@
mCurrentQuietProfiles.clear();
if (currentUserId == UserHandle.USER_CURRENT) {
- currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+ currentUserId = SystemServicesProxy.getInstance(mContext).getCurrentUser();
}
UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
List<UserInfo> profiles = userManager.getProfiles(currentUserId);
@@ -218,8 +218,8 @@
affiliatedTasks.put(taskKey.id, taskKey);
}
if (newLastStackActiveTime != -1) {
- Settings.Secure.putLongForUser(mContext.getContentResolver(),
- Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId);
+ Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
+ newLastStackActiveTime, currentUserId);
}
// Initialize the stacks
@@ -316,9 +316,8 @@
for (int i = 0; i < users.size(); i++) {
int userId = users.get(i).id;
if (userId != currentUserId) {
- Settings.Secure.putLongForUser(mContext.getContentResolver(),
- Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime,
- userId);
+ Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
+ legacyLastStackActiveTime, userId);
}
}
return legacyLastStackActiveTime;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 97a9659..c5a292f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -28,6 +28,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.Trace;
import android.util.Log;
import android.util.LruCache;
@@ -351,7 +352,12 @@
/** Preloads recents tasks using the specified plan to store the output. */
public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
boolean includeFrontMostExcludedTask) {
- plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
+ try {
+ Trace.beginSection("preloadPlan");
+ plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
+ } finally {
+ Trace.endSection();
+ }
}
/** Begins loading the heavy task data according to the specified options. */
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 24a2927..0c77036 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -1169,7 +1169,7 @@
}
public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) {
- if (mGrowRecents && getWindowManagerProxy().getDockSide() == WindowManager.DOCKED_TOP
+ if (mGrowRecents && mDockSide == WindowManager.DOCKED_TOP
&& getSnapAlgorithm().getMiddleTarget() != getSnapAlgorithm().getLastSplitTarget()
&& getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
mState.growAfterRecentsDrawn = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index be221bb..bf89b01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -279,7 +279,9 @@
public void toggleRecentApps() {
synchronized (mLock) {
mHandler.removeMessages(MSG_TOGGLE_RECENT_APPS);
- mHandler.obtainMessage(MSG_TOGGLE_RECENT_APPS, 0, 0, null).sendToTarget();
+ Message msg = mHandler.obtainMessage(MSG_TOGGLE_RECENT_APPS, 0, 0, null);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 0924089..1691e135 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -270,7 +270,7 @@
return;
}
- if (!mUserManager.isUserUnlocked(ActivityManager.getCurrentUser())) {
+ if (!mUserManager.isUserUnlocked(KeyguardUpdateMonitor.getCurrentUser())) {
mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
mTextView.setTextColor(Color.WHITE);
@@ -365,6 +365,9 @@
};
public void setDozing(boolean dozing) {
+ if (mDozing == dozing) {
+ return;
+ }
mDozing = dozing;
updateIndication();
updateDisclosure();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 2124011..95f32bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -18,6 +18,7 @@
import android.app.ActivityManager;
import android.content.Context;
+import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
@@ -55,6 +56,7 @@
protected final ViewGroup mContainer;
private final FalsingManager mFalsingManager;
private final DismissCallbackRegistry mDismissCallbackRegistry;
+ private final Handler mHandler;
protected KeyguardHostView mKeyguardView;
protected ViewGroup mRoot;
private boolean mShowingSoon;
@@ -66,6 +68,7 @@
mBouncerPromptReason = mCallback.getBouncerPromptReason();
}
};
+ private final Runnable mRemoveViewRunnable = this::removeView;
public KeyguardBouncer(Context context, ViewMediatorCallback callback,
LockPatternUtils lockPatternUtils, ViewGroup container,
@@ -77,6 +80,7 @@
KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
mFalsingManager = FalsingManager.getInstance(mContext);
mDismissCallbackRegistry = dismissCallbackRegistry;
+ mHandler = new Handler();
}
public void show(boolean resetSecuritySelection) {
@@ -179,10 +183,14 @@
mKeyguardView.cancelDismissAction();
mKeyguardView.cleanUp();
}
- if (destroyView) {
- removeView();
- } else if (mRoot != null) {
+ if (mRoot != null) {
mRoot.setVisibility(View.INVISIBLE);
+ if (destroyView) {
+
+ // We have a ViewFlipper that unregisters a broadcast when being detached, which may
+ // be slow because of AM lock contention during unlocking. We can delay it a bit.
+ mHandler.postDelayed(mRemoveViewRunnable, 50);
+ }
}
}
@@ -226,6 +234,7 @@
}
protected void ensureView() {
+ mHandler.removeCallbacks(mRemoveViewRunnable);
if (mRoot == null) {
inflateView();
}
@@ -233,6 +242,7 @@
protected void inflateView() {
removeView();
+ mHandler.removeCallbacks(mRemoveViewRunnable);
mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
mKeyguardView = (KeyguardHostView) mRoot.findViewById(R.id.keyguard_host_view);
mKeyguardView.setLockPatternUtils(mLockPatternUtils);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 4581204..52838b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -128,8 +128,7 @@
protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
NotificationData notificationData, boolean showAmbient) {
- if (notificationData.isAmbient(entry.key) && !showAmbient
- && !NotificationData.showNotificationEvenIfUnprovisioned(entry.notification)) {
+ if (notificationData.isAmbient(entry.key) && !showAmbient) {
return false;
}
if (!StatusBar.isTopLevelChild(entry)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 41fb5f7..f3ba5aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -58,6 +58,7 @@
import com.android.systemui.DockedStackExistsListener;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.UiOffloadThread;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -128,6 +129,7 @@
private final KeyguardMonitor mKeyguardMonitor;
private final LocationController mLocationController;
private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
+ private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
// Assume it's all good unless we hear otherwise. We don't always seem
// to get broadcasts that it *is* there.
@@ -472,30 +474,38 @@
}
private void updateManagedProfile() {
- try {
- final boolean showIcon;
- final int userId = ActivityManager.getService().getLastResumedActivityUserId();
- if (mUserManager.isManagedProfile(userId) && !mKeyguardMonitor.isShowing()) {
- showIcon = true;
- mIconController.setIcon(mSlotManagedProfile,
- R.drawable.stat_sys_managed_profile_status,
- mContext.getString(R.string.accessibility_managed_profile));
- } else if (mManagedProfileInQuietMode) {
- showIcon = true;
- mIconController.setIcon(mSlotManagedProfile,
- R.drawable.stat_sys_managed_profile_status_off,
- mContext.getString(R.string.accessibility_managed_profile));
- } else {
- showIcon = false;
+ // getLastResumedActivityUserId needds to acquire the AM lock, which may be contended in
+ // some cases. Since it doesn't really matter here whether it's updated in this frame
+ // or in the next one, we call this method from our UI offload thread.
+ mUiOffloadThread.submit(() -> {
+ final int userId;
+ try {
+ userId = ActivityManager.getService().getLastResumedActivityUserId();
+ boolean isManagedProfile = mUserManager.isManagedProfile(userId);
+ mHandler.post(() -> {
+ final boolean showIcon;
+ if (isManagedProfile && !mKeyguardMonitor.isShowing()) {
+ showIcon = true;
+ mIconController.setIcon(mSlotManagedProfile,
+ R.drawable.stat_sys_managed_profile_status,
+ mContext.getString(R.string.accessibility_managed_profile));
+ } else if (mManagedProfileInQuietMode) {
+ showIcon = true;
+ mIconController.setIcon(mSlotManagedProfile,
+ R.drawable.stat_sys_managed_profile_status_off,
+ mContext.getString(R.string.accessibility_managed_profile));
+ } else {
+ showIcon = false;
+ }
+ if (mManagedProfileIconVisible != showIcon) {
+ mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
+ mManagedProfileIconVisible = showIcon;
+ }
+ });
+ } catch (RemoteException e) {
+ Log.w(TAG, "updateManagedProfile: ", e);
}
- if (mManagedProfileIconVisible != showIcon) {
- mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
- mManagedProfileIconVisible = showIcon;
- }
- } catch (RemoteException ex) {
- Log.w(TAG, "updateManagedProfile: ", ex);
- // ignore
- }
+ });
}
private void updateForegroundInstantApps() {
@@ -503,26 +513,22 @@
ArraySet<Pair<String, Integer>> notifs = new ArraySet<>(mCurrentNotifs);
IPackageManager pm = AppGlobals.getPackageManager();
mCurrentNotifs.clear();
- try {
- ArraySet<Integer> stacksToCheck = new ArraySet<>();
- int[] STACKS_TO_CHECK = new int[]{
- StackId.FULLSCREEN_WORKSPACE_STACK_ID,
- StackId.DOCKED_STACK_ID,
- };
- int focusedId = ActivityManager.getService().getFocusedStackId();
- if (focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID
- || focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID) {
- checkStack(StackId.FULLSCREEN_WORKSPACE_STACK_ID, notifs, noMan, pm);
+ mUiOffloadThread.submit(() -> {
+ try {
+ int focusedId = ActivityManager.getService().getFocusedStackId();
+ if (focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID) {
+ checkStack(StackId.FULLSCREEN_WORKSPACE_STACK_ID, notifs, noMan, pm);
+ }
+ if (mDockedStackExists) {
+ checkStack(StackId.DOCKED_STACK_ID, notifs, noMan, pm);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
}
- if (mDockedStackExists) {
- checkStack(StackId.DOCKED_STACK_ID, notifs, noMan, pm);
- }
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- // Cancel all the leftover notifications that don't have a foreground process anymore.
- notifs.forEach(v -> noMan.cancelAsUser(v.first, SystemMessage.NOTE_INSTANT_APPS,
- new UserHandle(v.second)));
+ // Cancel all the leftover notifications that don't have a foreground process anymore.
+ notifs.forEach(v -> noMan.cancelAsUser(v.first, SystemMessage.NOTE_INSTANT_APPS,
+ new UserHandle(v.second)));
+ });
}
private void checkStack(int stackId, ArraySet<Pair<String, Integer>> notifs,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index fc73c0f..d798fbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -83,6 +83,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.SystemService;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -131,6 +132,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
+import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.classifier.FalsingManager;
@@ -149,6 +151,7 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
import com.android.systemui.recents.events.activity.UndockingTaskEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.ActivatableNotificationView;
@@ -220,8 +223,6 @@
import android.content.pm.UserInfo;
import android.os.Build;
import android.os.Handler;
-import android.service.dreams.DreamService;
-import android.service.dreams.IDreamManager;
import android.service.notification.NotificationListenerService;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -538,6 +539,7 @@
private ViewMediatorCallback mKeyguardViewMediatorCallback;
protected ScrimController mScrimController;
protected DozeScrimController mDozeScrimController;
+ private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
private final Runnable mAutohide = new Runnable() {
@Override
@@ -778,8 +780,6 @@
mAccessibilityManager = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
- mDreamManager = IDreamManager.Stub.asInterface(
- ServiceManager.checkService(DreamService.DREAM_SERVICE));
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
@@ -1532,13 +1532,7 @@
}
void awakenDreams() {
- if (mDreamManager != null) {
- try {
- mDreamManager.awaken();
- } catch (RemoteException e) {
- // fine, stay asleep then
- }
- }
+ SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
}
public UserHandle getCurrentUserHandle() {
@@ -4348,7 +4342,7 @@
checkBarModes();
updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
- mStatusBarKeyguardViewManager.isSecure(),
+ mUnlockMethodCache.isMethodSecure(),
mStatusBarKeyguardViewManager.isOccluded());
Trace.endSection();
}
@@ -5173,7 +5167,6 @@
protected boolean mDisableNotificationAlerts = false;
protected DevicePolicyManager mDevicePolicyManager;
- protected IDreamManager mDreamManager;
protected PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -6816,12 +6809,8 @@
return false;
}
- boolean inUse = mPowerManager.isScreenOn();
- try {
- inUse = inUse && !mDreamManager.isDreaming();
- } catch (RemoteException e) {
- Log.d(TAG, "failed to query dream manager", e);
- }
+ boolean inUse = mPowerManager.isScreenOn()
+ && !SystemServicesProxy.getInstance(mContext).isDreaming();
if (!inUse && !isDozing()) {
if (DEBUG) {
@@ -6898,11 +6887,13 @@
@Override
public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
- try {
- mBarService.onNotificationExpansionChanged(key, userAction, expanded);
- } catch (RemoteException e) {
- // Ignore.
- }
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onNotificationExpansionChanged(key, userAction, expanded);
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+ });
}
public boolean isKeyguardSecure() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
index d6c080a..7bce33a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
@@ -124,13 +124,11 @@
mKeyguardFadingAway = true;
mKeyguardFadingAwayDelay = delay;
mKeyguardFadingAwayDuration = fadeoutDuration;
- notifyKeyguardChanged();
}
public void notifyKeyguardDoneFading() {
mKeyguardFadingAway = false;
mKeyguardGoingAway = false;
- notifyKeyguardChanged();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 53671a1..e1acc9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -872,7 +872,14 @@
private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
@Override
public void onKeyguardShowingChanged() {
- notifyAdapters();
+
+ // When Keyguard is going away, we don't need to update our items immediately which
+ // helps making the transition faster.
+ if (!mKeyguardMonitor.isShowing()) {
+ mHandler.post(UserSwitcherController.this::notifyAdapters);
+ } else {
+ notifyAdapters();
+ }
}
};
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index aadae0f..0aac1c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -21,15 +21,21 @@
import android.os.MessageQueue;
import android.support.test.InstrumentationRegistry;
import android.testing.LeakCheck;
+import android.util.Log;
import org.junit.Before;
import org.junit.Rule;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
/**
* Base class that does System UI specific setup.
*/
public abstract class SysuiTestCase {
+ private static final String TAG = "SysuiTestCase";
+
private Handler mHandler;
@Rule
public SysuiTestableContext mContext = new SysuiTestableContext(
@@ -57,6 +63,15 @@
waitForIdleSync(mHandler);
}
+ protected void waitForUiOffloadThread() {
+ Future<?> future = Dependency.get(UiOffloadThread.class).submit(() -> {});
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Failed to wait for ui offload thread.", e);
+ }
+ }
+
public static void waitForIdleSync(Handler h) {
validateThread(h.getLooper());
Idler idler = new Idler(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
index 131a70b..308670d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
@@ -23,6 +23,8 @@
import android.support.test.runner.AndroidJUnit4;
import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.UiOffloadThread;
import org.junit.Before;
import org.junit.Test;
@@ -35,7 +37,7 @@
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class DismissCallbackRegistryTest {
+public class DismissCallbackRegistryTest extends SysuiTestCase {
private final DismissCallbackRegistry mDismissCallbackRegistry = new DismissCallbackRegistry();
private @Mock IKeyguardDismissCallback mMockCallback;
@@ -50,6 +52,7 @@
public void testCancelled() throws Exception {
mDismissCallbackRegistry.addCallback(mMockCallback);
mDismissCallbackRegistry.notifyDismissCancelled();
+ waitForUiOffloadThread();
verify(mMockCallback).onDismissCancelled();
}
@@ -58,6 +61,7 @@
mDismissCallbackRegistry.addCallback(mMockCallback);
mDismissCallbackRegistry.addCallback(mMockCallback2);
mDismissCallbackRegistry.notifyDismissCancelled();
+ waitForUiOffloadThread();
verify(mMockCallback).onDismissCancelled();
verify(mMockCallback2).onDismissCancelled();
}
@@ -66,6 +70,7 @@
public void testSucceeded() throws Exception {
mDismissCallbackRegistry.addCallback(mMockCallback);
mDismissCallbackRegistry.notifyDismissSucceeded();
+ waitForUiOffloadThread();
verify(mMockCallback).onDismissSucceeded();
}
@@ -74,6 +79,7 @@
mDismissCallbackRegistry.addCallback(mMockCallback);
mDismissCallbackRegistry.addCallback(mMockCallback2);
mDismissCallbackRegistry.notifyDismissSucceeded();
+ waitForUiOffloadThread();
verify(mMockCallback).onDismissSucceeded();
verify(mMockCallback2).onDismissSucceeded();
}
@@ -83,6 +89,7 @@
mDismissCallbackRegistry.addCallback(mMockCallback);
mDismissCallbackRegistry.notifyDismissSucceeded();
mDismissCallbackRegistry.notifyDismissSucceeded();
+ waitForUiOffloadThread();
verify(mMockCallback, times(1)).onDismissSucceeded();
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index 0e30fb2..de8a518 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -26,6 +26,7 @@
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import com.android.internal.annotations.GuardedBy;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -96,7 +97,7 @@
mNodeFromOriginalWindow = info;
} else {
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
- throw new RuntimeException("Callback with unexpected interactionId"); // Remove
+ return;
}
mSingleNodeCallbackHappened = true;
@@ -119,7 +120,7 @@
mNodesWithReplacementActions = infos;
} else {
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
- throw new RuntimeException("Callback with unexpected interactionId"); // Remove
+ return;
}
callbackForSingleNode = mSingleNodeCallbackHappened;
callbackForMultipleNodes = mMultiNodeCallbackHappened;
@@ -147,7 +148,7 @@
if (DEBUG) {
Slog.e(LOG_TAG, "Extra callback");
}
- throw new RuntimeException("Extra callback"); // Replace with return before submit
+ return;
}
if (mNodeFromOriginalWindow != null) {
replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
@@ -172,7 +173,7 @@
if (DEBUG) {
Slog.e(LOG_TAG, "Extra callback");
}
- throw new RuntimeException("Extra callback"); // Replace with return before submit
+ return;
}
if (mNodesFromOriginalWindow != null) {
for (int i = 0; i < mNodesFromOriginalWindow.size(); i++) {
@@ -180,7 +181,8 @@
}
}
recycleReplaceActionNodesLocked();
- nodesToReturn = mNodesFromOriginalWindow;
+ nodesToReturn = (mNodesFromOriginalWindow == null)
+ ? null : new ArrayList<>(mNodesFromOriginalWindow);
mDone = true;
}
try {
@@ -195,6 +197,12 @@
@GuardedBy("mLock")
private void replaceActionsOnInfoLocked(AccessibilityNodeInfo info) {
info.removeAllActions();
+ info.setClickable(false);
+ info.setFocusable(false);
+ info.setContextClickable(false);
+ info.setScrollable(false);
+ info.setLongClickable(false);
+ info.setDismissable(false);
// We currently only replace actions for the root node
if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID)
&& mNodesWithReplacementActions != null) {
@@ -213,6 +221,12 @@
info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
}
+ info.setClickable(nodeWithReplacementActions.isClickable());
+ info.setFocusable(nodeWithReplacementActions.isFocusable());
+ info.setContextClickable(nodeWithReplacementActions.isContextClickable());
+ info.setScrollable(nodeWithReplacementActions.isScrollable());
+ info.setLongClickable(nodeWithReplacementActions.isLongClickable());
+ info.setDismissable(nodeWithReplacementActions.isDismissable());
}
}
}
@@ -220,6 +234,7 @@
@GuardedBy("mLock")
private void recycleReplaceActionNodesLocked() {
+ if (mNodesWithReplacementActions == null) return;
for (int i = mNodesWithReplacementActions.size() - 1; i >= 0; i--) {
AccessibilityNodeInfo nodeWithReplacementAction = mNodesWithReplacementActions.get(i);
nodeWithReplacementAction.recycle();
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 6e87f88..e380f2c 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -938,7 +938,12 @@
if (pointerIdBits == ALL_POINTER_ID_BITS) {
event = prototype;
} else {
- event = prototype.split(pointerIdBits);
+ try {
+ event = prototype.split(pointerIdBits);
+ } catch (IllegalArgumentException e) {
+ Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e);
+ return;
+ }
}
if (action == MotionEvent.ACTION_DOWN) {
event.setDownTime(event.getEventTime());
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index b65f54e..61057dd 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -1697,7 +1697,7 @@
mHandler.removeMessages(MESSAGE_USER_SWITCHED);
/* disable and enable BT when detect a user switch */
- if (mEnable && mBluetooth != null) {
+ if (mBluetooth != null && isEnabled()) {
try {
mBluetoothLock.readLock().lock();
if (mBluetooth != null) {
@@ -1766,6 +1766,8 @@
mState = BluetoothAdapter.STATE_OFF;
// enable
addActiveLog(REASON_USER_SWITCH, true);
+ // mEnable flag could have been reset on disableBLE. Reenable it.
+ mEnable = true;
handleEnable(mQuietEnable);
} else if (mBinding || mBluetooth != null) {
Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED);
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 96f6f2d..11296ab 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -960,14 +960,18 @@
return result;
}
}
- // Otherwise wait until https probe completes and use its result.
+ // Otherwise wait until http and https probes completes and use their results.
try {
+ httpProbe.join();
+ if (httpProbe.result().isPortal()) {
+ return httpProbe.result();
+ }
httpsProbe.join();
+ return httpsProbe.result();
} catch (InterruptedException e) {
- validationLog("Error: https probe wait interrupted!");
+ validationLog("Error: http or https probe wait interrupted!");
return CaptivePortalProbeResult.FAILED;
}
- return httpsProbe.result();
}
private URL makeURL(String url) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 0e55c3f..b0d76e8 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -63,6 +63,7 @@
import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManagerInternal;
import android.provider.Settings;
import android.util.KeyValueListParser;
import android.util.Slog;
@@ -751,6 +752,13 @@
}
}
+ private void cancelJobsForNonExistentUsers() {
+ UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ synchronized (mLock) {
+ mJobs.removeJobsOfNonUsers(umi.getUserIds());
+ }
+ }
+
void cancelJobsForPackageAndUid(String pkgName, int uid) {
synchronized (mLock) {
final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
@@ -941,6 +949,8 @@
} catch (RemoteException e) {
// ignored; both services live in system_server
}
+ // Remove any jobs that are not associated with any of the current users.
+ cancelJobsForNonExistentUsers();
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mLock) {
// Let's go!
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 497d36f..22eed3b 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -35,6 +35,7 @@
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.IoThread;
import com.android.server.job.controllers.JobStatus;
@@ -171,6 +172,14 @@
return removed;
}
+ /**
+ * Remove the jobs of users not specified in the whitelist.
+ * @param whitelist Array of User IDs whose jobs are not to be removed.
+ */
+ public void removeJobsOfNonUsers(int[] whitelist) {
+ mJobSet.removeJobsOfNonUsers(whitelist);
+ }
+
@VisibleForTesting
public void clear() {
mJobSet.clear();
@@ -839,6 +848,17 @@
return didRemove;
}
+ // Remove the jobs all users not specified by the whitelist of user ids
+ public void removeJobsOfNonUsers(int[] whitelist) {
+ for (int jobIndex = mJobs.size() - 1; jobIndex >= 0; jobIndex--) {
+ int jobUserId = UserHandle.getUserId(mJobs.keyAt(jobIndex));
+ // check if job's user id is not in the whitelist
+ if (!ArrayUtils.contains(whitelist, jobUserId)) {
+ mJobs.removeAt(jobIndex);
+ }
+ }
+ }
+
public boolean contains(JobStatus job) {
final int uid = job.getUid();
ArraySet<JobStatus> jobs = mJobs.get(uid);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 5a5e658..803b0dc 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -153,7 +153,7 @@
final ApplicationInfo applicationInfo =
mContext.getPackageManager().getApplicationInfoAsUser(sbn.getPackageName(),
0, UserHandle.getUserId(sbn.getUid()));
- if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
+ if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
return true;
}
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index e184f83..f00ef38 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -286,7 +286,7 @@
private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
final int userId = UserHandle.getUserId(r.uid);
final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
- if (applicationInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) {
+ if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
// O apps should not have the default channel.
return false;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 30c4009..b115422 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3684,6 +3684,11 @@
}
@Override
+ public int[] getUserIds() {
+ return UserManagerService.this.getUserIds();
+ }
+
+ @Override
public boolean isUserUnlockingOrUnlocked(int userId) {
synchronized (mUserStates) {
int state = mUserStates.get(userId, -1);
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index fb7ccc7..4044fdb 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -368,7 +368,7 @@
// There is an active service, update it if needed
updateCurrentVrServiceLocked(mVrModeEnabled, mCurrentVrService.getComponent(),
- mCurrentVrService.getUserId(), null);
+ mCurrentVrService.getUserId(), mCurrentVrModeComponent);
}
}
@@ -675,7 +675,7 @@
* @param enabled new state for VR mode.
* @param component new component to be bound as a VR listener.
* @param userId user owning the component to be bound.
- * @param calling the component currently using VR mode, or null to leave unchanged.
+ * @param calling the component currently using VR mode.
*
* @return {@code true} if the component/user combination specified is valid.
*/
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 5545e2b..c982f08 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -19,6 +19,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -98,18 +99,27 @@
private final Runnable mRemoveStartingWindow = () -> {
StartingSurface surface = null;
synchronized (mWindowMap) {
+ if (mContainer == null) {
+ if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "mContainer was null while trying to"
+ + " remove starting window");
+ return;
+ }
if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Remove starting " + mContainer
+ ": startingWindow=" + mContainer.startingWindow
+ " startingView=" + mContainer.startingSurface);
- if (mContainer == null) {
- return;
- }
if (mContainer.startingWindow != null) {
surface = mContainer.startingSurface;
mContainer.startingData = null;
mContainer.startingSurface = null;
mContainer.startingWindow = null;
mContainer.startingDisplayed = false;
+ if (surface == null && DEBUG_STARTING_WINDOW) {
+ Slog.v(TAG_WM, "startingWindow was set but startingSurface==null, couldn't "
+ + "remove");
+ }
+ } else if (DEBUG_STARTING_WINDOW) {
+ Slog.v(TAG_WM, "Tried to remove starting window but startingWindow was null:"
+ + mContainer);
}
}
if (surface != null) {
@@ -127,6 +137,8 @@
synchronized (mWindowMap) {
if (mContainer == null) {
+ if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "mContainer was null while trying to"
+ + " add starting window");
return;
}
startingData = mContainer.startingData;
@@ -135,6 +147,8 @@
if (startingData == null) {
// Animation has been canceled... do nothing.
+ if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "startingData was nulled out before handling"
+ + " mAddStartingWindow: " + mContainer);
return;
}
@@ -174,6 +188,8 @@
if (abort) {
surface.remove();
}
+ } else if (DEBUG_STARTING_WINDOW) {
+ Slog.v(TAG_WM, "Surface returned was null: " + mContainer);
}
};
@@ -450,7 +466,9 @@
boolean allowTaskSnapshot, boolean activityCreated) {
synchronized(mWindowMap) {
if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "setAppStartingWindow: token=" + mToken
- + " pkg=" + pkg + " transferFrom=" + transferFrom);
+ + " pkg=" + pkg + " transferFrom=" + transferFrom + " newTask=" + newTask
+ + " taskSwitch=" + taskSwitch + " processRunning=" + processRunning
+ + " allowTaskSnapshot=" + allowTaskSnapshot);
if (mContainer == null) {
Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + mToken);
@@ -536,7 +554,7 @@
return false;
}
- if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating StartingData");
+ if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SplashScreenStartingData");
mContainer.startingData = new SplashScreenStartingData(mService, pkg, theme,
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
mContainer.getMergedOverrideConfiguration());
@@ -574,6 +592,7 @@
return false;
}
+ if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SnapshotStartingData");
mContainer.startingData = new SnapshotStartingData(mService, snapshot);
scheduleAddStartingWindow();
return true;
@@ -583,6 +602,8 @@
synchronized (mWindowMap) {
if (mHandler.hasCallbacks(mRemoveStartingWindow)) {
// Already scheduled.
+ if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Trying to remove starting window but "
+ + "already scheduled");
return;
}
@@ -597,8 +618,7 @@
return;
}
- if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, Debug.getCallers(1)
- + ": Schedule remove starting " + mContainer
+ if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Schedule remove starting " + mContainer
+ " startingWindow=" + mContainer.startingWindow);
mHandler.post(mRemoveStartingWindow);
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 57b0fe2..17db253 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -38,6 +38,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
@@ -53,7 +54,6 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.Build;
import android.os.Debug;
import android.os.IBinder;
import android.os.SystemClock;
@@ -155,6 +155,9 @@
StartingSurface startingSurface;
boolean startingDisplayed;
boolean startingMoved;
+ // True if the hidden state of this token was forced to false due to a transferred starting
+ // window.
+ private boolean mHiddenSetFromTransferredStartingWindow;
boolean firstWindowDrawn;
private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
new WindowState.UpdateReportedVisibilityResults();
@@ -185,7 +188,7 @@
ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
- private boolean mDisbalePreviewScreenshots;
+ private boolean mDisablePreviewScreenshots;
Task mLastParent;
@@ -790,14 +793,22 @@
if (getController() != null) {
getController().removeStartingWindow();
}
- } else if (mChildren.size() == 0 && startingData != null) {
+ } else if (mChildren.size() == 0) {
// If this is the last window and we had requested a starting transition window,
// well there is no point now.
if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Nulling last startingData");
startingData = null;
+ if (mHiddenSetFromTransferredStartingWindow) {
+ // We set the hidden state to false for the token from a transferred starting window.
+ // We now reset it back to true since the starting window was the last window in the
+ // token.
+ hidden = true;
+ }
} 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 (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Last window, removing starting window "
+ + win);
if (getController() != null) {
getController().removeStartingWindow();
}
@@ -1171,6 +1182,7 @@
"Removing starting " + tStartingWindow + " from " + fromToken);
fromToken.removeChild(tStartingWindow);
fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
+ fromToken.mHiddenSetFromTransferredStartingWindow = false;
addWindow(tStartingWindow);
// Propagate other interesting state between the tokens. If the old token is displayed,
@@ -1186,6 +1198,7 @@
if (!fromToken.hidden) {
hidden = false;
hiddenRequested = false;
+ mHiddenSetFromTransferredStartingWindow = true;
}
setClientHidden(fromToken.mClientHidden);
fromToken.mAppAnimator.transferCurrentAnimation(
@@ -1362,7 +1375,7 @@
* windows in this app token where not considered drawn as of the last pass.
*/
boolean updateDrawnWindowStates(WindowState w) {
- if (DEBUG_STARTING_WINDOW && w == startingWindow) {
+ if (DEBUG_STARTING_WINDOW_VERBOSE && w == startingWindow) {
Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
+ " allDrawn=" + allDrawn + " freezingScreen=" + mAppAnimator.freezingScreen);
}
@@ -1544,7 +1557,7 @@
* See {@link Activity#setDisablePreviewScreenshots}.
*/
void setDisablePreviewScreenshots(boolean disable) {
- mDisbalePreviewScreenshots = disable;
+ mDisablePreviewScreenshots = disable;
}
/**
@@ -1556,7 +1569,7 @@
* screenshot.
*/
boolean shouldUseAppThemeSnapshot() {
- return mDisbalePreviewScreenshots || forAllWindows(w -> (w.mAttrs.flags & FLAG_SECURE) != 0,
+ return mDisablePreviewScreenshots || forAllWindows(w -> (w.mAttrs.flags & FLAG_SECURE) != 0,
true /* topToBottom */);
}
@@ -1604,11 +1617,13 @@
pw.print(" mIsExiting="); pw.println(mIsExiting);
}
if (startingWindow != null || startingSurface != null
- || startingDisplayed || startingMoved) {
+ || startingDisplayed || startingMoved || mHiddenSetFromTransferredStartingWindow) {
pw.print(prefix); pw.print("startingWindow="); pw.print(startingWindow);
pw.print(" startingSurface="); pw.print(startingSurface);
pw.print(" startingDisplayed="); pw.print(startingDisplayed);
- pw.print(" startingMoved="); pw.println(startingMoved);
+ pw.print(" startingMoved="); pw.print(startingMoved);
+ pw.println(" mHiddenSetFromTransferredStartingWindow="
+ + mHiddenSetFromTransferredStartingWindow);
}
if (!mFrozenBounds.isEmpty()) {
pw.print(prefix); pw.print("mFrozenBounds="); pw.println(mFrozenBounds);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index bbf7d9f..4941610 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1899,6 +1899,11 @@
return true;
}
+ /** @return 'true' if removal of this display content is deferred due to active animation. */
+ boolean isRemovalDeferred() {
+ return mDeferredRemoval;
+ }
+
boolean animateForIme(float interpolatedValue, float animationTarget,
float dividerAnimationTarget) {
boolean updated = false;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index be3558b..233e75b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -181,7 +181,12 @@
final int size = mChildren.size();
for (int i = 0; i < size; ++i) {
- displaysInFocusOrder.put(i, mChildren.get(i).getDisplayId());
+ final DisplayContent displayContent = mChildren.get(i);
+ if (displayContent.isRemovalDeferred()) {
+ // Don't report displays that are going to be removed soon.
+ continue;
+ }
+ displaysInFocusOrder.put(i, displayContent.getDisplayId());
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 5e7b910..3a116bb 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -39,6 +39,7 @@
import static com.android.internal.policy.DecorView.getColorViewLeftInset;
import static com.android.internal.policy.DecorView.getColorViewTopInset;
import static com.android.internal.policy.DecorView.getNavigationBarRect;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -113,7 +114,7 @@
private final Rect mStableInsets = new Rect();
private final Rect mContentInsets = new Rect();
private final Rect mFrame = new Rect();
- private final TaskSnapshot mSnapshot;
+ private TaskSnapshot mSnapshot;
private final CharSequence mTitle;
private boolean mHasDrawn;
private long mShownTime;
@@ -232,10 +233,14 @@
final long now = SystemClock.uptimeMillis();
if (mSizeMismatch && now - mShownTime < SIZE_MISMATCH_MINIMUM_TIME_MS) {
mHandler.postAtTime(this::remove, mShownTime + SIZE_MISMATCH_MINIMUM_TIME_MS);
+ if (DEBUG_STARTING_WINDOW) {
+ Slog.v(TAG, "Defer removing snapshot surface in " + (now - mShownTime) + "ms");
+ }
return;
}
}
try {
+ if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Removing snapshot surface");
mSession.remove(mWindow);
} catch (RemoteException e) {
// Local call.
@@ -254,6 +259,8 @@
private void drawSnapshot() {
final GraphicBuffer buffer = mSnapshot.getSnapshot();
+ if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Drawing snapshot surface sizeMismatch="
+ + mSizeMismatch);
if (mSizeMismatch) {
// The dimensions of the buffer and the window don't match, so attaching the buffer
// will fail. Better create a child window with the exact dimensions and fill the parent
@@ -267,6 +274,9 @@
mHasDrawn = true;
}
reportDrawn();
+
+ // In case window manager leaks us, make sure we don't retain the snapshot.
+ mSnapshot = null;
}
private void drawSizeMatchSnapshot(GraphicBuffer buffer) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ca8c484..6d3e0ed 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -655,39 +655,44 @@
final LinkedList<WindowContainer> thisParentChain = mTmpChain1;
final LinkedList<WindowContainer> otherParentChain = mTmpChain2;
- getParents(thisParentChain);
- other.getParents(otherParentChain);
+ try {
+ getParents(thisParentChain);
+ other.getParents(otherParentChain);
- // Find the common ancestor of both containers.
- WindowContainer commonAncestor = null;
- WindowContainer thisTop = thisParentChain.peekLast();
- WindowContainer otherTop = otherParentChain.peekLast();
- while (thisTop != null && otherTop != null && thisTop == otherTop) {
- commonAncestor = thisParentChain.removeLast();
- otherParentChain.removeLast();
- thisTop = thisParentChain.peekLast();
- otherTop = otherParentChain.peekLast();
+ // Find the common ancestor of both containers.
+ WindowContainer commonAncestor = null;
+ WindowContainer thisTop = thisParentChain.peekLast();
+ WindowContainer otherTop = otherParentChain.peekLast();
+ while (thisTop != null && otherTop != null && thisTop == otherTop) {
+ commonAncestor = thisParentChain.removeLast();
+ otherParentChain.removeLast();
+ thisTop = thisParentChain.peekLast();
+ otherTop = otherParentChain.peekLast();
+ }
+
+ // Containers don't belong to the same hierarchy???
+ if (commonAncestor == null) {
+ throw new IllegalArgumentException("No in the same hierarchy this="
+ + thisParentChain + " other=" + otherParentChain);
+ }
+
+ // Children are always considered greater than their parents, so if one of the containers
+ // we are comparing it the parent of the other then whichever is the child is greater.
+ if (commonAncestor == this) {
+ return -1;
+ } else if (commonAncestor == other) {
+ return 1;
+ }
+
+ // The position of the first non-common ancestor in the common ancestor list determines
+ // which is greater the which.
+ final WindowList<WindowContainer> list = commonAncestor.mChildren;
+ return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast())
+ ? 1 : -1;
+ } finally {
+ mTmpChain1.clear();
+ mTmpChain2.clear();
}
-
- // Containers don't belong to the same hierarchy???
- if (commonAncestor == null) {
- throw new IllegalArgumentException("No in the same hierarchy this="
- + thisParentChain + " other=" + otherParentChain);
- }
-
- // Children are always considered greater than their parents, so if one of the containers
- // we are comparing it the parent of the other then whichever is the child is greater.
- if (commonAncestor == this) {
- return -1;
- } else if (commonAncestor == other) {
- return 1;
- }
-
- // The position of the first non-common ancestor in the common ancestor list determines
- // which is greater the which.
- final WindowList<WindowContainer> list = commonAncestor.mChildren;
- return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast())
- ? 1 : -1;
}
private void getParents(LinkedList<WindowContainer> parents) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 1b61fca..8279b51 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -51,7 +51,8 @@
static final boolean DEBUG_APP_ORIENTATION = false;
static final boolean DEBUG_CONFIGURATION = false;
static final boolean DEBUG_APP_TRANSITIONS = false;
- static final boolean DEBUG_STARTING_WINDOW = false;
+ static final boolean DEBUG_STARTING_WINDOW_VERBOSE = false;
+ static final boolean DEBUG_STARTING_WINDOW = DEBUG_STARTING_WINDOW_VERBOSE || true;
static final boolean DEBUG_WALLPAPER = false;
static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
static final boolean DEBUG_DRAG = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6be7900..c8eb98c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1521,7 +1521,7 @@
// Try using the target SDK of the root window
if (attachedWindow != null) {
return attachedWindow.mAppToken != null
- && attachedWindow.mAppToken.mTargetSdk > Build.VERSION_CODES.N_MR1;
+ && attachedWindow.mAppToken.mTargetSdk >= Build.VERSION_CODES.O;
} else {
// Otherwise, look at the package
try {
@@ -1532,7 +1532,7 @@
throw new SecurityException("Package " + packageName + " not in UID "
+ callingUid);
}
- if (appInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) {
+ if (appInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 344c616..2ffa152 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -80,6 +80,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
@@ -138,6 +139,7 @@
import com.android.server.input.InputWindowHandle;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
@@ -170,7 +172,7 @@
final int mOwnerUid;
/** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */
final boolean mOwnerCanAddInternalSystemWindow;
- final IWindowId mWindowId;
+ final WindowId mWindowId;
WindowToken mToken;
// The same object as mToken if this is an app window and null for non-app windows.
AppWindowToken mAppToken;
@@ -587,20 +589,7 @@
mAppToken = mToken.asAppWindowToken();
mOwnerUid = ownerId;
mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
- mWindowId = new IWindowId.Stub() {
- @Override
- public void registerFocusObserver(IWindowFocusObserver observer) {
- WindowState.this.registerFocusObserver(observer);
- }
- @Override
- public void unregisterFocusObserver(IWindowFocusObserver observer) {
- WindowState.this.unregisterFocusObserver(observer);
- }
- @Override
- public boolean isFocused() {
- return WindowState.this.isFocused();
- }
- };
+ mWindowId = new WindowId(this);
mAttrs.copyFrom(a);
mViewVisibility = viewVisibility;
mPolicy = mService.mPolicy;
@@ -1306,7 +1295,7 @@
* otherwise.
*/
boolean wouldBeVisibleIfPolicyIgnored() {
- return mHasSurface && mPolicyVisibility && !isParentWindowHidden()
+ return mHasSurface && !isParentWindowHidden()
&& !mAnimatingExit && !mDestroying && (!mIsWallpaper || mWallpaperVisible);
}
@@ -3797,7 +3786,7 @@
private void logPerformShow(String prefix) {
if (DEBUG_VISIBILITY
- || (DEBUG_STARTING_WINDOW && mAttrs.type == TYPE_APPLICATION_STARTING)) {
+ || (DEBUG_STARTING_WINDOW_VERBOSE && mAttrs.type == TYPE_APPLICATION_STARTING)) {
Slog.v(TAG, prefix + this
+ ": mDrawState=" + mWinAnimator.drawStateToString()
+ " readyForDisplay=" + isReadyForDisplay()
@@ -4435,6 +4424,40 @@
}
}
+ private static final class WindowId extends IWindowId.Stub {
+ private final WeakReference<WindowState> mOuter;
+
+ private WindowId(WindowState outer) {
+
+ // Use a weak reference for the outer class. This is important to prevent the following
+ // leak: Since we send this class to the client process, binder will keep it alive as
+ // long as the client keeps it alive. Now, if the window is removed, we need to clear
+ // out our reference so even though this class is kept alive we don't leak WindowState,
+ // which can keep a whole lot of classes alive.
+ mOuter = new WeakReference<>(outer);
+ }
+
+ @Override
+ public void registerFocusObserver(IWindowFocusObserver observer) {
+ final WindowState outer = mOuter.get();
+ if (outer != null) {
+ outer.registerFocusObserver(observer);
+ }
+ }
+ @Override
+ public void unregisterFocusObserver(IWindowFocusObserver observer) {
+ final WindowState outer = mOuter.get();
+ if (outer != null) {
+ outer.unregisterFocusObserver(observer);
+ }
+ }
+ @Override
+ public boolean isFocused() {
+ final WindowState outer = mOuter.get();
+ return outer != null && outer.isFocused();
+ }
+ }
+
boolean usesRelativeZOrdering() {
if (!isChildWindow()) {
return false;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index e1c6bc3..d75afcf 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -30,6 +30,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
@@ -519,7 +520,7 @@
// This must be called while inside a transaction.
boolean commitFinishDrawingLocked() {
- if (DEBUG_STARTING_WINDOW &&
+ if (DEBUG_STARTING_WINDOW_VERBOSE &&
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
Slog.i(TAG, "commitFinishDrawingLocked: " + mWin + " cur mDrawState="
+ drawStateToString());
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
index 267d2a6..0812783 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
@@ -99,7 +99,7 @@
when(mMockContext.getPackageManager()).thenReturn(mPm);
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
- upgrade.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+ upgrade.targetSdkVersion = Build.VERSION_CODES.O;
try {
when(mPm.getApplicationInfoAsUser(eq(pkg), anyInt(), anyInt())).thenReturn(legacy);
when(mPm.getApplicationInfoAsUser(eq(pkg2), anyInt(), anyInt())).thenReturn(upgrade);
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index cb0ee25..2c9c114 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -119,7 +119,7 @@
final ApplicationInfo legacy = new ApplicationInfo();
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
final ApplicationInfo upgrade = new ApplicationInfo();
- upgrade.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+ upgrade.targetSdkVersion = Build.VERSION_CODES.O;
when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java
index 8afe853..72820f1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java
@@ -25,6 +25,9 @@
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -45,12 +48,13 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
/**
@@ -63,15 +67,50 @@
private static final int NON_ROOT_NODE_ID = 0xAAAA5555;
private static final long INTERROGATING_TID = 0x1234FACE;
- private static final AccessibilityAction[] ACTIONS_FROM_REPLACER =
- {ACTION_CLICK, ACTION_EXPAND};
- private static final AccessibilityAction[] A11Y_FOCUS_ACTIONS =
- {ACTION_ACCESSIBILITY_FOCUS, ACTION_CLEAR_ACCESSIBILITY_FOCUS};
// We expect both the replacer actions and a11y focus actions to appear
private static final AccessibilityAction[] REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE =
{ACTION_CLICK, ACTION_EXPAND, ACTION_ACCESSIBILITY_FOCUS,
ACTION_CLEAR_ACCESSIBILITY_FOCUS};
+ private static final Matcher<AccessibilityNodeInfo> HAS_NO_ACTIONS =
+ new BaseMatcher<AccessibilityNodeInfo>() {
+ @Override
+ public boolean matches(Object o) {
+ AccessibilityNodeInfo node = (AccessibilityNodeInfo) o;
+ if (!node.getActionList().isEmpty()) return false;
+ return (!node.isScrollable() && !node.isLongClickable() && !node.isClickable()
+ && !node.isContextClickable() && !node.isDismissable() && !node.isFocusable());
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Has no actions");
+ }
+ };
+
+ private static final Matcher<AccessibilityNodeInfo> HAS_EXPECTED_ACTIONS_ON_ROOT =
+ new BaseMatcher<AccessibilityNodeInfo>() {
+ @Override
+ public boolean matches(Object o) {
+ AccessibilityNodeInfo node = (AccessibilityNodeInfo) o;
+ List<AccessibilityAction> actions = node.getActionList();
+ if ((actions.size() != 4) || !actions.contains(ACTION_CLICK)
+ || !actions.contains(ACTION_EXPAND)
+ || !actions.contains(ACTION_ACCESSIBILITY_FOCUS)) {
+ return false;
+ }
+ return (!node.isScrollable() && !node.isLongClickable()
+ && !node.isLongClickable() && node.isClickable()
+ && !node.isContextClickable() && !node.isDismissable()
+ && !node.isFocusable());
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Has only 4 actions expected on root");
+ }
+ };
+
@Mock IAccessibilityInteractionConnectionCallback mMockServiceCallback;
@Mock IAccessibilityInteractionConnection mMockReplacerConnection;
@@ -118,9 +157,10 @@
eq(INTERACTION_ID));
AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId());
- assertInfoHasExactlyTheseActions(infoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
+ assertThat(infoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
}
+ @Test
public void testCallbacks_singleNonrootNodeThenReplacer_returnsNodeWithNoActions()
throws RemoteException {
AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain();
@@ -136,9 +176,10 @@
eq(INTERACTION_ID));
AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
assertEquals(NON_ROOT_NODE_ID, infoSentToService.getSourceNodeId());
- assertTrue(infoSentToService.getActionList().isEmpty());
+ assertThat(infoSentToService, HAS_NO_ACTIONS);
}
+ @Test
public void testCallbacks_replacerThenSingleRootNode_returnsNodeWithReplacedActions()
throws RemoteException {
mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(),
@@ -154,9 +195,10 @@
eq(INTERACTION_ID));
AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId());
- assertInfoHasExactlyTheseActions(infoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
+ assertThat(infoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
}
+ @Test
public void testCallbacks_multipleNodesThenReplacer_clearsActionsAndAddsSomeToRoot()
throws RemoteException {
mActionReplacingCallback
@@ -173,11 +215,11 @@
mInfoListCaptor.getValue(), AccessibilityNodeInfo.ROOT_NODE_ID);
AccessibilityNodeInfo otherInfoSentToService = getNodeWithIdFromList(
mInfoListCaptor.getValue(), NON_ROOT_NODE_ID);
- assertInfoHasExactlyTheseActions(
- rootInfoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
- assertTrue(otherInfoSentToService.getActionList().isEmpty());
+ assertThat(rootInfoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
+ assertThat(otherInfoSentToService, HAS_NO_ACTIONS);
}
+ @Test
public void testCallbacks_replacerThenMultipleNodes_clearsActionsAndAddsSomeToRoot()
throws RemoteException {
mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(),
@@ -194,18 +236,18 @@
mInfoListCaptor.getValue(), AccessibilityNodeInfo.ROOT_NODE_ID);
AccessibilityNodeInfo otherInfoSentToService = getNodeWithIdFromList(
mInfoListCaptor.getValue(), NON_ROOT_NODE_ID);
- assertInfoHasExactlyTheseActions(
- rootInfoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
- assertTrue(otherInfoSentToService.getActionList().isEmpty());
+ assertThat(rootInfoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
+ assertThat(otherInfoSentToService, HAS_NO_ACTIONS);
}
+ @Test
public void testConstructor_actionReplacerThrowsException_passesDataToService()
throws RemoteException {
doThrow(RemoteException.class).when(mMockReplacerConnection)
.findAccessibilityNodeInfoByAccessibilityId(eq(AccessibilityNodeInfo.ROOT_NODE_ID),
- (Region) anyObject(), mInteractionIdCaptor.capture(),
- eq(mActionReplacingCallback), eq(0), eq(INTERROGATING_PID),
- eq(INTERROGATING_TID), (MagnificationSpec) anyObject(), eq(null));
+ (Region) anyObject(), anyInt(), (ActionReplacingCallback) anyObject(),
+ eq(0), eq(INTERROGATING_PID), eq(INTERROGATING_TID),
+ (MagnificationSpec) anyObject(), eq(null));
ActionReplacingCallback actionReplacingCallback = new ActionReplacingCallback(
mMockServiceCallback, mMockReplacerConnection, INTERACTION_ID, INTERROGATING_PID,
INTERROGATING_TID);
@@ -214,16 +256,17 @@
AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain();
infoFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, APP_WINDOW_ID);
infoFromApp.addAction(ACTION_CONTEXT_CLICK);
+ infoFromApp.setContextClickable(true);
actionReplacingCallback.setFindAccessibilityNodeInfoResult(infoFromApp, INTERACTION_ID);
verify(mMockServiceCallback).setFindAccessibilityNodeInfoResult(mInfoCaptor.capture(),
eq(INTERACTION_ID));
AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId());
- assertEquals(1, infoSentToService.getActionList().size());
- assertEquals(ACTION_CONTEXT_CLICK, infoSentToService.getActionList().get(0));
+ assertThat(infoSentToService, HAS_NO_ACTIONS);
}
+ @Test
public void testSetPerformAccessibilityActionResult_actsAsPassThrough() throws RemoteException {
mActionReplacingCallback.setPerformAccessibilityActionResult(true, INTERACTION_ID);
verify(mMockServiceCallback).setPerformAccessibilityActionResult(true, INTERACTION_ID);
@@ -236,9 +279,9 @@
AccessibilityNodeInfo root = AccessibilityNodeInfo.obtain();
root.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID,
AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
- for (AccessibilityAction action : ACTIONS_FROM_REPLACER) {
- root.addAction(action);
- }
+ root.addAction(ACTION_CLICK);
+ root.addAction(ACTION_EXPAND);
+ root.setClickable(true);
// Second node should have no effect
AccessibilityNodeInfo other = AccessibilityNodeInfo.obtain();
@@ -249,13 +292,6 @@
return Arrays.asList(root, other);
}
- private void assertInfoHasExactlyTheseActions(
- AccessibilityNodeInfo info, AccessibilityAction[] actions) {
- List<AccessibilityAction> nodeActions = info.getActionList();
- assertEquals(new HashSet<AccessibilityAction>(nodeActions),
- new HashSet<AccessibilityAction>(Arrays.asList(actions)));
- }
-
private AccessibilityNodeInfo getNodeWithIdFromList(
List<AccessibilityNodeInfo> infos, long id) {
for (AccessibilityNodeInfo info : infos) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index f9254ad..856e940 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -34,6 +34,7 @@
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseIntArray;
import java.util.Arrays;
import java.util.LinkedList;
@@ -239,7 +240,7 @@
@Test
@Ignore
public void testFocusedWindowMultipleDisplays() throws Exception {
- // Create a focusable window and check that focus is calcualted correctly
+ // Create a focusable window and check that focus is calculated correctly
final WindowState window1 =
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1");
assertEquals(window1, sWm.mRoot.computeFocusedWindow());
@@ -310,6 +311,24 @@
assertEquals(afterStackCount - 1, mDisplayContent.getStaskPosById(PINNED_STACK_ID));
}
+ /**
+ * Test that WM does not report displays to AM that are pending to be removed.
+ */
+ @Test
+ public void testDontReportDeferredRemoval() {
+ // Create a display and add an animating window to it.
+ final DisplayContent dc = createNewDisplay();
+ final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
+ window.mAnimatingExit = true;
+ // Request display removal, it should be deferred.
+ dc.removeIfPossible();
+ // Request ordered display ids from WM.
+ final SparseIntArray orderedDisplayIds = new SparseIntArray();
+ sWm.getDisplaysInFocusOrder(orderedDisplayIds);
+ // Make sure that display that is marked for removal is not reported.
+ assertEquals(-1, orderedDisplayIds.indexOfValue(dc.getDisplayId()));
+ }
+
private static void verifySizes(DisplayContent displayContent, int expectedBaseWidth,
int expectedBaseHeight, int expectedBaseDensity) {
assertEquals(displayContent.mBaseDisplayWidth, expectedBaseWidth);
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 90f713b..0d1850f 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -34,42 +34,43 @@
namespace aapt {
-constexpr const char* sXliffNamespaceUri =
- "urn:oasis:names:tc:xliff:document:1.2";
+constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
-/**
- * Returns true if the element is <skip> or <eat-comment> and can be safely
- * ignored.
- */
-static bool ShouldIgnoreElement(const StringPiece& ns,
- const StringPiece& name) {
+// Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
+static bool ShouldIgnoreElement(const StringPiece& ns, const StringPiece& name) {
return ns.empty() && (name == "skip" || name == "eat-comment");
}
-static uint32_t ParseFormatType(const StringPiece& piece) {
- if (piece == "reference")
+static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) {
+ if (piece == "reference") {
return android::ResTable_map::TYPE_REFERENCE;
- else if (piece == "string")
+ } else if (piece == "string") {
return android::ResTable_map::TYPE_STRING;
- else if (piece == "integer")
+ } else if (piece == "integer") {
return android::ResTable_map::TYPE_INTEGER;
- else if (piece == "boolean")
+ } else if (piece == "boolean") {
return android::ResTable_map::TYPE_BOOLEAN;
- else if (piece == "color")
+ } else if (piece == "color") {
return android::ResTable_map::TYPE_COLOR;
- else if (piece == "float")
+ } else if (piece == "float") {
return android::ResTable_map::TYPE_FLOAT;
- else if (piece == "dimension")
+ } else if (piece == "dimension") {
return android::ResTable_map::TYPE_DIMENSION;
- else if (piece == "fraction")
+ } else if (piece == "fraction") {
return android::ResTable_map::TYPE_FRACTION;
- else if (piece == "enum")
- return android::ResTable_map::TYPE_ENUM;
- else if (piece == "flags")
- return android::ResTable_map::TYPE_FLAGS;
+ }
return 0;
}
+static uint32_t ParseFormatType(const StringPiece& piece) {
+ if (piece == "enum") {
+ return android::ResTable_map::TYPE_ENUM;
+ } else if (piece == "flags") {
+ return android::ResTable_map::TYPE_FLAGS;
+ }
+ return ParseFormatTypeNoEnumsOrFlags(piece);
+}
+
static uint32_t ParseFormatAttribute(const StringPiece& str) {
uint32_t mask = 0;
for (StringPiece part : util::Tokenize(str, '|')) {
@@ -83,9 +84,7 @@
return mask;
}
-/**
- * A parsed resource ready to be added to the ResourceTable.
- */
+// A parsed resource ready to be added to the ResourceTable.
struct ParsedResource {
ResourceName name;
ConfigDescription config;
@@ -416,8 +415,7 @@
can_be_bag = false;
// Items have their type encoded in the type attribute.
- if (Maybe<StringPiece> maybe_type =
- xml::FindNonEmptyAttribute(parser, "type")) {
+ if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
resource_type = maybe_type.value().to_string();
} else {
diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
@@ -425,13 +423,11 @@
return false;
}
- if (Maybe<StringPiece> maybe_format =
- xml::FindNonEmptyAttribute(parser, "format")) {
+ if (Maybe<StringPiece> maybe_format = xml::FindNonEmptyAttribute(parser, "format")) {
// An explicit format for this resource was specified. The resource will
- // retain
- // its type in its name, but the accepted value for this type is
+ // retain its type in its name, but the accepted value for this type is
// overridden.
- resource_format = ParseFormatType(maybe_format.value());
+ resource_format = ParseFormatTypeNoEnumsOrFlags(maybe_format.value());
if (!resource_format) {
diag_->Error(DiagMessage(out_resource->source)
<< "'" << maybe_format.value()
@@ -1157,21 +1153,25 @@
return true;
}
-bool ResourceParser::ParseArray(xml::XmlPullParser* parser,
- ParsedResource* out_resource) {
- return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_ANY);
+bool ResourceParser::ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+ uint32_t resource_format = android::ResTable_map::TYPE_ANY;
+ if (Maybe<StringPiece> format_attr = xml::FindNonEmptyAttribute(parser, "format")) {
+ resource_format = ParseFormatTypeNoEnumsOrFlags(format_attr.value());
+ if (resource_format == 0u) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "'" << format_attr.value() << "' is an invalid format");
+ return false;
+ }
+ }
+ return ParseArrayImpl(parser, out_resource, resource_format);
}
-bool ResourceParser::ParseIntegerArray(xml::XmlPullParser* parser,
- ParsedResource* out_resource) {
- return ParseArrayImpl(parser, out_resource,
- android::ResTable_map::TYPE_INTEGER);
+bool ResourceParser::ParseIntegerArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+ return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_INTEGER);
}
-bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser,
- ParsedResource* out_resource) {
- return ParseArrayImpl(parser, out_resource,
- android::ResTable_map::TYPE_STRING);
+bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+ return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_STRING);
}
bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser,
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 8062c2e6..faa6607 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -571,14 +571,39 @@
}
TEST_F(ResourceParserTest, ParseStringArray) {
- std::string input =
- "<string-array name=\"foo\">\n"
- " <item>\"Werk\"</item>\n"
- "</string-array>\n";
+ std::string input = R"EOF(
+ <string-array name="foo">
+ <item>"Werk"</item>"
+ </string-array>)EOF";
ASSERT_TRUE(TestParse(input));
EXPECT_NE(nullptr, test::GetValue<Array>(&table_, "array/foo"));
}
+TEST_F(ResourceParserTest, ParseArrayWithFormat) {
+ std::string input = R"EOF(
+ <array name="foo" format="string">
+ <item>100</item>
+ </array>)EOF";
+ ASSERT_TRUE(TestParse(input));
+
+ Array* array = test::GetValue<Array>(&table_, "array/foo");
+ ASSERT_NE(nullptr, array);
+
+ ASSERT_EQ(1u, array->items.size());
+
+ String* str = ValueCast<String>(array->items[0].get());
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(std::string("100"), *str->value);
+}
+
+TEST_F(ResourceParserTest, ParseArrayWithBadFormat) {
+ std::string input = R"EOF(
+ <array name="foo" format="integer">
+ <item>Hi</item>
+ </array>)EOF";
+ ASSERT_FALSE(TestParse(input));
+}
+
TEST_F(ResourceParserTest, ParsePlural) {
std::string input =
"<plurals name=\"foo\">\n"
diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp
index 66aff82..5fbe77e 100644
--- a/tools/aapt2/filter/ConfigFilter.cpp
+++ b/tools/aapt2/filter/ConfigFilter.cpp
@@ -38,6 +38,43 @@
config_mask_ |= diff_mask;
}
+// Returns true if the locale script of the config should be considered matching
+// the locale script of entry.
+//
+// If both the scripts are empty, the scripts are considered matching for
+// backward compatibility reasons.
+//
+// If only one script is empty, we try to compute it based on the provided
+// language and country. If we could not compute it, we assume it's either a
+// new language we don't know about, or a private use language. We return true
+// since we don't know any better and they might as well be a match.
+//
+// Finally, when we have two scripts (one of which could be computed), we return
+// true if and only if they are an exact match.
+static bool ScriptsMatch(const ConfigDescription& config, const ConfigDescription& entry) {
+ const char* config_script = config.localeScript;
+ const char* entry_script = entry.localeScript;
+ if (config_script[0] == '\0' && entry_script[0] == '\0') {
+ return true; // both scripts are empty. We match for backward compatibility reasons.
+ }
+
+ char script_buffer[sizeof(config.localeScript)];
+ if (config_script[0] == '\0') {
+ android::localeDataComputeScript(script_buffer, config.language, config.country);
+ if (script_buffer[0] == '\0') { // We can't compute the script, so we match.
+ return true;
+ }
+ config_script = script_buffer;
+ } else if (entry_script[0] == '\0') {
+ android::localeDataComputeScript(script_buffer, entry.language, entry.country);
+ if (script_buffer[0] == '\0') { // We can't compute the script, so we match.
+ return true;
+ }
+ entry_script = script_buffer;
+ }
+ return memcmp(config_script, entry_script, sizeof(config.localeScript)) == 0;
+}
+
bool AxisConfigFilter::Match(const ConfigDescription& config) const {
const uint32_t mask = ConfigDescription::DefaultConfig().diff(config);
if ((config_mask_ & mask) == 0) {
@@ -57,12 +94,16 @@
// If the locales differ, but the languages are the same and
// the locale we are matching only has a language specified,
// we match.
- if (config.language[0] &&
- memcmp(config.language, target.language, sizeof(config.language)) ==
- 0) {
- if (config.country[0] == 0) {
- matched_axis |= android::ResTable_config::CONFIG_LOCALE;
- }
+ //
+ // Exception: we won't match if a script is specified for at least
+ // one of the locales and it's different from the other locale's
+ // script. (We will compute the other script if at least one of the
+ // scripts were explicitly set. In cases we can't compute an script,
+ // we match.)
+ if (config.language[0] != '\0' && config.country[0] == '\0' &&
+ config.localeVariant[0] == '\0' && config.language[0] == entry.first.language[0] &&
+ config.language[1] == entry.first.language[1] && ScriptsMatch(config, entry.first)) {
+ matched_axis |= android::ResTable_config::CONFIG_LOCALE;
}
} else if ((diff & diff_mask) ==
android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) {
diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp
index 586dd5f..4a31080 100644
--- a/tools/aapt2/filter/ConfigFilter_test.cpp
+++ b/tools/aapt2/filter/ConfigFilter_test.cpp
@@ -109,4 +109,20 @@
EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("kok-rIN")));
}
+TEST(ConfigFilterTest, MatchesScripts) {
+ AxisConfigFilter filter;
+
+ // "sr" gets automatically computed the script "Cyrl"
+ filter.AddConfig(test::ParseConfigOrDie("sr"));
+
+ // "sr" -> "b+sr+Cyrl"
+ EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("b+sr+Cyrl")));
+
+ // The incoming "sr" is also auto-computed to "b+sr+Cyrl".
+ EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("sr")));
+
+ // "sr" -> "b+sr+Cyrl", which doesn't match "Latn".
+ EXPECT_FALSE(filter.Match(test::ParseConfigOrDie("b+sr+Latn")));
+}
+
} // namespace aapt
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e59b74f0..cda078a 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -452,6 +452,15 @@
public static final int SAP_START_FAILURE_NO_CHANNEL = 1;
/**
+ * Interface IP mode unspecified.
+ *
+ * @see updateInterfaceIpState(String, int)
+ *
+ * @hide
+ */
+ public static final int IFACE_IP_MODE_UNSPECIFIED = -1;
+
+ /**
* Interface IP mode for configuration error.
*
* @see updateInterfaceIpState(String, int)
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
index 92c7e36..ebf5c2a 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
@@ -139,7 +139,7 @@
public String getDeviceInfoHex() {
return String.format(
- Locale.US, "%04x%04x%04x%04x", 6, mDeviceInfo, mCtrlPort, mMaxThroughput);
+ Locale.US, "%04x%04x%04x", mDeviceInfo, mCtrlPort, mMaxThroughput);
}
public String toString() {