aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorandroid-build-prod (mdb) <android-build-team-robot@google.com>2020-09-17 17:54:36 +0000
committerandroid-build-prod (mdb) <android-build-team-robot@google.com>2020-09-17 17:54:36 +0000
commit15fbfb09b48107723939599d1a8d0043d3f1c1cc (patch)
tree62c08a00334878f5774e4622ced16ce5d7b31153
parent05a06c3d05d5bd64c0403d0e88952f0f87bd0e52 (diff)
parent5bc1596595c5884dd022f29ac273e6ed542e783a (diff)
downloadplatform_external_robolectric-shadows-simpleperf-release.tar.gz
platform_external_robolectric-shadows-simpleperf-release.tar.bz2
platform_external_robolectric-shadows-simpleperf-release.zip
Snap for 6844436 from 5bc1596595c5884dd022f29ac273e6ed542e783a to simpleperf-releasesimpleperf-release
Change-Id: I46adc50ea5f07281c9a1721d32bd665d6cff1d26
-rw-r--r--Android.bp4
-rw-r--r--gen_test_config.mk2
-rw-r--r--processor/sdks.txt1
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java76
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java14
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java91
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowCrossProfileAppsTest.java397
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java40
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java26
-rw-r--r--run_robolectric_module_tests.mk3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java55
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityThread.java43
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAnnotationValidations.java19
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java69
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java45
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java160
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java63
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowCrossProfileApps.java456
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java15
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowFingerprintManager.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowHardwareRenderer.java7
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageDecoder.java43
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java23
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java44
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java21
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPersistentDataBlockManager.java42
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeAnimator.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java19
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java40
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java169
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java20
-rw-r--r--soong/robolectric.go3
34 files changed, 1890 insertions, 143 deletions
diff --git a/Android.bp b/Android.bp
index 88fa2a526..f1dda073e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -102,6 +102,10 @@ java_library_host {
// Copy the build.prop
":robolectric_build_props",
],
+ visibility: [
+ ":__subpackages__",
+ "//prebuilts/misc/common/robolectric",
+ ],
}
//#############################################
diff --git a/gen_test_config.mk b/gen_test_config.mk
index b478e833b..48c346ac0 100644
--- a/gen_test_config.mk
+++ b/gen_test_config.mk
@@ -12,7 +12,7 @@ LOCAL_JAVA_RESOURCE_FILES += $(test_config_dir):com/android/tools/test_config.pr
# Define variables to be written into the generated test_config.properties file.
android_merged_manifest := $(strip $(call intermediates-dir-for,APPS,$(LOCAL_INSTRUMENTATION_FOR),,COMMON)/manifest/AndroidManifest.xml)
-android_resource_apk := $(strip $(call apk-location-for,$(LOCAL_INSTRUMENTATION_FOR)))
+android_resource_apk := $(strip $(call intermediates-dir-for,APPS,$(LOCAL_INSTRUMENTATION_FOR))/package.apk)
# Snapshot the written variables so they cannot be polluted before the module is built.
$(test_config_file): private_android_merged_manifest := $(android_merged_manifest)
diff --git a/processor/sdks.txt b/processor/sdks.txt
index c6f88b4b1..e61922009 100644
--- a/processor/sdks.txt
+++ b/processor/sdks.txt
@@ -10,3 +10,4 @@ prebuilts/misc/common/robolectric/android-all/android-all-7.1.0_r7-robolectric-r
prebuilts/misc/common/robolectric/android-all/android-all-8.0.0_r4-robolectric-r1.jar
prebuilts/misc/common/robolectric/android-all/android-all-8.1.0-robolectric-4611349.jar
prebuilts/misc/common/robolectric/android-all/android-all-9-robolectric-4913185-2.jar
+prebuilts/misc/common/robolectric/android-all/android-all-9plus-robolectric-5616371.jar
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java
index 631d28020..81a25062b 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java
@@ -5,6 +5,7 @@ import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
import static android.app.AppOpsManager.OPSTR_GPS;
+import static android.app.AppOpsManager.OPSTR_SEND_SMS;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_GPS;
import static android.app.AppOpsManager.OP_SEND_SMS;
@@ -101,24 +102,28 @@ public class ShadowAppOpsManagerTest {
}
@Test
- @Config(minSdk = VERSION_CODES.O_MR1)
- public void noModeSet_atLeastO_noteProxyOpNoThrow_shouldReturnModeAllowed() {
- assertThat(appOps.noteProxyOpNoThrow(OP_GPS, PACKAGE_NAME1)).isEqualTo(MODE_ALLOWED);
+ @Config(minSdk = VERSION_CODES.R)
+ public void noModeSet_atLeastR_noteProxyOpNoThrow_shouldReturnModeAllowed() {
+ assertThat(appOps.noteProxyOpNoThrow(OPSTR_GPS, PACKAGE_NAME1, UID_1, null, null))
+ .isEqualTo(MODE_ALLOWED);
}
@Test
- @Config(minSdk = VERSION_CODES.O_MR1)
- public void setMode_withModeDefault_atLeastO_noteProxyOpNoThrow_shouldReturnModeDefault() {
+ @Config(minSdk = VERSION_CODES.R)
+ public void setMode_withModeDefault_atLeastR_noteProxyOpNoThrow_shouldReturnModeDefault() {
appOps.setMode(OP_GPS, Binder.getCallingUid(), PACKAGE_NAME1, MODE_DEFAULT);
- assertThat(appOps.noteProxyOpNoThrow(OP_GPS, PACKAGE_NAME1)).isEqualTo(MODE_DEFAULT);
+ assertThat(appOps.noteProxyOpNoThrow(OPSTR_GPS, PACKAGE_NAME1, UID_1, null, null))
+ .isEqualTo(MODE_DEFAULT);
}
@Test
- @Config(minSdk = VERSION_CODES.P)
- public void setMode_noteProxyOpNoThrow_atLeastO() {
- assertThat(appOps.noteProxyOpNoThrow(OP_GPS, PACKAGE_NAME1)).isEqualTo(MODE_ALLOWED);
+ @Config(minSdk = VERSION_CODES.R)
+ public void setMode_noteProxyOpNoThrow_atLeastR() {
+ assertThat(appOps.noteProxyOpNoThrow(OPSTR_GPS, PACKAGE_NAME1, UID_1, null, null))
+ .isEqualTo(MODE_ALLOWED);
appOps.setMode(OP_GPS, Binder.getCallingUid(), PACKAGE_NAME1, MODE_ERRORED);
- assertThat(appOps.noteProxyOpNoThrow(OP_GPS, PACKAGE_NAME1)).isEqualTo(MODE_ERRORED);
+ assertThat(appOps.noteProxyOpNoThrow(OPSTR_GPS, PACKAGE_NAME1, UID_1, null, null))
+ .isEqualTo(MODE_ERRORED);
}
@Test
@@ -135,6 +140,7 @@ public class ShadowAppOpsManagerTest {
}
@Test
+ @Config(maxSdk = VERSION_CODES.Q)
public void noteOp() {
assertThat(appOps.noteOp(OP_GPS, UID_1, PACKAGE_NAME1)).isEqualTo(MODE_ALLOWED);
// Use same op more than once
@@ -144,12 +150,31 @@ public class ShadowAppOpsManagerTest {
}
@Test
+ @Config(minSdk = VERSION_CODES.R)
+ public void noteOp_atLeastR() {
+ assertThat(appOps.noteOp(OPSTR_GPS, UID_1, PACKAGE_NAME1, null, null)).isEqualTo(MODE_ALLOWED);
+ // Use same op more than once
+ assertThat(appOps.noteOp(OPSTR_GPS, UID_1, PACKAGE_NAME1, null, null)).isEqualTo(MODE_ALLOWED);
+
+ assertThat(appOps.noteOp(OPSTR_SEND_SMS, UID_1, PACKAGE_NAME1, null, null)).isEqualTo(MODE_ALLOWED);
+ }
+
+ @Test
+ @Config(maxSdk = VERSION_CODES.Q)
public void getOpsForPackage_noOps() {
List<PackageOps> results = appOps.getOpsForPackage(UID_1, PACKAGE_NAME1, NO_OP_FILTER);
assertOps(results);
}
@Test
+ @Config(minSdk = VERSION_CODES.R)
+ public void getOpsForPackage_noOps_atLeastR() {
+ List<PackageOps> results = appOps.getOpsForPackage(UID_1, PACKAGE_NAME1);
+ assertOps(results);
+ }
+
+ @Test
+ @Config(maxSdk = VERSION_CODES.Q)
public void getOpsForPackage_hasOps() {
appOps.noteOp(OP_GPS, UID_1, PACKAGE_NAME1);
appOps.noteOp(OP_SEND_SMS, UID_1, PACKAGE_NAME1);
@@ -164,6 +189,22 @@ public class ShadowAppOpsManagerTest {
}
@Test
+ @Config(minSdk = VERSION_CODES.R)
+ public void getOpsForPackage_hasOps_atLeastR() {
+ appOps.noteOp(OPSTR_GPS, UID_1, PACKAGE_NAME1, null, null);
+ appOps.noteOp(OPSTR_SEND_SMS, UID_1, PACKAGE_NAME1, null, null);
+
+ // PACKAGE_NAME2 has ops.
+ List<PackageOps> results = appOps.getOpsForPackage(UID_1, PACKAGE_NAME1);
+ assertOps(results, OP_GPS, OP_SEND_SMS);
+
+ // PACKAGE_NAME2 has no ops.
+ results = appOps.getOpsForPackage(UID_2, PACKAGE_NAME2);
+ assertOps(results);
+ }
+
+ @Test
+ @Config(maxSdk = VERSION_CODES.Q)
public void getOpsForPackage_withOpFilter() {
List<PackageOps> results = appOps.getOpsForPackage(UID_1, PACKAGE_NAME1, new int[] {OP_GPS});
assertOps(results);
@@ -178,6 +219,21 @@ public class ShadowAppOpsManagerTest {
}
@Test
+ @Config(minSdk = VERSION_CODES.R)
+ public void getOpsForPackage_withOpFilter_atLeastR() {
+ List<PackageOps> results = appOps.getOpsForPackage(UID_1, PACKAGE_NAME1, OPSTR_GPS);
+ assertOps(results);
+
+ appOps.noteOp(OPSTR_SEND_SMS, UID_1, PACKAGE_NAME1, null, null);
+ results = appOps.getOpsForPackage(UID_1, PACKAGE_NAME1, OPSTR_GPS);
+ assertOps(results);
+
+ appOps.noteOp(OPSTR_GPS, UID_1, PACKAGE_NAME1, null, null);
+ results = appOps.getOpsForPackage(UID_1, PACKAGE_NAME1, OPSTR_GPS);
+ assertOps(results, OP_GPS);
+ }
+
+ @Test
@Config(minSdk = VERSION_CODES.LOLLIPOP)
public void setRestrictions() {
appOps.setRestriction(
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java
index 77ae3e48e..fd5adab9b 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java
@@ -16,6 +16,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.ServiceConnection;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.view.LayoutInflater;
@@ -218,4 +219,17 @@ public class ShadowContextImplTest {
public void onServiceDisconnected(ComponentName name) {}
};
}
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void startActivityAsUser() {
+ Intent intent = new Intent();
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Bundle options = new Bundle();
+
+ context.startActivityAsUser(intent, options, Process.myUserHandle());
+
+ Intent launchedActivityIntent = shadowOf(context).getNextStartedActivity();
+ assertThat(launchedActivityIntent).isEqualTo(intent);
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java
index d30abb2f8..3ccd80cb8 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java
@@ -2,6 +2,8 @@ package org.robolectric.shadows;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Build.VERSION_CODES.KITKAT;
+import static android.os.Build.VERSION_CODES.M;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
@@ -24,6 +26,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.UserHandle;
import android.view.LayoutInflater;
import android.view.View;
import androidx.test.core.app.ApplicationProvider;
@@ -39,6 +42,8 @@ import org.robolectric.R;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
@RunWith(AndroidJUnit4.class)
public class ShadowContextWrapperTest {
@@ -150,6 +155,92 @@ public class ShadowContextWrapperTest {
assertThat(resultReceiver.resultCode).isEqualTo(1);
}
+ @Test
+ @Config(minSdk = KITKAT)
+ public void sendOrderedBroadcastAsUser_shouldReturnValues() throws Exception {
+ String action = "test";
+
+ IntentFilter lowFilter = new IntentFilter(action);
+ lowFilter.setPriority(1);
+ BroadcastReceiver lowReceiver = broadcastReceiver("Low");
+ contextWrapper.registerReceiver(lowReceiver, lowFilter);
+
+ IntentFilter highFilter = new IntentFilter(action);
+ highFilter.setPriority(2);
+ BroadcastReceiver highReceiver = broadcastReceiver("High");
+ contextWrapper.registerReceiver(highReceiver, highFilter);
+
+ final FooReceiver resultReceiver = new FooReceiver();
+ contextWrapper.sendOrderedBroadcastAsUser(
+ new Intent(action), null, null, resultReceiver, null, 1, "initial", null);
+ assertThat(transcript).containsExactly("High notified of test", "Low notified of test");
+ assertThat(resultReceiver.resultCode).isEqualTo(1);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void sendOrderedBroadcastAsUser_withAppOp_shouldReturnValues() throws Exception {
+ String action = "test";
+
+ IntentFilter lowFilter = new IntentFilter(action);
+ lowFilter.setPriority(1);
+ BroadcastReceiver lowReceiver = broadcastReceiver("Low");
+ contextWrapper.registerReceiver(lowReceiver, lowFilter);
+
+ IntentFilter highFilter = new IntentFilter(action);
+ highFilter.setPriority(2);
+ BroadcastReceiver highReceiver = broadcastReceiver("High");
+ contextWrapper.registerReceiver(highReceiver, highFilter);
+
+ final FooReceiver resultReceiver = new FooReceiver();
+
+ ReflectionHelpers.callInstanceMethod(contextWrapper, "sendOrderedBroadcastAsUser",
+ ClassParameter.from(Intent.class, new Intent(action)),
+ ClassParameter.from(UserHandle.class, null),
+ ClassParameter.from(String.class, null),
+ ClassParameter.from(int.class, 1),
+ ClassParameter.from(BroadcastReceiver.class, resultReceiver),
+ ClassParameter.from(Handler.class, null),
+ ClassParameter.from(int.class, 1),
+ ClassParameter.from(String.class, "initial"),
+ ClassParameter.from(Bundle.class, null)
+ );
+
+ assertThat(transcript).containsExactly("High notified of test", "Low notified of test");
+ assertThat(resultReceiver.resultCode).isEqualTo(1);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void sendOrderedBroadcastAsUser_withAppOpAndOptions_shouldReturnValues() throws Exception {
+ String action = "test";
+
+ IntentFilter lowFilter = new IntentFilter(action);
+ lowFilter.setPriority(1);
+ BroadcastReceiver lowReceiver = broadcastReceiver("Low");
+ contextWrapper.registerReceiver(lowReceiver, lowFilter);
+
+ IntentFilter highFilter = new IntentFilter(action);
+ highFilter.setPriority(2);
+ BroadcastReceiver highReceiver = broadcastReceiver("High");
+ contextWrapper.registerReceiver(highReceiver, highFilter);
+
+ final FooReceiver resultReceiver = new FooReceiver();
+
+ ReflectionHelpers.callInstanceMethod(contextWrapper, "sendOrderedBroadcastAsUser",
+ ClassParameter.from(Intent.class, new Intent(action)),
+ ClassParameter.from(UserHandle.class, null),
+ ClassParameter.from(String.class, null),
+ ClassParameter.from(int.class, 1),
+ ClassParameter.from(Bundle.class, null),
+ ClassParameter.from(BroadcastReceiver.class, resultReceiver),
+ ClassParameter.from(Handler.class, null),
+ ClassParameter.from(int.class, 1),
+ ClassParameter.from(String.class, "initial"),
+ ClassParameter.from(Bundle.class, null)
+ );
+ }
+
private static final class FooReceiver extends BroadcastReceiver {
private int resultCode;
private SettableFuture<Void> settableFuture = SettableFuture.create();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCrossProfileAppsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCrossProfileAppsTest.java
new file mode 100644
index 000000000..cae91513a
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCrossProfileAppsTest.java
@@ -0,0 +1,397 @@
+package org.robolectric.shadows;
+
+import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Application;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.CrossProfileApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.UserHandle;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowCrossProfileApps.StartedActivity;
+import org.robolectric.shadows.ShadowCrossProfileApps.StartedMainActivity;
+
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = P)
+public class ShadowCrossProfileAppsTest {
+
+ private final Application application = ApplicationProvider.getApplicationContext();
+ private final CrossProfileApps crossProfileApps =
+ application.getSystemService(CrossProfileApps.class);
+ private final PackageManager packageManager = ((Context) application).getPackageManager();
+
+ private final UserHandle userHandle1 = UserHandle.of(10);
+ private final UserHandle userHandle2 = UserHandle.of(11);
+
+ @Test
+ public void getTargetUserProfiles_noProfilesAdded_shouldReturnEmpty() {
+ assertThat(crossProfileApps.getTargetUserProfiles()).isEmpty();
+ }
+
+ @Test
+ public void getTargetUserProfiles_oneProfileAdded_shouldReturnProfileAdded() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ assertThat(crossProfileApps.getTargetUserProfiles()).containsExactly(userHandle1);
+ }
+
+ @Test
+ public void getTargetUserProfiles_oneProfileAddedTwice_shouldReturnSingleProfileAdded() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ assertThat(crossProfileApps.getTargetUserProfiles()).containsExactly(userHandle1);
+ }
+
+ @Test
+ public void getTargetUserProfiles_multipleProfilesAdded_shouldReturnAllProfilesAddedInOrder() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle2);
+
+ assertThat(crossProfileApps.getTargetUserProfiles())
+ .containsExactly(userHandle1, userHandle2)
+ .inOrder();
+ }
+
+ @Test
+ public void
+ getTargetUserProfiles_multipleProfilesAddedInAlternateOrder_shouldReturnAllProfilesAddedInOrder() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle2);
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ assertThat(crossProfileApps.getTargetUserProfiles())
+ .containsExactly(userHandle2, userHandle1)
+ .inOrder();
+ }
+
+ @Test
+ public void
+ getTargetUserProfiles_multipleProfilesAddedAndFirstRemoved_shouldReturnSecondProfile() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle2);
+ shadowOf(crossProfileApps).removeTargetUserProfile(userHandle1);
+
+ assertThat(crossProfileApps.getTargetUserProfiles()).containsExactly(userHandle2);
+ }
+
+ @Test
+ public void getTargetUserProfiles_multipleProfilesAddedAndCleared_shouldReturnEmpty() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle2);
+ shadowOf(crossProfileApps).clearTargetUserProfiles();
+
+ assertThat(crossProfileApps.getTargetUserProfiles()).isEmpty();
+ }
+
+ @Test
+ public void getProfileSwitchingLabel_shouldNotBeEmpty() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ CharSequence label = crossProfileApps.getProfileSwitchingLabel(userHandle1);
+ assertThat(label.toString()).isNotEmpty();
+ }
+
+ @Test
+ public void getProfileSwitchingLabel_shouldBeDifferentForDifferentProfiles() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle2);
+
+ assertThat(crossProfileApps.getProfileSwitchingLabel(userHandle1).toString())
+ .isNotEqualTo(crossProfileApps.getProfileSwitchingLabel(userHandle2).toString());
+ }
+
+ @Test
+ public void getProfileSwitchingLabel_userNotAvailable_shouldThrowSecurityException() {
+ assertThrowsSecurityException(() -> crossProfileApps.getProfileSwitchingLabel(userHandle1));
+ }
+
+ @Test
+ public void getProfileSwitchingIconDrawable_shouldNotBeNull() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ Drawable icon = crossProfileApps.getProfileSwitchingIconDrawable(userHandle1);
+ assertThat(icon).isNotNull();
+ }
+
+ @Test
+ public void getProfileSwitchingIconDrawable_shouldBeDifferentForDifferentProfiles() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle2);
+
+ assertThat(crossProfileApps.getProfileSwitchingIconDrawable(userHandle1))
+ .isNotEqualTo(crossProfileApps.getProfileSwitchingIconDrawable(userHandle2));
+ }
+
+ @Test
+ public void getProfileSwitchingIconDrawable_userNotAvailable_shouldThrowSecurityException() {
+ assertThrowsSecurityException(
+ () -> crossProfileApps.getProfileSwitchingIconDrawable(userHandle1));
+ }
+
+ @Test
+ public void startMainActivity_launcherActivityInManifest_shouldSucceed() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ ComponentName component =
+ ComponentName.createRelative(application, ".shadows.TestActivityAlias");
+ crossProfileApps.startMainActivity(component, userHandle1);
+
+ StartedActivity startedActivity = shadowOf(crossProfileApps).peekNextStartedActivity();
+ assertThat(startedActivity.getComponentName()).isEqualTo(component);
+ assertThat(startedActivity.getUserHandle()).isEqualTo(userHandle1);
+ assertThat(startedActivity).isEqualTo(new StartedActivity(component, userHandle1));
+ }
+
+ @Test
+ public void
+ startMainActivity_launcherActivityInManifest_withoutCrossProfilePermission_shouldSucceed() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ shadowOf(application).denyPermissions(INTERACT_ACROSS_PROFILES);
+
+ ComponentName component =
+ ComponentName.createRelative(application, ".shadows.TestActivityAlias");
+ crossProfileApps.startMainActivity(component, userHandle1);
+
+ StartedActivity startedActivity = shadowOf(crossProfileApps).peekNextStartedActivity();
+ assertThat(startedActivity.getComponentName()).isEqualTo(component);
+ assertThat(startedActivity.getUserHandle()).isEqualTo(userHandle1);
+ assertThat(startedActivity).isEqualTo(new StartedActivity(component, userHandle1));
+ }
+
+ @Test
+ public void startMainActivity_launcherActivityInManifest_shouldStillAddStartedMainActivity() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ ComponentName component =
+ ComponentName.createRelative(application, ".shadows.TestActivityAlias");
+ crossProfileApps.startMainActivity(component, userHandle1);
+
+ StartedMainActivity startedMainActivity =
+ shadowOf(crossProfileApps).peekNextStartedMainActivity();
+ assertThat(startedMainActivity.getComponentName()).isEqualTo(component);
+ assertThat(startedMainActivity.getUserHandle()).isEqualTo(userHandle1);
+ assertThat(startedMainActivity).isEqualTo(new StartedMainActivity(component, userHandle1));
+ }
+
+ @Test
+ public void startMainActivity_nonLauncherActivityInManifest_shouldThrowSecurityException() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ ComponentName component = ComponentName.createRelative(application, ".shadows.TestActivity");
+ assertThrowsSecurityException(() -> crossProfileApps.startMainActivity(component, userHandle1));
+ }
+
+ @Test
+ public void startMainActivity_nonExistentActivity_shouldThrowSecurityException() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ ComponentName component =
+ ComponentName.createRelative(application, ".shadows.FakeTestActivity");
+ assertThrowsSecurityException(() -> crossProfileApps.startMainActivity(component, userHandle1));
+ }
+
+ @Test
+ public void startMainActivity_userNotAvailable_shouldThrowSecurityException() {
+ ComponentName component =
+ ComponentName.createRelative(application, ".shadows.TestActivityAlias");
+ assertThrowsSecurityException(() -> crossProfileApps.startMainActivity(component, userHandle1));
+ }
+
+ @Test
+ @Config(sdk = Q)
+ public void startActivity_launcherActivityInManifest_shouldSucceed() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ shadowOf(application).grantPermissions(INTERACT_ACROSS_PROFILES);
+
+ ComponentName component =
+ ComponentName.createRelative(application, ".shadows.TestActivityAlias");
+ crossProfileApps.startActivity(component, userHandle1);
+
+ StartedActivity startedMainActivity = shadowOf(crossProfileApps).peekNextStartedActivity();
+ assertThat(startedMainActivity.getComponentName()).isEqualTo(component);
+ assertThat(startedMainActivity.getUserHandle()).isEqualTo(userHandle1);
+ assertThat(startedMainActivity).isEqualTo(new StartedActivity(component, userHandle1));
+ }
+
+ @Test
+ @Config(sdk = Q)
+ public void startActivity_nonLauncherActivityInManifest_shouldSucceed() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ shadowOf(application).grantPermissions(INTERACT_ACROSS_PROFILES);
+
+ ComponentName component = ComponentName.createRelative(application, ".shadows.TestActivity");
+
+ crossProfileApps.startActivity(component, userHandle1);
+
+ StartedActivity startedMainActivity = shadowOf(crossProfileApps).peekNextStartedActivity();
+ assertThat(startedMainActivity.getComponentName()).isEqualTo(component);
+ assertThat(startedMainActivity.getUserHandle()).isEqualTo(userHandle1);
+ assertThat(startedMainActivity).isEqualTo(new StartedActivity(component, userHandle1));
+ }
+
+ @Test
+ @Config(sdk = Q)
+ public void startActivity_nonExistentActivity_shouldThrowSecurityException() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ shadowOf(application).grantPermissions(INTERACT_ACROSS_PROFILES);
+
+ ComponentName component =
+ ComponentName.createRelative(application, ".shadows.FakeTestActivity");
+ assertThrowsSecurityException(() -> crossProfileApps.startActivity(component, userHandle1));
+ }
+
+ @Test
+ @Config(sdk = Q)
+ public void startActivity_userNotAvailable_shouldThrowSecurityException() {
+ shadowOf(application).grantPermissions(INTERACT_ACROSS_PROFILES);
+
+ ComponentName component =
+ ComponentName.createRelative(application, ".shadows.TestActivityAlias");
+ assertThrowsSecurityException(() -> crossProfileApps.startActivity(component, userHandle1));
+ }
+
+ @Test
+ @Config(sdk = Q)
+ public void startActivity_withoutPermission_shouldThrowSecurityException() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ shadowOf(application).denyPermissions(INTERACT_ACROSS_PROFILES);
+
+ ComponentName component = ComponentName.createRelative(application, ".shadows.TestActivity");
+
+ assertThrowsSecurityException(() -> crossProfileApps.startActivity(component, userHandle1));
+ }
+
+ @Test
+ public void addTargetProfile_currentUserHandle_shouldThrowIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> shadowOf(crossProfileApps).addTargetUserProfile(Process.myUserHandle()));
+ }
+
+ @Test
+ public void peekNextStartedActivity_activityStarted_shouldReturnAndNotConsumeActivity() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ ComponentName component =
+ ComponentName.createRelative(application, ".shadows.TestActivityAlias");
+ crossProfileApps.startMainActivity(component, userHandle1);
+
+ StartedActivity startedActivity = shadowOf(crossProfileApps).peekNextStartedActivity();
+
+ assertThat(startedActivity).isEqualTo(new StartedActivity(component, userHandle1));
+ assertThat(shadowOf(crossProfileApps).peekNextStartedActivity()).isSameAs(startedActivity);
+ }
+
+ @Test
+ public void peekNextStartedActivity_activityNotStarted_shouldReturnNull() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ assertThat(shadowOf(crossProfileApps).peekNextStartedActivity()).isNull();
+ }
+
+ @Test
+ public void getNextStartedActivity_activityStarted_shouldReturnAndConsumeActivity() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ ComponentName component =
+ ComponentName.createRelative(application, ".shadows.TestActivityAlias");
+ crossProfileApps.startMainActivity(component, userHandle1);
+
+ StartedActivity startedActivity = shadowOf(crossProfileApps).getNextStartedActivity();
+
+ assertThat(startedActivity).isEqualTo(new StartedActivity(component, userHandle1));
+ assertThat(shadowOf(crossProfileApps).peekNextStartedActivity()).isNull();
+ }
+
+ @Test
+ public void getNextStartedActivity_activityNotStarted_shouldReturnNull() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ assertThat(shadowOf(crossProfileApps).getNextStartedActivity()).isNull();
+ }
+
+ @Test
+ public void
+ clearNextStartedActivities_activityStarted_shouldClearReferencesToStartedActivities() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+ ComponentName component =
+ ComponentName.createRelative(application, ".shadows.TestActivityAlias");
+ crossProfileApps.startMainActivity(component, userHandle1);
+
+ shadowOf(crossProfileApps).clearNextStartedActivities();
+
+ assertThat(shadowOf(crossProfileApps).peekNextStartedActivity()).isNull();
+ }
+
+ @Test
+ public void clearNextStartedActivities_activityNotStarted_shouldBeNoOp() {
+ shadowOf(crossProfileApps).addTargetUserProfile(userHandle1);
+
+ shadowOf(crossProfileApps).clearNextStartedActivities();
+
+ assertThat(shadowOf(crossProfileApps).peekNextStartedActivity()).isNull();
+ }
+
+ // BEGIN-INTERNAL
+ @Test
+ @Config(minSdk = R)
+ public void clearInteractAcrossProfilesAppOps_clearsAppOps() {
+ String testPackage1 = "com.example.testpackage1";
+ String testPackage2 = "com.example.testpackage2";
+ shadowOf(packageManager).installPackage(buildTestPackageInfo(testPackage1));
+ shadowOf(packageManager).installPackage(buildTestPackageInfo(testPackage2));
+ shadowOf(crossProfileApps).setInteractAcrossProfilesAppOp(testPackage1, MODE_ALLOWED);
+ shadowOf(crossProfileApps).setInteractAcrossProfilesAppOp(testPackage2, MODE_ALLOWED);
+
+ shadowOf(crossProfileApps).clearInteractAcrossProfilesAppOps();
+
+ assertThat(shadowOf(crossProfileApps).getInteractAcrossProfilesAppOp(testPackage1))
+ .isEqualTo(MODE_DEFAULT);
+ assertThat(shadowOf(crossProfileApps).getInteractAcrossProfilesAppOp(testPackage2))
+ .isEqualTo(MODE_DEFAULT);
+ }
+
+ private PackageInfo buildTestPackageInfo(String packageName) {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.packageName = packageName;
+ packageInfo.applicationInfo.name = "test";
+ return packageInfo;
+ }
+ // END-INTERNAL
+
+ private static void assertThrowsSecurityException(Runnable runnable) {
+ assertThrows(SecurityException.class, runnable);
+ }
+
+ private static <T extends Throwable> void assertThrows(Class<T> clazz, Runnable runnable) {
+ try {
+ runnable.run();
+ } catch (Throwable t) {
+ if (clazz.isInstance(t)) {
+ // expected
+ return;
+ } else {
+ fail("did not throw " + clazz.getName() + ", threw " + t + " instead");
+ }
+ }
+ fail("did not throw " + clazz.getName());
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
index dce3c7248..46a8688d3 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
@@ -97,6 +97,8 @@ import org.robolectric.R;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowPackageManager.PackageSetting;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
@RunWith(AndroidJUnit4.class)
public class ShadowPackageManagerTest {
@@ -107,6 +109,7 @@ public class ShadowPackageManagerTest {
private static final String TEST_PACKAGE2_NAME = "com.a.second.package";
private static final String TEST_PACKAGE2_LABEL = "A Second App";
private static final String TEST_APP2_PATH = "/values/app/application2.apk";
+ private static final Object USER_ID = 1;
protected ShadowPackageManager shadowPackageManager;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@@ -932,6 +935,43 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = JELLY_BEAN_MR1)
+ public void resolveActivityAsUser_Match() throws Exception {
+ Intent i = new Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER);
+ ResolveInfo info = new ResolveInfo();
+ info.nonLocalizedLabel = TEST_PACKAGE_LABEL;
+ info.activityInfo = new ActivityInfo();
+ info.activityInfo.name = "name";
+ info.activityInfo.packageName = TEST_PACKAGE_NAME;
+ shadowPackageManager.addResolveInfoForIntent(i, info);
+
+ ResolveInfo resolvedActivity =
+ ReflectionHelpers.callInstanceMethod(packageManager, "resolveActivityAsUser",
+ ClassParameter.from(Intent.class, i),
+ ClassParameter.from(int.class, 0),
+ ClassParameter.from(int.class, USER_ID));
+
+ assertThat(resolvedActivity).isNotNull();
+ assertThat(resolvedActivity.activityInfo.name).isEqualTo("name");
+ assertThat(resolvedActivity.activityInfo.packageName).isEqualTo(TEST_PACKAGE_NAME);
+ }
+
+ @Test
+ @Config(minSdk = JELLY_BEAN_MR1)
+ public void resolveActivityAsUser_NoMatch() throws Exception {
+ Intent i = new Intent();
+ i.setComponent(new ComponentName("foo.bar", "No Activity"));
+
+ ResolveInfo resolvedActivity =
+ ReflectionHelpers.callInstanceMethod(packageManager, "resolveActivityAsUser",
+ ClassParameter.from(Intent.class, i),
+ ClassParameter.from(int.class, 0),
+ ClassParameter.from(int.class, USER_ID));
+
+ assertThat(resolvedActivity).isNull();
+ }
+
+ @Test
public void queryIntentServices_EmptyResult() throws Exception {
Intent i = new Intent(Intent.ACTION_MAIN, null);
i.addCategory(Intent.CATEGORY_LAUNCHER);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
index cd67c4c63..e05b8f868 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
@@ -34,6 +34,11 @@ public class ShadowUserManagerTest {
private UserManager userManager;
private Context context;
+ private static final int TEST_USER_HANDLE = 0;
+ private static final int PROFILE_USER_HANDLE = 2;
+ private static final String PROFILE_USER_NAME = "profile";
+ private static final int PROFILE_USER_FLAGS = 0;
+
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
@@ -131,6 +136,18 @@ public class ShadowUserManagerTest {
}
@Test
+ @Config(minSdk = N)
+ public void isManagedProfileWithHandle() {
+ shadowOf(userManager).addUser(TEST_USER_HANDLE, "secondary user", 0);
+ shadowOf(userManager).addProfile(
+ TEST_USER_HANDLE,
+ PROFILE_USER_HANDLE,
+ "another managed profile",
+ ShadowUserManager.FLAG_MANAGED_PROFILE);
+ assertThat(userManager.isManagedProfile(PROFILE_USER_HANDLE)).isTrue();
+ }
+
+ @Test
@Config(minSdk = LOLLIPOP)
public void enforcePermissionChecks() throws Exception {
shadowOf(userManager).enforcePermissionChecks(true);
@@ -335,6 +352,15 @@ public class ShadowUserManagerTest {
}
}
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void getProfiles_addedProfile_containsProfile() {
+ shadowOf(userManager).addProfile(
+ TEST_USER_HANDLE, PROFILE_USER_HANDLE, PROFILE_USER_NAME, PROFILE_USER_FLAGS);
+
+ assertThat(userManager.getProfiles(TEST_USER_HANDLE).get(0).id).isEqualTo(PROFILE_USER_HANDLE);
+ }
+
// Create user handle from parcel since UserHandle.of() was only added in later APIs.
private static UserHandle newUserHandle(int uid) {
Parcel userParcel = Parcel.obtain();
diff --git a/run_robolectric_module_tests.mk b/run_robolectric_module_tests.mk
index 4d5cb95c5..43c93ae61 100644
--- a/run_robolectric_module_tests.mk
+++ b/run_robolectric_module_tests.mk
@@ -51,7 +51,8 @@ copy_android_all_jar_pairs := \
$(android_all_source_dir)/android-all-8.0.0_r4-robolectric-r1.jar:$(android_all_target_dir)/android-all-8.0.0_r4-robolectric-r1.jar \
$(android_all_source_dir)/android-all-8.1.0-robolectric-4611349.jar:$(android_all_target_dir)/android-all-8.1.0-robolectric-4611349.jar \
$(android_all_source_dir)/android-all-9-robolectric-4913185-2.jar:$(android_all_target_dir)/android-all-9-robolectric-4913185-2.jar \
- $(local_android_all_source_jar):$(android_all_target_dir)/android-all-Q-robolectric-r0.jar
+ $(android_all_source_dir)/android-all-9plus-robolectric-5616371.jar:$(android_all_target_dir)/android-all-9plus-robolectric-5616371.jar \
+ $(local_android_all_source_jar):$(android_all_target_dir)/android-all-R-robolectric-r0.jar
copy_android_all_jars := $(call copy-many-files, $(copy_android_all_jar_pairs))
# If debugging the tests was requested, set up the JVM parameters to enable it.
diff --git a/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java b/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java
index 463c8170f..f6bf1bc6a 100644
--- a/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java
+++ b/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java
@@ -126,30 +126,6 @@ public final class DisplayConfig {
public int logicalHeight;
/**
- * @hide
- * Number of overscan pixels on the left side of the display.
- */
- public int overscanLeft;
-
- /**
- * @hide
- * Number of overscan pixels on the top side of the display.
- */
- public int overscanTop;
-
- /**
- * @hide
- * Number of overscan pixels on the right side of the display.
- */
- public int overscanRight;
-
- /**
- * @hide
- * Number of overscan pixels on the bottom side of the display.
- */
- public int overscanBottom;
-
- /**
* The rotation of the display relative to its natural orientation.
* May be one of {@link Surface#ROTATION_0},
* {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180},
@@ -281,12 +257,6 @@ public final class DisplayConfig {
largestNominalAppHeight = other.largestNominalAppHeight;
logicalWidth = other.logicalWidth;
logicalHeight = other.logicalHeight;
- if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) {
- overscanLeft = other.overscanLeft;
- overscanTop = other.overscanTop;
- overscanRight = other.overscanRight;
- overscanBottom = other.overscanBottom;
- }
rotation = other.rotation;
if (RuntimeEnvironment.getApiLevel() >= M) {
modeId = other.modeId;
@@ -339,10 +309,6 @@ public final class DisplayConfig {
&& largestNominalAppHeight == other.largestNominalAppHeight
&& logicalWidth == other.logicalWidth
&& logicalHeight == other.logicalHeight
- && overscanLeft == other.overscanLeft
- && overscanTop == other.overscanTop
- && overscanRight == other.overscanRight
- && overscanBottom == other.overscanBottom
&& rotation == other.rotation
&& modeId == other.modeId
&& defaultModeId == other.defaultModeId
@@ -380,10 +346,6 @@ public final class DisplayConfig {
largestNominalAppHeight = other.largestNominalAppHeight;
logicalWidth = other.logicalWidth;
logicalHeight = other.logicalHeight;
- overscanLeft = other.overscanLeft;
- overscanTop = other.overscanTop;
- overscanRight = other.overscanRight;
- overscanBottom = other.overscanBottom;
rotation = other.rotation;
modeId = other.modeId;
defaultModeId = other.defaultModeId;
@@ -422,12 +384,6 @@ public final class DisplayConfig {
other.largestNominalAppHeight = largestNominalAppHeight;
other.logicalWidth = logicalWidth;
other.logicalHeight = logicalHeight;
- if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) {
- other.overscanLeft = overscanLeft;
- other.overscanTop = overscanTop;
- other.overscanRight = overscanRight;
- other.overscanBottom = overscanBottom;
- }
other.rotation = rotation;
if (RuntimeEnvironment.getApiLevel() >= M) {
other.modeId = modeId;
@@ -475,17 +431,6 @@ public final class DisplayConfig {
sb.append(logicalWidth);
sb.append(" x ");
sb.append(logicalHeight);
- if (overscanLeft != 0 || overscanTop != 0 || overscanRight != 0 || overscanBottom != 0) {
- sb.append(", overscan (");
- sb.append(overscanLeft);
- sb.append(",");
- sb.append(overscanTop);
- sb.append(",");
- sb.append(overscanRight);
- sb.append(",");
- sb.append(overscanBottom);
- sb.append(")");
- }
sb.append(", largest app ");
sb.append(largestNominalAppWidth);
sb.append(" x ");
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityThread.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityThread.java
index 29c9a8519..f41493e1a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityThread.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityThread.java
@@ -1,14 +1,20 @@
package org.robolectric.shadows;
+import static android.os.Build.VERSION_CODES.R;
+
import android.app.ActivityThread;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
+import android.os.UserHandle;
+
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
+
import javax.annotation.Nonnull;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
@@ -71,18 +77,51 @@ public class ShadowActivityThread {
} catch (PackageManager.NameNotFoundException e) {
throw new RemoteException(e.getMessage());
}
+ } else if (method.getName().equals("getInstalledApplications")) {
+ int flags = (Integer) args[0];
+ int userId = (Integer) args[1];
+ return new ParceledListSlice<>(
+ RuntimeEnvironment.application
+ .getApplicationContext()
+ .createContextAsUser(UserHandle.of(userId), /* flags= */ 0)
+ .getPackageManager()
+ .getInstalledApplications(flags));
} else if (method.getName().equals("notifyPackageUse")) {
return null;
} else if (method.getName().equals("getPackageInstaller")) {
return null;
- } else if (method.getName().equals("getSplitPermissions")) {
- return Collections.emptyList();
}
throw new UnsupportedOperationException("sorry, not supporting " + method + " yet!");
}
});
}
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = R)
+ public static Object getPermissionManager() {
+ ClassLoader classLoader = ShadowActivityThread.class.getClassLoader();
+ Class<?> iPermissionManagerClass;
+ try {
+ iPermissionManagerClass = classLoader.loadClass("android.permission.IPermissionManager");
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ return Proxy.newProxyInstance(
+ classLoader,
+ new Class[] {iPermissionManagerClass},
+ new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, @Nonnull Method method, Object[] args)
+ throws Exception {
+ if (method.getName().equals("getSplitPermissions")) {
+ return Collections.emptyList();
+ }
+ return method.getDefaultValue();
+ }
+ });
+ }
+ // END-INTERNAL
+
@Implementation
public static Object currentActivityThread() {
return RuntimeEnvironment.getActivityThread();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAnnotationValidations.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAnnotationValidations.java
new file mode 100644
index 000000000..45f3a79e2
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAnnotationValidations.java
@@ -0,0 +1,19 @@
+package org.robolectric.shadows;
+
+import java.lang.annotation.Annotation;
+import com.android.internal.util.AnnotationValidations;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(value = AnnotationValidations.class)
+public class ShadowAnnotationValidations {
+ @Implementation
+ public static void validate(Class<? extends Annotation> annotation,
+ Annotation ignored, int value) {
+ }
+
+ @Implementation
+ public static void validate(Class<? extends Annotation> annotation,
+ Annotation ignored, long value) {
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java
index c250dfcd9..04a2f79f7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java
@@ -6,6 +6,7 @@ import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.P;
// BEGIN-INTERNAL
import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
// END-INTERNAL
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
@@ -13,6 +14,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributedOpEntry;
import android.app.AppOpsManager.OnOpChangedListener;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.PackageOps;
@@ -23,6 +25,7 @@ import android.os.Binder;
import android.os.Build;
import android.util.LongSparseArray;
import android.util.LongSparseLongArray;
+
import com.android.internal.app.IAppOpsService;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
@@ -129,6 +132,24 @@ public class ShadowAppOpsManager {
}
// END-INTERNAL
+ /**
+ * Like {@link #unsafeCheckOpNoThrow(String, int, String)} but returns the <em>raw</em> mode
+ * associated with the op. Does not throw a security exception, does not translate {@link
+ * AppOpsManager#MODE_FOREGROUND}.
+ */
+ @Implementation(minSdk = Q)
+ protected int unsafeCheckOpRawNoThrow(String op, int uid, String packageName) {
+ return unsafeCheckOpRawNoThrow(AppOpsManager.strOpToOp(op), uid, packageName);
+ }
+
+ private int unsafeCheckOpRawNoThrow(int op, int uid, String packageName) {
+ Integer mode = appModeMap.get(getOpMapKey(uid, packageName, op));
+ if (mode == null) {
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ return mode;
+ }
+
@Implementation(minSdk = P)
@Deprecated // renamed to unsafeCheckOpNoThrow
protected int checkOpNoThrow(String op, int uid, String packageName) {
@@ -151,7 +172,7 @@ public class ShadowAppOpsManager {
return mode;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation(minSdk = KITKAT, maxSdk = Q)
public int noteOp(int op, int uid, String packageName) {
mStoredOps.put(getInternalKey(uid, packageName), op);
@@ -159,13 +180,33 @@ public class ShadowAppOpsManager {
return AppOpsManager.MODE_ALLOWED;
}
- @Implementation(minSdk = M)
+ @Implementation(minSdk = R)
+ @HiddenApi
+ public int noteOp(int op, int uid, String packageName, String message) {
+ mStoredOps.put(getInternalKey(uid, packageName), op);
+
+ // Permission check not currently implemented in this shadow.
+ return AppOpsManager.MODE_ALLOWED;
+ }
+
+ @Implementation(minSdk = M, maxSdk = Q)
@HiddenApi
protected int noteProxyOpNoThrow(int op, String proxiedPackageName) {
mStoredOps.put(getInternalKey(Binder.getCallingUid(), proxiedPackageName), op);
return checkOpNoThrow(op, Binder.getCallingUid(), proxiedPackageName);
}
+ @Implementation(minSdk = R)
+ @HiddenApi
+ protected int noteProxyOpNoThrow(int op, String proxiedPackageName, int proxiedUid,
+ String featureId, String message) {
+ if (featureId != null) {
+ throw new RuntimeException("non null featureIds are not supported by Robolectric yet");
+ }
+ mStoredOps.put(getInternalKey(proxiedUid, proxiedPackageName), op);
+ return checkOpNoThrow(op, proxiedUid, proxiedPackageName);
+ }
+
@Implementation(minSdk = KITKAT)
@HiddenApi
public List<PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) {
@@ -274,8 +315,28 @@ public class ShadowAppOpsManager {
final LongSparseArray<String> proxyPackages = new LongSparseArray<>();
proxyPackages.put(key, PROXY_PACKAGE);
- return new OpEntry(op, false, AppOpsManager.MODE_ALLOWED, accessTimes,
- durations, rejectTimes, proxyUids, proxyPackages);
+ if (RuntimeEnvironment.getApiLevel() <= Build.VERSION_CODES.Q) {
+ return ReflectionHelpers.callConstructor(
+ OpEntry.class,
+ ClassParameter.from(int.class, op),
+ ClassParameter.from(boolean.class, false),
+ ClassParameter.from(int.class, AppOpsManager.MODE_ALLOWED),
+ ClassParameter.from(LongSparseLongArray.class, accessTimes),
+ ClassParameter.from(LongSparseLongArray.class, durations),
+ ClassParameter.from(LongSparseLongArray.class, rejectTimes),
+ ClassParameter.from(LongSparseLongArray.class, proxyUids),
+ ClassParameter.from(LongSparseArray.class, proxyPackages));
+ }
+
+ LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = new LongSparseArray<>();
+ LongSparseArray<AppOpsManager.NoteOpEvent> rejectEvents = new LongSparseArray<>();
+
+ accessEvents.put(key, new AppOpsManager.NoteOpEvent(OP_TIME, DURATION,
+ new AppOpsManager.OpEventProxyInfo(PROXY_UID, PROXY_PACKAGE, null)));
+ rejectEvents.put(key, new AppOpsManager.NoteOpEvent(REJECT_TIME, -1, null));
+
+ return new OpEntry(op, AppOpsManager.MODE_ALLOWED, Collections.singletonMap(null,
+ new AttributedOpEntry(op, false, accessEvents, rejectEvents)));
// END-INTERNAL
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java
index 821027bec..118d2f64e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java
@@ -21,6 +21,8 @@ import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
+
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
@@ -79,6 +81,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.storage.VolumeInfo;
+import android.permission.IPermissionManager;
import android.telecom.TelecomManager;
import android.util.Pair;
import com.google.common.base.Function;
@@ -115,7 +118,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
private Context context;
- @Implementation
+ @Implementation(maxSdk = Q)
protected void __constructor__(Object contextImpl, Object pm) {
try {
invokeConstructor(
@@ -129,6 +132,24 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
context = (Context) contextImpl;
}
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = R)
+ protected void __constructor__(
+ Object contextImpl, Object packageManager, Object permissionManager) {
+ try {
+ invokeConstructor(
+ ApplicationPackageManager.class,
+ realObject,
+ from(Class.forName(ShadowContextImpl.CLASS_NAME), contextImpl),
+ from(IPackageManager.class, packageManager),
+ from(IPermissionManager.class, permissionManager));
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ context = (Context) contextImpl;
+ }
+ // END-INTERNAL
+
@Implementation
public List<PackageInfo> getInstalledPackages(int flags) {
List<PackageInfo> result = new ArrayList<>();
@@ -999,8 +1020,8 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
}
@Implementation
- protected Drawable getApplicationIcon(ApplicationInfo info) {
- return null;
+ protected Drawable getApplicationIcon(ApplicationInfo info) throws NameNotFoundException {
+ return getApplicationIcon(info.packageName);
}
@Implementation(minSdk = LOLLIPOP)
@@ -1086,7 +1107,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
@Implementation(minSdk = N)
protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
throws NameNotFoundException {
- return null;
+ return getPackageInfo(packageName, flags);
}
@Implementation
@@ -1198,6 +1219,13 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
throw new NameNotFoundException(packageName);
}
+ @Implementation
+ protected ApplicationInfo getApplicationInfoAsUser(
+ String packageName, int flags, UserHandle userId) throws NameNotFoundException {
+ // Currently does not use the user ID.
+ return getApplicationInfo(packageName, flags);
+ }
+
/**
* Returns all the values added via {@link
* ShadowPackageManager#addSystemSharedLibraryName(String)}.
@@ -1320,7 +1348,11 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
@Implementation(minSdk = N)
protected List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
- return null;
+ List<PackageInfo> packages = new ArrayList<>();
+ for (String packageName : packagesForUserId.getOrDefault(userId, new ArrayList<>())) {
+ packages.add(packageInfos.get(packageName));
+ }
+ return packages;
}
@Implementation(minSdk = JELLY_BEAN_MR2)
@@ -1328,9 +1360,10 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
return null;
}
+ /** Behaves as {@link #resolveActivity(Intent, int)} and currently ignores userId. */
@Implementation(minSdk = JELLY_BEAN_MR1)
protected ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
- return null;
+ return resolveActivity(intent, flags);
}
@Implementation
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java
index 109c191c9..2b9be09fa 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java
@@ -8,13 +8,9 @@ import static org.robolectric.shadow.api.Shadow.directlyOn;
import android.annotation.NonNull;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
+import android.content.res.loader.AssetsProvider;
import android.os.Build;
-import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Objects;
+
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -28,6 +24,13 @@ import org.robolectric.shadows.ShadowApkAssets.Picker;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Objects;
+
// transliterated from
// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_content_res_ApkAssets.cpp
@@ -51,6 +54,12 @@ public class ShadowArscApkAssets9 extends ShadowApkAssets {
// using ::android::base::unique_fd;
//
// namespace android {
+ // BEGIN-INTERNAL
+ private static final int PROPERTY_SYSTEM = 1 << 0;
+ private static final int PROPERTY_DYNAMIC = 1 << 1;
+ private static final int PROPERTY_LOADER = 1 << 2;
+ private static final int PROPERTY_OVERLAY = 1 << 3;
+ // END-INTERNAL
private static final String FRAMEWORK_APK_PATH =
ReflectionHelpers.getStaticField(AssetManager.class, "FRAMEWORK_APK_PATH");
@@ -167,6 +176,25 @@ public class ShadowArscApkAssets9 extends ShadowApkAssets {
ClassParameter.from(boolean.class, system)));
}
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = Build.VERSION_CODES.R)
+ protected static ApkAssets loadFromPath(String path, int flags)
+ throws IOException {
+ boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM;
+
+ if (FRAMEWORK_APK_PATH.equals(path)) {
+ path = RuntimeEnvironment.getAndroidFrameworkJarPath();
+ }
+
+ String finalPath = path;
+ return getFromCacheOrLoad(
+ new Key(null, path, system, false, false),
+ () -> directlyOn(ApkAssets.class, "loadFromPath",
+ ClassParameter.from(String.class, finalPath),
+ ClassParameter.from(int.class, flags)));
+ }
+ // END-INTERNAL
+
@Implementation
protected static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
boolean forceSharedLibrary) throws IOException {
@@ -178,19 +206,51 @@ public class ShadowArscApkAssets9 extends ShadowApkAssets {
ClassParameter.from(boolean.class, forceSharedLibrary)));
}
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = Build.VERSION_CODES.R)
+ protected static @NonNull ApkAssets loadFromPath(@NonNull String path, int flags,
+ AssetsProvider assets) throws IOException {
+ boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM;
+ boolean forceSharedLibrary = (flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC;
+ return getFromCacheOrLoad(
+ new Key(null, path, system, forceSharedLibrary, false),
+ () -> directlyOn(ApkAssets.class, "loadFromPath",
+ ClassParameter.from(String.class, path),
+ ClassParameter.from(int.class, flags),
+ ClassParameter.from(AssetsProvider.class, assets)));
+ }
+ // END-INTERNAL
+
@Implementation
protected static ApkAssets loadFromFd(FileDescriptor fd,
String friendlyName, boolean system, boolean forceSharedLibrary)
throws IOException {
return getFromCacheOrLoad(
new Key(fd, friendlyName, system, forceSharedLibrary, false),
- () -> directlyOn(ApkAssets.class, "loadFromPath",
+ () -> directlyOn(ApkAssets.class, "loadFromFd",
ClassParameter.from(FileDescriptor.class, fd),
ClassParameter.from(String.class, friendlyName),
ClassParameter.from(boolean.class, system),
ClassParameter.from(boolean.class, forceSharedLibrary)));
}
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = Build.VERSION_CODES.R)
+ protected static ApkAssets loadFromFd(FileDescriptor fd,
+ String friendlyName, int flags, AssetsProvider assets)
+ throws IOException {
+ boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM;
+ boolean forceSharedLibrary = (flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC;
+ return getFromCacheOrLoad(
+ new Key(fd, friendlyName, system, forceSharedLibrary, false),
+ () -> directlyOn(ApkAssets.class, "loadFromFd",
+ ClassParameter.from(FileDescriptor.class, fd),
+ ClassParameter.from(String.class, friendlyName),
+ ClassParameter.from(int.class, flags),
+ ClassParameter.from(AssetsProvider.class, assets)));
+ }
+ // END-INTERNAL
+
@Implementation
protected static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
throws IOException {
@@ -206,7 +266,7 @@ public class ShadowArscApkAssets9 extends ShadowApkAssets {
// static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
// jboolean force_shared_lib, jboolean overlay) {
- @Implementation
+ @Implementation(maxSdk = Build.VERSION_CODES.Q)
protected static long nativeLoad(String java_path, boolean system,
boolean force_shared_lib, boolean overlay) throws IOException {
String path = java_path;
@@ -239,9 +299,50 @@ public class ShadowArscApkAssets9 extends ShadowApkAssets {
return Registries.NATIVE_APK_ASSETS_REGISTRY.register(apk_assets);
}
+ // BEGIN-INTERNAL
+ // static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
+ // jstring java_path, const jint property_flags, jobject assets_provider)
+ @Implementation(minSdk = Build.VERSION_CODES.R)
+ protected static long nativeLoad(int format, String java_path,
+ int flags, AssetsProvider asset) throws IOException {
+ String path = java_path;
+ if (path == null) {
+ return 0;
+ }
+
+ ATRACE_NAME(String.format("LoadApkAssets(%s)", path));
+
+ boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM;
+ boolean overlay = (flags & PROPERTY_OVERLAY) == PROPERTY_OVERLAY;
+ boolean force_shared_lib = (flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC;
+ CppApkAssets apk_assets;
+ try {
+ if (overlay) {
+ apk_assets = CppApkAssets.LoadOverlay(path, system);
+ } else if (force_shared_lib) {
+ apk_assets =
+ CppApkAssets.LoadAsSharedLibrary(path, system);
+ } else {
+ apk_assets = CppApkAssets.Load(path, system);
+ }
+ } catch (OutOfMemoryError e) {
+ OutOfMemoryError outOfMemoryError = new OutOfMemoryError("Failed to load " + path);
+ outOfMemoryError.initCause(e);
+ throw outOfMemoryError;
+ }
+
+ if (apk_assets == null) {
+ String error_msg = String.format("Failed to load asset path %s", path);
+ throw new IOException(error_msg);
+ }
+ return Registries.NATIVE_APK_ASSETS_REGISTRY.register(apk_assets);
+ }
+ // END-INTERNAL
+
// static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
-// jstring friendly_name, jboolean system, jboolean force_shared_lib) {
- @Implementation
+// jstring friendly_name, jboolean system, jboolean
+// force_shared_lib) {
+ @Implementation(maxSdk = Build.VERSION_CODES.Q)
protected static long nativeLoadFromFd(FileDescriptor file_descriptor,
String friendly_name, boolean system, boolean force_shared_lib) {
String friendly_name_utf8 = friendly_name;
@@ -275,6 +376,45 @@ public class ShadowArscApkAssets9 extends ShadowApkAssets {
// return ShadowArscAssetManager9.NATIVE_APK_ASSETS_REGISTRY.getNativeObjectId(apk_assets);
}
+ // BEGIN-INTERNAL
+ // static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
+ // jobject file_descriptor, jstring friendly_name,
+ // const jint property_flags, jobject assets_provider) {
+ @Implementation(minSdk = Build.VERSION_CODES.R)
+ protected static long nativeLoadFromFd(int format, FileDescriptor fd,
+ String friendlyName, int flags, AssetsProvider asset) {
+ String friendly_name_utf8 = friendlyName;
+ if (friendly_name_utf8 == null) {
+ return 0;
+ }
+
+ throw new UnsupportedOperationException();
+ // ATRACE_NAME(String.format("LoadApkAssetsFd(%s)", friendly_name_utf8));
+ //
+ // int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+ // if (fd < 0) {
+ // throw new IllegalArgumentException("Bad FileDescriptor");
+ // }
+ //
+ // unique_fd dup_fd(.dup(fd));
+ // if (dup_fd < 0) {
+ // throw new IOException(errno);
+ // return 0;
+ // }
+ //
+ // ApkAssets apk_assets = ApkAssets.LoadFromFd(std.move(dup_fd),
+ // friendly_name_utf8,
+ // system, force_shared_lib);
+ // if (apk_assets == null) {
+ // String error_msg = String.format("Failed to load asset path %s from fd %d",
+ // friendly_name_utf8, dup_fd.get());
+ // throw new IOException(error_msg);
+ // return 0;
+ // }
+ // return ShadowArscAssetManager9.NATIVE_APK_ASSETS_REGISTRY.getNativeObjectId(apk_assets);
+ }
+ // END-INTERNAL
+
// static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
@Implementation
protected static String nativeGetAssetPath(long ptr) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java
index c6c2747a5..2b29bdb4d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java
@@ -4,6 +4,7 @@ import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
import static org.robolectric.shadow.api.Shadow.directlyOn;
@@ -179,6 +180,52 @@ public class ShadowContextImpl {
realContextImpl);
}
+ /** Behaves as {@link #sendOrderedBroadcast} and currently ignores userHandle. */
+ @Implementation(minSdk = KITKAT)
+ protected void sendOrderedBroadcastAsUser(
+ Intent intent,
+ UserHandle userHandle,
+ String receiverPermission,
+ BroadcastReceiver resultReceiver,
+ Handler scheduler,
+ int initialCode,
+ String initialData,
+ Bundle initialExtras) {
+ sendOrderedBroadcast(
+ intent,
+ receiverPermission,
+ resultReceiver,
+ scheduler,
+ initialCode,
+ initialData,
+ initialExtras
+ );
+ }
+
+ /** Behaves as {@link #sendOrderedBroadcast}. Currently ignores userHandle, appOp, and options. */
+ @Implementation(minSdk = M)
+ protected void sendOrderedBroadcastAsUser(
+ Intent intent,
+ UserHandle userHandle,
+ String receiverPermission,
+ int appOp,
+ Bundle options,
+ BroadcastReceiver resultReceiver,
+ Handler scheduler,
+ int initialCode,
+ String initialData,
+ Bundle initialExtras) {
+ sendOrderedBroadcast(
+ intent,
+ receiverPermission,
+ resultReceiver,
+ scheduler,
+ initialCode,
+ initialData,
+ initialExtras
+ );
+ }
+
@Implementation
protected void sendStickyBroadcast(Intent intent) {
getShadowInstrumentation().sendStickyBroadcast(intent, realContextImpl);
@@ -253,6 +300,22 @@ public class ShadowContextImpl {
getShadowInstrumentation().unbindService(serviceConnection);
}
+ /**
+ * Behaves as {@link #startActivity}. The user parameter is ignored.
+ */
+ @Implementation(minSdk = LOLLIPOP)
+ protected void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+ // TODO: Remove this once {@link com.android.server.wmActivityTaskManagerService} is
+ // properly shadowed.
+ directlyOn(
+ realContextImpl,
+ ShadowContextImpl.CLASS_NAME,
+ "startActivity",
+ ClassParameter.from(Intent.class, intent),
+ ClassParameter.from(Bundle.class, options)
+ );
+ }
+
@Implementation(minSdk = JELLY_BEAN_MR1)
protected int getUserId() {
return 0;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCrossProfileApps.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCrossProfileApps.java
new file mode 100644
index 000000000..66ebe84eb
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCrossProfileApps.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.robolectric.shadows;
+
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.Manifest;
+import android.Manifest.permission;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.Mode;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.CrossProfileApps;
+import android.content.pm.ICrossProfileApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+/** Robolectric implementation of {@link CrossProfileApps}. */
+@Implements(value = CrossProfileApps.class, minSdk = P)
+public class ShadowCrossProfileApps {
+
+ // BEGIN-INTERNAL
+ private final static String INTERACT_ACROSS_PROFILES_APPOP = AppOpsManager.permissionToOp(
+ Manifest.permission.INTERACT_ACROSS_PROFILES);
+ private static final Set<String> configurableInteractAcrossProfilePackages = new HashSet<>();
+ // END-INTERNAL
+
+ private final Set<UserHandle> targetUserProfiles = new LinkedHashSet<>();
+ private final List<StartedMainActivity> startedMainActivities = new ArrayList<>();
+ private final List<StartedActivity> startedActivities =
+ Collections.synchronizedList(new ArrayList<>());
+ private final Map<String, Integer> packageNameAppOpModes = new HashMap<>();
+
+ private Context context;
+ private PackageManager packageManager;
+
+ @Implementation
+ protected void __constructor__(Context context, ICrossProfileApps service) {
+ this.context = context;
+ this.packageManager = context.getPackageManager();
+ }
+
+ /**
+ * Returns a list of {@link UserHandle}s currently accessible. This list is populated from calls
+ * to {@link #addTargetUserProfile(UserHandle)}.
+ */
+ @Implementation
+ protected List<UserHandle> getTargetUserProfiles() {
+ return ImmutableList.copyOf(targetUserProfiles);
+ }
+
+ /**
+ * Returns a {@link Drawable} that can be shown for profile switching, which is guaranteed to
+ * always be the same for a particular user and to be distinct between users.
+ */
+ @Implementation
+ protected Drawable getProfileSwitchingIconDrawable(UserHandle userHandle) {
+ verifyCanAccessUser(userHandle);
+ return new ColorDrawable(userHandle.getIdentifier());
+ }
+
+ /**
+ * Returns a {@link CharSequence} that can be shown as a label for profile switching, which is
+ * guaranteed to always be the same for a particular user and to be distinct between users.
+ */
+ @Implementation
+ protected CharSequence getProfileSwitchingLabel(UserHandle userHandle) {
+ verifyCanAccessUser(userHandle);
+ return "Switch to " + userHandle;
+ }
+
+ /**
+ * Simulates starting the main activity specified in the specified profile, performing the same
+ * security checks done by the real {@link CrossProfileApps}.
+ *
+ * <p>The most recent main activity started can be queried by {@link #peekNextStartedActivity()}
+ * ()}.
+ */
+ @Implementation
+ protected void startMainActivity(ComponentName componentName, UserHandle targetUser) {
+ verifyCanAccessUser(targetUser);
+ verifyActivityInManifest(componentName, /* requireMainActivity= */ true);
+ startedMainActivities.add(new StartedMainActivity(componentName, targetUser));
+ startedActivities.add(new StartedActivity(componentName, targetUser));
+ }
+
+ /**
+ * Simulates starting the activity specified in the specified profile, performing the same
+ * security checks done by the real {@link CrossProfileApps}.
+ *
+ * <p>The most recent main activity started can be queried by {@link #peekNextStartedActivity()}
+ * ()}.
+ */
+ @Implementation(minSdk = Q)
+ @SystemApi
+ @RequiresPermission(permission.INTERACT_ACROSS_PROFILES)
+ protected void startActivity(ComponentName componentName, UserHandle targetUser) {
+ verifyCanAccessUser(targetUser);
+ verifyActivityInManifest(componentName, /* requireMainActivity= */ false);
+ verifyHasInteractAcrossProfilesPermission();
+ startedActivities.add(new StartedActivity(componentName, targetUser));
+ }
+
+ /** Adds {@code userHandle} to the list of accessible handles. */
+ public void addTargetUserProfile(UserHandle userHandle) {
+ if (userHandle.equals(Process.myUserHandle())) {
+ throw new IllegalArgumentException("Cannot target current user");
+ }
+ targetUserProfiles.add(userHandle);
+ }
+
+ /** Removes {@code userHandle} from the list of accessible handles, if present. */
+ public void removeTargetUserProfile(UserHandle userHandle) {
+ if (userHandle.equals(Process.myUserHandle())) {
+ throw new IllegalArgumentException("Cannot target current user");
+ }
+ targetUserProfiles.remove(userHandle);
+ }
+
+ /** Clears the list of accessible handles. */
+ public void clearTargetUserProfiles() {
+ targetUserProfiles.clear();
+ }
+
+ /**
+ * Returns the most recent {@link ComponentName}, {@link UserHandle} pair started by {@link
+ * CrossProfileApps#startMainActivity(ComponentName, UserHandle)}, wrapped in {@link
+ * StartedMainActivity}.
+ *
+ * @deprecated Use {@link #peekNextStartedActivity()} instead.
+ */
+ @Nullable
+ @Deprecated
+ public StartedMainActivity peekNextStartedMainActivity() {
+ if (startedMainActivities.isEmpty()) {
+ return null;
+ } else {
+ return Iterables.getLast(startedMainActivities);
+ }
+ }
+
+ /**
+ * Returns the most recent {@link ComponentName}, {@link UserHandle} pair started by {@link
+ * CrossProfileApps#startMainActivity(ComponentName, UserHandle)} or {@link
+ * CrossProfileApps#startActivity(ComponentName, UserHandle)}, wrapped in {@link StartedActivity}.
+ */
+ @Nullable
+ public StartedActivity peekNextStartedActivity() {
+ if (startedActivities.isEmpty()) {
+ return null;
+ } else {
+ return Iterables.getLast(startedActivities);
+ }
+ }
+
+ /**
+ * Consumes the most recent {@link ComponentName}, {@link UserHandle} pair started by {@link
+ * CrossProfileApps#startMainActivity(ComponentName, UserHandle)} or {@link
+ * CrossProfileApps#startActivity(ComponentName, UserHandle)}, and returns it wrapped in {@link
+ * StartedActivity}.
+ */
+ @Nullable
+ public StartedActivity getNextStartedActivity() {
+ if (startedActivities.isEmpty()) {
+ return null;
+ } else {
+ return startedActivities.remove(startedActivities.size() - 1);
+ }
+ }
+
+ /**
+ * Clears all records of {@link StartedActivity}s from calls to {@link
+ * CrossProfileApps#startActivity(ComponentName, UserHandle)} or {@link
+ * CrossProfileApps#startMainActivity(ComponentName, UserHandle)}.
+ */
+ public void clearNextStartedActivities() {
+ startedActivities.clear();
+ }
+
+ private void verifyCanAccessUser(UserHandle userHandle) {
+ if (!targetUserProfiles.contains(userHandle)) {
+ throw new SecurityException(
+ "Not allowed to access "
+ + userHandle
+ + " (did you forget to call addTargetUserProfile?)");
+ }
+ }
+
+ private void verifyHasInteractAcrossProfilesPermission() {
+ if (context.checkSelfPermission(permission.INTERACT_ACROSS_PROFILES)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Attempt to launch activity without required "
+ + permission.INTERACT_ACROSS_PROFILES
+ + " permission");
+ }
+ }
+
+ /**
+ * Ensures that {@code component} is present in the manifest as an exported and enabled activity.
+ * This check and the error thrown are the same as the check done by the real {@link
+ * CrossProfileApps}.
+ *
+ * <p>If {@code requireMainActivity} is true, then this also asserts that the activity is a
+ * launcher activity.
+ */
+ private void verifyActivityInManifest(ComponentName component, boolean requireMainActivity) {
+ Intent launchIntent = new Intent();
+ if (requireMainActivity) {
+ launchIntent
+ .setAction(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+ .setPackage(component.getPackageName());
+ } else {
+ launchIntent.setComponent(component);
+ }
+
+ boolean existsMatchingActivity =
+ Iterables.any(
+ packageManager.queryIntentActivities(
+ launchIntent, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE),
+ resolveInfo -> {
+ ActivityInfo activityInfo = resolveInfo.activityInfo;
+ return TextUtils.equals(activityInfo.packageName, component.getPackageName())
+ && TextUtils.equals(activityInfo.name, component.getClassName())
+ && activityInfo.exported;
+ });
+ if (!existsMatchingActivity) {
+ throw new SecurityException(
+ "Attempt to launch activity without "
+ + " category Intent.CATEGORY_LAUNCHER or activity is not exported"
+ + component);
+ }
+ }
+
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = R)
+ @RequiresPermission(
+ allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ protected void setInteractAcrossProfilesAppOp(String packageName, @Mode int newMode) {
+ packageNameAppOpModes.put(packageName, newMode);
+ }
+
+ /**
+ * Returns the app-op mode associated with the given package name. If not set, returns {@code
+ * null}.
+ */
+ @Nullable
+ public @Mode Integer getInteractAcrossProfilesAppOp(String packageName) {
+ return packageNameAppOpModes.get(packageName);
+ }
+
+ public void addCrossProfilePackage(String packageName){
+ configurableInteractAcrossProfilePackages.add(packageName);
+ }
+
+ @Implementation(minSdk = R)
+ protected void resetInteractAcrossProfilesAppOps(
+ @NonNull Collection<String> previousCrossProfilePackages,
+ @NonNull Set<String> newCrossProfilePackages) {
+
+ final List<String> unsetCrossProfilePackages =
+ previousCrossProfilePackages.stream()
+ .filter(packageName -> !newCrossProfilePackages.contains(packageName))
+ .collect(Collectors.toList());
+
+ for (String packageName : unsetCrossProfilePackages) {
+ if (!canConfigureInteractAcrossProfiles(packageName)) {
+ setInteractAcrossProfilesAppOp(packageName,
+ AppOpsManager.opToDefaultMode(INTERACT_ACROSS_PROFILES_APPOP));
+ }
+ }
+ }
+
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = R)
+ protected void clearInteractAcrossProfilesAppOps() {
+ findAllPackageNames().forEach(
+ packageName -> setInteractAcrossProfilesAppOp(
+ packageName, AppOpsManager.opToDefaultMode(INTERACT_ACROSS_PROFILES_APPOP)));
+ }
+
+ private List<String> findAllPackageNames() {
+ return context.getPackageManager()
+ .getInstalledApplications(/* flags= */ 0)
+ .stream()
+ .map(applicationInfo -> applicationInfo.packageName)
+ .collect(Collectors.toList());
+ }
+ // END-INTERNAL
+
+ @Implementation
+ protected boolean canConfigureInteractAcrossProfiles(@NonNull String packageName) {
+ return configurableInteractAcrossProfilePackages.contains(packageName);
+ }
+
+ @Implementation
+ protected boolean canUserAttemptToConfigureInteractAcrossProfiles(@NonNull String packageName) {
+ PackageInfo packageInfo;
+ try {
+ packageInfo = packageManager.getPackageInfo(packageName, /* flags= */ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ if (packageInfo == null || packageInfo.requestedPermissions == null) {
+ return false;
+ }
+ return Arrays.asList(packageInfo.requestedPermissions).contains(
+ Manifest.permission.INTERACT_ACROSS_PROFILES);
+ }
+
+ @Resetter
+ public static void reset() {
+ configurableInteractAcrossProfilePackages.clear();
+ }
+ // END-INTERNAL
+
+ /**
+ * Container object to hold parameters passed to {@link #startMainActivity(ComponentName,
+ * UserHandle)}.
+ *
+ * @deprecated Use {@link #peekNextStartedActivity()} and {@link StartedActivity} instead.
+ */
+ @Deprecated
+ public static class StartedMainActivity {
+
+ private final ComponentName componentName;
+ private final UserHandle userHandle;
+
+ public StartedMainActivity(ComponentName componentName, UserHandle userHandle) {
+ this.componentName = checkNotNull(componentName);
+ this.userHandle = checkNotNull(userHandle);
+ }
+
+ public ComponentName getComponentName() {
+ return componentName;
+ }
+
+ public UserHandle getUserHandle() {
+ return userHandle;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ StartedMainActivity that = (StartedMainActivity) o;
+ return Objects.equals(componentName, that.componentName)
+ && Objects.equals(userHandle, that.userHandle);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(componentName, userHandle);
+ }
+ }
+
+ /**
+ * Container object to hold parameters passed to {@link #startMainActivity(ComponentName,
+ * UserHandle)} or {@link #startActivity(ComponentName, UserHandle)}.
+ */
+ public static final class StartedActivity {
+
+ private final ComponentName componentName;
+ private final UserHandle userHandle;
+
+ public StartedActivity(ComponentName componentName, UserHandle userHandle) {
+ this.componentName = checkNotNull(componentName);
+ this.userHandle = checkNotNull(userHandle);
+ }
+
+ public ComponentName getComponentName() {
+ return componentName;
+ }
+
+ public UserHandle getUserHandle() {
+ return userHandle;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ StartedActivity that = (StartedActivity) o;
+ return Objects.equals(componentName, that.componentName)
+ && Objects.equals(userHandle, that.userHandle);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(componentName, userHandle);
+ }
+ }
+} \ No newline at end of file
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
index fdb56ec3e..b5c67555c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
@@ -9,6 +9,7 @@ import static android.os.Build.VERSION_CODES.N_MR1;
import static android.os.Build.VERSION_CODES.O;
// BEGIN-INTERNAL
import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
// END-INTERNAL
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
@@ -32,6 +33,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Process;
import android.text.TextUtils;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -91,6 +93,7 @@ public class ShadowDevicePolicyManager {
private final Map<PackageAndPermission, Integer> appPermissionGrantStateMap = new HashMap<>();
private final Map<ComponentName, byte[]> passwordResetTokens = new HashMap<>();
private final Set<ComponentName> componentsWithActivatedTokens = new HashSet<>();
+ private Set<String> defaultCrossProfilePackages = new HashSet<>();
private Collection<String> packagesToFailForSetApplicationHidden = Collections.emptySet();
private Set<String> crossProfileCalendarPackages = Collections.emptySet();
private Context context;
@@ -869,5 +872,17 @@ public class ShadowDevicePolicyManager {
enforceProfileOwner(admin);
crossProfileCalendarPackages = packageNames;
}
+
+ @Implementation(minSdk = R)
+ protected Set<String> getDefaultCrossProfilePackages() {
+ return new HashSet<>(defaultCrossProfilePackages);
+ }
+
+ public void addDefaultCrossProfilePackage(String packageName) {
+ defaultCrossProfilePackages.add(packageName);
+ }
+ public void setDefaultCrossProfilePackages(Set<String> defaultCrossProfilePackages) {
+ this.defaultCrossProfilePackages = new HashSet<>(defaultCrossProfilePackages);
+ }
// END-INTERNAL
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFingerprintManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFingerprintManager.java
index ea7b6842b..949ff2aa4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFingerprintManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFingerprintManager.java
@@ -47,7 +47,7 @@ public class ShadowFingerprintManager {
AuthenticationResult result;
if (RuntimeEnvironment.getApiLevel() >= N_MR1) {
- result = new AuthenticationResult(pendingCryptoObject, null, 0);
+ result = new AuthenticationResult(pendingCryptoObject, null, 0, true);
} else {
result = ReflectionHelpers.callConstructor(AuthenticationResult.class,
ClassParameter.from(CryptoObject.class, pendingCryptoObject),
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHardwareRenderer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHardwareRenderer.java
index 458f0fb82..3e17f926f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHardwareRenderer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHardwareRenderer.java
@@ -2,6 +2,7 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
import android.graphics.HardwareRenderer;
import org.robolectric.annotation.Implementation;
@@ -16,5 +17,11 @@ public class ShadowHardwareRenderer {
protected static long nCreateProxy(boolean translucent, long rootRenderNode) {
return ++nextCreateProxy;
}
+
+ @Implementation(minSdk = R)
+ protected static long nCreateProxy(
+ boolean translucent, boolean isWideGamut, long rootRenderNode) {
+ return nCreateProxy(translucent, rootRenderNode);
+ }
}
// END-INTERNAL \ No newline at end of file
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageDecoder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageDecoder.java
index 87f4d7574..fbe48ac8b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageDecoder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageDecoder.java
@@ -253,34 +253,67 @@ public class ShadowImageDecoder {
// native method implementations...
- @Implementation
+ @Implementation(maxSdk = Build.VERSION_CODES.Q)
protected static ImageDecoder nCreate(long asset, Source source) throws IOException {
return ImageDecoder_nCreateAsset(asset, source);
}
- @Implementation
+ @Implementation(maxSdk = Build.VERSION_CODES.Q)
protected static ImageDecoder nCreate(ByteBuffer buffer, int position,
int limit, Source src) throws IOException {
return ImageDecoder_nCreateByteBuffer(buffer, position, limit, src);
}
- @Implementation
+ @Implementation(maxSdk = Build.VERSION_CODES.Q)
protected static ImageDecoder nCreate(byte[] data, int offset, int length,
Source src) throws IOException {
return ImageDecoder_nCreateByteArray(data, offset, length, src);
}
- @Implementation
+ @Implementation(maxSdk = Build.VERSION_CODES.Q)
protected static ImageDecoder nCreate(InputStream is, byte[] storage, Source source) {
return ImageDecoder_nCreateInputStream(is, storage, source);
}
// The fd must be seekable.
- @Implementation
+ @Implementation(maxSdk = Build.VERSION_CODES.Q)
protected static ImageDecoder nCreate(FileDescriptor fd, Source src) throws IOException {
return ImageDecoder_nCreateFd(fd, src);
}
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = Build.VERSION_CODES.R)
+ protected static ImageDecoder nCreate(long asset, boolean preferAnimation, Source source)
+ throws IOException {
+ return ImageDecoder_nCreateAsset(asset, source);
+ }
+
+ @Implementation(minSdk = Build.VERSION_CODES.R)
+ protected static ImageDecoder nCreate(ByteBuffer buffer, int position,
+ int limit, boolean preferAnimation, Source src) throws IOException {
+ return ImageDecoder_nCreateByteBuffer(buffer, position, limit, src);
+ }
+
+ @Implementation(minSdk = Build.VERSION_CODES.R)
+ protected static ImageDecoder nCreate(byte[] data, int offset, int length,
+ boolean preferAnimation, Source src) throws IOException {
+ return ImageDecoder_nCreateByteArray(data, offset, length, src);
+ }
+
+ @Implementation(minSdk = Build.VERSION_CODES.R)
+ protected static ImageDecoder nCreate(InputStream is, byte[] storage, boolean preferAnimation,
+ Source source) {
+ return ImageDecoder_nCreateInputStream(is, storage, source);
+ }
+
+ // The fd must be seekable.
+ @Implementation(minSdk = Build.VERSION_CODES.R)
+ protected static ImageDecoder nCreate(FileDescriptor fd, boolean preferAnimation, Source src)
+ throws IOException {
+ return ImageDecoder_nCreateFd(fd, src);
+ }
+ // END-INTERNAL
+
@Implementation(maxSdk = Build.VERSION_CODES.P)
protected static Bitmap nDecodeBitmap(long nativePtr,
ImageDecoder decoder,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
index 7c5fdaeb3..68587acbc 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
@@ -172,17 +172,22 @@ public class ShadowInstrumentation {
.execStartActivity(who, contextThread, token, target, intent, requestCode, options);
}
+ /**
+ * Behaves as {@link #execStartActivity(Context, IBinder, IBinder, String, Intent, int, Bundle).
+ *
+ * <p>Currently ignores the user.
+ */
@Implementation(minSdk = JELLY_BEAN_MR1)
protected ActivityResult execStartActivity(
- Context who,
- IBinder contextThread,
- IBinder token,
- String resultWho,
- Intent intent,
- int requestCode,
- Bundle options,
- UserHandle user) {
- throw new UnsupportedOperationException("Implement me!!");
+ Context who,
+ IBinder contextThread,
+ IBinder token,
+ String resultWho,
+ Intent intent,
+ int requestCode,
+ Bundle options,
+ UserHandle user) {
+ return execStartActivity(who, contextThread, token, resultWho, intent, requestCode, options);
}
@Implementation(minSdk = M)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java
index 3189be66b..85a295d7a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java
@@ -338,6 +338,50 @@ public class ShadowMotionEvent {
pointerPropertiesObjArray,
pointerCoordsObjArray);
}
+
+ @Implementation(minSdk = android.os.Build.VERSION_CODES.R)
+ @HiddenApi
+ protected static long nativeInitialize(
+ long nativePtr,
+ int deviceId,
+ int source,
+ int displayId,
+ int action,
+ int flags,
+ int edgeFlags,
+ int metaState,
+ int buttonState,
+ int classification,
+ float xOffset,
+ float yOffset,
+ float xPrecision,
+ float yPrecision,
+ long downTimeNanos,
+ long eventTimeNanos,
+ int pointerCount,
+ PointerProperties[] pointerPropertiesObjArray,
+ PointerCoords[] pointerCoordsObjArray) {
+ return
+ nativeInitialize(
+ nativePtr,
+ deviceId,
+ source,
+ action,
+ flags,
+ edgeFlags,
+ metaState,
+ buttonState,
+ xOffset,
+ yOffset,
+ xPrecision,
+ yPrecision,
+ downTimeNanos,
+ eventTimeNanos,
+ pointerCount,
+ pointerPropertiesObjArray,
+ pointerCoordsObjArray);
+ }
+
// END-INTERNAL
@Implementation(maxSdk = KITKAT_WATCH)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
index ff8caad0f..d8a3011c3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
@@ -107,6 +107,7 @@ public class ShadowPackageManager {
static final Map<String, String> packageInstallerMap = new HashMap<>();
static final Map<Integer, String[]> packagesForUid = new HashMap<>();
static final Map<String, Integer> uidForPackage = new HashMap<>();
+ static final Map<Integer, List<String>> packagesForUserId = new HashMap<>();
static final Map<Integer, String> namesForUid = new HashMap<>();
static final Map<Integer, Integer> verificationResults = new HashMap<>();
static final Map<Integer, Long> verificationTimeoutExtension = new HashMap<>();
@@ -574,6 +575,13 @@ public class ShadowPackageManager {
return packagesForUid.get(uid);
}
+ public void setInstalledPackagesForUserId(int userId, List<String> packages) {
+ packagesForUserId.put(userId, packages);
+ for (String packageName : packages) {
+ addPackage(packageName);
+ }
+ }
+
public void setPackageArchiveInfo(String archiveFilePath, PackageInfo packageInfo) {
packageArchiveInfo.put(archiveFilePath, packageInfo);
}
@@ -1115,6 +1123,7 @@ public class ShadowPackageManager {
pendingDeleteCallbacks.clear();
hiddenPackages.clear();
sequenceNumberChangedPackagesMap.clear();
+ packagesForUserId.clear();
packageSettings.clear();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java
index a4f2add69..8c2f6e1fa 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java
@@ -72,15 +72,5 @@ public class ShadowPackageParser {
public boolean hasFeature(String feature) {
return false;
}
-
- @Override
- public String[] getOverlayPaths(String targetPackageName, String targetPath) {
- return null;
- }
-
- @Override
- public String[] getOverlayApks(String targetPackageName) {
- return null;
- }
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java
index dcb9719b3..e358fc19b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java
@@ -7,6 +7,7 @@ import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.R;
import static org.robolectric.RuntimeEnvironment.castNativePtr;
import android.os.BadParcelableException;
@@ -311,6 +312,16 @@ public class ShadowParcel {
NATIVE_PTR_TO_PARCEL.get(nativePtr).writeString(val);
}
+ @Implementation(minSdk = R)
+ protected static void nativeWriteString8(long nativePtr, String val) {
+ nativeWriteString(nativePtr, val);
+ }
+
+ @Implementation(minSdk = R)
+ protected static void nativeWriteString16(long nativePtr, String val) {
+ nativeWriteString(nativePtr, val);
+ }
+
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeWriteStrongBinder(int nativePtr, IBinder val) {
@@ -399,6 +410,16 @@ public class ShadowParcel {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).readString();
}
+ @Implementation(minSdk = R)
+ protected static String nativeReadString8(long nativePtr) {
+ return nativeReadString(nativePtr);
+ }
+
+ @Implementation(minSdk = R)
+ protected static String nativeReadString16(long nativePtr) {
+ return nativeReadString(nativePtr);
+ }
+
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
protected static IBinder nativeReadStrongBinder(int nativePtr) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPersistentDataBlockManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPersistentDataBlockManager.java
new file mode 100644
index 000000000..a48480df1
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPersistentDataBlockManager.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.robolectric.shadows;
+
+import android.service.persistentdata.PersistentDataBlockManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(PersistentDataBlockManager.class)
+public class ShadowPersistentDataBlockManager {
+ private static int sDataBlockSize = 0;
+
+ @Resetter
+ public static void reset() {
+ sDataBlockSize = 0;
+ }
+
+ @Implementation
+ protected int getDataBlockSize() {
+ return sDataBlockSize;
+ }
+
+ public static void setDataBlockSize(int dataBlockSize) {
+ sDataBlockSize = dataBlockSize;
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeAnimator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeAnimator.java
index 61a73f771..ec3a005d2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeAnimator.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeAnimator.java
@@ -7,7 +7,7 @@ import static org.robolectric.shadow.api.Shadow.directlyOn;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
-import android.view.RenderNodeAnimator;
+import android.graphics.animation.RenderNodeAnimator;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
index fdbe2b45e..d7dae9b36 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
@@ -9,6 +9,7 @@ import static android.os.Build.VERSION_CODES.N_MR1;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
import android.accounts.IAccountManager;
import android.app.IAlarmManager;
@@ -23,6 +24,7 @@ import android.app.usage.IUsageStatsManager;
import android.content.Context;
import android.content.IClipboard;
import android.content.IRestrictionsManager;
+import android.content.pm.ICrossProfileApps;
import android.content.pm.IShortcutService;
import android.hardware.display.IColorDisplayManager;
import android.hardware.fingerprint.IFingerprintService;
@@ -35,6 +37,7 @@ import android.media.IMediaRouterService;
import android.media.session.ISessionManager;
import android.net.IConnectivityManager;
import android.net.INetworkScoreService;
+import android.net.ITetheringConnector;
import android.net.nsd.INsdManager;
import android.net.wifi.IWifiManager;
import android.net.wifi.p2p.IWifiP2pManager;
@@ -44,10 +47,13 @@ import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
import android.os.IInterface;
import android.os.IPowerManager;
+import android.os.IThermalService;
import android.os.IUserManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.storage.IStorageManager;
+import android.service.persistentdata.IPersistentDataBlockService;
+
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.appwidget.IAppWidgetService;
@@ -162,6 +168,10 @@ public class ShadowServiceManager {
map.put(
Context.MEDIA_SESSION_SERVICE,
createDeepBinder(ISessionManager.class, "android.media.session.ISessionManager"));
+ map.put(
+ Context.PERSISTENT_DATA_BLOCK_SERVICE,
+ createBinder(IPersistentDataBlockService.class,
+ "android.service.persistentdata.IPersistentDataBlockService"));
}
if (RuntimeEnvironment.getApiLevel() >= M) {
map.put(
@@ -185,6 +195,9 @@ public class ShadowServiceManager {
map.put(
Context.SLICE_SERVICE,
createBinder(ISliceManager.class, "android.app.slice.SliceManager"));
+ map.put(
+ Context.CROSS_PROFILE_APPS_SERVICE,
+ createBinder(ICrossProfileApps.class, "android.content.pm.ICrossProfileApps"));
}
// BEGIN-INTERNAL
if (RuntimeEnvironment.getApiLevel() >= Q) {
@@ -195,6 +208,12 @@ public class ShadowServiceManager {
map.put(Context.ROLE_SERVICE,
createBinder(IRoleManager.class, "android.app.role.IRoleManager"));
}
+ if (RuntimeEnvironment.getApiLevel() >= R) {
+ map.put(Context.TETHERING_SERVICE,
+ createBinder(ITetheringConnector.class, "android.net.ITetheringConnector"));
+ map.put(Context.THERMAL_SERVICE,
+ createBinder(IThermalService.class, "android.os.IThermalService"));
+ }
// END-INTERNAL
SERVICES = Collections.unmodifiableMap(map);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java
index c7815c2ac..b925f4317 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java
@@ -11,12 +11,8 @@ import android.content.Context;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.WeakHashMap;
+import android.util.ArrayMap;
+
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -24,6 +20,16 @@ import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.stream.Collectors;
+
@SuppressWarnings({"UnusedDeclaration"})
@Implements(Settings.class)
public class ShadowSettings {
@@ -244,7 +250,6 @@ public class ShadowSettings {
}
}
- // BEGIN-INTERNAL
@Implements(value = Settings.Config.class, minSdk = Build.VERSION_CODES.Q)
public static class ShadowConfig {
private static final Map<ContentResolver, Map<String, String>> dataMap = new WeakHashMap<>();
@@ -265,6 +270,26 @@ public class ShadowSettings {
return get(cr).get(name);
}
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = Build.VERSION_CODES.R)
+ protected static Map<String, String> getStrings(ContentResolver cr, String prefix,
+ List<String> names) {
+ List<String> concatNames = new ArrayList<>();
+ for (String name : names) {
+ concatNames.add(prefix + "/" + name);
+ }
+
+ Map<String, String> values = get(cr);
+ Map<String, String> arrayMap = new ArrayMap<>();
+ for (String name : concatNames) {
+ if (values.containsKey(name)) {
+ arrayMap.put(name, values.get(name));
+ }
+ }
+ return arrayMap;
+ }
+ // END-INTERNAL
+
private static Map<String, String> get(ContentResolver cr) {
Map<String, String> map = dataMap.get(cr);
if (map == null) {
@@ -274,7 +299,6 @@ public class ShadowSettings {
return map;
}
}
- // END-INTERNAL
/**
* Sets the value of the {@link Settings.System#AIRPLANE_MODE_ON} setting.
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java
index ba6398760..3223c3ca2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java
@@ -6,9 +6,13 @@ import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.N_MR1;
+import static android.os.Build.VERSION_CODES.R;
+
import static org.robolectric.shadow.api.Shadow.directlyOn;
import android.Manifest.permission;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -17,17 +21,23 @@ import android.os.IUserManager;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
/**
* Robolectric implementation of {@link android.os.UserManager}.
@@ -44,6 +54,7 @@ public class ShadowUserManager {
public static final int FLAG_ADMIN = UserInfo.FLAG_ADMIN;
public static final int FLAG_GUEST = UserInfo.FLAG_GUEST;
public static final int FLAG_RESTRICTED = UserInfo.FLAG_RESTRICTED;
+ public static final int FLAG_MANAGED_PROFILE = UserInfo.FLAG_MANAGED_PROFILE;
private static Map<Integer, Integer> userPidMap = new HashMap<>();
@@ -58,6 +69,8 @@ public class ShadowUserManager {
private long nextUserSerial = 0;
private Map<Integer, UserState> userState = new HashMap<>();
private Map<Integer, UserInfo> userInfoMap = new HashMap<>();
+ private Map<Integer, List<UserInfo>> profiles = new HashMap<>();
+ private Map<Integer, Integer> profileToParent = new HashMap<>();
private Context context;
private boolean enforcePermissions;
@@ -106,6 +119,47 @@ public class ShadowUserManager {
return ImmutableList.copyOf(userProfiles.keySet());
}
+ /**
+ * If any profiles have been added using {@link #addProfile}, return those profiles.
+ *
+ * Otherwise follow real android behaviour.
+ */
+ @Implementation(minSdk = LOLLIPOP)
+ protected List<UserInfo> getProfiles(int userHandle) {
+ if (profiles.containsKey(userHandle)) {
+ return ImmutableList.copyOf(profiles.get(userHandle));
+ }
+
+ if (profileToParent.containsKey(userHandle)
+ && profiles.containsKey(profileToParent.get(userHandle))) {
+ return ImmutableList.copyOf(profiles.get(profileToParent.get(userHandle)));
+ }
+
+ return directlyOn(
+ realObject, UserManager.class, "getProfiles", ClassParameter.from(int.class, userHandle));
+ }
+
+ /** Add a profile to be returned by {@link #getProfiles(int)}.**/
+ public void addProfile(
+ int userHandle, int profileUserHandle, String profileName, int profileFlags) {
+ UserInfo userInfo = new UserInfo(profileUserHandle, profileName, profileFlags);
+ profiles.putIfAbsent(userHandle, new ArrayList<>());
+ profiles.get(userHandle).add(userInfo);
+ userInfoMap.put(profileUserHandle, userInfo);
+ profileToParent.put(profileUserHandle, userHandle);
+ }
+
+ /**
+ * If this profile has been added using {@link #addProfile}, return its parent.
+ */
+ @Implementation(minSdk = LOLLIPOP)
+ protected UserInfo getProfileParent(int userHandle) {
+ if (!profileToParent.containsKey(userHandle)) {
+ return null;
+ }
+ return userInfoMap.get(profileToParent.get(userHandle));
+ }
+
@Implementation(minSdk = N)
protected boolean isUserUnlocked() {
return userUnlocked;
@@ -131,12 +185,52 @@ public class ShadowUserManager {
protected boolean isManagedProfile() {
if (enforcePermissions && !hasManageUsersPermission()) {
throw new SecurityException(
- "You need MANAGE_USERS permission to: check if specified user a " +
- "managed profile outside your profile group");
+ "You need MANAGE_USERS permission to: check if specified user a " +
+ "managed profile outside your profile group");
}
return managedProfile;
}
+ /**
+ * If permissions are enforced (see {@link #enforcePermissionChecks(boolean)}) and the application
+ * doesn't have the {@link android.Manifest.permission#MANAGE_USERS} permission, throws a {@link
+ * SecurityManager} exception.
+ *
+ * @return true if the profile added has FLAG_MANAGED_PROFILE
+ * @see #enforcePermissionChecks(boolean)
+ * @see #addProfile(int, int, String, int)
+ * @see #addUser(int, String, int)
+ */
+ @Implementation(minSdk = N)
+ protected boolean isManagedProfile(int userHandle) {
+ if (enforcePermissions && !hasManageUsersPermission()) {
+ throw new SecurityException(
+ "You need MANAGE_USERS permission to: check if specified user a "
+ + "managed profile outside your profile group");
+ }
+ UserInfo info = getUserInfo(userHandle);
+ return info != null && ((info.flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE);
+ }
+
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = R)
+ protected boolean isProfile() {
+ return isManagedProfile();
+ }
+
+ /**
+ * Compared to real Android, userId is not used, instead
+ * managedProfile determines if user has badge.
+ *
+ * @param userId ignored, uses managedProfile field
+ * @return true if managedProfile field is true
+ */
+ @Implementation(minSdk = R)
+ protected boolean hasBadge(int userId) {
+ return isProfile();
+ }
+ // END-INTERNAL
+
public void enforcePermissionChecks(boolean enforcePermissions) {
this.enforcePermissions = enforcePermissions;
}
@@ -154,6 +248,13 @@ public class ShadowUserManager {
return bundle != null && bundle.getBoolean(restrictionKey);
}
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = R)
+ protected boolean hasUserRestrictionForUser(String restrictionKey, UserHandle userHandle) {
+ return hasUserRestriction(restrictionKey, userHandle);
+ }
+ // END-INTERNAL
+
public void setUserRestriction(UserHandle userHandle, String restrictionKey, boolean value) {
Bundle bundle = getUserRestrictionsForUser(userHandle);
bundle.putBoolean(restrictionKey, value);
@@ -208,6 +309,16 @@ public class ShadowUserManager {
return userProfiles.inverse().get(serialNumber);
}
+ /**
+ * @see #addProfile(int, int, String, int)
+ * @see #addUser(int, String, int)
+ */
+ @Implementation
+ protected int getUserSerialNumber(@UserIdInt int userHandle) {
+ Long result = userProfiles.get(UserHandle.of(userHandle));
+ return result != null ? result.intValue() : -1;
+ }
+
private boolean hasManageUsersPermission() {
return context.getPackageManager().checkPermission(permission.MANAGE_USERS, context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
}
@@ -357,8 +468,8 @@ public class ShadowUserManager {
UserState state = userState.get(handle.getIdentifier());
if (state == UserState.STATE_RUNNING_LOCKED
- || state == UserState.STATE_RUNNING_UNLOCKED
- || state == UserState.STATE_RUNNING_UNLOCKING) {
+ || state == UserState.STATE_RUNNING_UNLOCKED
+ || state == UserState.STATE_RUNNING_UNLOCKING) {
return true;
} else {
return false;
@@ -374,9 +485,9 @@ public class ShadowUserManager {
UserState state = userState.get(handle.getIdentifier());
if (state == UserState.STATE_RUNNING_LOCKED
- || state == UserState.STATE_RUNNING_UNLOCKED
- || state == UserState.STATE_RUNNING_UNLOCKING
- || state == UserState.STATE_STOPPING) {
+ || state == UserState.STATE_RUNNING_UNLOCKED
+ || state == UserState.STATE_RUNNING_UNLOCKING
+ || state == UserState.STATE_STOPPING) {
return true;
} else {
return false;
@@ -442,6 +553,29 @@ public class ShadowUserManager {
return true;
}
+ // BEGIN-INTERNAL
+ @Implementation(minSdk = R)
+ protected UserInfo createProfileForUserEvenWhenDisallowed(String name,
+ @NonNull String userType, @UserInfo.UserInfoFlag int flags, @UserIdInt int userId,
+ String[] disallowedPackages) throws UserManager.UserOperationException {
+ List<UserInfo> userIdProfiles = profiles.computeIfAbsent(userId, ignored -> new ArrayList<>());
+ int profileUserId = userIdProfiles.isEmpty() ? 10 : findMaxProfileId(userIdProfiles) + 1;
+ UserInfo profileUserInfo = new UserInfo(profileUserId, name, flags);
+ userIdProfiles.add(profileUserInfo);
+ profileToParent.put(profileUserId, userId);
+ addUserProfile(UserHandle.of(profileUserId));
+ return profileUserInfo;
+ }
+
+ /** Assumes the given list of profile infos is non-empty. */
+ private int findMaxProfileId(List<UserInfo> userIdProfiles) {
+ return Collections.max(
+ userIdProfiles.stream()
+ .map(userInfo -> userInfo.id)
+ .collect(Collectors.toList()));
+ }
+ // END-INTERNAL
+
/**
* Switches the current user to {@code userHandle}.
*
@@ -464,15 +598,16 @@ public class ShadowUserManager {
*/
public void addUser(int id, String name, int flags) {
UserHandle userHandle =
- id == UserHandle.USER_SYSTEM ? Process.myUserHandle() : new UserHandle(id);
+ id == UserHandle.USER_SYSTEM ? Process.myUserHandle() : new UserHandle(id);
addUserProfile(userHandle);
setSerialNumberForUser(userHandle, (long) id);
+ profiles.putIfAbsent(id, new ArrayList<>());
userInfoMap.put(id, new UserInfo(id, name, flags));
userPidMap.put(
- id,
- id == UserHandle.USER_SYSTEM
- ? Process.myUid()
- : id * UserHandle.PER_USER_RANGE + ShadowProcess.getRandomApplicationUid());
+ id,
+ id == UserHandle.USER_SYSTEM
+ ? Process.myUid()
+ : id * UserHandle.PER_USER_RANGE + ShadowProcess.getRandomApplicationUid());
}
@Resetter
@@ -484,4 +619,4 @@ public class ShadowUserManager {
userPidMap.put(UserHandle.USER_SYSTEM, Process.myUid());
}
}
-}
+} \ No newline at end of file
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java
index 2730cef99..964cdda19 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java
@@ -116,8 +116,7 @@ public class ShadowViewRootImpl {
ClassParameter.from(boolean.class, false),
ClassParameter.from(boolean.class, false),
ClassParameter.from(int.class, 0));
-
- } else if (apiLevel >= Build.VERSION_CODES.P) {
+ } else if (apiLevel <= Build.VERSION_CODES.P) {
ReflectionHelpers.callInstanceMethod(ViewRootImpl.class, component, "dispatchResized",
ClassParameter.from(Rect.class, frame),
ClassParameter.from(Rect.class, zeroSizedRect),
@@ -133,7 +132,22 @@ public class ShadowViewRootImpl {
ClassParameter.from(int.class, 0),
ClassParameter.from(android.view.DisplayCutout.ParcelableWrapper.class,
new android.view.DisplayCutout.ParcelableWrapper()));
-
+ } else if (apiLevel >= Build.VERSION_CODES.R) {
+ // BEGIN-INTERNAL
+ ReflectionHelpers.callInstanceMethod(ViewRootImpl.class, component, "dispatchResized",
+ ClassParameter.from(Rect.class, frame),
+ ClassParameter.from(Rect.class, zeroSizedRect),
+ ClassParameter.from(Rect.class, zeroSizedRect),
+ ClassParameter.from(Rect.class, zeroSizedRect),
+ ClassParameter.from(boolean.class, true),
+ ClassParameter.from(MergedConfiguration.class, new MergedConfiguration()),
+ ClassParameter.from(Rect.class, frame),
+ ClassParameter.from(boolean.class, false),
+ ClassParameter.from(boolean.class, false),
+ ClassParameter.from(int.class, 0),
+ ClassParameter.from(android.view.DisplayCutout.ParcelableWrapper.class,
+ new android.view.DisplayCutout.ParcelableWrapper()));
+ // END-INTERNAL
} else {
throw new RuntimeException("Could not find AndroidRuntimeAdapter for API level: " + apiLevel);
}
diff --git a/soong/robolectric.go b/soong/robolectric.go
index 836cd158f..0ef5bd29e 100644
--- a/soong/robolectric.go
+++ b/soong/robolectric.go
@@ -77,6 +77,9 @@ func (b *buildProps) GenerateAndroidBuildActions(ctx android.ModuleContext) {
"ro.product.cpu.abilist=armeabi-v7a",
"ro.product.cpu.abilist32=armeabi-v7a,armeabi",
"ro.product.cpu.abilist64=armeabi-v7a,armeabi",
+ "",
+ "# temp fix for robolectric freezing issue b/150011638",
+ "persist.debug.new_insets=0",
}
b.output = android.PathForModuleGen(ctx, "build.prop")