Merge "Report folds in SupportedWindowFeatures." into main
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 22d34e6..cbb3be7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9958,6 +9958,17 @@
}
/**
+ * @hide
+ */
+ public void onGetCredentialException(String errorType, String errorMsg) {
+ if (getCredentialManagerCallback() == null) {
+ Log.w(AUTOFILL_LOG_TAG, "onGetCredentialException called but no callback found");
+ return;
+ }
+ getCredentialManagerCallback().onError(new GetCredentialException(errorType, errorMsg));
+ }
+
+ /**
* Gets the unique, logical identifier of this view in the activity, for autofill purposes.
*
* <p>The autofill id is created on demand, unless it is explicitly set by
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 364c94f..64e5a5b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -50,6 +50,7 @@
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.credentials.GetCredentialException;
import android.credentials.GetCredentialResponse;
import android.graphics.Rect;
import android.metrics.LogMaker;
@@ -105,6 +106,7 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -2400,6 +2402,13 @@
final Bundle responseData = new Bundle();
responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
+ Serializable exception = data.getSerializableExtra(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
+ GetCredentialException.class);
+ if (exception != null && Flags.autofillCredmanIntegration()) {
+ responseData.putSerializable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, exception);
+ }
final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE);
if (newClientState != null) {
responseData.putBundle(EXTRA_CLIENT_STATE, newClientState);
@@ -2926,6 +2935,48 @@
}
}
+ private void onGetCredentialException(int sessionId, AutofillId id, String errorType,
+ String errorMsg) {
+ synchronized (mLock) {
+ if (sessionId != mSessionId) {
+ Log.w(TAG, "onGetCredentialException afm sessionIds don't match");
+ return;
+ }
+
+ final AutofillClient client = getClient();
+ if (client == null) {
+ Log.w(TAG, "onGetCredentialException afm client id null");
+ return;
+ }
+ ArrayList<AutofillId> failedIds = new ArrayList<>();
+ final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
+ Helper.toArray(new ArrayList<>(Collections.singleton(id))));
+ if (views == null || views.length == 0) {
+ Log.w(TAG, "onGetCredentialException afm client view not found");
+ return;
+ }
+
+ final View view = views[0];
+ if (view == null) {
+ Log.i(TAG, "onGetCredentialException View is null");
+
+ // Most likely view has been removed after the initial request was sent to the
+ // the service; this is fine, but we need to update the view status in the
+ // server side so it can be triggered again.
+ Log.d(TAG, "onGetCredentialException(): no View with id " + id);
+ failedIds.add(id);
+ }
+ if (id.isVirtualInt()) {
+ Log.i(TAG, "onGetCredentialException afm client id is virtual");
+ // TODO(b/326314286): Handle virtual views
+ } else {
+ Log.i(TAG, "onGetCredentialException afm client id is NOT virtual");
+ view.onGetCredentialException(errorType, errorMsg);
+ }
+ handleFailedIdsLocked(failedIds);
+ }
+ }
+
private void onGetCredentialResponse(int sessionId, AutofillId id,
GetCredentialResponse response) {
synchronized (mLock) {
@@ -4382,6 +4433,15 @@
}
@Override
+ public void onGetCredentialException(int sessionId, AutofillId id,
+ String errorType, String errorMsg) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.onGetCredentialException(sessionId, id, errorType, errorMsg));
+ }
+ }
+
+ @Override
public void autofillContent(int sessionId, AutofillId id, ClipData content) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index e838027..904a7e0 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -49,9 +49,12 @@
void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values,
boolean hideHighlight);
- void onGetCredentialResponse(int sessionId, in AutofillId id,
+ void onGetCredentialResponse(int sessionId, in AutofillId id,
in GetCredentialResponse response);
+ void onGetCredentialException(int sessionId, in AutofillId id,
+ in String errorType, in String errorMsg);
+
/**
* Autofills the activity with rich content data (e.g. an image) from a dataset.
*/
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 104b7cd..c87b7cd 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -73,6 +73,11 @@
<bool name="auto_data_switch_ping_test_before_switch">true</bool>
<java-symbol type="bool" name="auto_data_switch_ping_test_before_switch" />
+ <!-- TODO: remove after V -->
+ <!-- Boolean indicating whether allow to use a roaming nonDDS if user enabled its roaming. -->
+ <bool name="auto_data_switch_allow_roaming">true</bool>
+ <java-symbol type="bool" name="auto_data_switch_allow_roaming" />
+
<!-- Define the tolerated gap of score for auto data switch decision, larger than which the
device will switch to the SIM with higher score. The score is used in conjunction with the
score table defined in
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 8d6ddf2..37e6780 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -28,19 +28,10 @@
visibility: ["//visibility:private"],
}
-java_defaults {
- name: "FrameworksCoreTests-resources",
- aaptflags: [
- "-0 .dat",
- "-0 .gld",
- "-c fa",
- ],
- resource_dirs: ["res"],
-}
-
android_test {
name: "FrameworksCoreTests",
- defaults: ["FrameworksCoreTests-resources"],
+ // FrameworksCoreTestsRavenwood references the .aapt.srcjar
+ use_resource_processor: false,
srcs: [
"src/**/*.java",
@@ -126,6 +117,7 @@
certificate: "platform",
+ resource_dirs: ["res"],
resource_zips: [":FrameworksCoreTests_apks_as_resources"],
java_resources: [":FrameworksCoreTests_unit_test_cert_der"],
@@ -136,22 +128,6 @@
],
}
-// FrameworksCoreTestsRavenwood pulls in the R.java class from this one.
-// Note, "FrameworksCoreTests" and "FrameworksCoreTests-resonly" _might_ not have indentical
-// R.java (not sure if there's a guarantee), but that doesn't matter as long as
-// FrameworksCoreTestsRavenwood consistently uses the R definition in this module.
-android_app {
- name: "FrameworksCoreTests-resonly",
- defaults: ["FrameworksCoreTests-resources"],
-
- // FrameworksCoreTestsRavenwood references the .aapt.srcjar
- use_resource_processor: false,
- libs: [
- "framework-res",
- ],
- sdk_version: "core_platform",
-}
-
// Rules to copy all the test apks to the intermediate raw resource directory
java_genrule {
name: "FrameworksCoreTests_apks_as_resources",
@@ -249,11 +225,7 @@
"src/com/android/internal/util/**/*.java",
"src/com/android/internal/power/EnergyConsumerStatsTest.java",
- // Pull in R.java from FrameworksCoreTests-resonly, not from FrameworksCoreTests,
- // to avoid having a dependency to FrameworksCoreTests.
- // This way, when updating source files and running this test, we don't need to
- // rebuild the entire FrameworksCoreTests, which would be slow.
- ":FrameworksCoreTests-resonly{.aapt.srcjar}",
+ ":FrameworksCoreTests{.aapt.srcjar}",
":FrameworksCoreTests-aidl",
":FrameworksCoreTests-helpers",
":FrameworksCoreTestDoubles-sources",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index b57e2d2..b87c2f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -228,6 +228,14 @@
mExpandedMovementBounds.set(bounds);
}
+ /** Updates the min and max sizes based on the size spec and aspect ratio. */
+ public void updateMinMaxSize(float aspectRatio) {
+ final Size minSize = mSizeSpecSource.getMinSize(aspectRatio);
+ mMinSize.set(minSize.getWidth(), minSize.getHeight());
+ final Size maxSize = mSizeSpecSource.getMaxSize(aspectRatio);
+ mMaxSize.set(maxSize.getWidth(), maxSize.getHeight());
+ }
+
/** Sets the max possible size for resize. */
public void setMaxSize(int width, int height) {
mMaxSize.set(width, height);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e018ecc..6a1a62ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1037,6 +1037,7 @@
private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation,
TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) {
mPipDisplayLayoutState.rotateTo(endRotation);
+ mPipBoundsState.updateMinMaxSize(mPipBoundsState.getAspectRatio());
final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 4684077..2cdec81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -976,8 +976,16 @@
mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
hotseatKeepClearArea);
onDisplayRotationChangedNotInPip(mContext, launcherRotation);
+ // cache current min/max size
+ Point minSize = mPipBoundsState.getMinSize();
+ Point maxSize = mPipBoundsState.getMaxSize();
+ mPipBoundsState.updateMinMaxSize(pictureInPictureParams.getAspectRatioFloat());
final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams);
+ // restore min/max size, as this is referenced later in OnDisplayChangingListener and needs
+ // to reflect the pre-rotation state for it to work
+ mPipBoundsState.setMinSize(minSize.x, minSize.y);
+ mPipBoundsState.setMaxSize(maxSize.x, maxSize.y);
// sync mPipBoundsState with the newly calculated bounds.
mPipBoundsState.setNormalBounds(entryBounds);
return entryBounds;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index e7dd31c..c1adfff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -467,17 +467,11 @@
}
private void updatePinchResizeSizeConstraints(float aspectRatio) {
- final int minWidth, minHeight, maxWidth, maxHeight;
-
- minWidth = mSizeSpecSource.getMinSize(aspectRatio).getWidth();
- minHeight = mSizeSpecSource.getMinSize(aspectRatio).getHeight();
- maxWidth = mSizeSpecSource.getMaxSize(aspectRatio).getWidth();
- maxHeight = mSizeSpecSource.getMaxSize(aspectRatio).getHeight();
-
- mPipResizeGestureHandler.updateMinSize(minWidth, minHeight);
- mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight);
- mPipBoundsState.setMaxSize(maxWidth, maxHeight);
- mPipBoundsState.setMinSize(minWidth, minHeight);
+ mPipBoundsState.updateMinMaxSize(aspectRatio);
+ mPipResizeGestureHandler.updateMinSize(mPipBoundsState.getMinSize().x,
+ mPipBoundsState.getMinSize().y);
+ mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
}
/**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/assist/data/repository/AssistRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/assist/data/repository/AssistRepositoryTest.kt
new file mode 100644
index 0000000..80077a21
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/assist/data/repository/AssistRepositoryTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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.assist.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AssistRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val underTest = kosmos.assistRepository
+
+ @Test
+ fun invocationType() =
+ testScope.runTest {
+ val invocationType by collectLastValue(underTest.latestInvocationType)
+ underTest.setLatestInvocationType(2)
+ runCurrent()
+
+ assertThat(invocationType).isEqualTo(2)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/assist/domain/interactor/AssistInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/assist/domain/interactor/AssistInteractorTest.kt
new file mode 100644
index 0000000..c12f1ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/assist/domain/interactor/AssistInteractorTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 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.assist.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AssistInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val underTest = kosmos.assistInteractor
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONTEXTUAL_TIPS, Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_POWER_OFF)
+ fun onAssistantStarted() =
+ testScope.runTest {
+ val invocationType by collectLastValue(underTest.latestInvocationType)
+ underTest.onAssistantStarted(3)
+ runCurrent()
+
+ assertThat(invocationType).isEqualTo(3)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 74ea58c..d30f33f 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -31,6 +31,7 @@
import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.assist.domain.interactor.AssistInteractor;
import com.android.systemui.assist.ui.DefaultUiController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -149,6 +150,7 @@
private final SecureSettings mSecureSettings;
private final SelectedUserInteractor mSelectedUserInteractor;
private final ActivityManager mActivityManager;
+ private final AssistInteractor mInteractor;
private final DeviceProvisionedController mDeviceProvisionedController;
@@ -192,7 +194,8 @@
DisplayTracker displayTracker,
SecureSettings secureSettings,
SelectedUserInteractor selectedUserInteractor,
- ActivityManager activityManager) {
+ ActivityManager activityManager,
+ AssistInteractor interactor) {
mContext = context;
mDeviceProvisionedController = controller;
mCommandQueue = commandQueue;
@@ -206,6 +209,7 @@
mSecureSettings = secureSettings;
mSelectedUserInteractor = selectedUserInteractor;
mActivityManager = activityManager;
+ mInteractor = interactor;
registerVoiceInteractionSessionListener();
registerVisualQueryRecognitionStatusListener();
@@ -314,6 +318,7 @@
assistComponent,
legacyDeviceState);
logStartAssistLegacy(legacyInvocationType, legacyDeviceState);
+ mInteractor.onAssistantStarted(legacyInvocationType);
startAssistInternal(args, assistComponent, isService);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/data/repository/AssistRepository.kt b/packages/SystemUI/src/com/android/systemui/assist/data/repository/AssistRepository.kt
new file mode 100644
index 0000000..c416c24
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/data/repository/AssistRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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.assist.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+@SysUISingleton
+class AssistRepository @Inject constructor() {
+ private val _latestInvocationType =
+ MutableSharedFlow<Int>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ /** The type of the latest invocation of the assistant. */
+ val latestInvocationType: SharedFlow<Int> = _latestInvocationType.asSharedFlow()
+
+ /** Sets the type of the latest invocation of the assistant. */
+ fun setLatestInvocationType(type: Int) {
+ _latestInvocationType.tryEmit(type)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/domain/interactor/AssistInteractor.kt b/packages/SystemUI/src/com/android/systemui/assist/domain/interactor/AssistInteractor.kt
new file mode 100644
index 0000000..d9e46aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/domain/interactor/AssistInteractor.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.assist.domain.interactor
+
+import com.android.systemui.Flags
+import com.android.systemui.assist.data.repository.AssistRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.SharedFlow
+
+@SysUISingleton
+class AssistInteractor
+@Inject
+constructor(
+ private val repository: AssistRepository,
+) {
+ /** The type of the latest invocation of the assistant. */
+ val latestInvocationType: SharedFlow<Int> = repository.latestInvocationType
+
+ /** Notifies that Assistant has been started. */
+ fun onAssistantStarted(type: Int) {
+ if (Flags.enableContextualTips() && Flags.enableContextualTipForPowerOff()) {
+ repository.setLatestInvocationType(type)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 4e940f1..840b309 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -18,7 +18,7 @@
import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
-import static com.android.systemui.Flags.legacyLeAudioSharing;
+import static com.android.settingslib.flags.Flags.legacyLeAudioSharing;
import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
import android.animation.Animator;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 8e0191e..1e31755 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -16,6 +16,8 @@
package com.android.systemui.media.dialog;
+import static com.android.settingslib.flags.Flags.legacyLeAudioSharing;
+
import android.app.AlertDialog;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastAssistant;
@@ -492,6 +494,7 @@
@Override
public boolean isBroadcastSupported() {
+ if (!legacyLeAudioSharing()) return false;
boolean isBluetoothLeDevice = false;
if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index c379d0e..eb6a320 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -16,7 +16,7 @@
package com.android.systemui.media.dialog;
-import static com.android.systemui.Flags.legacyLeAudioSharing;
+import static com.android.settingslib.flags.Flags.legacyLeAudioSharing;
import android.content.Context;
import android.os.Bundle;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index 1002cc3..38d31ed 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.dialog
+import com.android.settingslib.flags.Flags.legacyLeAudioSharing
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -44,6 +45,7 @@
mediaOutputDialogFactory.createDialogForSystemRouting()
}
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG -> {
+ if (!legacyLeAudioSharing()) return
val packageName: String? =
intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME)
launchMediaOutputBroadcastDialogIfPossible(packageName)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 2e7829d..2f92afa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -1247,7 +1247,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(com.android.settingslib.flags.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
fun bindBroadcastButton() {
initMediaViewHolderMocks()
initDeviceMediaData(true, APP_NAME)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index e2cf87a..0879884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -28,6 +28,7 @@
import androidx.test.filters.SmallTest;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.media.MediaOutputConstants;
import com.android.systemui.SysuiTestCase;
@@ -84,7 +85,20 @@
}
@Test
+ public void launchMediaOutputBroadcastDialog_flagOff_broadcastDialogFactoryNotCalled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
+ Intent intent = new Intent(
+ MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
+ intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ }
+
+ @Test
public void launchMediaOutputBroadcastDialog_ExtraPackageName_BroadcastDialogFactoryCalled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
Intent intent = new Intent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
@@ -97,6 +111,7 @@
@Test
public void launchMediaOutputBroadcastDialog_WrongExtraKey_DialogBroadcastFactoryNotCalled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
Intent intent = new Intent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
@@ -108,6 +123,7 @@
@Test
public void launchMediaOutputBroadcastDialog_NoExtra_BroadcastDialogFactoryNotCalled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
Intent intent = new Intent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index d9ddc8e..84300da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -50,6 +50,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.SysuiTestCase;
@@ -177,7 +178,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getStopButtonVisibility_remoteBLEDevice_returnVisible() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -189,7 +190,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getStopButtonVisibility_remoteNonBLEDevice_returnGone() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -210,7 +211,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOnAndConnectBleDevice_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -223,7 +224,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOnAndNoBleDevice_returnsFalse() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -236,7 +237,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_notSupportBroadcastAndflagOn_returnsFalse() {
FeatureFlagUtils.setEnabled(mContext,
FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
@@ -245,7 +246,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOffAndConnectToBleDevice_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -258,7 +259,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOffAndNoBleDevice_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -271,7 +272,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_noBleDeviceAndEnabledBroadcast_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -284,7 +285,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_noBleDeviceAndDisabledBroadcast_returnsFalse() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -297,7 +298,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getBroadcastIconVisibility_isBroadcasting_returnVisible() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -309,7 +310,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getBroadcastIconVisibility_noBroadcasting_returnGone() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -321,7 +322,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getBroadcastIconVisibility_remoteNonLeDevice_returnGone() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -374,7 +375,7 @@
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getStopButtonText_supportsBroadcast_returnsBroadcastText() {
String stopText = mContext.getText(R.string.media_output_broadcast).toString();
MediaDevice mMediaDevice = mock(MediaDevice.class);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/assist/data/repository/AssistRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/data/repository/AssistRepositoryKosmos.kt
new file mode 100644
index 0000000..96155ed
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/data/repository/AssistRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.assist.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.assistRepository by Kosmos.Fixture { AssistRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/assist/domain/interactor/AssistInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/domain/interactor/AssistInteractorKosmos.kt
new file mode 100644
index 0000000..c3c1131
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/domain/interactor/AssistInteractorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.assist.domain.interactor
+
+import com.android.systemui.assist.data.repository.assistRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.assistInteractor by Kosmos.Fixture { AssistInteractor(assistRepository) }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index c4341b9..c7b844b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -193,6 +193,7 @@
import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.PrintWriter;
+import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -2802,9 +2803,10 @@
final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId(
authenticationId);
+ Dataset dataset = null;
// Authenticated a dataset - reset view state regardless if we got a response or a dataset
if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
- final Dataset dataset = authenticatedResponse.getDatasets().get(datasetIdx);
+ dataset = authenticatedResponse.getDatasets().get(datasetIdx);
if (dataset == null) {
Slog.w(TAG, "no dataset with index " + datasetIdx + " on fill response");
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
@@ -2819,12 +2821,28 @@
mSessionFlags.mExpiredResponse = false;
final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
+ final Serializable exception = data.getSerializable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
if (sDebug) {
Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result
+ ", clientState=" + newClientState + ", authenticationId=" + authenticationId);
}
+ if (Flags.autofillCredmanDevIntegration() && exception != null
+ && exception instanceof GetCredentialException) {
+ if (dataset != null && dataset.getFieldIds().size() == 1) {
+ if (sDebug) {
+ Slog.d(TAG, "setAuthenticationResultLocked(): result returns with"
+ + "Credential Manager Exception");
+ }
+ AutofillId autofillId = dataset.getFieldIds().get(0);
+ sendCredentialManagerResponseToApp(/*response=*/ null,
+ (GetCredentialException) exception, autofillId);
+ }
+ return;
+ }
+
if (result instanceof FillResponse) {
if (sDebug) {
Slog.d(TAG, "setAuthenticationResultLocked(): received FillResponse from"
@@ -2840,13 +2858,21 @@
}
if (Flags.autofillCredmanDevIntegration()) {
GetCredentialResponse response = (GetCredentialResponse) result;
- sendCredentialManagerResponseToApp(response,
- /*exception=*/ null, response.getAutofillId());
+ if (dataset != null && dataset.getFieldIds().size() == 1) {
+ AutofillId autofillId = dataset.getFieldIds().get(0);
+ if (sDebug) {
+ Slog.d(TAG, "Received GetCredentialResponse from authentication flow,"
+ + "for autofillId: " + autofillId);
+ }
+ sendCredentialManagerResponseToApp(response,
+ /*exception=*/ null, autofillId);
+ }
} else if (Flags.autofillCredmanIntegration()) {
- Dataset dataset = getDatasetFromCredentialResponse(
+ Dataset datasetFromCredentialResponse = getDatasetFromCredentialResponse(
(GetCredentialResponse) result);
- if (dataset != null) {
- autoFill(requestId, datasetIdx, dataset, false, UI_TYPE_UNKNOWN);
+ if (datasetFromCredentialResponse != null) {
+ autoFill(requestId, datasetIdx, datasetFromCredentialResponse,
+ false, UI_TYPE_UNKNOWN);
}
}
} else if (result instanceof Dataset) {
@@ -2863,12 +2889,12 @@
if (sDebug) Slog.d(TAG, "Updating client state from auth dataset");
mClientState = newClientState;
}
- Dataset dataset = getEffectiveDatasetForAuthentication((Dataset) result);
+ Dataset datasetFromResult = getEffectiveDatasetForAuthentication((Dataset) result);
final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
if (!isAuthResultDatasetEphemeral(oldDataset, data)) {
- authenticatedResponse.getDatasets().set(datasetIdx, dataset);
+ authenticatedResponse.getDatasets().set(datasetIdx, datasetFromResult);
}
- autoFill(requestId, datasetIdx, dataset, false, UI_TYPE_UNKNOWN);
+ autoFill(requestId, datasetIdx, datasetFromResult, false, UI_TYPE_UNKNOWN);
} else {
Slog.w(TAG, "invalid index (" + datasetIdx + ") for authentication id "
+ authenticationId);
@@ -5052,12 +5078,16 @@
}
private void addCredentialManagerCallbackForDataset(Dataset dataset, int requestId) {
+ AutofillId autofillId = null;
+ if (dataset != null && dataset.getFieldIds().size() == 1) {
+ autofillId = dataset.getFieldIds().get(0);
+ }
+ final AutofillId finalAutofillId = autofillId;
final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
Slog.d(TAG, "onReceiveResult from Credential Manager bottom sheet");
- boolean isCredmanCallbackInvoked = false;
GetCredentialResponse getCredentialResponse =
resultData.getParcelable(
CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
@@ -5065,7 +5095,7 @@
if (Flags.autofillCredmanDevIntegration()) {
sendCredentialManagerResponseToApp(getCredentialResponse,
- /*exception=*/ null, getCredentialResponse.getAutofillId());
+ /*exception=*/ null, finalAutofillId);
} else {
Dataset datasetFromCredential = getDatasetFromCredentialResponse(
getCredentialResponse);
@@ -5082,7 +5112,9 @@
Slog.w(TAG, "Credman bottom sheet from pinned "
+ "entry failed with: + " + exception[0] + " , "
+ exception[1]);
- // TODO(b/326313420): Propagate exception
+ sendCredentialManagerResponseToApp(/*response=*/ null,
+ new GetCredentialException(exception[0], exception[1]),
+ finalAutofillId);
}
} else {
Slog.d(TAG, "Unknown resultCode from credential "
@@ -6380,7 +6412,8 @@
}
}
if (exception != null) {
- // TODO(b/326313420): Add Exception support
+ mClient.onGetCredentialException(id, viewId, exception.getType(),
+ exception.getMessage());
} else if (response != null) {
mClient.onGetCredentialResponse(id, viewId, response);
} else {
diff --git a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
new file mode 100644
index 0000000..de6382e
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+
+import static com.android.internal.util.CollectionUtils.any;
+import static com.android.server.companion.MetricUtils.logRemoveAssociation;
+import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
+import static com.android.server.companion.CompanionDeviceManagerService.PerUserAssociationSet;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class response for Association removal.
+ */
+@SuppressLint("LongLogTag")
+public class AssociationRevokeProcessor {
+
+ private static final String TAG = "CDM_AssociationRevokeProcessor";
+ private static final boolean DEBUG = false;
+ private final @NonNull Context mContext;
+ private final @NonNull CompanionDeviceManagerService mService;
+ private final @NonNull AssociationStoreImpl mAssociationStore;
+ private final @NonNull PackageManagerInternal mPackageManagerInternal;
+ private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private final @NonNull SystemDataTransferRequestStore mSystemDataTransferRequestStore;
+ private final @NonNull CompanionApplicationController mCompanionAppController;
+ private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
+ private final ActivityManager mActivityManager;
+
+ /**
+ * A structure that consists of a set of revoked associations that pending for role holder
+ * removal per each user.
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #addToPendingRoleHolderRemoval(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
+ */
+ @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
+ private final PerUserAssociationSet mRevokedAssociationsPendingRoleHolderRemoval =
+ new PerUserAssociationSet();
+ /**
+ * Contains uid-s of packages pending to be removed from the role holder list (after
+ * revocation of an association), which will happen one the package is no longer visible to the
+ * user.
+ * For quicker uid -> (userId, packageName) look-up this is not a {@code Set<Integer>} but
+ * a {@code Map<Integer, String>} which maps uid-s to packageName-s (userId-s can be derived
+ * from uid-s using {@link UserHandle#getUserId(int)}).
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #addToPendingRoleHolderRemoval(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ */
+ @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
+ private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>();
+
+ AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service,
+ @NonNull AssociationStoreImpl associationStore,
+ @NonNull PackageManagerInternal packageManager,
+ @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
+ @NonNull CompanionApplicationController applicationController,
+ @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore) {
+ mService = service;
+ mContext = service.getContext();
+ mActivityManager = mContext.getSystemService(ActivityManager.class);
+ mAssociationStore = associationStore;
+ mPackageManagerInternal = packageManager;
+ mOnPackageVisibilityChangeListener =
+ new OnPackageVisibilityChangeListener(mActivityManager);
+ mDevicePresenceMonitor = devicePresenceMonitor;
+ mCompanionAppController = applicationController;
+ mSystemDataTransferRequestStore = systemDataTransferRequestStore;
+ }
+
+ // TODO: also revoke notification access
+ void disassociateInternal(int associationId) {
+ final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ final String deviceProfile = association.getDeviceProfile();
+
+ if (!maybeRemoveRoleHolderForAssociation(association)) {
+ // Need to remove the app from list of the role holders, but will have to do it later
+ // (the app is in foreground at the moment).
+ addToPendingRoleHolderRemoval(association);
+ }
+
+ // Need to check if device still present now because CompanionDevicePresenceMonitor will
+ // remove current connected device after mAssociationStore.removeAssociation
+ final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId);
+
+ // Removing the association.
+ mAssociationStore.removeAssociation(associationId);
+ // Do not need to persistUserState since CompanionDeviceManagerService will get callback
+ // from #onAssociationChanged, and it will handle the persistUserState which including
+ // active and revoked association.
+ logRemoveAssociation(deviceProfile);
+
+ // Remove all the system data transfer requests for the association.
+ mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
+
+ if (!wasPresent || !association.isNotifyOnDeviceNearby()) return;
+ // The device was connected and the app was notified: check if we need to unbind the app
+ // now.
+ final boolean shouldStayBound = any(
+ mAssociationStore.getAssociationsForPackage(userId, packageName),
+ it -> it.isNotifyOnDeviceNearby()
+ && mDevicePresenceMonitor.isDevicePresent(it.getId()));
+ if (shouldStayBound) return;
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ }
+
+ /**
+ * First, checks if the companion application should be removed from the list role holders when
+ * upon association's removal, i.e.: association's profile (matches the role) is not null,
+ * the application does not have other associations with the same profile, etc.
+ *
+ * <p>
+ * Then, if establishes that the application indeed has to be removed from the list of the role
+ * holders, checks if it could be done right now -
+ * {@link android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, java.util.concurrent.Executor, java.util.function.Consumer) RoleManager#removeRoleHolderAsUser()}
+ * will kill the application's process, which leads poor user experience if the application was
+ * in foreground when this happened, to avoid this CDMS delays invoking
+ * {@code RoleManager.removeRoleHolderAsUser()} until the app is no longer in foreground.
+ *
+ * @return {@code true} if the application does NOT need be removed from the list of the role
+ * holders OR if the application was successfully removed from the list of role holders.
+ * I.e.: from the role-management perspective the association is done with.
+ * {@code false} if the application needs to be removed from the list of role the role
+ * holders, BUT it CDMS would prefer to do it later.
+ * I.e.: application is in the foreground at the moment, but invoking
+ * {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process,
+ * which would lead to the poor UX, hence need to try later.
+ */
+ boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
+ if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association);
+ final String deviceProfile = association.getDeviceProfile();
+
+ if (deviceProfile == null) {
+ // No role was granted to for this association, there is nothing else we need to here.
+ return true;
+ }
+ // Do not need to remove the system role since it was pre-granted by the system.
+ if (deviceProfile.equals(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) {
+ return true;
+ }
+
+ // Check if the applications is associated with another devices with the profile. If so,
+ // it should remain the role holder.
+ final int id = association.getId();
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ final boolean roleStillInUse = any(
+ mAssociationStore.getAssociationsForPackage(userId, packageName),
+ it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId());
+ if (roleStillInUse) {
+ // Application should remain a role holder, there is nothing else we need to here.
+ return true;
+ }
+
+ final int packageProcessImportance = getPackageProcessImportance(userId, packageName);
+ if (packageProcessImportance <= IMPORTANCE_VISIBLE) {
+ // Need to remove the app from the list of role holders, but the process is visible to
+ // the user at the moment, so we'll need to it later: log and return false.
+ Slog.i(TAG, "Cannot remove role holder for the removed association id=" + id
+ + " now - process is visible.");
+ return false;
+ }
+
+ removeRoleHolderForAssociation(mContext, association);
+ return true;
+ }
+
+ @SuppressLint("MissingPermission")
+ private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
+ return Binder.withCleanCallingIdentity(() -> {
+ final int uid =
+ mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+ return mActivityManager.getUidImportance(uid);
+ });
+ }
+
+ /**
+ * Set revoked flag for active association and add the revoked association and the uid into
+ * the caches.
+ *
+ * @see #mRevokedAssociationsPendingRoleHolderRemoval
+ * @see #mUidsPendingRoleHolderRemoval
+ * @see OnPackageVisibilityChangeListener
+ */
+ void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ // First: set revoked flag
+ association = (new AssociationInfo.Builder(association)).setRevoked(true).build();
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+ // Second: add to the set.
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ mRevokedAssociationsPendingRoleHolderRemoval.forUser(association.getUserId())
+ .add(association);
+ if (!mUidsPendingRoleHolderRemoval.containsKey(uid)) {
+ mUidsPendingRoleHolderRemoval.put(uid, packageName);
+
+ if (mUidsPendingRoleHolderRemoval.size() == 1) {
+ // Just added first uid: start the listener
+ mOnPackageVisibilityChangeListener.startListening();
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove the revoked association from the cache and also remove the uid from the map if
+ * there are other associations with the same package still pending for role holder removal.
+ *
+ * @see #mRevokedAssociationsPendingRoleHolderRemoval
+ * @see #mUidsPendingRoleHolderRemoval
+ * @see OnPackageVisibilityChangeListener
+ */
+ private void removeFromPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */ 0, userId);
+
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)
+ .remove(association);
+
+ final boolean shouldKeepUidForRemoval = any(
+ getPendingRoleHolderRemovalAssociationsForUser(userId),
+ ai -> packageName.equals(ai.getPackageName()));
+ // Do not remove the uid from the map since other associations with
+ // the same packageName still pending for role holder removal.
+ if (!shouldKeepUidForRemoval) {
+ mUidsPendingRoleHolderRemoval.remove(uid);
+ }
+
+ if (mUidsPendingRoleHolderRemoval.isEmpty()) {
+ // The set is empty now - can "turn off" the listener.
+ mOnPackageVisibilityChangeListener.stopListening();
+ }
+ }
+ }
+
+ /**
+ * @return a copy of the revoked associations set (safeguarding against
+ * {@code ConcurrentModificationException}-s).
+ */
+ @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
+ @UserIdInt int userId) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ // Return a copy.
+ return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
+ }
+ }
+
+ private String getPackageNameByUid(int uid) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ return mUidsPendingRoleHolderRemoval.get(uid);
+ }
+ }
+
+ /**
+ * An OnUidImportanceListener class which watches the importance of the packages.
+ * In this class, we ONLY interested in the importance of the running process is greater than
+ * {@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_VISIBLE} for the uids have been added
+ * into the {@link #mUidsPendingRoleHolderRemoval}. Lastly remove the role holder for the
+ * revoked associations for the same packages.
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
+ */
+ private class OnPackageVisibilityChangeListener implements
+ ActivityManager.OnUidImportanceListener {
+ final @NonNull ActivityManager mAm;
+
+ OnPackageVisibilityChangeListener(@NonNull ActivityManager am) {
+ this.mAm = am;
+ }
+
+ @SuppressLint("MissingPermission")
+ void startListening() {
+ Binder.withCleanCallingIdentity(
+ () -> mAm.addOnUidImportanceListener(
+ /* listener */ OnPackageVisibilityChangeListener.this,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE));
+ }
+
+ @SuppressLint("MissingPermission")
+ void stopListening() {
+ Binder.withCleanCallingIdentity(
+ () -> mAm.removeOnUidImportanceListener(
+ /* listener */ OnPackageVisibilityChangeListener.this));
+ }
+
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
+ // The lower the importance value the more "important" the process is.
+ // We are only interested when the process ceases to be visible.
+ return;
+ }
+
+ final String packageName = getPackageNameByUid(uid);
+ if (packageName == null) {
+ // Not interested in this uid.
+ return;
+ }
+
+ final int userId = UserHandle.getUserId(uid);
+
+ boolean needToPersistStateForUser = false;
+
+ for (AssociationInfo association :
+ getPendingRoleHolderRemovalAssociationsForUser(userId)) {
+ if (!packageName.equals(association.getPackageName())) continue;
+
+ if (!maybeRemoveRoleHolderForAssociation(association)) {
+ // Did not remove the role holder, will have to try again later.
+ continue;
+ }
+
+ removeFromPendingRoleHolderRemoval(association);
+ needToPersistStateForUser = true;
+ }
+
+ if (needToPersistStateForUser) {
+ mService.postPersistUserState(userId);
+ }
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index ba1f51b..09c7793 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -22,7 +22,6 @@
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
@@ -39,7 +38,6 @@
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
-import static com.android.server.companion.MetricUtils.logRemoveAssociation;
import static com.android.server.companion.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.PackageUtils.getPackageInfo;
@@ -49,7 +47,6 @@
import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
-import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS;
@@ -61,7 +58,6 @@
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.NotificationManager;
@@ -149,7 +145,7 @@
static final String TAG = "CDM_CompanionDeviceManagerService";
static final boolean DEBUG = false;
- /** Range of Association IDs allocated for a user.*/
+ /** Range of Association IDs allocated for a user. */
private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
@@ -162,8 +158,6 @@
private static final int MAX_CN_LENGTH = 500;
private final ActivityManager mActivityManager;
- private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
-
private PersistentDataStore mPersistentStore;
private final PersistUserStateHandler mUserPersistenceHandler;
@@ -175,6 +169,7 @@
private CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private CompanionApplicationController mCompanionAppController;
private CompanionTransportManager mTransportManager;
+ private AssociationRevokeProcessor mAssociationRevokeProcessor;
private final ActivityTaskManagerInternal mAtmInternal;
private final ActivityManagerInternal mAmInternal;
@@ -193,33 +188,6 @@
@GuardedBy("mPreviouslyUsedIds")
private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>();
- /**
- * A structure that consists of a set of revoked associations that pending for role holder
- * removal per each user.
- *
- * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
- * @see #addToPendingRoleHolderRemoval(AssociationInfo)
- * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
- * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
- */
- @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
- private final PerUserAssociationSet mRevokedAssociationsPendingRoleHolderRemoval =
- new PerUserAssociationSet();
- /**
- * Contains uid-s of packages pending to be removed from the role holder list (after
- * revocation of an association), which will happen one the package is no longer visible to the
- * user.
- * For quicker uid -> (userId, packageName) look-up this is not a {@code Set<Integer>} but
- * a {@code Map<Integer, String>} which maps uid-s to packageName-s (userId-s can be derived
- * from uid-s using {@link UserHandle#getUserId(int)}).
- *
- * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
- * @see #addToPendingRoleHolderRemoval(AssociationInfo)
- * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
- */
- @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
- private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>();
-
private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
new RemoteCallbackList<>();
@@ -243,8 +211,6 @@
mAssociationStore = new AssociationStoreImpl();
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
- mOnPackageVisibilityChangeListener =
- new OnPackageVisibilityChangeListener(mActivityManager);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mObservableUuidStore = new ObservableUuidStore();
}
@@ -276,6 +242,9 @@
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
+ mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
+ mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
+ mSystemDataTransferRequestStore);
// TODO(b/279663946): move context sync to a dedicated system service
mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
@@ -307,12 +276,13 @@
mBackupRestoreProcessor.addToPendingAppInstall(association);
} else if (!association.isRevoked()) {
activeAssociations.add(association);
- } else if (maybeRemoveRoleHolderForAssociation(association)) {
+ } else if (mAssociationRevokeProcessor.maybeRemoveRoleHolderForAssociation(
+ association)) {
// Nothing more to do here, but we'll need to persist all the associations to the
// disk afterwards.
usersToPersistStateFor.add(association.getUserId());
} else {
- addToPendingRoleHolderRemoval(association);
+ mAssociationRevokeProcessor.addToPendingRoleHolderRemoval(association);
}
}
@@ -374,7 +344,7 @@
final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
- for (AssociationInfo ai:
+ for (AssociationInfo ai :
mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) {
Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId());
@@ -495,7 +465,7 @@
final String packageName = uuid.getPackageName();
final int userId = uuid.getUserId();
- switch(event) {
+ switch (event) {
case EVENT_BT_CONNECTED:
if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
mCompanionAppController.bindCompanionApplication(
@@ -544,8 +514,8 @@
/**
* @return whether the package should be bound (i.e. at least one of the devices associated with
- * the package is currently present OR the UUID to be observed by this package is
- * currently present).
+ * the package is currently present OR the UUID to be observed by this package is
+ * currently present).
*/
private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
final List<AssociationInfo> packageAssociations =
@@ -599,7 +569,8 @@
allAssociations = new ArrayList<>(
mAssociationStore.getAssociationsForUser(userId));
// ... and add the revoked (removed) association, that are yet to be permanently removed.
- allAssociations.addAll(getPendingRoleHolderRemovalAssociationsForUser(userId));
+ allAssociations.addAll(
+ mAssociationRevokeProcessor.getPendingRoleHolderRemovalAssociationsForUser(userId));
// ... and add the restored associations that are pending missing package installation.
allAssociations.addAll(mBackupRestoreProcessor
.getAssociationsPendingAppInstallForUser(userId));
@@ -654,7 +625,7 @@
}
// Clear role holders
for (AssociationInfo association : associationsForPackage) {
- maybeRemoveRoleHolderForAssociation(association);
+ mAssociationRevokeProcessor.maybeRemoveRoleHolderForAssociation(association);
}
// Clear the uuids to be observed.
for (ObservableUuid uuid : uuidsTobeObserved) {
@@ -712,7 +683,7 @@
final int id = association.getId();
Slog.i(TAG, "Removing inactive self-managed association id=" + id);
- disassociateInternal(id);
+ mAssociationRevokeProcessor.disassociateInternal(id);
}
}
@@ -857,7 +828,7 @@
final AssociationInfo association =
getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
- disassociateInternal(association.getId());
+ mAssociationRevokeProcessor.disassociateInternal(association.getId());
}
@Override
@@ -866,7 +837,7 @@
final AssociationInfo association =
getAssociationWithCallerChecks(associationId);
- disassociateInternal(association.getId());
+ mAssociationRevokeProcessor.disassociateInternal(association.getId());
}
@Override
@@ -902,9 +873,9 @@
}
/**
- * @deprecated Use
- * {@link NotificationManager#isNotificationListenerAccessGranted(ComponentName)} instead.
- */
+ * @deprecated Use
+ * {@link NotificationManager#isNotificationListenerAccessGranted(ComponentName)} instead.
+ */
@Deprecated
@Override
public boolean hasNotificationAccess(ComponentName component) throws RemoteException {
@@ -1300,7 +1271,7 @@
return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this,
mAssociationStore, mDevicePresenceMonitor, mTransportManager,
mSystemDataTransferProcessor, mAssociationRequestsProcessor,
- mBackupRestoreProcessor)
+ mBackupRestoreProcessor, mAssociationRevokeProcessor)
.exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
err.getFileDescriptor(), args);
}
@@ -1381,7 +1352,7 @@
// another association by the time when it is activated from the package installation.
final Set<AssociationInfo> pendingAssociations = mBackupRestoreProcessor
.getAssociationsPendingAppInstallForUser(userId);
- for (AssociationInfo it: pendingAssociations) {
+ for (AssociationInfo it : pendingAssociations) {
usedIds.put(it.getId(), true);
}
@@ -1407,198 +1378,6 @@
}
}
- // TODO: also revoke notification access
- void disassociateInternal(int associationId) {
- final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
- final String deviceProfile = association.getDeviceProfile();
-
- if (!maybeRemoveRoleHolderForAssociation(association)) {
- // Need to remove the app from list of the role holders, but will have to do it later
- // (the app is in foreground at the moment).
- addToPendingRoleHolderRemoval(association);
- }
-
- // Need to check if device still present now because CompanionDevicePresenceMonitor will
- // remove current connected device after mAssociationStore.removeAssociation
- final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId);
-
- // Removing the association.
- mAssociationStore.removeAssociation(associationId);
- // Do not need to persistUserState since CompanionDeviceManagerService will get callback
- // from #onAssociationChanged, and it will handle the persistUserState which including
- // active and revoked association.
- logRemoveAssociation(deviceProfile);
-
- // Remove all the system data transfer requests for the association.
- mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
-
- if (!wasPresent || !association.isNotifyOnDeviceNearby()) return;
- // The device was connected and the app was notified: check if we need to unbind the app
- // now.
- final boolean shouldStayBound = any(
- mAssociationStore.getAssociationsForPackage(userId, packageName),
- it -> it.isNotifyOnDeviceNearby()
- && mDevicePresenceMonitor.isDevicePresent(it.getId()));
- if (shouldStayBound) return;
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
- }
-
- /**
- * First, checks if the companion application should be removed from the list role holders when
- * upon association's removal, i.e.: association's profile (matches the role) is not null,
- * the application does not have other associations with the same profile, etc.
- *
- * <p>
- * Then, if establishes that the application indeed has to be removed from the list of the role
- * holders, checks if it could be done right now -
- * {@link android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, java.util.concurrent.Executor, java.util.function.Consumer) RoleManager#removeRoleHolderAsUser()}
- * will kill the application's process, which leads poor user experience if the application was
- * in foreground when this happened, to avoid this CDMS delays invoking
- * {@code RoleManager.removeRoleHolderAsUser()} until the app is no longer in foreground.
- *
- * @return {@code true} if the application does NOT need be removed from the list of the role
- * holders OR if the application was successfully removed from the list of role holders.
- * I.e.: from the role-management perspective the association is done with.
- * {@code false} if the application needs to be removed from the list of role the role
- * holders, BUT it CDMS would prefer to do it later.
- * I.e.: application is in the foreground at the moment, but invoking
- * {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process,
- * which would lead to the poor UX, hence need to try later.
- */
-
- private boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
- if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association);
-
- final String deviceProfile = association.getDeviceProfile();
- if (deviceProfile == null) {
- // No role was granted to for this association, there is nothing else we need to here.
- return true;
- }
- // Do not need to remove the system role since it was pre-granted by the system.
- if (deviceProfile.equals(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) {
- return true;
- }
-
- // Check if the applications is associated with another devices with the profile. If so,
- // it should remain the role holder.
- final int id = association.getId();
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
- final boolean roleStillInUse = any(
- mAssociationStore.getAssociationsForPackage(userId, packageName),
- it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId());
- if (roleStillInUse) {
- // Application should remain a role holder, there is nothing else we need to here.
- return true;
- }
-
- final int packageProcessImportance = getPackageProcessImportance(userId, packageName);
- if (packageProcessImportance <= IMPORTANCE_VISIBLE) {
- // Need to remove the app from the list of role holders, but the process is visible to
- // the user at the moment, so we'll need to it later: log and return false.
- Slog.i(TAG, "Cannot remove role holder for the removed association id=" + id
- + " now - process is visible.");
- return false;
- }
-
- removeRoleHolderForAssociation(getContext(), association);
- return true;
- }
-
- private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
- return Binder.withCleanCallingIdentity(() -> {
- final int uid =
- mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
- return mActivityManager.getUidImportance(uid);
- });
- }
-
- /**
- * Set revoked flag for active association and add the revoked association and the uid into
- * the caches.
- *
- * @see #mRevokedAssociationsPendingRoleHolderRemoval
- * @see #mUidsPendingRoleHolderRemoval
- * @see OnPackageVisibilityChangeListener
- */
- private void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
- // First: set revoked flag.
- association = (new AssociationInfo.Builder(association))
- .setRevoked(true)
- .build();
-
- final String packageName = association.getPackageName();
- final int userId = association.getUserId();
- final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
-
- // Second: add to the set.
- synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
- mRevokedAssociationsPendingRoleHolderRemoval.forUser(association.getUserId())
- .add(association);
- if (!mUidsPendingRoleHolderRemoval.containsKey(uid)) {
- mUidsPendingRoleHolderRemoval.put(uid, packageName);
-
- if (mUidsPendingRoleHolderRemoval.size() == 1) {
- // Just added first uid: start the listener
- mOnPackageVisibilityChangeListener.startListening();
- }
- }
- }
- }
-
- /**
- * Remove the revoked association from the cache and also remove the uid from the map if
- * there are other associations with the same package still pending for role holder removal.
- *
- * @see #mRevokedAssociationsPendingRoleHolderRemoval
- * @see #mUidsPendingRoleHolderRemoval
- * @see OnPackageVisibilityChangeListener
- */
- private void removeFromPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
- final String packageName = association.getPackageName();
- final int userId = association.getUserId();
- final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
-
- synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
- mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)
- .remove(association);
-
- final boolean shouldKeepUidForRemoval = any(
- getPendingRoleHolderRemovalAssociationsForUser(userId),
- ai -> packageName.equals(ai.getPackageName()));
- // Do not remove the uid from the map since other associations with
- // the same packageName still pending for role holder removal.
- if (!shouldKeepUidForRemoval) {
- mUidsPendingRoleHolderRemoval.remove(uid);
- }
-
- if (mUidsPendingRoleHolderRemoval.isEmpty()) {
- // The set is empty now - can "turn off" the listener.
- mOnPackageVisibilityChangeListener.stopListening();
- }
- }
- }
-
- /**
- * @return a copy of the revoked associations set (safeguarding against
- * {@code ConcurrentModificationException}-s).
- */
- private @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
- @UserIdInt int userId) {
- synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
- // Return a copy.
- return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
- }
- }
-
- private String getPackageNameByUid(int uid) {
- synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
- return mUidsPendingRoleHolderRemoval.get(uid);
- }
- }
-
void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
final PackageInfo packageInfo =
getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
@@ -1704,11 +1483,11 @@
private final AssociationStore.OnChangeListener mAssociationStoreChangeListener =
new AssociationStore.OnChangeListener() {
- @Override
- public void onAssociationChanged(int changeType, AssociationInfo association) {
- onAssociationChangedInternal(changeType, association);
- }
- };
+ @Override
+ public void onAssociationChanged(int changeType, AssociationInfo association) {
+ onAssociationChangedInternal(changeType, association);
+ }
+ };
private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback =
new CompanionDevicePresenceMonitor.Callback() {
@@ -1731,7 +1510,7 @@
public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
onDevicePresenceEventByUuidInternal(uuid, event);
}
- };
+ };
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override
@@ -1887,73 +1666,8 @@
}
}
- /**
- * An OnUidImportanceListener class which watches the importance of the packages.
- * In this class, we ONLY interested in the importance of the running process is greater than
- * {@link RunningAppProcessInfo.IMPORTANCE_VISIBLE} for the uids have been added into the
- * {@link mUidsPendingRoleHolderRemoval}. Lastly remove the role holder for the revoked
- * associations for the same packages.
- *
- * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
- * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
- * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
- */
- private class OnPackageVisibilityChangeListener implements
- ActivityManager.OnUidImportanceListener {
- final @NonNull ActivityManager mAm;
-
- OnPackageVisibilityChangeListener(@NonNull ActivityManager am) {
- this.mAm = am;
- }
-
- void startListening() {
- Binder.withCleanCallingIdentity(
- () -> mAm.addOnUidImportanceListener(
- /* listener */ OnPackageVisibilityChangeListener.this,
- RunningAppProcessInfo.IMPORTANCE_VISIBLE));
- }
-
- void stopListening() {
- Binder.withCleanCallingIdentity(
- () -> mAm.removeOnUidImportanceListener(
- /* listener */ OnPackageVisibilityChangeListener.this));
- }
-
- @Override
- public void onUidImportance(int uid, int importance) {
- if (importance <= RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
- // The lower the importance value the more "important" the process is.
- // We are only interested when the process ceases to be visible.
- return;
- }
-
- final String packageName = getPackageNameByUid(uid);
- if (packageName == null) {
- // Not interested in this uid.
- return;
- }
-
- final int userId = UserHandle.getUserId(uid);
-
- boolean needToPersistStateForUser = false;
-
- for (AssociationInfo association :
- getPendingRoleHolderRemovalAssociationsForUser(userId)) {
- if (!packageName.equals(association.getPackageName())) continue;
-
- if (!maybeRemoveRoleHolderForAssociation(association)) {
- // Did not remove the role holder, will have to try again later.
- continue;
- }
-
- removeFromPendingRoleHolderRemoval(association);
- needToPersistStateForUser = true;
- }
-
- if (needToPersistStateForUser) {
- mUserPersistenceHandler.postPersistUserState(userId);
- }
- }
+ void postPersistUserState(@UserIdInt int userId) {
+ mUserPersistenceHandler.postPersistUserState(userId);
}
static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 5663434..de4f2b6 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -45,6 +45,7 @@
private static final String TAG = "CDM_CompanionDeviceShellCommand";
private final CompanionDeviceManagerService mService;
+ private final AssociationRevokeProcessor mRevokeProcessor;
private final AssociationStoreImpl mAssociationStore;
private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final CompanionTransportManager mTransportManager;
@@ -59,7 +60,8 @@
CompanionTransportManager transportManager,
SystemDataTransferProcessor systemDataTransferProcessor,
AssociationRequestsProcessor associationRequestsProcessor,
- BackupRestoreProcessor backupRestoreProcessor) {
+ BackupRestoreProcessor backupRestoreProcessor,
+ AssociationRevokeProcessor revokeProcessor) {
mService = service;
mAssociationStore = associationStore;
mDevicePresenceMonitor = devicePresenceMonitor;
@@ -67,6 +69,7 @@
mSystemDataTransferProcessor = systemDataTransferProcessor;
mAssociationRequestsProcessor = associationRequestsProcessor;
mBackupRestoreProcessor = backupRestoreProcessor;
+ mRevokeProcessor = revokeProcessor;
}
@Override
@@ -126,7 +129,7 @@
final AssociationInfo association =
mService.getAssociationWithCallerChecks(userId, packageName, address);
if (association != null) {
- mService.disassociateInternal(association.getId());
+ mRevokeProcessor.disassociateInternal(association.getId());
}
}
break;
@@ -138,7 +141,7 @@
mAssociationStore.getAssociationsForPackage(userId, packageName);
for (AssociationInfo association : userAssociations) {
if (sanitizeWithCallerChecks(mService.getContext(), association) != null) {
- mService.disassociateInternal(association.getId());
+ mRevokeProcessor.disassociateInternal(association.getId());
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2dd2f8f..e222878 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18462,7 +18462,8 @@
(WindowProcessController) procsToKill.get(i);
final ProcessRecord pr = (ProcessRecord) wpc.mOwner;
if (ActivityManager.isProcStateBackground(pr.mState.getSetProcState())
- && pr.mReceivers.numberOfCurReceivers() == 0) {
+ && pr.mReceivers.numberOfCurReceivers() == 0
+ && !pr.mState.hasStartedServices()) {
pr.killLocked("remove task", ApplicationExitInfo.REASON_USER_REQUESTED,
ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
} else {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 9568116..31328ae 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3322,7 +3322,8 @@
reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
}
if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
- && ActivityManager.isProcStateBackground(state.getSetProcState())) {
+ && ActivityManager.isProcStateBackground(state.getSetProcState())
+ && !state.hasStartedServices()) {
app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
success = false;
diff --git a/services/core/java/com/android/server/inputmethod/ClientController.java b/services/core/java/com/android/server/inputmethod/ClientController.java
index 86f4db9..0381a31 100644
--- a/services/core/java/com/android/server/inputmethod/ClientController.java
+++ b/services/core/java/com/android/server/inputmethod/ClientController.java
@@ -33,36 +33,10 @@
import java.util.function.Consumer;
/**
- * Store and manage {@link InputMethodManagerService} clients. This class was designed to be a
- * singleton in {@link InputMethodManagerService} since it stores information about all clients,
- * still the current client will be defined per display.
- *
- * <p>
- * As part of the re-architecture plan (described in go/imms-rearchitecture-plan), the following
- * fields and methods will be moved out from IMMS and placed here:
- * <ul>
- * <li>mClients (ArrayMap of ClientState indexed by IBinder)</li>
- * </ul>
- * <p>
- * Nested Classes (to move from IMMS):
- * <ul>
- * <li>ClientDeathRecipient</li>
- * <li>ClientState<</li>
- * </ul>
- * <p>
- * Methods to rewrite and/or extract from IMMS and move here:
- * <ul>
- * <li>addClient</li>
- * <li>removeClient</li>
- * <li>verifyClientAndPackageMatch</li>
- * <li>setImeTraceEnabledForAllClients (make it reactive)</li>
- * </ul>
+ * Store and manage {@link InputMethodManagerService} clients.
*/
-// TODO(b/314150112): Update the Javadoc above, by removing the re-architecture steps, once this
-// class is finalized
final class ClientController {
- // TODO(b/314150112): Make this field private when breaking the cycle with IMMS.
@GuardedBy("ImfLock.class")
private final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 76956c88..307b70a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2193,7 +2193,7 @@
}
}
- // TODO(b/314150112): Move this method to InputMethodBindingController
+ // TODO(b/325515685): Move this method to InputMethodBindingController
/**
* Hide the IME if the removed user is the current user.
*/
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a1dac04..8cc242c 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -701,7 +701,7 @@
pkgSetting.setUninstallReason(PackageManager.UNINSTALL_REASON_UNKNOWN, userId);
pkgSetting.setFirstInstallTime(System.currentTimeMillis(), userId);
// Clear any existing archive state.
- mPm.mInstallerService.mPackageArchiver.clearArchiveState(packageName, userId);
+ mPm.mInstallerService.mPackageArchiver.clearArchiveState(pkgSetting, userId);
mPm.mSettings.writePackageRestrictionsLPr(userId);
mPm.mSettings.writeKernelMappingLPr(pkgSetting);
installed = true;
@@ -829,7 +829,8 @@
if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
- if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED && doRestore) {
+ final boolean succeeded = request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED;
+ if (succeeded && doRestore) {
// Pass responsibility to the Backup Manager. It will perform a
// restore if appropriate, then pass responsibility back to the
// Package Manager to run the post-install observer callbacks
@@ -843,10 +844,27 @@
// need to be snapshotted or restored for the package.
//
// TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
- if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
+ if (succeeded && !doRestore && update) {
doRestore = performRollbackManagerRestore(userId, token, request);
}
+ if (succeeded && !request.hasPostInstallRunnable()) {
+ boolean hasNeverBeenRestored =
+ packageSetting != null && packageSetting.isPendingRestore();
+ request.setPostInstallRunnable(() -> {
+ // Permissions should be restored on each user that has the app installed for the
+ // first time, unless it's an unarchive install for an archived app, in which case
+ // the permissions should be restored on each user that has the app updated.
+ int[] userIdsToRestorePermissions = hasNeverBeenRestored
+ ? request.getUpdateBroadcastUserIds()
+ : request.getFirstTimeBroadcastUserIds();
+ for (int restorePermissionUserId : userIdsToRestorePermissions) {
+ mPm.restorePermissionsAndUpdateRolesForNewUserInstall(request.getName(),
+ restorePermissionUserId);
+ }
+ });
+ }
+
if (doRestore) {
if (packageSetting != null) {
synchronized (mPm.mLock) {
@@ -2327,7 +2345,7 @@
installerPackageName);
}
// Clear any existing archive state.
- mPm.mInstallerService.mPackageArchiver.clearArchiveState(pkgName, userId);
+ mPm.mInstallerService.mPackageArchiver.clearArchiveState(ps, userId);
} else if (allUsers != null) {
// The caller explicitly specified INSTALL_ALL_USERS flag.
// Thus, updating the settings to install the app for all users.
@@ -2351,7 +2369,7 @@
installerPackageName);
}
// Clear any existing archive state.
- mPm.mInstallerService.mPackageArchiver.clearArchiveState(pkgName,
+ mPm.mInstallerService.mPackageArchiver.clearArchiveState(ps,
currentUserId);
} else {
ps.setInstalled(false, currentUserId);
@@ -2851,7 +2869,6 @@
mPm.notifyInstantAppPackageInstalled(request.getPkg().getPackageName(),
request.getNewUsers());
- request.populateBroadcastUsers();
final int[] firstUserIds = request.getFirstTimeBroadcastUserIds();
if (request.getPkg().getStaticSharedLibraryName() == null) {
@@ -2863,12 +2880,6 @@
mPm.mRequiredInstallerPackage,
/* packageSender= */ mPm, launchedForRestore, killApp, update, archived);
- // Work that needs to happen on first install within each user
- for (int userId : firstUserIds) {
- mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
- userId);
- }
-
if (request.isAllNewUsers() && !update) {
mPm.notifyPackageAdded(packageName, request.getAppId());
} else {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 4fb0c22..43075a2 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -692,6 +692,14 @@
}
}
+ public void setPostInstallRunnable(Runnable runnable) {
+ mPostInstallRunnable = runnable;
+ }
+
+ public boolean hasPostInstallRunnable() {
+ return mPostInstallRunnable != null;
+ }
+
public void runPostInstallRunnable() {
if (mPostInstallRunnable != null) {
mPostInstallRunnable.run();
@@ -753,6 +761,7 @@
public void setNewUsers(int[] newUsers) {
mNewUsers = newUsers;
+ populateBroadcastUsers();
}
public void setOriginPackage(String originPackage) {
@@ -829,10 +838,11 @@
}
/**
- * Determine the set of users who are adding this package for the first time vs. those who are
- * seeing an update.
+ * Determine the set of users who are adding this package for the first time (aka "new" users)
+ * vs. those who are seeing an update (aka "update" users). The lists can be calculated as soon
+ * as the "new" users are set.
*/
- public void populateBroadcastUsers() {
+ private void populateBroadcastUsers() {
assertScanResultExists();
mFirstTimeBroadcastUserIds = EMPTY_INT_ARRAY;
mFirstTimeBroadcastInstantUserIds = EMPTY_INT_ARRAY;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index d8d8dd2..3f9e989 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -2209,8 +2209,10 @@
for (UserHandle user : users) {
mPackageManagerInternal.forEachInstalledPackage(pkg -> {
final String packageName = pkg.getPackageName();
- if (mPackageManagerInternal.getIncrementalStatesInfo(packageName,
- Process.myUid(), user.getIdentifier()).isLoading()) {
+ final IncrementalStatesInfo info =
+ mPackageManagerInternal.getIncrementalStatesInfo(packageName,
+ Process.myUid(), user.getIdentifier());
+ if (info != null && info.isLoading()) {
mPackageManagerInternal.registerInstalledLoadingProgressCallback(
packageName, new PackageLoadingProgressCallback(packageName, user),
user.getIdentifier());
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index cdd52a4..2b20bfd 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -356,19 +356,34 @@
}
void clearArchiveState(String packageName, int userId) {
+ final PackageSetting ps;
synchronized (mPm.mLock) {
- PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
- if (ps != null) {
- ps.setArchiveState(/* archiveState= */ null, userId);
- }
+ ps = mPm.mSettings.getPackageLPr(packageName);
}
- File iconsDir = getIconsDir(packageName, userId);
+ clearArchiveState(ps, userId);
+ }
+
+ void clearArchiveState(PackageSetting ps, int userId) {
+ synchronized (mPm.mLock) {
+ if (ps == null || ps.getUserStateOrDefault(userId).getArchiveState() == null) {
+ // No archive states to clear
+ return;
+ }
+ if (DEBUG) {
+ Slog.e(TAG, "Clearing archive states for " + ps.getPackageName());
+ }
+ ps.setArchiveState(/* archiveState= */ null, userId);
+ }
+ File iconsDir = getIconsDir(ps.getPackageName(), userId);
if (!iconsDir.exists()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Icons are already deleted at " + iconsDir.getAbsolutePath());
+ }
return;
}
// TODO(b/319238030) Move this into installd.
if (!FileUtils.deleteContentsAndDir(iconsDir)) {
- Slog.e(TAG, "Failed to clean up archive files for " + packageName);
+ Slog.e(TAG, "Failed to clean up archive files for " + ps.getPackageName());
} else {
if (DEBUG) {
Slog.e(TAG, "Deleted icons at " + iconsDir.getAbsolutePath());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dadafd7..3c256b1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -721,7 +721,6 @@
PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy;
- @GuardedBy("mAvailableFeatures")
private final ArrayMap<String, FeatureInfo> mAvailableFeatures;
@Watched
@@ -2983,13 +2982,11 @@
public boolean hasSystemFeature(String name, int version) {
// allow instant applications
- synchronized (mAvailableFeatures) {
- final FeatureInfo feat = mAvailableFeatures.get(name);
- if (feat == null) {
- return false;
- } else {
- return feat.version >= version;
- }
+ final FeatureInfo feat = mAvailableFeatures.get(name);
+ if (feat == null) {
+ return false;
+ } else {
+ return feat.version >= version;
}
}
@@ -5335,10 +5332,8 @@
public @NonNull ParceledListSlice<FeatureInfo> getSystemAvailableFeatures() {
// allow instant applications
ArrayList<FeatureInfo> res;
- synchronized (mAvailableFeatures) {
- res = new ArrayList<>(mAvailableFeatures.size() + 1);
- res.addAll(mAvailableFeatures.values());
- }
+ res = new ArrayList<>(mAvailableFeatures.size() + 1);
+ res.addAll(mAvailableFeatures.values());
final FeatureInfo fi = new FeatureInfo();
fi.reqGlEsVersion = SystemProperties.getInt("ro.opengles.version",
FeatureInfo.GL_ES_VERSION_UNDEFINED);
@@ -6542,9 +6537,7 @@
mOverlayConfigSignaturePackage,
mRecentsPackage);
final ArrayMap<String, FeatureInfo> availableFeatures;
- synchronized (mAvailableFeatures) {
- availableFeatures = new ArrayMap<>(mAvailableFeatures);
- }
+ availableFeatures = new ArrayMap<>(mAvailableFeatures);
final ArraySet<String> protectedBroadcasts;
synchronized (mProtectedBroadcasts) {
protectedBroadcasts = new ArraySet<>(mProtectedBroadcasts);
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 5e8778d..9a7916a 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -53,7 +53,7 @@
* as install) led to the request.
*/
final class ReconcilePackageUtils {
- private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE;
+ private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE || true;
public static List<ReconciledPackage> reconcilePackages(
List<InstallRequest> installRequests,
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
index 3b260ca..4df919d 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
@@ -57,7 +57,7 @@
* {@link #isCompatibleXmlFormat} to return true for all legacy versions
* that are compatible with the new one.
*/
- private static final int VERSION = 1;
+ private static final int VERSION = 2;
private static final String XML_TAG_METADATA = "metadata";
private static final String XML_ATTR_ID = "id";
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 2bee095..1353ff0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3750,8 +3750,7 @@
// Boost the adjacent TaskFragment for dimmer if needed.
final TaskFragment taskFragment = wc.asTaskFragment();
- if (taskFragment != null && taskFragment.isEmbedded()
- && taskFragment.isVisibleRequested()) {
+ if (taskFragment != null && taskFragment.isEmbedded()) {
final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
if (adjacentTf != null && adjacentTf.shouldBoostDimmer()) {
adjacentTf.assignLayer(t, layer++);
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 723c52f..ca72638 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -150,7 +150,8 @@
@Override
public void onFinalErrorReceived(ComponentName componentName, String errorType,
String message) {
- respondToClientWithErrorAndFinish(errorType, message);
+ Slog.d(TAG, "onFinalErrorReceived");
+ respondToFinalReceiverWithFailureAndFinish(this.mFinalResponseReceiver, errorType, message);
}
@Override
@@ -163,6 +164,13 @@
message = "The UI was interrupted - please try again.";
}
mRequestSessionMetric.collectFrameworkException(exception);
+ respondToFinalReceiverWithFailureAndFinish(finalResponseReceiver, exception, message);
+ }
+
+ private void respondToFinalReceiverWithFailureAndFinish(
+ ResultReceiver finalResponseReceiver,
+ String exception, String message
+ ) {
if (finalResponseReceiver != null) {
Bundle resultData = new Bundle();
resultData.putStringArray(
@@ -170,16 +178,16 @@
new String[] {exception, message});
finalResponseReceiver.send(Constants.FAILURE_CREDMAN_SELECTOR, resultData);
} else {
- respondToClientWithErrorAndFinish(exception, message);
+ Slog.w(TAG, "onUiCancellation called but finalResponseReceiver not found");
}
+ finishSession(/*propagateCancellation=*/false);
}
@Override
public void onUiSelectorInvocationFailure() {
String exception = GetCandidateCredentialsException.TYPE_NO_CREDENTIAL;
mRequestSessionMetric.collectFrameworkException(exception);
- respondToClientWithErrorAndFinish(exception,
- "No credentials available.");
+ // TODO(): Propagate through final receiver
}
@Override
diff --git a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
index 21ac9e4..bc8c2b0 100644
--- a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -82,7 +82,7 @@
if (resultData == null) {
return null;
}
- return resultData.getParcelableExtra(
+ return resultData.getSerializableExtra(
CredentialProviderService.EXTRA_CREATE_CREDENTIAL_EXCEPTION,
CreateCredentialException.class);
}
@@ -94,7 +94,7 @@
if (resultData == null) {
return null;
}
- return resultData.getParcelableExtra(
+ return resultData.getSerializableExtra(
CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
GetCredentialException.class);
}
diff --git a/services/tests/PackageManagerServiceTests/preverifieddomains/Android.bp b/services/tests/PackageManagerServiceTests/preverifieddomains/Android.bp
new file mode 100644
index 0000000..39ef501
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/preverifieddomains/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2024 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 {
+ default_team: "trendy_team_framework_android_packages",
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "PreVerifiedDomainsTests",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "compatibility-device-util-axt",
+ "androidx.test.runner",
+ "truth",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+ test_suites: ["device-tests"],
+}
diff --git a/services/tests/PackageManagerServiceTests/preverifieddomains/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/preverifieddomains/AndroidManifest.xml
new file mode 100644
index 0000000..ad731fc
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/preverifieddomains/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.server.pm.test.preverifieddomains">
+
+ <application android:label="PreVerified Domains Tests">
+ <activity
+ android:name="com.android.server.pm.test.preverifieddomains.FakeInstantAppInstallerActivity"
+ android:enabled="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.INSTALL_INSTANT_APP_PACKAGE_TEST" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="file"/>
+ <data android:mimeType="application/vnd.android.package-archive"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.pm.test.preverifieddomains"
+ android:label="Package Manager Service Tests for pre-verified domains">
+ </instrumentation>
+
+</manifest>
+
diff --git a/services/tests/PackageManagerServiceTests/preverifieddomains/AndroidTest.xml b/services/tests/PackageManagerServiceTests/preverifieddomains/AndroidTest.xml
new file mode 100644
index 0000000..45e193b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/preverifieddomains/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<configuration description="Runs Package Manager Service Pre-Verified Domains Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="PreVerifiedDomainsTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="PreVerifiedDomainsTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.server.pm.test.preverifieddomains" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/services/tests/PackageManagerServiceTests/preverifieddomains/src/com/android/server/pm/test/preverifieddomains/FakeInstantAppInstallerActivity.kt b/services/tests/PackageManagerServiceTests/preverifieddomains/src/com/android/server/pm/test/preverifieddomains/FakeInstantAppInstallerActivity.kt
new file mode 100644
index 0000000..d330490
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/preverifieddomains/src/com/android/server/pm/test/preverifieddomains/FakeInstantAppInstallerActivity.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm.test.preverifieddomains
+
+import android.app.Activity
+
+class FakeInstantAppInstallerActivity : Activity()
diff --git a/services/tests/PackageManagerServiceTests/preverifieddomains/src/com/android/server/pm/test/preverifieddomains/PreVerifiedDomainsTests.kt b/services/tests/PackageManagerServiceTests/preverifieddomains/src/com/android/server/pm/test/preverifieddomains/PreVerifiedDomainsTests.kt
new file mode 100644
index 0000000..7043216
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/preverifieddomains/src/com/android/server/pm/test/preverifieddomains/PreVerifiedDomainsTests.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm.test.preverifieddomains
+
+import android.app.UiAutomation
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.Flags
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
+import android.content.pm.PackageManager
+import android.os.Build
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.DeviceConfigStateManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.AfterClass
+import org.junit.Assert.assertThrows
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.Test
+import org.junit.function.ThrowingRunnable
+import org.junit.runner.RunWith
+
+/**
+ * Pre-verified domains API tests. These tests require the device's default instant app
+ * installer to be disabled temporarily and is only able to run on ENG builds.
+ */
+@RunWith(AndroidJUnit4::class)
+class PreVerifiedDomainsTests {
+ companion object {
+ private const val PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT =
+ "pre_verified_domains_count_limit"
+ private const val PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT =
+ "pre_verified_domain_length_limit"
+ private const val TEMP_COUNT_LIMIT = 10
+ private const val TEMP_LENGTH_LIMIT = 15
+ private val testDomains = setOf("com.foo", "com.bar")
+
+ private val uiAutomation: UiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ private lateinit var packageManager: PackageManager
+ private var defaultInstantAppInstaller: ComponentName? = null
+ private lateinit var fakeInstantAppInstaller: ComponentName
+
+ @JvmStatic
+ @BeforeClass
+ fun setupBeforeClass() {
+ val context = InstrumentationRegistry.getInstrumentation().getContext()
+ packageManager = context.packageManager
+ defaultInstantAppInstaller = packageManager.getInstantAppInstallerComponent()
+ fakeInstantAppInstaller = ComponentName(
+ context.packageName,
+ context.packageName + ".FakeInstantAppInstallerActivity")
+ // By disabling the original instant app installer, this test app becomes the instant
+ // app installer
+ uiAutomation.adoptShellPermissionIdentity()
+ try {
+ // Enable the fake instant app installer before disabling the default one
+ packageManager.setComponentEnabledSetting(
+ fakeInstantAppInstaller,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP
+ )
+ if (defaultInstantAppInstaller != null) {
+ packageManager.setComponentEnabledSetting(
+ defaultInstantAppInstaller!!,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ 0
+ )
+ }
+ } finally {
+ uiAutomation.dropShellPermissionIdentity()
+ }
+ assertThat(fakeInstantAppInstaller).isEqualTo(
+ packageManager.getInstantAppInstallerComponent())
+ }
+
+ @JvmStatic
+ @AfterClass
+ fun restoreInstantAppInstaller() {
+ uiAutomation.adoptShellPermissionIdentity()
+ try {
+ // Enable the original instant app installer before disabling the temporary one, so
+ // there won't be a time when the device doesn't have a valid instant app installer
+ if (defaultInstantAppInstaller != null) {
+ packageManager.setComponentEnabledSetting(
+ defaultInstantAppInstaller!!,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ 0
+ )
+ }
+ // Be careful not to let this test process killed, or the test will be considered
+ // as failed
+ packageManager.setComponentEnabledSetting(
+ fakeInstantAppInstaller,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP
+ )
+ } finally {
+ uiAutomation.dropShellPermissionIdentity()
+ }
+ }
+ }
+
+ private lateinit var packageInstaller: PackageInstaller
+ private lateinit var context: Context
+ private lateinit var packageManager: PackageManager
+ private var mDefaultCountLimit: String? = null
+ private var mDefaultLengthLimit: String? = null
+
+ @JvmField
+ @Rule
+ val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Before
+ fun setUp() {
+ context = InstrumentationRegistry.getInstrumentation().getContext()
+ packageManager = context.packageManager
+ packageInstaller = packageManager.packageInstaller
+ mDefaultCountLimit = getLimitFromDeviceConfig(PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT)
+ mDefaultLengthLimit = getLimitFromDeviceConfig(PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT)
+ }
+
+ @After
+ fun cleanUp() {
+ setLimitInDeviceConfig(PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT, mDefaultCountLimit)
+ setLimitInDeviceConfig(PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT, mDefaultLengthLimit)
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
+ @Test
+ fun testSetPreVerifiedDomainsExceedsCountLimit() {
+ // Temporarily change the count limit to a much smaller number so the test can exceed it
+ setLimitInDeviceConfig(
+ PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT,
+ TEMP_COUNT_LIMIT.toString()
+ )
+ val domains = mutableSetOf<String>()
+ for (i in 0 until(TEMP_COUNT_LIMIT + 1)) {
+ domains.add("domain$i")
+ }
+
+ uiAutomation.adoptShellPermissionIdentity(android.Manifest.permission.ACCESS_INSTANT_APPS)
+ try {
+ assertThrows(
+ IllegalArgumentException::class.java,
+ ThrowingRunnable {
+ createSessionWithPreVerifiedDomains(domains)
+ }
+ )
+ } finally {
+ uiAutomation.dropShellPermissionIdentity()
+ }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
+ @Test
+ fun testSetPreVerifiedDomainsExceedsLengthLimit() {
+ // Temporarily change the count limit to a much smaller number so the test can exceed it
+ setLimitInDeviceConfig(
+ PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT,
+ TEMP_LENGTH_LIMIT.toString()
+ )
+ val invalidDomain = "a".repeat(TEMP_LENGTH_LIMIT + 1)
+
+ uiAutomation.adoptShellPermissionIdentity(android.Manifest.permission.ACCESS_INSTANT_APPS)
+ try {
+ assertThrows(
+ "Pre-verified domain: [" +
+ invalidDomain + " ] exceeds maximum length allowed: " +
+ TEMP_LENGTH_LIMIT,
+ IllegalArgumentException::class.java,
+ ThrowingRunnable {
+ createSessionWithPreVerifiedDomains(setOf(invalidDomain))
+ }
+ )
+ } finally {
+ uiAutomation.dropShellPermissionIdentity()
+ }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
+ @Test
+ fun testSetAndGetPreVerifiedDomains() {
+ // Fake instant app installers can only work on ENG builds
+ assumeTrue("eng" == Build.TYPE)
+ var session: PackageInstaller.Session? = null
+ uiAutomation.adoptShellPermissionIdentity(android.Manifest.permission.ACCESS_INSTANT_APPS)
+ try {
+ val sessionId = createSessionWithPreVerifiedDomains(testDomains)
+ session = packageInstaller.openSession(sessionId)
+ assertThat(session.getPreVerifiedDomains()).isEqualTo(testDomains)
+ } finally {
+ uiAutomation.dropShellPermissionIdentity()
+ session?.abandon()
+ }
+ }
+
+ private fun createSessionWithPreVerifiedDomains(domains: Set<String>): Int {
+ val sessionParam = PackageInstaller.SessionParams(MODE_FULL_INSTALL)
+ val sessionId = packageInstaller.createSession(sessionParam)
+ val session = packageInstaller.openSession(sessionId)
+ try {
+ session.setPreVerifiedDomains(domains)
+ } catch (e: Exception) {
+ session.abandon()
+ throw e
+ }
+ return sessionId
+ }
+
+ private fun getLimitFromDeviceConfig(propertyName: String): String? {
+ val stateManager = DeviceConfigStateManager(
+ context,
+ NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ propertyName
+ )
+ return stateManager.get()
+ }
+
+ private fun setLimitInDeviceConfig(propertyName: String, value: String?) {
+ val stateManager = DeviceConfigStateManager(
+ context,
+ NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ propertyName
+ )
+ val currentValue = stateManager.get()
+ if (currentValue != value) {
+ // Only change the value if the current value is different
+ stateManager.set(value)
+ }
+ }
+}