Merge "Update destination bounds if rotation finishes first" into rvc-dev
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 31d7d08..65e772cb 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -25,12 +25,14 @@
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
+import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
@@ -572,6 +574,37 @@
}
/**
+ * Register this network agent with a testing harness.
+ *
+ * The returned Messenger sends messages to the Handler. This allows a test to send
+ * this object {@code CMD_*} messages as if they came from ConnectivityService, which
+ * is useful for testing the behavior.
+ *
+ * @hide
+ */
+ public Messenger registerForTest(final Network network) {
+ log("Registering NetworkAgent for test");
+ synchronized (mRegisterLock) {
+ mNetwork = network;
+ mInitialConfiguration = null;
+ }
+ return new Messenger(mHandler);
+ }
+
+ /**
+ * Waits for the handler to be idle.
+ * This is useful for testing, and has smaller scope than an accessor to mHandler.
+ * TODO : move the implementation in common library with the tests
+ * @hide
+ */
+ @VisibleForTesting
+ public boolean waitForIdle(final long timeoutMs) {
+ final ConditionVariable cv = new ConditionVariable(false);
+ mHandler.post(cv::open);
+ return cv.block(timeoutMs);
+ }
+
+ /**
* @return The Network associated with this agent, or null if it's not registered yet.
*/
@Nullable
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 73c6b3d..52d6fdfb 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -680,11 +680,13 @@
public void restrictCapabilitesForTestNetwork() {
final long originalCapabilities = mNetworkCapabilities;
final NetworkSpecifier originalSpecifier = mNetworkSpecifier;
+ final int originalSignalStrength = mSignalStrength;
clearAll();
// Reset the transports to only contain TRANSPORT_TEST.
mTransportTypes = (1 << TRANSPORT_TEST);
mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES;
mNetworkSpecifier = originalSpecifier;
+ mSignalStrength = originalSignalStrength;
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
old mode 100644
new mode 100755
index 5418833..fe340c4
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4748,6 +4748,14 @@
public static final String SHOW_BATTERY_PERCENT = "status_bar_show_battery_percent";
/**
+ * Whether or not to enable multiple audio focus.
+ * When enabled, requires more management by user over application playback activity,
+ * for instance pausing media apps when another starts.
+ * @hide
+ */
+ public static final String MULTI_AUDIO_FOCUS_ENABLED = "multi_audio_focus_enabled";
+
+ /**
* IMPORTANT: If you add a new public settings you also have to add it to
* PUBLIC_SETTINGS below. If the new setting is hidden you have to add
* it to PRIVATE_SETTINGS below. Also add a validator that can validate
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 1efa3dd..b47b2b4 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -31,6 +31,7 @@
import android.content.Intent;
import android.graphics.Rect;
import android.os.Build;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
@@ -558,9 +559,10 @@
}
}
- void reportResult(@Nullable List<Dataset> inlineSuggestionsData) {
+ void reportResult(@Nullable List<Dataset> inlineSuggestionsData,
+ @Nullable Bundle clientState) {
try {
- mCallback.onSuccess(inlineSuggestionsData);
+ mCallback.onSuccess(inlineSuggestionsData, clientState);
} catch (RemoteException e) {
Log.e(TAG, "Error calling back with the inline suggestions data: " + e);
}
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index 526a749..21738d80 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.os.Bundle;
import android.service.autofill.Dataset;
import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
import android.util.Log;
@@ -55,14 +56,15 @@
if (response == null) {
mProxy.logEvent(AutofillProxy.REPORT_EVENT_NO_RESPONSE);
- mProxy.reportResult(/* inlineSuggestionsData */ null);
+ mProxy.reportResult(/* inlineSuggestionsData */ null, /* clientState */ null);
return;
}
List<Dataset> inlineSuggestions = response.getInlineSuggestions();
+ Bundle clientState = response.getClientState();
if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) {
mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE);
- mProxy.reportResult(inlineSuggestions);
+ mProxy.reportResult(inlineSuggestions, clientState);
return;
}
diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl
index 24af1e5..609e382 100644
--- a/core/java/android/service/autofill/augmented/IFillCallback.aidl
+++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl
@@ -30,7 +30,7 @@
*/
interface IFillCallback {
void onCancellable(in ICancellationSignal cancellation);
- void onSuccess(in @nullable List<Dataset> inlineSuggestionsData);
+ void onSuccess(in @nullable List<Dataset> inlineSuggestionsData, in @nullable Bundle clientState);
boolean isCompleted();
void cancel();
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
old mode 100644
new mode 100755
index 8477aa3..3e1f72d
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6145,6 +6145,17 @@
}
}
+ /** @hide
+ * TODO: make this a @SystemApi */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void setMultiAudioFocusEnabled(boolean enabled) {
+ try {
+ getService().setMultiAudioFocusEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
//---------------------------------------------------------
// Inner classes
//--------------------
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index d237975..ed566a5 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -31,6 +31,7 @@
import android.util.Log;
import android.util.Pair;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import libcore.io.IoUtils;
@@ -586,7 +587,9 @@
private static final int WEBP_CHUNK_SIZE_BYTE_LENGTH = 4;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ @GuardedBy("sFormatter")
private static SimpleDateFormat sFormatter;
+ @GuardedBy("sFormatterTz")
private static SimpleDateFormat sFormatterTz;
// See Exchangeable image file format for digital still cameras: Exif version 2.2.
@@ -2426,12 +2429,17 @@
try {
// The exif field is in local time. Parsing it as if it is UTC will yield time
// since 1/1/1970 local time
- Date datetime = sFormatter.parse(dateTimeString, pos);
+ Date datetime;
+ synchronized (sFormatter) {
+ datetime = sFormatter.parse(dateTimeString, pos);
+ }
if (offsetString != null) {
dateTimeString = dateTimeString + " " + offsetString;
ParsePosition position = new ParsePosition(0);
- datetime = sFormatterTz.parse(dateTimeString, position);
+ synchronized (sFormatterTz) {
+ datetime = sFormatterTz.parse(dateTimeString, position);
+ }
}
if (datetime == null) return -1;
@@ -2473,7 +2481,10 @@
ParsePosition pos = new ParsePosition(0);
try {
- Date datetime = sFormatter.parse(dateTimeString, pos);
+ final Date datetime;
+ synchronized (sFormatter) {
+ datetime = sFormatter.parse(dateTimeString, pos);
+ }
if (datetime == null) return -1;
return datetime.getTime();
} catch (IllegalArgumentException e) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
old mode 100644
new mode 100755
index bb10e1f..e3b67f8
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -301,4 +301,6 @@
// WARNING: read warning at top of file, new methods that need to be used by native
// code via IAudioManager.h need to be added to the top section.
+
+ oneway void setMultiAudioFocusEnabled(in boolean enabled);
}
diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index c222985..dd3b6db 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -19,7 +19,6 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-
import android.util.Log;
/**
@@ -205,6 +204,20 @@
public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
String[] inputPortNames, String[] outputPortNames, Bundle properties,
boolean isPrivate) {
+ // Check num ports for out-of-range values. Typical values will be
+ // between zero and three. More than 16 would be very unlikely
+ // because the port index field in the USB packet is only 4 bits.
+ // This check is mainly just to prevent OutOfMemoryErrors when
+ // fuzz testing.
+ final int maxPorts = 256; // arbitrary and very high
+ if (numInputPorts < 0 || numInputPorts > maxPorts) {
+ throw new IllegalArgumentException("numInputPorts out of range = "
+ + numInputPorts);
+ }
+ if (numOutputPorts < 0 || numOutputPorts > maxPorts) {
+ throw new IllegalArgumentException("numOutputPorts out of range = "
+ + numOutputPorts);
+ }
mType = type;
mId = id;
mInputPortCount = numInputPorts;
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
index 6b1f259..1d23c31 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
@@ -17,6 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="enabled_by_admin" msgid="6630472777476410137">"ବ୍ୟବସ୍ଥାପକଙ୍କ ଦ୍ୱାରା ସକ୍ଷମ କରାଯାଇଛି"</string>
+ <string name="enabled_by_admin" msgid="6630472777476410137">"ଆଡମିନଙ୍କ ଦ୍ୱାରା ସକ୍ଷମ କରାଯାଇଛି"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ବ୍ୟବସ୍ଥାପକଙ୍କ ଦ୍ଵାରା ଅକ୍ଷମ କରାଯାଇଛି"</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 3024b84..a62d76f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -401,6 +401,11 @@
}
private List<MediaDevice> buildDisconnectedBluetoothDevice() {
+ if (mBluetoothAdapter == null) {
+ Log.w(TAG, "buildDisconnectedBluetoothDevice() BluetoothAdapter is null");
+ return new ArrayList<>();
+ }
+
final List<BluetoothDevice> bluetoothDevices =
mBluetoothAdapter.getMostRecentlyConnectedDevices();
final CachedBluetoothDeviceManager cachedDeviceManager =
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 7ddd64c..206c859 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -691,4 +691,28 @@
assertThat(mLocalMediaManager.mMediaDevices).hasSize(7);
verify(mCallback).onDeviceListUpdate(any());
}
+
+ @Test
+ public void onDeviceListAdded_bluetoothAdapterIsNull_noDisconnectedDeviceAdded() {
+ final List<MediaDevice> devices = new ArrayList<>();
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+ final MediaDevice device3 = mock(MediaDevice.class);
+ mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
+ devices.add(device1);
+ devices.add(device2);
+ mLocalMediaManager.mMediaDevices.add(device3);
+ mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+
+ mShadowBluetoothAdapter = null;
+
+ when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ verify(mCallback).onDeviceListUpdate(any());
+ }
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 01a2b69..03f6df0 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -101,7 +101,8 @@
Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
Settings.System.SCREEN_BRIGHTNESS_FLOAT,
- Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT
+ Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT,
+ Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
);
private static final Set<String> BACKUP_BLACKLISTED_GLOBAL_SETTINGS =
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index 5bf44c6..8051998 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -71,12 +71,13 @@
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask) {
if (task.configuration.windowConfiguration.getWindowingMode()
- != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || !mDividerOptional.isPresent()) {
return;
}
- if (homeTaskVisible) {
- showRecentApps(false /* triggeredFromAltTab */);
+ final Divider divider = mDividerOptional.get();
+ if (divider.isMinimized()) {
+ divider.onUndockingTask();
}
}
};
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index 9328611..ab25747 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -44,7 +44,8 @@
android:extractNativeLibs="false"
android:persistent="true">
<service android:name="com.android.server.connectivity.tethering.TetheringService"
- android:permission="android.permission.MAINLINE_NETWORK_STACK">
+ android:permission="android.permission.MAINLINE_NETWORK_STACK"
+ android:exported="true">
<intent-filter>
<action android:name="android.net.ITetheringConnector"/>
</intent-filter>
diff --git a/packages/Tethering/AndroidManifest_InProcess.xml b/packages/Tethering/AndroidManifest_InProcess.xml
index 02ea551..bf1f001 100644
--- a/packages/Tethering/AndroidManifest_InProcess.xml
+++ b/packages/Tethering/AndroidManifest_InProcess.xml
@@ -24,7 +24,8 @@
<application>
<service android:name="com.android.server.connectivity.tethering.TetheringService"
android:process="system"
- android:permission="android.permission.MAINLINE_NETWORK_STACK">
+ android:permission="android.permission.MAINLINE_NETWORK_STACK"
+ android:exported="true">
<intent-filter>
<action android:name="android.net.ITetheringConnector.InProcess"/>
</intent-filter>
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index d1805d9..1bc026c 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -815,26 +815,27 @@
}
}
- void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId) {
+ void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId,
+ @Nullable Bundle clientState) {
synchronized (mLock) {
if (mAugmentedAutofillEventHistory == null
|| mAugmentedAutofillEventHistory.getSessionId() != sessionId) {
return;
}
mAugmentedAutofillEventHistory.addEvent(
- new Event(Event.TYPE_DATASET_SELECTED, suggestionId, null, null, null,
+ new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null,
null, null, null, null, null, null));
}
}
- void logAugmentedAutofillShown(int sessionId) {
+ void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState) {
synchronized (mLock) {
if (mAugmentedAutofillEventHistory == null
|| mAugmentedAutofillEventHistory.getSessionId() != sessionId) {
return;
}
mAugmentedAutofillEventHistory.addEvent(
- new Event(Event.TYPE_DATASETS_SHOWN, null, null, null, null, null,
+ new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
null, null, null, null, null));
}
@@ -1185,15 +1186,16 @@
}
@Override
- public void logAugmentedAutofillShown(int sessionId) {
- AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId);
+ public void logAugmentedAutofillShown(int sessionId, Bundle clientState) {
+ AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId,
+ clientState);
}
@Override
public void logAugmentedAutofillSelected(int sessionId,
- String suggestionId) {
+ String suggestionId, Bundle clientState) {
AutofillManagerServiceImpl.this.logAugmentedAutofillSelected(sessionId,
- suggestionId);
+ suggestionId, clientState);
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index b6bc7c5..6cec8d8 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -168,12 +168,12 @@
focusedId, focusedValue, requestTime, inlineSuggestionsRequest,
new IFillCallback.Stub() {
@Override
- public void onSuccess(
- @Nullable List<Dataset> inlineSuggestionsData) {
+ public void onSuccess(@Nullable List<Dataset> inlineSuggestionsData,
+ @Nullable Bundle clientState) {
mCallbacks.resetLastResponse();
maybeRequestShowInlineSuggestions(sessionId,
inlineSuggestionsRequest, inlineSuggestionsData,
- focusedId, inlineSuggestionsCallback,
+ clientState, focusedId, inlineSuggestionsCallback,
client, onErrorCallback, remoteRenderService);
requestAutofill.complete(null);
}
@@ -238,7 +238,8 @@
private void maybeRequestShowInlineSuggestions(int sessionId,
@Nullable InlineSuggestionsRequest request,
- @Nullable List<Dataset> inlineSuggestionsData, @NonNull AutofillId focusedId,
+ @Nullable List<Dataset> inlineSuggestionsData, @Nullable Bundle clientState,
+ @NonNull AutofillId focusedId,
@Nullable Function<InlineSuggestionsResponse, Boolean> inlineSuggestionsCallback,
@NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback,
@Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
@@ -256,7 +257,7 @@
@Override
public void autofill(Dataset dataset) {
mCallbacks.logAugmentedAutofillSelected(sessionId,
- dataset.getId());
+ dataset.getId(), clientState);
try {
final ArrayList<AutofillId> fieldIds = dataset.getFieldIds();
final int size = fieldIds.size();
@@ -287,7 +288,7 @@
return;
}
if (inlineSuggestionsCallback.apply(inlineSuggestionsResponse)) {
- mCallbacks.logAugmentedAutofillShown(sessionId);
+ mCallbacks.logAugmentedAutofillShown(sessionId, clientState);
}
}
@@ -310,8 +311,9 @@
void setLastResponse(int sessionId);
- void logAugmentedAutofillShown(int sessionId);
+ void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState);
- void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId);
+ void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId,
+ @Nullable Bundle clientState);
}
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 51427c1..1a58d60 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2151,7 +2151,8 @@
private boolean checkNetworkSignalStrengthWakeupPermission(int pid, int uid) {
return checkAnyPermissionOf(pid, uid,
android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP,
- NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS);
}
private void enforceConnectivityRestrictedNetworksPermission() {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index b0a586d..14fe0c5 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3215,10 +3215,14 @@
@Override
public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) {
- Slog.d(TAG, "unlockUserKey: " + userId);
+ boolean isFsEncrypted = StorageManager.isFileEncryptedNativeOrEmulated();
+ Slog.d(TAG, "unlockUserKey: " + userId
+ + " isFileEncryptedNativeOrEmulated: " + isFsEncrypted
+ + " hasToken: " + (token != null)
+ + " hasSecret: " + (secret != null));
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
- if (StorageManager.isFileEncryptedNativeOrEmulated()) {
+ if (isFsEncrypted) {
try {
mVold.unlockUserKey(userId, serialNumber, encodeBytes(token),
encodeBytes(secret));
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
old mode 100644
new mode 100755
index 02d8571..ae22e69
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -8419,6 +8419,23 @@
}
//======================
+ // Multi Audio Focus
+ //======================
+ public void setMultiAudioFocusEnabled(boolean enabled) {
+ enforceModifyAudioRoutingPermission();
+ if (mMediaFocusControl != null) {
+ boolean mafEnabled = mMediaFocusControl.getMultiAudioFocusEnabled();
+ if (mafEnabled != enabled) {
+ mMediaFocusControl.updateMultiAudioFocus(enabled);
+ if (!enabled) {
+ mDeviceBroker.postBroadcastBecomingNoisy();
+ }
+ }
+ }
+ }
+
+
+ //======================
// misc
//======================
private final HashMap<IBinder, AudioPolicyProxy> mAudioPolicies =
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
old mode 100644
new mode 100755
index 7bf2bc8..bfab991
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -30,6 +30,7 @@
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
+import android.provider.Settings;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -80,6 +81,7 @@
private final Context mContext;
private final AppOpsManager mAppOps;
private PlayerFocusEnforcer mFocusEnforcer; // never null
+ private boolean mMultiAudioFocusEnabled = false;
private boolean mRingOrCallActive = false;
@@ -91,6 +93,8 @@
mContext = cntxt;
mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
mFocusEnforcer = pfe;
+ mMultiAudioFocusEnabled = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.MULTI_AUDIO_FOCUS_ENABLED, 0) != 0;
}
protected void dump(PrintWriter pw) {
@@ -100,6 +104,7 @@
pw.println("\n");
// log
mEventLogger.dump(pw);
+ dumpMultiAudioFocus(pw);
}
//=================================================================
@@ -194,6 +199,14 @@
mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
}
}
+
+ if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
+ for (FocusRequester multifr : mMultiAudioFocusList) {
+ if (isLockedFocusOwner(multifr)) {
+ multifr.handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
+ }
+ }
+ }
}
/**
@@ -203,17 +216,30 @@
*/
@GuardedBy("mAudioFocusLock")
private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
- boolean forceDuck) {
+ boolean forceDuck) {
final List<String> clientsToRemove = new LinkedList<String>();
// going through the audio focus stack to signal new focus, traversing order doesn't
// matter as all entries respond to the same external focus gain
- for (FocusRequester focusLoser : mFocusStack) {
- final boolean isDefinitiveLoss =
- focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
- if (isDefinitiveLoss) {
- clientsToRemove.add(focusLoser.getClientId());
+ if (!mFocusStack.empty()) {
+ for (FocusRequester focusLoser : mFocusStack) {
+ final boolean isDefinitiveLoss =
+ focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
+ if (isDefinitiveLoss) {
+ clientsToRemove.add(focusLoser.getClientId());
+ }
}
}
+
+ if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
+ for (FocusRequester multifocusLoser : mMultiAudioFocusList) {
+ final boolean isDefinitiveLoss =
+ multifocusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
+ if (isDefinitiveLoss) {
+ clientsToRemove.add(multifocusLoser.getClientId());
+ }
+ }
+ }
+
for (String clientToRemove : clientsToRemove) {
removeFocusStackEntry(clientToRemove, false /*signal*/,
true /*notifyFocusFollowers*/);
@@ -222,6 +248,8 @@
private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
+ ArrayList<FocusRequester> mMultiAudioFocusList = new ArrayList<FocusRequester>();
+
/**
* Helper function:
* Display in the log the current entries in the audio focus stack
@@ -287,6 +315,22 @@
}
}
}
+
+ if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
+ Iterator<FocusRequester> listIterator = mMultiAudioFocusList.iterator();
+ while (listIterator.hasNext()) {
+ FocusRequester fr = listIterator.next();
+ if (fr.hasSameClient(clientToRemove)) {
+ listIterator.remove();
+ fr.release();
+ }
+ }
+
+ if (signal) {
+ // notify the new top of the stack it gained focus
+ notifyTopOfAudioFocusStack();
+ }
+ }
}
/**
@@ -410,6 +454,16 @@
removeFocusEntryForExtPolicy(mCb);
} else {
removeFocusStackEntryOnDeath(mCb);
+ if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
+ Iterator<FocusRequester> listIterator = mMultiAudioFocusList.iterator();
+ while (listIterator.hasNext()) {
+ FocusRequester fr = listIterator.next();
+ if (fr.hasSameBinder(mCb)) {
+ listIterator.remove();
+ fr.release();
+ }
+ }
+ }
}
}
}
@@ -867,6 +921,35 @@
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
clientId, afdh, callingPackageName, Binder.getCallingUid(), this, sdk);
+
+ if (mMultiAudioFocusEnabled
+ && (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) {
+ if (enteringRingOrCall) {
+ if (!mMultiAudioFocusList.isEmpty()) {
+ for (FocusRequester multifr : mMultiAudioFocusList) {
+ multifr.handleFocusLossFromGain(focusChangeHint, nfr, forceDuck);
+ }
+ }
+ } else {
+ boolean needAdd = true;
+ if (!mMultiAudioFocusList.isEmpty()) {
+ for (FocusRequester multifr : mMultiAudioFocusList) {
+ if (multifr.getClientUid() == Binder.getCallingUid()) {
+ needAdd = false;
+ break;
+ }
+ }
+ }
+ if (needAdd) {
+ mMultiAudioFocusList.add(nfr);
+ }
+ nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
+ AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+ }
+ }
+
if (focusGrantDelayed) {
// focusGrantDelayed being true implies we can't reassign focus right now
// which implies the focus stack is not empty.
@@ -877,9 +960,7 @@
return requestResult;
} else {
// propagate the focus change through the stack
- if (!mFocusStack.empty()) {
- propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
- }
+ propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
// push focus requester at the top of the audio focus stack
mFocusStack.push(nfr);
@@ -970,4 +1051,39 @@
}
}.start();
}
+
+ public void updateMultiAudioFocus(boolean enabled) {
+ Log.d(TAG, "updateMultiAudioFocus( " + enabled + " )");
+ mMultiAudioFocusEnabled = enabled;
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.MULTI_AUDIO_FOCUS_ENABLED, enabled ? 1 : 0);
+ if (!mFocusStack.isEmpty()) {
+ final FocusRequester fr = mFocusStack.peek();
+ fr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null, false);
+ }
+ if (!enabled) {
+ if (!mMultiAudioFocusList.isEmpty()) {
+ for (FocusRequester multifr : mMultiAudioFocusList) {
+ multifr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null, false);
+ }
+ mMultiAudioFocusList.clear();
+ }
+ }
+ }
+
+ public boolean getMultiAudioFocusEnabled() {
+ return mMultiAudioFocusEnabled;
+ }
+
+ private void dumpMultiAudioFocus(PrintWriter pw) {
+ pw.println("Multi Audio Focus enabled :" + mMultiAudioFocusEnabled);
+ if (!mMultiAudioFocusList.isEmpty()) {
+ pw.println("Multi Audio Focus List:");
+ pw.println("------------------------------");
+ for (FocusRequester multifr : mMultiAudioFocusList) {
+ multifr.dump(pw);
+ }
+ pw.println("------------------------------");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index c3413e8..0d89997 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -622,6 +622,16 @@
return;
}
+ if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
+ && !TextUtils.equals(route.getId(),
+ routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId())) {
+ Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
+ + route);
+ routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
+ routerRecord, requestId);
+ return;
+ }
+
long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::requestCreateSessionOnHandler,
@@ -915,9 +925,6 @@
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterforSessionLocked(uniqueSessionId);
- if (routerRecord == null) {
- return;
- }
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
@@ -1272,15 +1279,6 @@
toOriginalRequestId(uniqueRequestId));
return;
}
- if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
- && !TextUtils.equals(route.getId(),
- mSystemProvider.getDefaultRoute().getId())) {
- Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
- + route);
- notifySessionCreationFailedToRouter(routerRecord,
- toOriginalRequestId(uniqueRequestId));
- return;
- }
SessionCreationRequest request =
new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
@@ -1404,11 +1402,11 @@
}
private void releaseSessionOnHandler(long uniqueRequestId,
- @NonNull RouterRecord routerRecord, @NonNull String uniqueSessionId) {
+ @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId) {
final RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
if (matchingRecord != routerRecord) {
- Slog.w(TAG, "Ignoring releasing session from non-matching router."
- + " packageName=" + routerRecord.mPackageName
+ Slog.w(TAG, "Ignoring releasing session from non-matching router. packageName="
+ + (routerRecord == null ? null : routerRecord.mPackageName)
+ " uniqueSessionId=" + uniqueSessionId);
return;
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 68224b5..75d6a09 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
@@ -48,6 +49,7 @@
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Display;
+import android.view.InsetsState;
import android.view.MagnificationSpec;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
@@ -1203,6 +1205,17 @@
updateUnaccountedSpace(windowState, regionInScreen, unaccountedSpace,
skipRemainingWindowsForTasks);
focusedWindowAdded |= windowState.isFocused();
+ } else if (isUntouchableNavigationBar(windowState)) {
+ // If this widow is navigation bar without touchable region, accounting the
+ // region of navigation bar inset because all touch events from this region
+ // would be received by launcher, i.e. this region is a un-touchable one
+ // for the application.
+ final InsetsState insetsState =
+ dc.getInsetsStateController().getRawInsetsState();
+ final Rect displayFrame =
+ insetsState.getSource(ITYPE_NAVIGATION_BAR).getFrame();
+ unaccountedSpace.op(displayFrame, unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
}
if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
@@ -1281,6 +1294,18 @@
return false;
}
+ private boolean isUntouchableNavigationBar(WindowState windowState) {
+ if (windowState.mAttrs.type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR) {
+ return false;
+ }
+
+ // Gets the touchable region.
+ Region touchableRegion = mTempRegion1;
+ windowState.getTouchableRegion(touchableRegion);
+
+ return touchableRegion.isEmpty();
+ }
+
private void updateUnaccountedSpace(WindowState windowState, Region regionInScreen,
Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks) {
if (windowState.mAttrs.type
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9c56788..e79b804 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2669,6 +2669,17 @@
return this;
}
+ // Ensure activity visibilities and update lockscreen occluded/dismiss state when
+ // finishing the top activity that occluded keyguard. So that, the
+ // ActivityStack#mTopActivityOccludesKeyguard can be updated and the activity below won't
+ // be resumed.
+ if (isState(PAUSED)
+ && mStackSupervisor.getKeyguardController().isKeyguardLocked()
+ && getStack().topActivityOccludesKeyguard()) {
+ getStack().ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
+ false /* preserveWindows */, false /* notifyClients */);
+ }
+
boolean activityRemoved = false;
// If this activity is currently visible, and the resumed activity is not yet visible, then
@@ -5125,26 +5136,48 @@
}
void startFreezingScreen() {
+ startFreezingScreen(ROTATION_UNDEFINED /* overrideOriginalDisplayRotation */);
+ }
+
+ void startFreezingScreen(int overrideOriginalDisplayRotation) {
ProtoLog.i(WM_DEBUG_ORIENTATION,
"Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s",
appToken, isVisible(), mFreezingScreen, mVisibleRequested,
new RuntimeException().fillInStackTrace());
- if (mVisibleRequested) {
- if (!mFreezingScreen) {
- mFreezingScreen = true;
- mWmService.registerAppFreezeListener(this);
- mWmService.mAppsFreezingScreen++;
- if (mWmService.mAppsFreezingScreen == 1) {
- mWmService.startFreezingDisplayLocked(0, 0, getDisplayContent());
- mWmService.mH.removeMessages(H.APP_FREEZE_TIMEOUT);
- mWmService.mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
+ if (!mVisibleRequested) {
+ return;
+ }
+
+ // If the override is given, the rotation of display doesn't change but we still want to
+ // cover the activity whose configuration is changing by freezing the display and running
+ // the rotation animation.
+ final boolean forceRotation = overrideOriginalDisplayRotation != ROTATION_UNDEFINED;
+ if (!mFreezingScreen) {
+ mFreezingScreen = true;
+ mWmService.registerAppFreezeListener(this);
+ mWmService.mAppsFreezingScreen++;
+ if (mWmService.mAppsFreezingScreen == 1) {
+ if (forceRotation) {
+ // Make sure normal rotation animation will be applied.
+ mDisplayContent.getDisplayRotation().cancelSeamlessRotation();
}
+ mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */,
+ mDisplayContent, overrideOriginalDisplayRotation);
+ mWmService.mH.removeMessages(H.APP_FREEZE_TIMEOUT);
+ mWmService.mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
}
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
- final WindowState w = mChildren.get(i);
- w.onStartFreezingScreen();
- }
+ }
+ if (forceRotation) {
+ // The rotation of the real display won't change, so in order to unfreeze the screen
+ // via {@link #checkAppWindowsReadyToShow}, the windows have to be able to call
+ // {@link WindowState#reportResized} (it is skipped if the window is freezing) to update
+ // the drawn state.
+ return;
+ }
+ final int count = mChildren.size();
+ for (int i = 0; i < count; i++) {
+ final WindowState w = mChildren.get(i);
+ w.onStartFreezingScreen();
}
}
@@ -6159,6 +6192,21 @@
ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
}
+ @Override
+ void onCancelFixedRotationTransform(int originalDisplayRotation) {
+ if (this != mDisplayContent.getLastOrientationSource()
+ || getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED) {
+ // Only need to handle the activity that should be rotated with display.
+ return;
+ }
+
+ // Perform rotation animation according to the rotation of this activity.
+ startFreezingScreen(originalDisplayRotation);
+ // This activity may relaunch or perform configuration change so once it has reported drawn,
+ // the screen can be unfrozen.
+ ensureActivityConfiguration(0 /* globalChanges */, !PRESERVE_WINDOWS);
+ }
+
void setRequestedOrientation(int requestedOrientation) {
setOrientation(requestedOrientation, mayFreezeScreenLocked());
mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged(
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8d46a67..36d5583 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -495,6 +495,7 @@
* The launching activity which is using fixed rotation transformation.
*
* @see #handleTopActivityLaunchingInDifferentOrientation
+ * @see DisplayRotation#shouldRotateSeamlessly
*/
ActivityRecord mFixedRotationLaunchingApp;
@@ -1238,7 +1239,7 @@
if (configChanged) {
mWaitingForConfig = true;
- mWmService.startFreezingDisplayLocked(0 /* exitAnim */, 0 /* enterAnim */, this);
+ mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
sendNewConfiguration();
}
@@ -1476,6 +1477,9 @@
sendNewConfiguration();
return true;
}
+ // The display won't rotate (e.g. the orientation from sensor has updated again before
+ // applying rotation to display), so clear it to stop using seamless rotation.
+ mFixedRotationLaunchingApp = null;
return false;
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index bef80f0..ebfe70c 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -537,8 +537,29 @@
}
void prepareNormalRotationAnimation() {
+ cancelSeamlessRotation();
final RotationAnimationPair anim = selectRotationAnimation();
- mService.startFreezingDisplayLocked(anim.mExit, anim.mEnter, mDisplayContent);
+ mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
+ }
+
+ /**
+ * This ensures that normal rotation animation is used. E.g. {@link #mRotatingSeamlessly} was
+ * set by previous {@link #updateRotationUnchecked}, but another orientation change happens
+ * before calling {@link DisplayContent#sendNewConfiguration} (remote rotation hasn't finished)
+ * and it doesn't choose seamless rotation.
+ */
+ void cancelSeamlessRotation() {
+ if (!mRotatingSeamlessly) {
+ return;
+ }
+ mDisplayContent.forAllWindows(w -> {
+ if (w.mSeamlesslyRotated) {
+ w.finishSeamlessRotation(false /* timeout */);
+ w.mSeamlesslyRotated = false;
+ }
+ }, true /* traverseTopToBottom */);
+ mSeamlessRotationCount = 0;
+ mRotatingSeamlessly = false;
}
private void prepareSeamlessRotation() {
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index b92ead1..5f33ea1 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -41,7 +41,6 @@
import android.os.Trace;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
@@ -117,8 +116,9 @@
private BlackFrame mEnteringBlackFrame;
private int mWidth, mHeight;
- private int mOriginalRotation;
- private int mOriginalWidth, mOriginalHeight;
+ private final int mOriginalRotation;
+ private final int mOriginalWidth;
+ private final int mOriginalHeight;
private int mCurRotation;
private Rect mOriginalDisplayRect = new Rect();
@@ -140,20 +140,18 @@
/** Intensity of light/whiteness of the layout after rotation occurs. */
private float mEndLuma;
- public ScreenRotationAnimation(Context context, DisplayContent displayContent,
- boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) {
- mService = service;
- mContext = context;
+ ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
+ mService = displayContent.mWmService;
+ mContext = mService.mContext;
mDisplayContent = displayContent;
displayContent.getBounds(mOriginalDisplayRect);
// Screenshot does NOT include rotation!
- final Display display = displayContent.getDisplay();
- int originalRotation = display.getRotation();
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+ final int realOriginalRotation = displayInfo.rotation;
final int originalWidth;
final int originalHeight;
- DisplayInfo displayInfo = displayContent.getDisplayInfo();
- if (fixedToUserRotation) {
+ if (displayContent.getDisplayRotation().isFixedToUserRotation()) {
// Emulated orientation.
mForceDefaultOrientation = true;
originalWidth = displayContent.mBaseDisplayWidth;
@@ -163,8 +161,8 @@
originalWidth = displayInfo.logicalWidth;
originalHeight = displayInfo.logicalHeight;
}
- if (originalRotation == Surface.ROTATION_90
- || originalRotation == Surface.ROTATION_270) {
+ if (realOriginalRotation == Surface.ROTATION_90
+ || realOriginalRotation == Surface.ROTATION_270) {
mWidth = originalHeight;
mHeight = originalWidth;
} else {
@@ -173,10 +171,18 @@
}
mOriginalRotation = originalRotation;
- mOriginalWidth = originalWidth;
- mOriginalHeight = originalHeight;
+ // If the delta is not zero, the rotation of display may not change, but we still want to
+ // apply rotation animation because there should be a top app shown as rotated. So the
+ // specified original rotation customizes the direction of animation to have better look
+ // when restoring the rotated app to the same rotation as current display.
+ final int delta = DisplayContent.deltaRotation(originalRotation, realOriginalRotation);
+ final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270;
+ mOriginalWidth = flipped ? originalHeight : originalWidth;
+ mOriginalHeight = flipped ? originalWidth : originalHeight;
mSurfaceRotationAnimationController = new SurfaceRotationAnimationController();
+ // Check whether the current screen contains any secure content.
+ final boolean isSecure = displayContent.hasSecureWindowOnScreen();
final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
try {
mBackColorSurface = displayContent.makeChildSurface(null)
@@ -202,7 +208,7 @@
t2.apply(true /* sync */);
// Capture a screenshot into the surface we just created.
- final int displayId = display.getDisplayId();
+ final int displayId = displayContent.getDisplayId();
final Surface surface = mService.mSurfaceFactory.get();
surface.copyFrom(mScreenshotLayer);
SurfaceControl.ScreenshotGraphicBuffer gb =
@@ -242,7 +248,7 @@
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
" FREEZE %s: CREATE", mScreenshotLayer);
- setRotation(t, originalRotation);
+ setRotation(t, realOriginalRotation);
t.apply();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9d87976..5c7d37b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -29,6 +29,7 @@
import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.StatusBarManager.DISABLE_MASK;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.content.pm.PackageManager.FEATURE_PC;
@@ -2941,7 +2942,7 @@
mClientFreezingScreen = true;
final long origId = Binder.clearCallingIdentity();
try {
- startFreezingDisplayLocked(exitAnim, enterAnim);
+ startFreezingDisplay(exitAnim, enterAnim);
mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT);
mH.sendEmptyMessageDelayed(H.CLIENT_FREEZE_TIMEOUT, 5000);
} finally {
@@ -5479,13 +5480,17 @@
return changed;
}
- void startFreezingDisplayLocked(int exitAnim, int enterAnim) {
- startFreezingDisplayLocked(exitAnim, enterAnim,
- getDefaultDisplayContentLocked());
+ void startFreezingDisplay(int exitAnim, int enterAnim) {
+ startFreezingDisplay(exitAnim, enterAnim, getDefaultDisplayContentLocked());
}
- void startFreezingDisplayLocked(int exitAnim, int enterAnim,
- DisplayContent displayContent) {
+ void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent) {
+ startFreezingDisplay(exitAnim, enterAnim, displayContent,
+ ROTATION_UNDEFINED /* overrideOriginalRotation */);
+ }
+
+ void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
+ int overrideOriginalRotation) {
if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {
return;
}
@@ -5529,14 +5534,12 @@
screenRotationAnimation.kill();
}
- // Check whether the current screen contains any secure content.
- boolean isSecure = displayContent.hasSecureWindowOnScreen();
-
displayContent.updateDisplayInfo();
- screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
- displayContent.getDisplayRotation().isFixedToUserRotation(), isSecure,
- this);
- displayContent.setRotationAnimation(screenRotationAnimation);
+ final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED
+ ? overrideOriginalRotation
+ : displayContent.getDisplayInfo().rotation;
+ displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent,
+ originalRotation));
}
void stopFreezingDisplayLocked() {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index e34b816..2d5c4c1 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -595,7 +595,17 @@
// The window may be detached or detaching.
return;
}
+ final int originalRotation = getWindowConfiguration().getRotation();
onConfigurationChanged(parent.getConfiguration());
+ onCancelFixedRotationTransform(originalRotation);
+ }
+
+ /**
+ * It is called when the window is using fixed rotation transform, and before display applies
+ * the same rotation, the rotation change for display is canceled, e.g. the orientation from
+ * sensor is updated to previous direction.
+ */
+ void onCancelFixedRotationTransform(int originalDisplayRotation) {
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 08f6409..bc66fa7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -32,6 +32,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
@@ -63,6 +64,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
@@ -1024,6 +1026,38 @@
}
/**
+ * Verify that complete finish request for a show-when-locked activity must ensure the
+ * keyguard occluded state being updated.
+ */
+ @Test
+ public void testCompleteFinishing_showWhenLocked() {
+ // Make keyguard locked and set the top activity show-when-locked.
+ KeyguardController keyguardController = mActivity.mStackSupervisor.getKeyguardController();
+ doReturn(true).when(keyguardController).isKeyguardLocked();
+ final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+ topActivity.mVisibleRequested = true;
+ topActivity.nowVisible = true;
+ topActivity.setState(RESUMED, "true");
+ doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
+ any() /* starting */, anyInt() /* configChanges */,
+ anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+ topActivity.setShowWhenLocked(true);
+
+ // Verify the stack-top activity is occluded keyguard.
+ assertEquals(topActivity, mStack.topRunningActivity());
+ assertTrue(mStack.topActivityOccludesKeyguard());
+
+ // Finish the top activity
+ topActivity.setState(PAUSED, "true");
+ topActivity.finishing = true;
+ topActivity.completeFinishing("test");
+
+ // Verify new top activity does not occlude keyguard.
+ assertEquals(mActivity, mStack.topRunningActivity());
+ assertFalse(mStack.topActivityOccludesKeyguard());
+ }
+
+ /**
* Verify destroy activity request completes successfully.
*/
@Test
@@ -1273,6 +1307,48 @@
}
@Test
+ public void testActivityOnCancelFixedRotationTransform() {
+ mService.mWindowManager.mIsFixedRotationTransformEnabled = true;
+ final DisplayRotation displayRotation = mActivity.mDisplayContent.getDisplayRotation();
+ spyOn(displayRotation);
+
+ final DisplayContent display = mActivity.mDisplayContent;
+ final int originalRotation = display.getRotation();
+
+ // Make {@link DisplayContent#sendNewConfiguration} not apply rotation immediately.
+ doReturn(true).when(displayRotation).isWaitingForRemoteRotation();
+ doReturn((originalRotation + 1) % 4).when(displayRotation).rotationForOrientation(
+ anyInt() /* orientation */, anyInt() /* lastRotation */);
+ // Set to visible so the activity can freeze the screen.
+ mActivity.setVisibility(true);
+
+ display.rotateInDifferentOrientationIfNeeded(mActivity);
+ display.mFixedRotationLaunchingApp = mActivity;
+ displayRotation.updateRotationUnchecked(false /* forceUpdate */);
+
+ assertTrue(displayRotation.isRotatingSeamlessly());
+
+ // Simulate the rotation has been updated to previous one, e.g. sensor updates before the
+ // remote rotation is completed.
+ doReturn(originalRotation).when(displayRotation).rotationForOrientation(
+ anyInt() /* orientation */, anyInt() /* lastRotation */);
+ display.updateOrientation();
+
+ final DisplayInfo rotatedInfo = mActivity.getFixedRotationTransformDisplayInfo();
+ mActivity.finishFixedRotationTransform();
+ final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
+ rotationAnim.setRotation(display.getPendingTransaction(), originalRotation);
+
+ // Because the display doesn't rotate, the rotated activity needs to cancel the fixed
+ // rotation. There should be a rotation animation to cover the change of activity.
+ verify(mActivity).onCancelFixedRotationTransform(rotatedInfo.rotation);
+ assertTrue(mActivity.isFreezingScreen());
+ assertFalse(displayRotation.isRotatingSeamlessly());
+ assertNotNull(rotationAnim);
+ assertTrue(rotationAnim.isRotating());
+ }
+
+ @Test
public void testActivityOnDifferentDisplayUpdatesProcessOverride() {
final ActivityRecord secondaryDisplayActivity =
createActivityOnDisplay(false /* defaultDisplay */, null /* process */);
diff --git a/startop/iorap/src/com/google/android/startop/iorap/DexOptEvent.java b/startop/iorap/src/com/google/android/startop/iorap/DexOptEvent.java
new file mode 100644
index 0000000..72c5eaa
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/DexOptEvent.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 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.google.android.startop.iorap;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Notifications for iorapd specifying when a package is updated by dexopt service.<br /><br />
+ *
+ * @hide
+ */
+public class DexOptEvent implements Parcelable {
+ public static final int TYPE_PACKAGE_UPDATE = 0;
+ private static final int TYPE_MAX = 0;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+ TYPE_PACKAGE_UPDATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ @Type public final int type;
+ public final String packageName;
+
+ @NonNull
+ public static DexOptEvent createPackageUpdate(String packageName) {
+ return new DexOptEvent(TYPE_PACKAGE_UPDATE, packageName);
+ }
+
+ private DexOptEvent(@Type int type, String packageName) {
+ this.type = type;
+ this.packageName = packageName;
+
+ checkConstructorArguments();
+ }
+
+ private void checkConstructorArguments() {
+ CheckHelpers.checkTypeInRange(type, TYPE_MAX);
+ Objects.requireNonNull(packageName, "packageName");
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{DexOptEvent: packageName: %s}", packageName);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (other instanceof DexOptEvent) {
+ return equals((DexOptEvent) other);
+ }
+ return false;
+ }
+
+ private boolean equals(DexOptEvent other) {
+ return packageName.equals(other.packageName);
+ }
+
+ //<editor-fold desc="Binder boilerplate">
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(type);
+ out.writeString(packageName);
+ }
+
+ private DexOptEvent(Parcel in) {
+ this.type = in.readInt();
+ this.packageName = in.readString();
+
+ checkConstructorArguments();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<DexOptEvent> CREATOR
+ = new Parcelable.Creator<DexOptEvent>() {
+ public DexOptEvent createFromParcel(Parcel in) {
+ return new DexOptEvent(in);
+ }
+
+ public DexOptEvent[] newArray(int size) {
+ return new DexOptEvent[size];
+ }
+ };
+ //</editor-fold>
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 0c25cfb..8f1d0ad 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -324,6 +324,11 @@
String[] updated = updatedPackages.toArray(new String[0]);
for (String packageName : updated) {
Log.d(TAG, "onPackagesUpdated: " + packageName);
+ invokeRemote(mIorapRemote,
+ (IIorap remote) ->
+ remote.onDexOptEvent(RequestId.nextValueForSequence(),
+ DexOptEvent.createPackageUpdate(packageName))
+ );
}
}
}
diff --git a/tests/net/java/android/net/CaptivePortalDataTest.kt b/tests/net/common/java/android/net/CaptivePortalDataTest.kt
similarity index 67%
rename from tests/net/java/android/net/CaptivePortalDataTest.kt
rename to tests/net/common/java/android/net/CaptivePortalDataTest.kt
index 0071438..bd1847b 100644
--- a/tests/net/java/android/net/CaptivePortalDataTest.kt
+++ b/tests/net/common/java/android/net/CaptivePortalDataTest.kt
@@ -16,17 +16,22 @@
package android.net
+import android.os.Build
import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
import com.android.testutils.assertParcelSane
import com.android.testutils.assertParcelingIsLossless
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.Q)
class CaptivePortalDataTest {
private val data = CaptivePortalData.Builder()
.setRefreshTime(123L)
@@ -63,6 +68,46 @@
assertNotEqualsAfterChange { it.setCaptive(false) }
}
+ @Test
+ fun testUserPortalUrl() {
+ assertEquals(Uri.parse("https://portal.example.com/test"), data.userPortalUrl)
+ }
+
+ @Test
+ fun testVenueInfoUrl() {
+ assertEquals(Uri.parse("https://venue.example.com/test"), data.venueInfoUrl)
+ }
+
+ @Test
+ fun testIsSessionExtendable() {
+ assertTrue(data.isSessionExtendable)
+ }
+
+ @Test
+ fun testByteLimit() {
+ assertEquals(456L, data.byteLimit)
+ // Test byteLimit unset.
+ assertEquals(-1L, CaptivePortalData.Builder(null).build().byteLimit)
+ }
+
+ @Test
+ fun testRefreshTimeMillis() {
+ assertEquals(123L, data.refreshTimeMillis)
+ }
+
+ @Test
+ fun testExpiryTimeMillis() {
+ assertEquals(789L, data.expiryTimeMillis)
+ // Test expiryTimeMillis unset.
+ assertEquals(-1L, CaptivePortalData.Builder(null).build().expiryTimeMillis)
+ }
+
+ @Test
+ fun testIsCaptive() {
+ assertTrue(data.isCaptive)
+ assertFalse(makeBuilder().setCaptive(false).build().isCaptive)
+ }
+
private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) =
CaptivePortalData.Builder(this).apply { mutator(this) }.build()