diff options
51 files changed, 1711 insertions, 707 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index bb87d96b742c..9de42c3b57d5 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1218,6 +1218,9 @@ public class AppOpsManager { OP_REQUEST_INSTALL_PACKAGES, OP_START_FOREGROUND, OP_SMS_FINANCIAL_TRANSACTIONS, + OP_MANAGE_IPSEC_TUNNELS, + OP_GET_USAGE_STATS, + OP_INSTANT_APP_START_FOREGROUND }; /** @@ -1598,7 +1601,7 @@ public class AppOpsManager { Manifest.permission.REQUEST_DELETE_PACKAGES, Manifest.permission.BIND_ACCESSIBILITY_SERVICE, Manifest.permission.ACCEPT_HANDOVER, - null, // no permission for OP_MANAGE_IPSEC_TUNNELS + Manifest.permission.MANAGE_IPSEC_TUNNELS, Manifest.permission.FOREGROUND_SERVICE, null, // no permission for OP_BLUETOOTH_SCAN Manifest.permission.USE_BIOMETRIC, diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java index 23c54f450a67..1c78b081120a 100644 --- a/core/java/android/os/VintfObject.java +++ b/core/java/android/os/VintfObject.java @@ -72,6 +72,8 @@ public class VintfObject { * ["android.hidl.manager@1.0", "android.hardware.camera.device@1.0", * "android.hardware.camera.device@3.2"]. There are no duplicates. * + * For AIDL HALs, the version is stripped away + * (e.g. "android.hardware.light"). * @hide */ @TestApi diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 436897848f55..cae1f3831b4a 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -24,6 +24,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; @@ -729,7 +730,13 @@ public class ChooserActivity extends ResolverActivity { /** * Returns true if app prediction service is defined and the component exists on device. */ - private boolean isAppPredictionServiceAvailable() { + @VisibleForTesting + public boolean isAppPredictionServiceAvailable() { + if (getPackageManager().getAppPredictionServicePackageName() == null) { + // Default AppPredictionService is not defined. + return false; + } + final String appPredictionServiceName = getString(R.string.config_defaultAppPredictionService); if (appPredictionServiceName == null) { @@ -1584,25 +1591,19 @@ public class ChooserActivity extends ResolverActivity { // ShareShortcutInfos directly. boolean resultMessageSent = false; for (int i = 0; i < driList.size(); i++) { - List<ChooserTarget> chooserTargets = new ArrayList<>(); + List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>(); for (int j = 0; j < resultList.size(); j++) { if (driList.get(i).getResolvedComponentName().equals( resultList.get(j).getTargetComponent())) { - ShortcutManager.ShareShortcutInfo shareShortcutInfo = resultList.get(j); - // Incoming results are ordered but without a score. Create a score - // based on the index in order to be sorted appropriately when joined - // with legacy direct share api results. - float score = Math.max(1.0f - (0.05f * j), 0.0f); - ChooserTarget chooserTarget = convertToChooserTarget(shareShortcutInfo, score); - chooserTargets.add(chooserTarget); - if (mDirectShareAppTargetCache != null && appTargets != null) { - mDirectShareAppTargetCache.put(chooserTarget, appTargets.get(j)); - } + matchingShortcuts.add(resultList.get(j)); } } - if (chooserTargets.isEmpty()) { + if (matchingShortcuts.isEmpty()) { continue; } + List<ChooserTarget> chooserTargets = convertToChooserTarget( + matchingShortcuts, resultList, appTargets, shortcutType); + final Message msg = Message.obtain(); msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT; msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null); @@ -1640,23 +1641,69 @@ public class ChooserActivity extends ResolverActivity { return false; } - private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut, - float score) { - ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo(); - Bundle extras = new Bundle(); - extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId()); - return new ChooserTarget( - // The name of this target. - shortcutInfo.getShortLabel(), - // Don't load the icon until it is selected to be shown - null, - // The ranking score for this target (0.0-1.0); the system will omit items with low - // scores when there are too many Direct Share items. - score, - // The name of the component to be launched if this target is chosen. - shareShortcut.getTargetComponent().clone(), - // The extra values here will be merged into the Intent when this target is chosen. - extras); + /** + * Converts a list of ShareShortcutInfos to ChooserTargets. + * @param matchingShortcuts List of shortcuts, all from the same package, that match the current + * share intent filter. + * @param allShortcuts List of all the shortcuts from all the packages on the device that are + * returned for the current sharing action. + * @param allAppTargets List of AppTargets. Null if the results are not from prediction service. + * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or + * TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE + * @return A list of ChooserTargets sorted by score in descending order. + */ + @VisibleForTesting + @NonNull + public List<ChooserTarget> convertToChooserTarget( + @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts, + @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts, + @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) { + // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted + // list instead of the actual rank value when converting a rank to a score. + List<Integer> scoreList = new ArrayList<>(); + if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) { + for (int i = 0; i < matchingShortcuts.size(); i++) { + int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank(); + if (!scoreList.contains(shortcutRank)) { + scoreList.add(shortcutRank); + } + } + Collections.sort(scoreList); + } + + List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size()); + for (int i = 0; i < matchingShortcuts.size(); i++) { + ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo(); + int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i)); + + float score; + if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) { + // Incoming results are ordered. Create a score based on index in the original list. + score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f); + } else { + // Create a score based on the rank of the shortcut. + int rankIndex = scoreList.indexOf(shortcutInfo.getRank()); + score = Math.max(1.0f - (0.01f * rankIndex), 0.0f); + } + + Bundle extras = new Bundle(); + extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId()); + ChooserTarget chooserTarget = new ChooserTarget(shortcutInfo.getShortLabel(), + null, // Icon will be loaded later if this target is selected to be shown. + score, matchingShortcuts.get(i).getTargetComponent().clone(), extras); + + chooserTargetList.add(chooserTarget); + if (mDirectShareAppTargetCache != null && allAppTargets != null) { + mDirectShareAppTargetCache.put(chooserTarget, + allAppTargets.get(indexInAllShortcuts)); + } + } + + // Sort ChooserTargets by score in descending order + Comparator<ChooserTarget> byScore = + (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore()); + Collections.sort(chooserTargetList, byScore); + return chooserTargetList; } private String convertServiceName(String packageName, String serviceName) { @@ -1748,8 +1795,7 @@ public class ChooserActivity extends ResolverActivity { if (!mIsAppPredictorComponentAvailable) { return null; } - if (mAppPredictor == null - && getPackageManager().getAppPredictionServicePackageName() != null) { + if (mAppPredictor == null) { final IntentFilter filter = getTargetIntentFilter(); Bundle extras = new Bundle(); extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter); diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp index 03057dc5e602..0002f8b4048a 100644 --- a/core/jni/android_hardware_SoundTrigger.cpp +++ b/core/jni/android_hardware_SoundTrigger.cpp @@ -606,12 +606,12 @@ android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz, goto exit; } memory = memoryDealer->allocate(offset + size); - if (memory == 0 || memory->pointer() == NULL) { + if (memory == 0 || memory->unsecurePointer() == NULL) { status = SOUNDTRIGGER_STATUS_ERROR; goto exit; } - nSoundModel = (struct sound_trigger_sound_model *)memory->pointer(); + nSoundModel = (struct sound_trigger_sound_model *)memory->unsecurePointer(); nSoundModel->type = type; nSoundModel->uuid = nUuid; @@ -737,18 +737,18 @@ android_hardware_SoundTrigger_startRecognition(JNIEnv *env, jobject thiz, return SOUNDTRIGGER_STATUS_ERROR; } sp<IMemory> memory = memoryDealer->allocate(totalSize); - if (memory == 0 || memory->pointer() == NULL) { + if (memory == 0 || memory->unsecurePointer() == NULL) { return SOUNDTRIGGER_STATUS_ERROR; } if (dataSize != 0) { - memcpy((char *)memory->pointer() + sizeof(struct sound_trigger_recognition_config), + memcpy((char *)memory->unsecurePointer() + sizeof(struct sound_trigger_recognition_config), nData, dataSize); env->ReleaseByteArrayElements(jData, nData, 0); } env->DeleteLocalRef(jData); struct sound_trigger_recognition_config *config = - (struct sound_trigger_recognition_config *)memory->pointer(); + (struct sound_trigger_recognition_config *)memory->unsecurePointer(); config->data_size = dataSize; config->data_offset = sizeof(struct sound_trigger_recognition_config); config->capture_requested = env->GetBooleanField(jConfig, diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index daa63475f706..c5049ecd3784 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -649,7 +649,7 @@ static jint writeToTrack(const sp<AudioTrack>& track, jint audioFormat, const T if ((size_t)sizeInBytes > track->sharedBuffer()->size()) { sizeInBytes = track->sharedBuffer()->size(); } - memcpy(track->sharedBuffer()->pointer(), data + offsetInSamples, sizeInBytes); + memcpy(track->sharedBuffer()->unsecurePointer(), data + offsetInSamples, sizeInBytes); written = sizeInBytes; } if (written >= 0) { diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp index 8553a2c24826..af34e7b7a7ff 100644 --- a/core/jni/android_view_InputChannel.cpp +++ b/core/jni/android_view_InputChannel.cpp @@ -16,6 +16,7 @@ #define LOG_TAG "InputChannel-JNI" +#include "android-base/stringprintf.h" #include <nativehelper/JNIHelp.h> #include "nativehelper/scoped_utf_chars.h" #include <android_runtime/AndroidRuntime.h> @@ -60,7 +61,7 @@ private: // ---------------------------------------------------------------------------- NativeInputChannel::NativeInputChannel(const sp<InputChannel>& inputChannel) : - mInputChannel(inputChannel), mDisposeCallback(NULL) { + mInputChannel(inputChannel), mDisposeCallback(nullptr) { } NativeInputChannel::~NativeInputChannel() { @@ -74,8 +75,8 @@ void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callb void NativeInputChannel::invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj) { if (mDisposeCallback) { mDisposeCallback(env, obj, mInputChannel, mDisposeData); - mDisposeCallback = NULL; - mDisposeData = NULL; + mDisposeCallback = nullptr; + mDisposeData = nullptr; } } @@ -96,14 +97,14 @@ static void android_view_InputChannel_setNativeInputChannel(JNIEnv* env, jobject sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, jobject inputChannelObj) { NativeInputChannel* nativeInputChannel = android_view_InputChannel_getNativeInputChannel(env, inputChannelObj); - return nativeInputChannel != NULL ? nativeInputChannel->getInputChannel() : NULL; + return nativeInputChannel != nullptr ? nativeInputChannel->getInputChannel() : nullptr; } void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj, InputChannelObjDisposeCallback callback, void* data) { NativeInputChannel* nativeInputChannel = android_view_InputChannel_getNativeInputChannel(env, inputChannelObj); - if (nativeInputChannel == NULL) { + if (nativeInputChannel == nullptr) { ALOGW("Cannot set dispose callback because input channel object has not been initialized."); } else { nativeInputChannel->setDisposeCallback(callback, data); @@ -131,27 +132,27 @@ static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel); if (result) { - String8 message; - message.appendFormat("Could not open input channel pair. status=%d", result); - jniThrowRuntimeException(env, message.string()); - return NULL; + std::string message = android::base::StringPrintf( + "Could not open input channel pair : %s", strerror(-result)); + jniThrowRuntimeException(env, message.c_str()); + return nullptr; } - jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL); + jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, nullptr); if (env->ExceptionCheck()) { - return NULL; + return nullptr; } jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, std::make_unique<NativeInputChannel>(serverChannel)); if (env->ExceptionCheck()) { - return NULL; + return nullptr; } jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, std::make_unique<NativeInputChannel>(clientChannel)); if (env->ExceptionCheck()) { - return NULL; + return nullptr; } env->SetObjectArrayElement(channelPair, 0, serverChannelObj); @@ -170,7 +171,7 @@ static void android_view_InputChannel_nativeDispose(JNIEnv* env, jobject obj, jb nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj); - android_view_InputChannel_setNativeInputChannel(env, obj, NULL); + android_view_InputChannel_setNativeInputChannel(env, obj, nullptr); delete nativeInputChannel; } } @@ -179,14 +180,14 @@ static void android_view_InputChannel_nativeRelease(JNIEnv* env, jobject obj, jb NativeInputChannel* nativeInputChannel = android_view_InputChannel_getNativeInputChannel(env, obj); if (nativeInputChannel) { - android_view_InputChannel_setNativeInputChannel(env, obj, NULL); + android_view_InputChannel_setNativeInputChannel(env, obj, nullptr); delete nativeInputChannel; } } static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj, jobject otherObj) { - if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) { + if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != nullptr) { jniThrowException(env, "java/lang/IllegalStateException", "Other object already has a native input channel."); return; @@ -195,12 +196,12 @@ static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj, NativeInputChannel* nativeInputChannel = android_view_InputChannel_getNativeInputChannel(env, obj); android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel); - android_view_InputChannel_setNativeInputChannel(env, obj, NULL); + android_view_InputChannel_setNativeInputChannel(env, obj, nullptr); } static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj, jobject parcelObj) { - if (android_view_InputChannel_getNativeInputChannel(env, obj) != NULL) { + if (android_view_InputChannel_getNativeInputChannel(env, obj) != nullptr) { jniThrowException(env, "java/lang/IllegalStateException", "This object already has a native input channel."); return; @@ -222,25 +223,26 @@ static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject obj, jobject parcelObj) { Parcel* parcel = parcelForJavaObject(env, parcelObj); - if (parcel) { - NativeInputChannel* nativeInputChannel = - android_view_InputChannel_getNativeInputChannel(env, obj); - if (nativeInputChannel) { - sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel(); - - parcel->writeInt32(1); - inputChannel->write(*parcel); - } else { - parcel->writeInt32(0); - } + if (parcel == nullptr) { + ALOGE("Could not obtain parcel for Java object"); + return; + } + + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, obj); + if (!nativeInputChannel) { + parcel->writeInt32(0); // not initialized + return; } + parcel->writeInt32(1); // initialized + nativeInputChannel->getInputChannel()->write(*parcel); } static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj) { NativeInputChannel* nativeInputChannel = android_view_InputChannel_getNativeInputChannel(env, obj); if (! nativeInputChannel) { - return NULL; + return nullptr; } jstring name = env->NewStringUTF(nativeInputChannel->getInputChannel()->getName().c_str()); @@ -250,10 +252,24 @@ static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj) static void android_view_InputChannel_nativeDup(JNIEnv* env, jobject obj, jobject otherObj) { NativeInputChannel* nativeInputChannel = android_view_InputChannel_getNativeInputChannel(env, obj); - if (nativeInputChannel) { - android_view_InputChannel_setNativeInputChannel(env, otherObj, - new NativeInputChannel(nativeInputChannel->getInputChannel()->dup())); + if (nativeInputChannel == nullptr) { + jniThrowRuntimeException(env, "InputChannel has no valid NativeInputChannel"); + return; + } + + sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel(); + if (inputChannel == nullptr) { + jniThrowRuntimeException(env, "NativeInputChannel has no corresponding InputChannel"); + return; + } + sp<InputChannel> dupInputChannel = inputChannel->dup(); + if (dupInputChannel == nullptr) { + std::string message = android::base::StringPrintf( + "Could not duplicate input channel %s", inputChannel->getName().c_str()); + jniThrowRuntimeException(env, message.c_str()); } + android_view_InputChannel_setNativeInputChannel(env, otherObj, + new NativeInputChannel(dupInputChannel)); } static jobject android_view_InputChannel_nativeGetToken(JNIEnv* env, jobject obj) { diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java index e1ccd7523eba..8891d3fd2dca 100644 --- a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java @@ -65,8 +65,10 @@ public class LegacyIntentClassificationFactoryTest { null, null, null, - 0, - 0); + null, + 0L, + 0L, + 0d); List<LabeledIntent> intents = mLegacyIntentClassificationFactory.create( InstrumentationRegistry.getContext(), @@ -101,8 +103,10 @@ public class LegacyIntentClassificationFactoryTest { null, null, null, - 0, - 0); + null, + 0L, + 0L, + 0d); List<LabeledIntent> intents = mLegacyIntentClassificationFactory.create( InstrumentationRegistry.getContext(), diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java index b9a1a8cc4e42..bcea5fea6a13 100644 --- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java @@ -83,9 +83,11 @@ public class TemplateClassificationIntentFactoryTest { null, null, null, + null, createRemoteActionTemplates(), - 0, - 0); + 0L, + 0L, + 0d); List<LabeledIntent> intents = mTemplateClassificationIntentFactory.create( @@ -124,9 +126,11 @@ public class TemplateClassificationIntentFactoryTest { null, null, null, + null, createRemoteActionTemplates(), - 0, - 0); + 0L, + 0L, + 0d); List<LabeledIntent> intents = mTemplateClassificationIntentFactory.create( @@ -162,8 +166,10 @@ public class TemplateClassificationIntentFactoryTest { null, null, null, - 0, - 0); + null, + 0L, + 0L, + 0d); mTemplateClassificationIntentFactory.create( InstrumentationRegistry.getContext(), @@ -196,9 +202,11 @@ public class TemplateClassificationIntentFactoryTest { null, null, null, + null, new RemoteActionTemplate[0], - 0, - 0); + 0L, + 0L, + 0d); mTemplateClassificationIntentFactory.create( InstrumentationRegistry.getContext(), diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index 67036fef30ba..c44b7d81868d 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -36,6 +36,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -51,6 +52,8 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager.ShareShortcutInfo; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -807,6 +810,108 @@ public class ChooserActivityTest { is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST)); } + /** + * The case when AppPrediction service is not defined in PackageManager is already covered + * as a test parameter {@link ChooserActivityTest#packageManagers}. This test is checking the + * case when the prediction service is defined but the component is not available on the device. + */ + @Test + public void testIsAppPredictionServiceAvailable() { + Intent sendIntent = createSendTextIntent(); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + + final ChooserWrapperActivity activity = mActivityRule + .launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + + if (activity.getPackageManager().getAppPredictionServicePackageName() == null) { + assertThat(activity.isAppPredictionServiceAvailable(), is(false)); + } else { + assertThat(activity.isAppPredictionServiceAvailable(), is(true)); + + sOverrides.resources = Mockito.spy(activity.getResources()); + when(sOverrides.resources.getString(R.string.config_defaultAppPredictionService)) + .thenReturn("ComponentNameThatDoesNotExist"); + + assertThat(activity.isAppPredictionServiceAvailable(), is(false)); + } + } + + @Test + public void testConvertToChooserTarget_predictionService() { + Intent sendIntent = createSendTextIntent(); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + + final ChooserWrapperActivity activity = mActivityRule + .launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + + List<ShareShortcutInfo> shortcuts = createShortcuts(activity); + + int[] expectedOrderAllShortcuts = {0, 1, 2, 3}; + float[] expectedScoreAllShortcuts = {1.0f, 0.99f, 0.98f, 0.97f}; + + List<ChooserTarget> chooserTargets = activity.convertToChooserTarget(shortcuts, shortcuts, + null, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE); + assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets, + expectedOrderAllShortcuts, expectedScoreAllShortcuts); + + List<ShareShortcutInfo> subset = new ArrayList<>(); + subset.add(shortcuts.get(1)); + subset.add(shortcuts.get(2)); + subset.add(shortcuts.get(3)); + + int[] expectedOrderSubset = {1, 2, 3}; + float[] expectedScoreSubset = {0.99f, 0.98f, 0.97f}; + + chooserTargets = activity.convertToChooserTarget(subset, shortcuts, null, + TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE); + assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets, + expectedOrderSubset, expectedScoreSubset); + } + + @Test + public void testConvertToChooserTarget_shortcutManager() { + Intent sendIntent = createSendTextIntent(); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + + final ChooserWrapperActivity activity = mActivityRule + .launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + + List<ShareShortcutInfo> shortcuts = createShortcuts(activity); + + int[] expectedOrderAllShortcuts = {2, 0, 3, 1}; + float[] expectedScoreAllShortcuts = {1.0f, 0.99f, 0.99f, 0.98f}; + + List<ChooserTarget> chooserTargets = activity.convertToChooserTarget(shortcuts, shortcuts, + null, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER); + assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets, + expectedOrderAllShortcuts, expectedScoreAllShortcuts); + + List<ShareShortcutInfo> subset = new ArrayList<>(); + subset.add(shortcuts.get(1)); + subset.add(shortcuts.get(2)); + subset.add(shortcuts.get(3)); + + int[] expectedOrderSubset = {2, 3, 1}; + float[] expectedScoreSubset = {1.0f, 0.99f, 0.98f}; + + chooserTargets = activity.convertToChooserTarget(subset, shortcuts, null, + TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER); + assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets, + expectedOrderSubset, expectedScoreSubset); + } + // This test is too long and too slow and should not be taken as an example for future tests. @Test public void testDirectTargetSelectionLogging() throws InterruptedException { @@ -1103,4 +1208,43 @@ public class ChooserActivityTest { return bitmap; } + + private List<ShareShortcutInfo> createShortcuts(Context context) { + Intent testIntent = new Intent("TestIntent"); + + List<ShareShortcutInfo> shortcuts = new ArrayList<>(); + shortcuts.add(new ShareShortcutInfo( + new ShortcutInfo.Builder(context, "shortcut1") + .setIntent(testIntent).setShortLabel("label1").setRank(3).build(), // 0 2 + new ComponentName("package1", "class1"))); + shortcuts.add(new ShareShortcutInfo( + new ShortcutInfo.Builder(context, "shortcut2") + .setIntent(testIntent).setShortLabel("label2").setRank(7).build(), // 1 3 + new ComponentName("package2", "class2"))); + shortcuts.add(new ShareShortcutInfo( + new ShortcutInfo.Builder(context, "shortcut3") + .setIntent(testIntent).setShortLabel("label3").setRank(1).build(), // 2 0 + new ComponentName("package3", "class3"))); + shortcuts.add(new ShareShortcutInfo( + new ShortcutInfo.Builder(context, "shortcut4") + .setIntent(testIntent).setShortLabel("label4").setRank(3).build(), // 3 2 + new ComponentName("package4", "class4"))); + + return shortcuts; + } + + private void assertCorrectShortcutToChooserTargetConversion(List<ShareShortcutInfo> shortcuts, + List<ChooserTarget> chooserTargets, int[] expectedOrder, float[] expectedScores) { + assertEquals(expectedOrder.length, chooserTargets.size()); + for (int i = 0; i < chooserTargets.size(); i++) { + ChooserTarget ct = chooserTargets.get(i); + ShortcutInfo si = shortcuts.get(expectedOrder[i]).getShortcutInfo(); + ComponentName cn = shortcuts.get(expectedOrder[i]).getTargetComponent(); + + assertEquals(si.getId(), ct.getIntentExtras().getString(Intent.EXTRA_SHORTCUT_ID)); + assertEquals(si.getShortLabel(), ct.getTitle()); + assertThat(Math.abs(expectedScores[i] - ct.getScore()) < 0.000001, is(true)); + assertEquals(cn.flattenToString(), ct.getComponentName().flattenToString()); + } + } } diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index 44e56eaf0593..1d567c73f376 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; @@ -85,6 +86,14 @@ public class ChooserWrapperActivity extends ChooserActivity { } @Override + public Resources getResources() { + if (sOverrides.resources != null) { + return sOverrides.resources; + } + return super.getResources(); + } + + @Override protected Bitmap loadThumbnail(Uri uri, Size size) { if (sOverrides.previewThumbnail != null) { return sOverrides.previewThumbnail; @@ -145,6 +154,7 @@ public class ChooserWrapperActivity extends ChooserActivity { public Bitmap previewThumbnail; public MetricsLogger metricsLogger; public int alternateProfileSetting; + public Resources resources; public void reset() { onSafelyStartCallback = null; @@ -157,6 +167,7 @@ public class ChooserWrapperActivity extends ChooserActivity { resolverListController = mock(ResolverListController.class); metricsLogger = mock(MetricsLogger.class); alternateProfileSetting = 0; + resources = null; } } } diff --git a/media/jni/android_media_MediaDataSource.cpp b/media/jni/android_media_MediaDataSource.cpp index 8c38d887f82b..9705b91dd52a 100644 --- a/media/jni/android_media_MediaDataSource.cpp +++ b/media/jni/android_media_MediaDataSource.cpp @@ -106,7 +106,8 @@ ssize_t JMediaDataSource::readAt(off64_t offset, size_t size) { } ALOGV("readAt %lld / %zu => %d.", (long long)offset, size, numread); - env->GetByteArrayRegion(mByteArrayObj, 0, numread, (jbyte*)mMemory->pointer()); + env->GetByteArrayRegion(mByteArrayObj, 0, numread, + (jbyte*)mMemory->unsecurePointer()); return numread; } diff --git a/media/jni/android_media_MediaDescrambler.cpp b/media/jni/android_media_MediaDescrambler.cpp index aa79ce0a44ab..c61365a448d3 100644 --- a/media/jni/android_media_MediaDescrambler.cpp +++ b/media/jni/android_media_MediaDescrambler.cpp @@ -220,7 +220,7 @@ status_t JDescrambler::descramble( return NO_MEMORY; } - memcpy(mMem->pointer(), + memcpy(mMem->unsecurePointer(), (const void*)((const uint8_t*)srcPtr + srcOffset), totalLength); DestinationBuffer dstBuffer; @@ -248,7 +248,8 @@ status_t JDescrambler::descramble( if (*status == Status::OK) { if (*bytesWritten > 0 && (ssize_t) *bytesWritten <= totalLength) { - memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), *bytesWritten); + memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->unsecurePointer(), + *bytesWritten); } else { // status seems OK but bytesWritten is invalid, we really // have no idea what is wrong. diff --git a/media/jni/android_media_MediaHTTPConnection.cpp b/media/jni/android_media_MediaHTTPConnection.cpp index 365e045689f0..53adff3e251e 100644 --- a/media/jni/android_media_MediaHTTPConnection.cpp +++ b/media/jni/android_media_MediaHTTPConnection.cpp @@ -148,7 +148,7 @@ static jint android_media_MediaHTTPConnection_native_readAt( byteArrayObj, 0, n, - (jbyte *)conn->getIMemory()->pointer()); + (jbyte *)conn->getIMemory()->unsecurePointer()); } return n; diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp index 3809bc4752a8..18fd1a01cfd6 100644 --- a/media/jni/android_media_MediaMetadataRetriever.cpp +++ b/media/jni/android_media_MediaMetadataRetriever.cpp @@ -396,8 +396,12 @@ static jobject android_media_MediaMetadataRetriever_getFrameAtTime( // Call native method to retrieve a video frame VideoFrame *videoFrame = NULL; sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option); + // TODO: Using unsecurePointer() has some associated security pitfalls + // (see declaration for details). + // Either document why it is safe in this case or address the + // issue (e.g. by copying). if (frameMemory != 0) { // cast the shared structure to a VideoFrame object - videoFrame = static_cast<VideoFrame *>(frameMemory->pointer()); + videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer()); } if (videoFrame == NULL) { ALOGE("getFrameAtTime: videoFrame is a NULL pointer"); @@ -423,7 +427,11 @@ static jobject android_media_MediaMetadataRetriever_getImageAtIndex( VideoFrame *videoFrame = NULL; sp<IMemory> frameMemory = retriever->getImageAtIndex(index, colorFormat); if (frameMemory != 0) { // cast the shared structure to a VideoFrame object - videoFrame = static_cast<VideoFrame *>(frameMemory->pointer()); + // TODO: Using unsecurePointer() has some associated security pitfalls + // (see declaration for details). + // Either document why it is safe in this case or address the + // issue (e.g. by copying). + videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer()); } if (videoFrame == NULL) { ALOGE("getImageAtIndex: videoFrame is a NULL pointer"); @@ -454,7 +462,11 @@ static jobject android_media_MediaMetadataRetriever_getThumbnailImageAtIndex( sp<IMemory> frameMemory = retriever->getImageAtIndex( index, colorFormat, true /*metaOnly*/, true /*thumbnail*/); if (frameMemory != 0) { - videoFrame = static_cast<VideoFrame *>(frameMemory->pointer()); + // TODO: Using unsecurePointer() has some associated security pitfalls + // (see declaration for details). + // Either document why it is safe in this case or address the + // issue (e.g. by copying). + videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer()); int32_t thumbWidth = videoFrame->mWidth; int32_t thumbHeight = videoFrame->mHeight; videoFrame = NULL; @@ -467,7 +479,11 @@ static jobject android_media_MediaMetadataRetriever_getThumbnailImageAtIndex( || thumbPixels * 6 >= maxPixels) { frameMemory = retriever->getImageAtIndex( index, colorFormat, false /*metaOnly*/, true /*thumbnail*/); - videoFrame = static_cast<VideoFrame *>(frameMemory->pointer()); + // TODO: Using unsecurePointer() has some associated security pitfalls + // (see declaration for details). + // Either document why it is safe in this case or address the + // issue (e.g. by copying). + videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer()); if (thumbPixels > maxPixels) { int downscale = ceil(sqrt(thumbPixels / (float)maxPixels)); @@ -514,11 +530,15 @@ static jobject android_media_MediaMetadataRetriever_getFrameAtIndex( size_t i = 0; for (; i < numFrames; i++) { sp<IMemory> frame = retriever->getFrameAtIndex(frameIndex + i, colorFormat); - if (frame == NULL || frame->pointer() == NULL) { + if (frame == NULL || frame->unsecurePointer() == NULL) { ALOGE("video frame at index %zu is a NULL pointer", frameIndex + i); break; } - VideoFrame *videoFrame = static_cast<VideoFrame *>(frame->pointer()); + // TODO: Using unsecurePointer() has some associated security pitfalls + // (see declaration for details). + // Either document why it is safe in this case or address the + // issue (e.g. by copying). + VideoFrame *videoFrame = static_cast<VideoFrame *>(frame->unsecurePointer()); jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType); env->CallBooleanMethod(arrayList, fields.arrayListAdd, bitmapObj); env->DeleteLocalRef(bitmapObj); @@ -551,7 +571,11 @@ static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture( // the method name to getEmbeddedPicture(). sp<IMemory> albumArtMemory = retriever->extractAlbumArt(); if (albumArtMemory != 0) { // cast the shared structure to a MediaAlbumArt object - mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer()); + // TODO: Using unsecurePointer() has some associated security pitfalls + // (see declaration for details). + // Either document why it is safe in this case or address the + // issue (e.g. by copying). + mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->unsecurePointer()); } if (mediaAlbumArt == NULL) { ALOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed."); diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h index 9d7410305c2c..01e4faae6f6c 100644 --- a/media/jni/soundpool/SoundPool.h +++ b/media/jni/soundpool/SoundPool.h @@ -61,7 +61,7 @@ public: audio_channel_mask_t channelMask() { return mChannelMask; } size_t size() { return mSize; } int state() { return mState; } - uint8_t* data() { return static_cast<uint8_t*>(mData->pointer()); } + uint8_t* data() { return static_cast<uint8_t*>(mData->unsecurePointer()); } status_t doLoad(); void startLoad() { mState = LOADING; } sp<IMemory> getIMemory() { return mData; } diff --git a/media/tests/audiotests/shared_mem_test.cpp b/media/tests/audiotests/shared_mem_test.cpp index 2f5749933b54..d586b6a6da17 100644 --- a/media/tests/audiotests/shared_mem_test.cpp +++ b/media/tests/audiotests/shared_mem_test.cpp @@ -92,7 +92,7 @@ int AudioTrackTest::Test01() { iMem = heap->allocate(BUF_SZ*sizeof(short)); - p = static_cast<uint8_t*>(iMem->pointer()); + p = static_cast<uint8_t*>(iMem->unsecurePointer()); memcpy(p, smpBuf, BUF_SZ*sizeof(short)); sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java index 2010620f76ed..033f1b10118c 100644 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java @@ -30,6 +30,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.security.KeyStoreException; import java.util.Optional; +import java.util.concurrent.TimeUnit; /** * State about encrypted backups that needs to be remembered. @@ -51,6 +52,9 @@ public class CryptoSettings { SECONDARY_KEY_LAST_ROTATED_AT }; + private static final long DEFAULT_SECONDARY_KEY_ROTATION_PERIOD = + TimeUnit.MILLISECONDS.convert(31, TimeUnit.DAYS); + private static final String KEY_ANCESTRAL_SECONDARY_KEY_VERSION = "ancestral_secondary_key_version"; @@ -202,6 +206,11 @@ public class CryptoSettings { .apply(); } + /** The number of milliseconds between secondary key rotation */ + public long backupSecondaryKeyRotationIntervalMs() { + return DEFAULT_SECONDARY_KEY_ROTATION_PERIOD; + } + /** Deletes all crypto settings related to backup (as opposed to restore). */ public void clearAllSettingsForBackup() { Editor sharedPrefsEditor = mSharedPreferences.edit(); diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java new file mode 100644 index 000000000000..91b57cf69795 --- /dev/null +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.backup.encryption.keys; + +import android.content.Context; +import android.util.Slog; + +import com.android.server.backup.encryption.CryptoSettings; +import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask; + +import java.io.File; +import java.time.Clock; +import java.util.Optional; + +/** + * Helps schedule rotations of secondary keys. + * + * <p>TODO(b/72028016) Replace with a job. + */ +public class SecondaryKeyRotationScheduler { + + private static final String TAG = "SecondaryKeyRotationScheduler"; + private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation"; + + private final Context mContext; + private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; + private final CryptoSettings mCryptoSettings; + private final Clock mClock; + + public SecondaryKeyRotationScheduler( + Context context, + RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager, + CryptoSettings cryptoSettings, + Clock clock) { + mContext = context; + mCryptoSettings = cryptoSettings; + mClock = clock; + mSecondaryKeyManager = secondaryKeyManager; + } + + /** + * Returns {@code true} if a sentinel file for forcing secondary key rotation is present. This + * is only for testing purposes. + */ + private boolean isForceRotationTestSentinelPresent() { + File file = new File(mContext.getFilesDir(), SENTINEL_FILE_PATH); + if (file.exists()) { + file.delete(); + return true; + } + return false; + } + + /** Start the key rotation task if it's time to do so */ + public void startRotationIfScheduled() { + if (isForceRotationTestSentinelPresent()) { + Slog.i(TAG, "Found force flag for secondary rotation. Starting now."); + startRotation(); + return; + } + + Optional<Long> maybeLastRotated = mCryptoSettings.getSecondaryLastRotated(); + if (!maybeLastRotated.isPresent()) { + Slog.v(TAG, "No previous rotation, scheduling from now."); + scheduleRotationFromNow(); + return; + } + + long lastRotated = maybeLastRotated.get(); + long now = mClock.millis(); + + if (lastRotated > now) { + Slog.i(TAG, "Last rotation was in the future. Clock must have changed. Rotate now."); + startRotation(); + return; + } + + long millisSinceLastRotation = now - lastRotated; + long rotationInterval = mCryptoSettings.backupSecondaryKeyRotationIntervalMs(); + if (millisSinceLastRotation >= rotationInterval) { + Slog.i( + TAG, + "Last rotation was more than " + + rotationInterval + + "ms (" + + millisSinceLastRotation + + "ms) in the past. Rotate now."); + startRotation(); + } + + Slog.v(TAG, "No rotation required, last " + lastRotated + "."); + } + + private void startRotation() { + scheduleRotationFromNow(); + new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager).run(); + } + + private void scheduleRotationFromNow() { + mCryptoSettings.setSecondaryLastRotated(mClock.millis()); + } +} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java new file mode 100644 index 000000000000..77cfded32173 --- /dev/null +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.backup.encryption.tasks; + +import android.security.keystore.recovery.InternalRecoveryServiceException; +import android.security.keystore.recovery.LockScreenRequiredException; +import android.util.Slog; + +import com.android.internal.util.Preconditions; +import com.android.server.backup.encryption.CryptoSettings; +import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; +import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; + +import java.security.UnrecoverableKeyException; +import java.util.Optional; + +/** + * Starts rotating to a new secondary key. Cannot complete until the screen is unlocked and the new + * key is synced. + */ +public class StartSecondaryKeyRotationTask { + private static final String TAG = "BE-StSecondaryKeyRotTsk"; + + private final CryptoSettings mCryptoSettings; + private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; + + public StartSecondaryKeyRotationTask( + CryptoSettings cryptoSettings, + RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager) { + mCryptoSettings = Preconditions.checkNotNull(cryptoSettings); + mSecondaryKeyManager = Preconditions.checkNotNull(secondaryKeyManager); + } + + /** Begin the key rotation */ + public void run() { + Slog.i(TAG, "Attempting to initiate a secondary key rotation."); + + Optional<String> maybeCurrentAlias = mCryptoSettings.getActiveSecondaryKeyAlias(); + if (!maybeCurrentAlias.isPresent()) { + Slog.w(TAG, "No active current alias. Cannot trigger a secondary rotation."); + return; + } + String currentAlias = maybeCurrentAlias.get(); + + Optional<String> maybeNextAlias = mCryptoSettings.getNextSecondaryKeyAlias(); + if (maybeNextAlias.isPresent()) { + String nextAlias = maybeNextAlias.get(); + if (nextAlias.equals(currentAlias)) { + // Shouldn't be possible, but guard against accidentally deleting the active key. + Slog.e(TAG, "Was already trying to rotate to what is already the active key."); + } else { + Slog.w(TAG, "Was already rotating to another key. Cancelling that."); + try { + mSecondaryKeyManager.remove(nextAlias); + } catch (Exception e) { + Slog.wtf(TAG, "Could not remove old key", e); + } + } + mCryptoSettings.removeNextSecondaryKeyAlias(); + } + + RecoverableKeyStoreSecondaryKey newSecondaryKey; + try { + newSecondaryKey = mSecondaryKeyManager.generate(); + } catch (LockScreenRequiredException e) { + Slog.e(TAG, "No lock screen is set - cannot generate a new key to rotate to.", e); + return; + } catch (InternalRecoveryServiceException e) { + Slog.e(TAG, "Internal error in Recovery Controller, failed to rotate key.", e); + return; + } catch (UnrecoverableKeyException e) { + Slog.e(TAG, "Failed to get key after generating, failed to rotate", e); + return; + } + + String alias = newSecondaryKey.getAlias(); + Slog.i(TAG, "Generated a new secondary key with alias '" + alias + "'."); + try { + mCryptoSettings.setNextSecondaryAlias(alias); + Slog.i(TAG, "Successfully set '" + alias + "' as next key to rotate to"); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Unexpected error setting next alias", e); + try { + mSecondaryKeyManager.remove(alias); + } catch (Exception err) { + Slog.wtf(TAG, "Failed to remove generated key after encountering error", err); + } + } + } +} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java new file mode 100644 index 000000000000..c31d19d8568c --- /dev/null +++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.backup.encryption.keys; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.platform.test.annotations.Presubmit; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.server.backup.encryption.CryptoSettings; +import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; + +import java.io.File; +import java.time.Clock; + +@Config(shadows = SecondaryKeyRotationSchedulerTest.ShadowStartSecondaryKeyRotationTask.class) +@RunWith(RobolectricTestRunner.class) +@Presubmit +public class SecondaryKeyRotationSchedulerTest { + private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation"; + + @Mock private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; + @Mock private Clock mClock; + + private CryptoSettings mCryptoSettings; + private SecondaryKeyRotationScheduler mScheduler; + private long mRotationIntervalMillis; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Context application = ApplicationProvider.getApplicationContext(); + + mCryptoSettings = CryptoSettings.getInstanceForTesting(application); + mRotationIntervalMillis = mCryptoSettings.backupSecondaryKeyRotationIntervalMs(); + + mScheduler = + new SecondaryKeyRotationScheduler( + application, mSecondaryKeyManager, mCryptoSettings, mClock); + ShadowStartSecondaryKeyRotationTask.reset(); + } + + @Test + public void startRotationIfScheduled_rotatesIfRotationWasFarEnoughInThePast() { + long lastRotated = 100009; + mCryptoSettings.setSecondaryLastRotated(lastRotated); + setNow(lastRotated + mRotationIntervalMillis); + + mScheduler.startRotationIfScheduled(); + + assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); + } + + @Test + public void startRotationIfScheduled_setsNewRotationTimeIfRotationWasFarEnoughInThePast() { + long lastRotated = 100009; + long now = lastRotated + mRotationIntervalMillis; + mCryptoSettings.setSecondaryLastRotated(lastRotated); + setNow(now); + + mScheduler.startRotationIfScheduled(); + + assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); + } + + @Test + public void startRotationIfScheduled_rotatesIfClockHasChanged() { + long lastRotated = 100009; + mCryptoSettings.setSecondaryLastRotated(lastRotated); + setNow(lastRotated - 1); + + mScheduler.startRotationIfScheduled(); + + assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); + } + + @Test + public void startRotationIfScheduled_rotatesIfSentinelFileIsPresent() throws Exception { + File file = new File(RuntimeEnvironment.application.getFilesDir(), SENTINEL_FILE_PATH); + file.createNewFile(); + + mScheduler.startRotationIfScheduled(); + + assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); + } + + @Test + public void startRotationIfScheduled_setsNextRotationIfClockHasChanged() { + long lastRotated = 100009; + long now = lastRotated - 1; + mCryptoSettings.setSecondaryLastRotated(lastRotated); + setNow(now); + + mScheduler.startRotationIfScheduled(); + + assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); + } + + @Test + public void startRotationIfScheduled_doesNothingIfRotationWasRecentEnough() { + long lastRotated = 100009; + mCryptoSettings.setSecondaryLastRotated(lastRotated); + setNow(lastRotated + mRotationIntervalMillis - 1); + + mScheduler.startRotationIfScheduled(); + + assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isFalse(); + } + + @Test + public void startRotationIfScheduled_doesNotSetRotationTimeIfRotationWasRecentEnough() { + long lastRotated = 100009; + mCryptoSettings.setSecondaryLastRotated(lastRotated); + setNow(lastRotated + mRotationIntervalMillis - 1); + + mScheduler.startRotationIfScheduled(); + + assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(lastRotated); + } + + @Test + public void startRotationIfScheduled_setsLastRotatedToNowIfNeverRotated() { + long now = 13295436; + setNow(now); + + mScheduler.startRotationIfScheduled(); + + assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); + } + + private void setNow(long timestamp) { + when(mClock.millis()).thenReturn(timestamp); + } + + @Implements(StartSecondaryKeyRotationTask.class) + public static class ShadowStartSecondaryKeyRotationTask { + private static boolean sRan = false; + + @Implementation + public void run() { + sRan = true; + } + + @Resetter + public static void reset() { + sRan = false; + } + } +} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java new file mode 100644 index 000000000000..4ac4fa8d06c9 --- /dev/null +++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.backup.encryption.tasks; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.Presubmit; +import android.security.keystore.recovery.RecoveryController; + +import com.android.server.backup.encryption.CryptoSettings; +import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; +import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; +import com.android.server.testing.shadows.ShadowRecoveryController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.security.SecureRandom; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowRecoveryController.class}) +@Presubmit +public class StartSecondaryKeyRotationTaskTest { + + private CryptoSettings mCryptoSettings; + private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; + private StartSecondaryKeyRotationTask mStartSecondaryKeyRotationTask; + + @Before + public void setUp() throws Exception { + mSecondaryKeyManager = + new RecoverableKeyStoreSecondaryKeyManager( + RecoveryController.getInstance(RuntimeEnvironment.application), + new SecureRandom()); + mCryptoSettings = CryptoSettings.getInstanceForTesting(RuntimeEnvironment.application); + mStartSecondaryKeyRotationTask = + new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager); + + ShadowRecoveryController.reset(); + } + + @Test + public void run_doesNothingIfNoActiveSecondaryExists() { + mStartSecondaryKeyRotationTask.run(); + + assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); + } + + @Test + public void run_doesNotRemoveExistingNextSecondaryKeyIfItIsAlreadyActive() throws Exception { + generateAnActiveKey(); + String activeAlias = mCryptoSettings.getActiveSecondaryKeyAlias().get(); + mCryptoSettings.setNextSecondaryAlias(activeAlias); + + mStartSecondaryKeyRotationTask.run(); + + assertThat(mSecondaryKeyManager.get(activeAlias).isPresent()).isTrue(); + } + + @Test + public void run_doesRemoveExistingNextSecondaryKeyIfItIsNotYetActive() throws Exception { + generateAnActiveKey(); + RecoverableKeyStoreSecondaryKey nextKey = mSecondaryKeyManager.generate(); + String nextAlias = nextKey.getAlias(); + mCryptoSettings.setNextSecondaryAlias(nextAlias); + + mStartSecondaryKeyRotationTask.run(); + + assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isFalse(); + } + + @Test + public void run_generatesANewNextSecondaryKey() throws Exception { + generateAnActiveKey(); + + mStartSecondaryKeyRotationTask.run(); + + assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isTrue(); + } + + @Test + public void run_generatesANewKeyThatExistsInKeyStore() throws Exception { + generateAnActiveKey(); + + mStartSecondaryKeyRotationTask.run(); + + String nextAlias = mCryptoSettings.getNextSecondaryKeyAlias().get(); + assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isTrue(); + } + + private void generateAnActiveKey() throws Exception { + RecoverableKeyStoreSecondaryKey secondaryKey = mSecondaryKeyManager.generate(); + mCryptoSettings.setActiveSecondaryKeyAlias(secondaryKey.getAlias()); + } +} diff --git a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml deleted file mode 100644 index 0c6d57dd6183..000000000000 --- a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<!-- - ~ Copyright (C) 2018 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 - --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="?android:attr/colorBackgroundFloating" /> - <corners - android:topLeftRadius="@dimen/biometric_dialog_corner_size" - android:topRightRadius="@dimen/biometric_dialog_corner_size" - android:bottomLeftRadius="@dimen/biometric_dialog_corner_size" - android:bottomRightRadius="@dimen/biometric_dialog_corner_size"/> -</shape> diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml deleted file mode 100644 index e687cdfac356..000000000000 --- a/packages/SystemUI/res/layout/biometric_dialog.xml +++ /dev/null @@ -1,190 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2018 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 - --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/layout" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <ImageView - android:id="@+id/background" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="center" /> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="bottom" - android:background="@color/biometric_dialog_dim_color" - android:orientation="vertical"> - - <!-- This is not a Space since Spaces cannot be clicked --> - <View - android:id="@+id/space" - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" - android:contentDescription="@string/biometric_dialog_empty_space_description"/> - - <ScrollView - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <!-- This is not a Space since Spaces cannot be clicked. The width of this changes - depending on horizontal/portrait orientation --> - <View - android:id="@+id/left_space" - android:layout_weight="1" - android:layout_width="0dp" - android:layout_height="match_parent" - android:contentDescription="@string/biometric_dialog_empty_space_description"/> - - <LinearLayout - android:id="@+id/dialog" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:background="@drawable/biometric_dialog_bg" - android:layout_marginBottom="@dimen/biometric_dialog_border_padding" - android:layout_marginLeft="@dimen/biometric_dialog_border_padding" - android:layout_marginRight="@dimen/biometric_dialog_border_padding"> - - <TextView - android:id="@+id/title" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginEnd="24dp" - android:layout_marginStart="24dp" - android:layout_marginTop="24dp" - android:gravity="@integer/biometric_dialog_text_gravity" - android:textSize="20sp" - android:textColor="?android:attr/textColorPrimary"/> - - <TextView - android:id="@+id/subtitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:layout_marginStart="24dp" - android:layout_marginEnd="24dp" - android:gravity="@integer/biometric_dialog_text_gravity" - android:textSize="16sp" - android:textColor="?android:attr/textColorPrimary"/> - - <TextView - android:id="@+id/description" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginEnd="24dp" - android:layout_marginStart="24dp" - android:gravity="@integer/biometric_dialog_text_gravity" - android:paddingTop="8dp" - android:textSize="16sp" - android:textColor="?android:attr/textColorPrimary"/> - - <ImageView - android:id="@+id/biometric_icon" - android:layout_width="@dimen/biometric_dialog_biometric_icon_size" - android:layout_height="@dimen/biometric_dialog_biometric_icon_size" - android:layout_gravity="center_horizontal" - android:layout_marginTop="48dp" - android:scaleType="fitXY" /> - - <TextView - android:id="@+id/error" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginEnd="24dp" - android:layout_marginStart="24dp" - android:paddingTop="16dp" - android:paddingBottom="24dp" - android:textSize="12sp" - android:gravity="center_horizontal" - android:accessibilityLiveRegion="polite" - android:textColor="@color/biometric_dialog_gray"/> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="72dip" - android:paddingTop="24dp" - android:layout_gravity="center_vertical" - style="?android:attr/buttonBarStyle" - android:orientation="horizontal" - android:measureWithLargestChild="true"> - <Space android:id="@+id/leftSpacer" - android:layout_width="12dp" - android:layout_height="match_parent" - android:visibility="visible" /> - <!-- Negative Button --> - <Button android:id="@+id/button2" - android:layout_width="wrap_content" - android:layout_height="match_parent" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:gravity="center" - android:maxLines="2" /> - <Space android:id="@+id/middleSpacer" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:visibility="visible" /> - <!-- Positive Button --> - <Button android:id="@+id/button1" - android:layout_width="wrap_content" - android:layout_height="match_parent" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:gravity="center" - android:maxLines="2" - android:text="@string/biometric_dialog_confirm" - android:visibility="gone"/> - <!-- Try Again Button --> - <Button android:id="@+id/button_try_again" - android:layout_width="wrap_content" - android:layout_height="match_parent" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:gravity="center" - android:maxLines="2" - android:text="@string/biometric_dialog_try_again" - android:visibility="gone"/> - <Space android:id="@+id/rightSpacer" - android:layout_width="12dip" - android:layout_height="match_parent" - android:visibility="visible" /> - </LinearLayout> - </LinearLayout> - - <!-- This is not a Space since Spaces cannot be clicked. The width of this changes - depending on horizontal/portrait orientation --> - <View - android:id="@+id/right_space" - android:layout_weight="1" - android:layout_width="0dp" - android:layout_height="match_parent" - android:contentDescription="@string/biometric_dialog_empty_space_description"/> - - </LinearLayout> - - </ScrollView> - - </LinearLayout> - -</FrameLayout> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 8c95b844ae2f..f853b638db46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -503,8 +503,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo * device is dozing when the light sensor is on. */ public void setAodFrontScrimAlpha(float alpha) { - if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn() - && mInFrontAlpha != alpha) { + if (((mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) + || mState == ScrimState.PULSING) && mInFrontAlpha != alpha) { mInFrontAlpha = alpha; updateScrims(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 2623b46ba50f..5d3cdc88aa99 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -243,7 +243,7 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test - public void transitionToPulsing() { + public void transitionToPulsing_withFrontAlphaUpdates() { // Pre-condition // Need to go to AoD first because PULSING doesn't change // the back scrim opacity - otherwise it would hide AoD wallpapers. @@ -267,11 +267,22 @@ public class ScrimControllerTest extends SysuiTestCase { true /* behind */, false /* bubble */); + // ... and when ambient goes dark, front scrim should be semi-transparent + mScrimController.setAodFrontScrimAlpha(0.5f); + mScrimController.finishAnimationsImmediately(); + // Front scrim should be semi-transparent + assertScrimAlpha(SEMI_TRANSPARENT /* front */, + OPAQUE /* back */, + TRANSPARENT /* bubble */); + mScrimController.setWakeLockScreenSensorActive(true); mScrimController.finishAnimationsImmediately(); - assertScrimAlpha(TRANSPARENT /* front */, + assertScrimAlpha(SEMI_TRANSPARENT /* front */, SEMI_TRANSPARENT /* back */, TRANSPARENT /* bubble */); + + // Reset value since enums are static. + mScrimController.setAodFrontScrimAlpha(0f); } @Test diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 548665ba3a32..8c60f1aa439b 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -34,6 +34,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.LongArrayQueue; import android.util.Slog; import android.util.Xml; @@ -86,6 +87,8 @@ public class PackageWatchdog { // Number of package failures within the duration above before we notify observers @VisibleForTesting static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5; + @VisibleForTesting + static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2); // Whether explicit health checks are enabled or not private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true; @@ -224,8 +227,10 @@ public class PackageWatchdog { * check state will be reset to a default depending on if the package is contained in * {@link mPackagesWithExplicitHealthCheckEnabled}. * - * @throws IllegalArgumentException if {@code packageNames} is empty - * or {@code durationMs} is less than 1 + * <p>If {@code packageNames} is empty, this will be a no-op. + * + * <p>If {@code durationMs} is less than 1, a default monitoring duration + * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used. */ public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames, long durationMs) { @@ -234,9 +239,9 @@ public class PackageWatchdog { return; } if (durationMs < 1) { - // TODO: Instead of failing, monitor for default? 48hrs? - throw new IllegalArgumentException("Invalid duration " + durationMs + "ms for observer " + Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer " + observer.getName() + ". Not observing packages " + packageNames); + durationMs = DEFAULT_OBSERVING_DURATION_MS; } List<MonitoredPackage> packages = new ArrayList<>(); @@ -969,6 +974,9 @@ public class PackageWatchdog { class MonitoredPackage { //TODO(b/120598832): VersionedPackage? private final String mName; + // Times when package failures happen sorted in ascending order + @GuardedBy("mLock") + private final LongArrayQueue mFailureHistory = new LongArrayQueue(); // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after // methods that could change the health check state: handleElapsedTimeLocked and // tryPassHealthCheckLocked @@ -988,12 +996,6 @@ public class PackageWatchdog { // of the package, see #getHealthCheckStateLocked @GuardedBy("mLock") private long mHealthCheckDurationMs = Long.MAX_VALUE; - // System uptime of first package failure - @GuardedBy("mLock") - private long mUptimeStartMs; - // Number of failures since mUptimeStartMs - @GuardedBy("mLock") - private int mFailures; MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) { this(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck); @@ -1028,20 +1030,17 @@ public class PackageWatchdog { */ @GuardedBy("mLock") public boolean onFailureLocked() { + // Sliding window algorithm: find out if there exists a window containing failures >= + // mTriggerFailureCount. final long now = mSystemClock.uptimeMillis(); - final long duration = now - mUptimeStartMs; - if (duration > mTriggerFailureDurationMs) { - // TODO(b/120598832): Reseting to 1 is not correct - // because there may be more than 1 failure in the last trigger window from now - // This is the RescueParty impl, will leave for now - mFailures = 1; - mUptimeStartMs = now; - } else { - mFailures++; + mFailureHistory.addLast(now); + while (now - mFailureHistory.peekFirst() > mTriggerFailureDurationMs) { + // Prune values falling out of the window + mFailureHistory.removeFirst(); } - boolean failed = mFailures >= mTriggerFailureCount; + boolean failed = mFailureHistory.size() >= mTriggerFailureCount; if (failed) { - mFailures = 0; + mFailureHistory.clear(); } return failed; } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 7f69a683b18a..146be5aa7044 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -882,20 +882,20 @@ public class AppOpsService extends IAppOpsService.Stub { final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); final String[] changedPkgs = intent.getStringArrayExtra( Intent.EXTRA_CHANGED_PACKAGE_LIST); - ArraySet<ModeCallback> callbacks; - synchronized (AppOpsService.this) { - callbacks = mOpModeWatchers.get(OP_PLAY_AUDIO); - if (callbacks == null) { - return; + for (int code : OPS_RESTRICTED_ON_SUSPEND) { + ArraySet<ModeCallback> callbacks; + synchronized (AppOpsService.this) { + callbacks = mOpModeWatchers.get(code); + if (callbacks == null) { + continue; + } + callbacks = new ArraySet<>(callbacks); } - callbacks = new ArraySet<>(callbacks); - } - for (int i = 0; i < changedUids.length; i++) { - final int changedUid = changedUids[i]; - final String changedPkg = changedPkgs[i]; - // We trust packagemanager to insert matching uid and packageNames in the - // extras - for (int code : OPS_RESTRICTED_ON_SUSPEND) { + for (int i = 0; i < changedUids.length; i++) { + final int changedUid = changedUids[i]; + final String changedPkg = changedPkgs[i]; + // We trust packagemanager to insert matching uid and packageNames in the + // extras notifyOpChanged(callbacks, code, changedUid, changedPkg); } } @@ -2852,9 +2852,11 @@ public class AppOpsService extends IAppOpsService.Stub { } private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) { + if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) { + return false; + } final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); - return ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code) - && pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid)); + return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid)); } private boolean isOpRestrictedLocked(int uid, int code, String packageName, diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java index b27d5ea30c67..f8ffb7c1c0e2 100644 --- a/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java +++ b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java @@ -23,7 +23,6 @@ import android.content.ServiceConnection; import android.media.tv.ITvRemoteProvider; import android.media.tv.ITvRemoteServiceInput; import android.os.Binder; -import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; @@ -49,7 +48,6 @@ final class TvRemoteProviderProxy implements ServiceConnection { private final ComponentName mComponentName; private final int mUserId; private final int mUid; - private final Handler mHandler; /** * State guarded by mLock. @@ -65,15 +63,14 @@ final class TvRemoteProviderProxy implements ServiceConnection { private boolean mRunning; private boolean mBound; private Connection mActiveConnection; - private boolean mConnectionReady; - public TvRemoteProviderProxy(Context context, ComponentName componentName, int userId, - int uid) { + TvRemoteProviderProxy(Context context, ProviderMethods provider, + ComponentName componentName, int userId, int uid) { mContext = context; + mProviderMethods = provider; mComponentName = componentName; mUserId = userId; mUid = uid; - mHandler = new Handler(); } public void dump(PrintWriter pw, String prefix) { @@ -82,11 +79,6 @@ final class TvRemoteProviderProxy implements ServiceConnection { pw.println(prefix + " mRunning=" + mRunning); pw.println(prefix + " mBound=" + mBound); pw.println(prefix + " mActiveConnection=" + mActiveConnection); - pw.println(prefix + " mConnectionReady=" + mConnectionReady); - } - - public void setProviderSink(ProviderMethods provider) { - mProviderMethods = provider; } public boolean hasComponentName(String packageName, String className) { @@ -101,7 +93,7 @@ final class TvRemoteProviderProxy implements ServiceConnection { } mRunning = true; - updateBinding(); + bind(); } } @@ -112,31 +104,19 @@ final class TvRemoteProviderProxy implements ServiceConnection { } mRunning = false; - updateBinding(); + unbind(); } } public void rebindIfDisconnected() { synchronized (mLock) { - if (mActiveConnection == null && shouldBind()) { + if (mActiveConnection == null && mRunning) { unbind(); bind(); } } } - private void updateBinding() { - if (shouldBind()) { - bind(); - } else { - unbind(); - } - } - - private boolean shouldBind() { - return mRunning; - } - private void bind() { if (!mBound) { if (DEBUG) { @@ -208,48 +188,19 @@ final class TvRemoteProviderProxy implements ServiceConnection { disconnect(); } - - private void onConnectionReady(Connection connection) { - synchronized (mLock) { - if (DEBUG) Slog.d(TAG, "onConnectionReady"); - if (mActiveConnection == connection) { - if (DEBUG) Slog.d(TAG, "mConnectionReady = true"); - mConnectionReady = true; - } - } - } - - private void onConnectionDied(Connection connection) { - if (mActiveConnection == connection) { - if (DEBUG) Slog.d(TAG, this + ": Service connection died"); - disconnect(); - } - } - private void disconnect() { synchronized (mLock) { if (mActiveConnection != null) { - mConnectionReady = false; mActiveConnection.dispose(); mActiveConnection = null; } } } - // Provider helpers - public void inputBridgeConnected(IBinder token) { - synchronized (mLock) { - if (DEBUG) Slog.d(TAG, this + ": inputBridgeConnected token: " + token); - if (mConnectionReady) { - mActiveConnection.onInputBridgeConnected(token); - } - } - } - - public interface ProviderMethods { + interface ProviderMethods { // InputBridge - void openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name, - int width, int height, int maxPointers); + boolean openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name, + int width, int height, int maxPointers); void closeInputBridge(TvRemoteProviderProxy provider, IBinder token); @@ -267,7 +218,7 @@ final class TvRemoteProviderProxy implements ServiceConnection { void sendPointerSync(TvRemoteProviderProxy provider, IBinder token); } - private final class Connection implements IBinder.DeathRecipient { + private final class Connection { private final ITvRemoteProvider mTvRemoteProvider; private final RemoteServiceInputProvider mServiceInputProvider; @@ -279,24 +230,16 @@ final class TvRemoteProviderProxy implements ServiceConnection { public boolean register() { if (DEBUG) Slog.d(TAG, "Connection::register()"); try { - mTvRemoteProvider.asBinder().linkToDeath(this, 0); mTvRemoteProvider.setRemoteServiceInputSink(mServiceInputProvider); - mHandler.post(new Runnable() { - @Override - public void run() { - onConnectionReady(Connection.this); - } - }); return true; } catch (RemoteException ex) { - binderDied(); + dispose(); + return false; } - return false; } public void dispose() { if (DEBUG) Slog.d(TAG, "Connection::dispose()"); - mTvRemoteProvider.asBinder().unlinkToDeath(this, 0); mServiceInputProvider.dispose(); } @@ -310,16 +253,6 @@ final class TvRemoteProviderProxy implements ServiceConnection { } } - @Override - public void binderDied() { - mHandler.post(new Runnable() { - @Override - public void run() { - onConnectionDied(Connection.this); - } - }); - } - void openInputBridge(final IBinder token, final String name, final int width, final int height, final int maxPointers) { synchronized (mLock) { @@ -330,9 +263,9 @@ final class TvRemoteProviderProxy implements ServiceConnection { } final long idToken = Binder.clearCallingIdentity(); try { - if (mProviderMethods != null) { - mProviderMethods.openInputBridge(TvRemoteProviderProxy.this, token, - name, width, height, maxPointers); + if (mProviderMethods.openInputBridge(TvRemoteProviderProxy.this, token, + name, width, height, maxPointers)) { + onInputBridgeConnected(token); } } finally { Binder.restoreCallingIdentity(idToken); @@ -356,9 +289,7 @@ final class TvRemoteProviderProxy implements ServiceConnection { } final long idToken = Binder.clearCallingIdentity(); try { - if (mProviderMethods != null) { - mProviderMethods.closeInputBridge(TvRemoteProviderProxy.this, token); - } + mProviderMethods.closeInputBridge(TvRemoteProviderProxy.this, token); } finally { Binder.restoreCallingIdentity(idToken); } @@ -381,9 +312,7 @@ final class TvRemoteProviderProxy implements ServiceConnection { } final long idToken = Binder.clearCallingIdentity(); try { - if (mProviderMethods != null) { - mProviderMethods.clearInputBridge(TvRemoteProviderProxy.this, token); - } + mProviderMethods.clearInputBridge(TvRemoteProviderProxy.this, token); } finally { Binder.restoreCallingIdentity(idToken); } @@ -412,10 +341,7 @@ final class TvRemoteProviderProxy implements ServiceConnection { } final long idToken = Binder.clearCallingIdentity(); try { - if (mProviderMethods != null) { - mProviderMethods.sendKeyDown(TvRemoteProviderProxy.this, token, - keyCode); - } + mProviderMethods.sendKeyDown(TvRemoteProviderProxy.this, token, keyCode); } finally { Binder.restoreCallingIdentity(idToken); } @@ -438,9 +364,7 @@ final class TvRemoteProviderProxy implements ServiceConnection { } final long idToken = Binder.clearCallingIdentity(); try { - if (mProviderMethods != null) { - mProviderMethods.sendKeyUp(TvRemoteProviderProxy.this, token, keyCode); - } + mProviderMethods.sendKeyUp(TvRemoteProviderProxy.this, token, keyCode); } finally { Binder.restoreCallingIdentity(idToken); } @@ -463,10 +387,8 @@ final class TvRemoteProviderProxy implements ServiceConnection { } final long idToken = Binder.clearCallingIdentity(); try { - if (mProviderMethods != null) { - mProviderMethods.sendPointerDown(TvRemoteProviderProxy.this, token, - pointerId, x, y); - } + mProviderMethods.sendPointerDown(TvRemoteProviderProxy.this, token, + pointerId, x, y); } finally { Binder.restoreCallingIdentity(idToken); } @@ -489,10 +411,8 @@ final class TvRemoteProviderProxy implements ServiceConnection { } final long idToken = Binder.clearCallingIdentity(); try { - if (mProviderMethods != null) { - mProviderMethods.sendPointerUp(TvRemoteProviderProxy.this, token, - pointerId); - } + mProviderMethods.sendPointerUp(TvRemoteProviderProxy.this, token, + pointerId); } finally { Binder.restoreCallingIdentity(idToken); } diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java index d27970f4882c..0d29edd02663 100644 --- a/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java +++ b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java @@ -45,7 +45,7 @@ final class TvRemoteProviderWatcher { private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE); private final Context mContext; - private final ProviderMethods mProvider; + private final TvRemoteProviderProxy.ProviderMethods mProvider; private final Handler mHandler; private final PackageManager mPackageManager; private final ArrayList<TvRemoteProviderProxy> mProviderProxies = new ArrayList<>(); @@ -54,10 +54,10 @@ final class TvRemoteProviderWatcher { private boolean mRunning; - public TvRemoteProviderWatcher(Context context, ProviderMethods provider, Handler handler) { + TvRemoteProviderWatcher(Context context, TvRemoteProviderProxy.ProviderMethods provider) { mContext = context; mProvider = provider; - mHandler = handler; + mHandler = new Handler(true); mUserId = UserHandle.myUserId(); mPackageManager = context.getPackageManager(); mUnbundledServicePackage = context.getString( @@ -116,12 +116,11 @@ final class TvRemoteProviderWatcher { int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name); if (sourceIndex < 0) { TvRemoteProviderProxy providerProxy = - new TvRemoteProviderProxy(mContext, + new TvRemoteProviderProxy(mContext, mProvider, new ComponentName(serviceInfo.packageName, serviceInfo.name), mUserId, serviceInfo.applicationInfo.uid); providerProxy.start(); mProviderProxies.add(targetIndex++, providerProxy); - mProvider.addProvider(providerProxy); } else if (sourceIndex >= targetIndex) { TvRemoteProviderProxy provider = mProviderProxies.get(sourceIndex); provider.start(); // restart the provider if needed @@ -135,7 +134,6 @@ final class TvRemoteProviderWatcher { if (targetIndex < mProviderProxies.size()) { for (int i = mProviderProxies.size() - 1; i >= targetIndex; i--) { TvRemoteProviderProxy providerProxy = mProviderProxies.get(i); - mProvider.removeProvider(providerProxy); mProviderProxies.remove(providerProxy); providerProxy.stop(); } @@ -212,10 +210,4 @@ final class TvRemoteProviderWatcher { scanPackages(); } }; - - public interface ProviderMethods { - void addProvider(TvRemoteProviderProxy providerProxy); - - void removeProvider(TvRemoteProviderProxy providerProxy); - } } diff --git a/services/core/java/com/android/server/tv/TvRemoteService.java b/services/core/java/com/android/server/tv/TvRemoteService.java index ba74791842d1..bee6fb34a899 100644 --- a/services/core/java/com/android/server/tv/TvRemoteService.java +++ b/services/core/java/com/android/server/tv/TvRemoteService.java @@ -17,11 +17,8 @@ package com.android.server.tv; import android.content.Context; -import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; -import android.os.Looper; -import android.os.Message; import android.util.ArrayMap; import android.util.Slog; @@ -29,7 +26,6 @@ import com.android.server.SystemService; import com.android.server.Watchdog; import java.io.IOException; -import java.util.ArrayList; import java.util.Map; /** @@ -44,9 +40,8 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { private static final boolean DEBUG = false; private static final boolean DEBUG_KEYS = false; + private final TvRemoteProviderWatcher mWatcher; private Map<IBinder, UinputBridge> mBridgeMap = new ArrayMap(); - private Map<IBinder, TvRemoteProviderProxy> mProviderMap = new ArrayMap(); - private ArrayList<TvRemoteProviderProxy> mProviderList = new ArrayList<>(); /** * State guarded by mLock. @@ -60,11 +55,10 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { */ private final Object mLock = new Object(); - public final UserHandler mHandler; - public TvRemoteService(Context context) { super(context); - mHandler = new UserHandler(new UserProvider(TvRemoteService.this), context); + mWatcher = new TvRemoteProviderWatcher(context, + new UserProvider(TvRemoteService.this)); Watchdog.getInstance().addMonitor(this); } @@ -80,21 +74,17 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { @Override public void onBootPhase(int phase) { + // All lifecycle methods are called from the system server's main looper thread. if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { if (DEBUG) Slog.d(TAG, "PHASE_THIRD_PARTY_APPS_CAN_START"); - mHandler.sendEmptyMessage(UserHandler.MSG_START); - } - } - //Outgoing calls. - private void informInputBridgeConnected(IBinder token) { - mHandler.obtainMessage(UserHandler.MSG_INPUT_BRIDGE_CONNECTED, 0, 0, token).sendToTarget(); + mWatcher.start(); // Also schedules the start of all providers. + } } - // Incoming calls. - private void openInputBridgeInternalLocked(TvRemoteProviderProxy provider, final IBinder token, - String name, int width, int height, - int maxPointers) { + private boolean openInputBridgeInternalLocked(final IBinder token, + String name, int width, int height, + int maxPointers) { if (DEBUG) { Slog.d(TAG, "openInputBridgeInternalLocked(), token: " + token + ", name: " + name + ", width: " + width + ", height: " + height + ", maxPointers: " + maxPointers); @@ -104,15 +94,11 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { //Create a new bridge, if one does not exist already if (mBridgeMap.containsKey(token)) { if (DEBUG) Slog.d(TAG, "RemoteBridge already exists"); - // Respond back with success. - informInputBridgeConnected(token); - return; + return true; } UinputBridge inputBridge = new UinputBridge(token, name, width, height, maxPointers); - mBridgeMap.put(token, inputBridge); - mProviderMap.put(token, provider); try { token.linkToDeath(new IBinder.DeathRecipient() { @@ -126,15 +112,13 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { } catch (RemoteException e) { if (DEBUG) Slog.d(TAG, "Token is already dead"); closeInputBridgeInternalLocked(token); - return; + return false; } - - // Respond back with success. - informInputBridgeConnected(token); - } catch (IOException ioe) { Slog.e(TAG, "Cannot create device for " + name); + return false; } + return true; } private void closeInputBridgeInternalLocked(IBinder token) { @@ -149,7 +133,6 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { } mBridgeMap.remove(token); - mProviderMap.remove(token); } private void clearInputBridgeInternalLocked(IBinder token) { @@ -220,47 +203,7 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { } } - private final class UserHandler extends Handler { - - public static final int MSG_START = 1; - public static final int MSG_INPUT_BRIDGE_CONNECTED = 2; - - private final TvRemoteProviderWatcher mWatcher; - private boolean mRunning; - - public UserHandler(UserProvider provider, Context context) { - super(Looper.getMainLooper(), null, true); - mWatcher = new TvRemoteProviderWatcher(context, provider, this); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_START: { - start(); - break; - } - case MSG_INPUT_BRIDGE_CONNECTED: { - IBinder token = (IBinder) msg.obj; - TvRemoteProviderProxy provider = mProviderMap.get(token); - if (provider != null) { - provider.inputBridgeConnected(token); - } - break; - } - } - } - - private void start() { - if (!mRunning) { - mRunning = true; - mWatcher.start(); // also starts all providers - } - } - } - - private final class UserProvider implements TvRemoteProviderWatcher.ProviderMethods, - TvRemoteProviderProxy.ProviderMethods { + private final class UserProvider implements TvRemoteProviderProxy.ProviderMethods { private final TvRemoteService mService; @@ -269,8 +212,8 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { } @Override - public void openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name, - int width, int height, int maxPointers) { + public boolean openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name, + int width, int height, int maxPointers) { if (DEBUG) { Slog.d(TAG, "openInputBridge(), token: " + token + ", name: " + name + ", width: " + width + @@ -278,10 +221,8 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { } synchronized (mLock) { - if (mProviderList.contains(provider)) { - mService.openInputBridgeInternalLocked(provider, token, name, width, height, - maxPointers); - } + return mService.openInputBridgeInternalLocked(token, name, width, + height, maxPointers); } } @@ -289,9 +230,7 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { public void closeInputBridge(TvRemoteProviderProxy provider, IBinder token) { if (DEBUG) Slog.d(TAG, "closeInputBridge(), token: " + token); synchronized (mLock) { - if (mProviderList.contains(provider)) { mService.closeInputBridgeInternalLocked(token); - } } } @@ -299,9 +238,7 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { public void clearInputBridge(TvRemoteProviderProxy provider, IBinder token) { if (DEBUG) Slog.d(TAG, "clearInputBridge(), token: " + token); synchronized (mLock) { - if (mProviderList.contains(provider)) { mService.clearInputBridgeInternalLocked(token); - } } } @@ -311,9 +248,7 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { Slog.d(TAG, "sendKeyDown(), token: " + token + ", keyCode: " + keyCode); } synchronized (mLock) { - if (mProviderList.contains(provider)) { mService.sendKeyDownInternalLocked(token, keyCode); - } } } @@ -323,9 +258,7 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { Slog.d(TAG, "sendKeyUp(), token: " + token + ", keyCode: " + keyCode); } synchronized (mLock) { - if (mProviderList.contains(provider)) { mService.sendKeyUpInternalLocked(token, keyCode); - } } } @@ -336,9 +269,7 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { Slog.d(TAG, "sendPointerDown(), token: " + token + ", pointerId: " + pointerId); } synchronized (mLock) { - if (mProviderList.contains(provider)) { mService.sendPointerDownInternalLocked(token, pointerId, x, y); - } } } @@ -348,9 +279,7 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { Slog.d(TAG, "sendPointerUp(), token: " + token + ", pointerId: " + pointerId); } synchronized (mLock) { - if (mProviderList.contains(provider)) { mService.sendPointerUpInternalLocked(token, pointerId); - } } } @@ -358,29 +287,7 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor { public void sendPointerSync(TvRemoteProviderProxy provider, IBinder token) { if (DEBUG_KEYS) Slog.d(TAG, "sendPointerSync(), token: " + token); synchronized (mLock) { - if (mProviderList.contains(provider)) { mService.sendPointerSyncInternalLocked(token); - } - } - } - - @Override - public void addProvider(TvRemoteProviderProxy provider) { - if (DEBUG) Slog.d(TAG, "addProvider " + provider); - synchronized (mLock) { - provider.setProviderSink(this); - mProviderList.add(provider); - Slog.d(TAG, "provider: " + provider.toString()); - } - } - - @Override - public void removeProvider(TvRemoteProviderProxy provider) { - if (DEBUG) Slog.d(TAG, "removeProvider " + provider); - synchronized (mLock) { - if (mProviderList.remove(provider) == false) { - Slog.e(TAG, "Unknown provider " + provider); - } } } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index b0f1e5d69be4..3cdb59beb23c 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -37,6 +37,7 @@ import android.app.UserSwitchObserver; import android.app.WallpaperColors; import android.app.WallpaperInfo; import android.app.WallpaperManager; +import android.app.WallpaperManager.SetWallpaperFlags; import android.app.admin.DevicePolicyManager; import android.app.backup.WallpaperBackupHelper; import android.content.BroadcastReceiver; @@ -73,7 +74,6 @@ import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SELinux; -import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -91,9 +91,9 @@ import android.util.SparseBooleanArray; import android.util.Xml; import android.view.Display; import android.view.DisplayInfo; -import android.view.IWindowManager; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; @@ -739,7 +739,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } private final Context mContext; - private final IWindowManager mIWindowManager; private final WindowManagerInternal mWindowManagerInternal; private final IPackageManager mIPackageManager; private final MyPackageMonitor mMonitor; @@ -792,7 +791,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub */ private final SparseArray<SparseArray<RemoteCallbackList<IWallpaperManagerCallback>>> mColorsChangedListeners; - private WallpaperData mLastWallpaper; + protected WallpaperData mLastWallpaper; private IWallpaperManagerCallback mKeyguardListener; private boolean mWaitingForUnlock; private boolean mShuttingDown; @@ -825,7 +824,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private SparseArray<DisplayData> mDisplayDatas = new SparseArray<>(); - private WallpaperData mFallbackWallpaper; + protected WallpaperData mFallbackWallpaper; private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray(); private int mCurrentUserId = UserHandle.USER_NULL; @@ -900,9 +899,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub */ final Rect cropHint = new Rect(0, 0, 0, 0); - WallpaperData(int userId, String inputFileName, String cropFileName) { + WallpaperData(int userId, File wallpaperDir, String inputFileName, String cropFileName) { this.userId = userId; - final File wallpaperDir = getWallpaperDir(userId); wallpaperFile = new File(wallpaperDir, inputFileName); cropFile = new File(wallpaperDir, cropFileName); } @@ -917,7 +915,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private static final class DisplayData { + @VisibleForTesting + static final class DisplayData { int mWidth = -1; int mHeight = -1; final Rect mPadding = new Rect(0, 0, 0, 0); @@ -1057,13 +1056,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken); - try { - mIWindowManager.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId); - } catch (RemoteException e) { - Slog.e(TAG, "Failed add wallpaper window token on display " + mDisplayId, e); - return; - } - + mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId); final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId); try { connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false, @@ -1081,10 +1074,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub void disconnectLocked() { if (DEBUG) Slog.v(TAG, "Removing window token: " + mToken); - try { - mIWindowManager.removeWindowToken(mToken, mDisplayId); - } catch (RemoteException e) { - } + mWindowManagerInternal.removeWindowToken(mToken, false/* removeWindows */, + mDisplayId); try { if (mEngine != null) { mEngine.destroy(); @@ -1562,6 +1553,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + @VisibleForTesting + WallpaperData getCurrentWallpaperData(@SetWallpaperFlags int which, int userId) { + synchronized (mLock) { + final SparseArray<WallpaperData> wallpaperDataMap = + which == FLAG_SYSTEM ? mWallpaperMap : mLockWallpaperMap; + return wallpaperDataMap.get(userId); + } + } + public WallpaperManagerService(Context context) { if (DEBUG) Slog.v(TAG, "WallpaperService startup"); mContext = context; @@ -1569,8 +1569,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mImageWallpaper = ComponentName.unflattenFromString( context.getResources().getString(R.string.image_wallpaper_component)); mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context); - mIWindowManager = IWindowManager.Stub.asInterface( - ServiceManager.getService(Context.WINDOW_SERVICE)); mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mIPackageManager = AppGlobals.getPackageManager(); mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); @@ -1600,7 +1598,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub getWallpaperSafeLocked(UserHandle.USER_SYSTEM, FLAG_SYSTEM); } - private static File getWallpaperDir(int userId) { + File getWallpaperDir(int userId) { return Environment.getUserSystemDirectory(userId); } @@ -1819,7 +1817,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // while locked, so pretend like the component was actually // bound into place wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent; - final WallpaperData fallback = new WallpaperData(wallpaper.userId, + final WallpaperData fallback = + new WallpaperData(wallpaper.userId, getWallpaperDir(wallpaper.userId), WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP); ensureSaneWallpaperData(fallback, DEFAULT_DISPLAY); bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply); @@ -2380,7 +2379,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // We know a-priori that there is no lock-only wallpaper currently - WallpaperData lockWP = new WallpaperData(userId, + WallpaperData lockWP = new WallpaperData(userId, getWallpaperDir(userId), WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP); lockWP.wallpaperId = sysWP.wallpaperId; lockWP.cropHint.set(sysWP.cropHint); @@ -2793,7 +2792,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private static JournaledFile makeJournaledFile(int userId) { + private JournaledFile makeJournaledFile(int userId) { final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath(); return new JournaledFile(new File(base), new File(base + ".tmp")); } @@ -2958,7 +2957,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // it now. if (wallpaper == null) { if (which == FLAG_LOCK) { - wallpaper = new WallpaperData(userId, + wallpaper = new WallpaperData(userId, getWallpaperDir(userId), WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP); mLockWallpaperMap.put(userId, wallpaper); ensureSaneWallpaperData(wallpaper, DEFAULT_DISPLAY); @@ -2966,7 +2965,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // sanity fallback: we're in bad shape, but establishing a known // valid system+lock WallpaperData will keep us from dying. Slog.wtf(TAG, "Didn't find wallpaper in non-lock case!"); - wallpaper = new WallpaperData(userId, WALLPAPER, WALLPAPER_CROP); + wallpaper = new WallpaperData(userId, getWallpaperDir(userId), + WALLPAPER, WALLPAPER_CROP); mWallpaperMap.put(userId, wallpaper); ensureSaneWallpaperData(wallpaper, DEFAULT_DISPLAY); } @@ -2985,7 +2985,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // Do this once per boot migrateFromOld(); - wallpaper = new WallpaperData(userId, WALLPAPER, WALLPAPER_CROP); + wallpaper = new WallpaperData(userId, getWallpaperDir(userId), + WALLPAPER, WALLPAPER_CROP); wallpaper.allowBackup = true; mWallpaperMap.put(userId, wallpaper); if (!wallpaper.cropExists()) { @@ -3037,7 +3038,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // keyguard-specific wallpaper for this user WallpaperData lockWallpaper = mLockWallpaperMap.get(userId); if (lockWallpaper == null) { - lockWallpaper = new WallpaperData(userId, + lockWallpaper = new WallpaperData(userId, getWallpaperDir(userId), WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP); mLockWallpaperMap.put(userId, lockWallpaper); } @@ -3088,8 +3089,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private void initializeFallbackWallpaper() { if (mFallbackWallpaper == null) { if (DEBUG) Slog.d(TAG, "Initialize fallback wallpaper"); - mFallbackWallpaper = new WallpaperData( - UserHandle.USER_SYSTEM, WALLPAPER, WALLPAPER_CROP); + final int systemUserId = UserHandle.USER_SYSTEM; + mFallbackWallpaper = new WallpaperData(systemUserId, getWallpaperDir(systemUserId), + WALLPAPER, WALLPAPER_CROP); mFallbackWallpaper.allowBackup = false; mFallbackWallpaper.wallpaperId = makeWallpaperIdLocked(); bindWallpaperComponentLocked(mImageWallpaper, true, false, mFallbackWallpaper, null); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 55db1a06664e..b35bd9e4e81a 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1535,6 +1535,11 @@ class ActivityStarter { final TaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null) ? mSourceRecord.getTaskRecord() : null; setNewTask(taskToAffiliate); + if (mService.getLockTaskController().isLockTaskModeViolation( + mStartActivity.getTaskRecord())) { + Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity); + return START_RETURN_LOCK_TASK_MODE_VIOLATION; + } } else if (mAddingToTask) { addOrReparentStartingActivity(targetTask, "adding to task"); } @@ -1654,9 +1659,8 @@ class ActivityStarter { final boolean isNewClearTask = (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); - if (mService.getLockTaskController().isInLockTaskMode() && (newTask - || mService.getLockTaskController().isLockTaskModeViolation(targetTask, - isNewClearTask))) { + if (!newTask && mService.getLockTaskController().isLockTaskModeViolation(targetTask, + isNewClearTask)) { Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity); return START_RETURN_LOCK_TASK_MODE_VIOLATION; } @@ -2538,7 +2542,8 @@ class ActivityStarter { final boolean onTop = (aOptions == null || !aOptions.getAvoidMoveToFront()) && !mLaunchTaskBehind; final ActivityStack stack = - mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams); + mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams, + mRequest.realCallingPid, mRequest.realCallingUid); return stack; } // Otherwise handle adjacent launch. @@ -2656,11 +2661,24 @@ class ActivityStarter { return this; } + /** + * Sets the pid of the caller who originally started the activity. + * + * Normally, the pid/uid would be the calling pid from the binder call. + * However, in case of a {@link PendingIntent}, the pid/uid pair of the caller is considered + * the original entity that created the pending intent, in contrast to setRealCallingPid/Uid, + * which represents the entity who invoked pending intent via {@link PendingIntent#send}. + */ ActivityStarter setCallingPid(int pid) { mRequest.callingPid = pid; return this; } + /** + * Sets the uid of the caller who originally started the activity. + * + * @see #setCallingPid + */ ActivityStarter setCallingUid(int uid) { mRequest.callingUid = uid; return this; @@ -2671,11 +2689,25 @@ class ActivityStarter { return this; } + /** + * Sets the pid of the caller who requested to launch the activity. + * + * The pid/uid represents the caller who launches the activity in this request. + * It will almost same as setCallingPid/Uid except when processing {@link PendingIntent}: + * the pid/uid will be the caller who called {@link PendingIntent#send()}. + * + * @see #setCallingPid + */ ActivityStarter setRealCallingPid(int pid) { mRequest.realCallingPid = pid; return this; } + /** + * Sets the uid of the caller who requested to launch the activity. + * + * @see #setRealCallingPid + */ ActivityStarter setRealCallingUid(int uid) { mRequest.realCallingUid = uid; return this; diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index caf87cd6a906..b30da5e156e2 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -280,13 +280,6 @@ public class LockTaskController { } /** - * @return true if currently in the lock task mode, otherwise, return false. - */ - boolean isInLockTaskMode() { - return !mLockTaskModeTasks.isEmpty(); - } - - /** * @return whether the requested task is disallowed to be launched. */ boolean isLockTaskModeViolation(TaskRecord task) { diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index d9e30a2da9a5..eb6b51e3388b 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -1615,7 +1615,8 @@ class RootActivityContainer extends ConfigurationContainer <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r, @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop) { - return getLaunchStack(r, options, candidateTask, onTop, null /* launchParams */); + return getLaunchStack(r, options, candidateTask, onTop, null /* launchParams */, + -1 /* no realCallingPid */, -1 /* no realCallingUid */); } /** @@ -1624,13 +1625,16 @@ class RootActivityContainer extends ConfigurationContainer * @param r The activity we are trying to launch. Can be null. * @param options The activity options used to the launch. Can be null. * @param candidateTask The possible task the activity might be launched in. Can be null. - * @params launchParams The resolved launch params to use. + * @param launchParams The resolved launch params to use. + * @param realCallingPid The pid from {@link ActivityStarter#setRealCallingPid} + * @param realCallingUid The uid from {@link ActivityStarter#setRealCallingUid} * * @return The stack to use for the launch or INVALID_STACK_ID. */ <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r, @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop, - @Nullable LaunchParamsController.LaunchParams launchParams) { + @Nullable LaunchParamsController.LaunchParams launchParams, int realCallingPid, + int realCallingUid) { int taskId = INVALID_TASK_ID; int displayId = INVALID_DISPLAY; //Rect bounds = null; @@ -1661,7 +1665,15 @@ class RootActivityContainer extends ConfigurationContainer if (launchParams != null && launchParams.mPreferredDisplayId != INVALID_DISPLAY) { displayId = launchParams.mPreferredDisplayId; } - if (displayId != INVALID_DISPLAY && canLaunchOnDisplay(r, displayId)) { + final boolean canLaunchOnDisplayFromStartRequest = + realCallingPid != 0 && realCallingUid > 0 && r != null + && mStackSupervisor.canPlaceEntityOnDisplay(displayId, realCallingPid, + realCallingUid, r.info); + // Checking if the activity's launch caller, or the realCallerId of the activity from + // start request (i.e. entity that invokes PendingIntent) is allowed to launch on the + // display. + if (displayId != INVALID_DISPLAY && (canLaunchOnDisplay(r, displayId) + || canLaunchOnDisplayFromStartRequest)) { if (r != null) { stack = (T) getValidLaunchStackOnDisplay(displayId, r, candidateTask, options, launchParams); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7ad8b58d1c7d..607a013abc51 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1216,7 +1216,8 @@ public class WindowManagerService extends IWindowManager.Stub mPropertiesChangedListener = properties -> { synchronized (mGlobalLock) { final int exclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP, - properties.getInt(KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0)); + DeviceConfig.getInt(DeviceConfig.NAMESPACE_WINDOW_MANAGER, + KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0)); final boolean excludedByPreQSticky = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 478bc88fe815..3154c7021255 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1999,6 +1999,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return LocalServices.getService(LockSettingsInternal.class); } + boolean hasUserSetupCompleted(DevicePolicyData userData) { + return userData.mUserSetupComplete; + } + boolean isBuildDebuggable() { return Build.IS_DEBUGGABLE; } @@ -8271,7 +8275,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return true; } - return getUserData(userHandle).mUserSetupComplete; + return mInjector.hasUserSetupCompleted(getUserData(userHandle)); } private boolean hasPaired(int userHandle) { diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index ad94e6159b87..8699669bf4a5 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -25,6 +25,7 @@ android_test { "mockito-target-extended-minus-junit4", "platform-test-annotations", "truth-prebuilt", + "testables", ], libs: [ diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java new file mode 100644 index 000000000000..307092d24d84 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wallpaper; + +import static android.app.WallpaperManager.FLAG_SYSTEM; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.reset; + +import android.app.AppGlobals; +import android.app.AppOpsManager; +import android.app.WallpaperManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.content.pm.ServiceInfo; +import android.hardware.display.DisplayManager; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.service.wallpaper.IWallpaperConnection; +import android.service.wallpaper.WallpaperService; +import android.testing.TestableContext; +import android.util.Log; +import android.util.SparseArray; +import android.view.Display; + +import androidx.test.filters.FlakyTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.internal.R; +import com.android.server.LocalServices; +import com.android.server.wallpaper.WallpaperManagerService.WallpaperData; +import com.android.server.wm.WindowManagerInternal; + +import org.hamcrest.CoreMatchers; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.quality.Strictness; + +import java.io.File; +import java.io.IOException; + +/** + * Tests for the {@link WallpaperManagerService} class. + * + * Build/Install/Run: + * atest FrameworksMockingServicesTests:WallpaperManagerServiceTests + */ +@Presubmit +@FlakyTest(bugId = 129797242) +@RunWith(AndroidJUnit4.class) +public class WallpaperManagerServiceTests { + private static final int DISPLAY_SIZE_DIMENSION = 100; + private static StaticMockitoSession sMockitoSession; + + @ClassRule + public static final TestableContext sContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getTargetContext(), null); + private static ComponentName sImageWallpaperComponentName; + private static ComponentName sDefaultWallpaperComponent; + + private IPackageManager mIpm = AppGlobals.getPackageManager(); + + @Mock + private DisplayManager mDisplayManager; + + @Rule + public final TemporaryFolder mFolder = new TemporaryFolder(); + private final SparseArray<File> mTempDirs = new SparseArray<>(); + private WallpaperManagerService mService; + + @BeforeClass + public static void setUpClass() { + sMockitoSession = mockitoSession() + .strictness(Strictness.LENIENT) + .spyStatic(LocalServices.class) + .spyStatic(WallpaperManager.class) + .startMocking(); + + final WindowManagerInternal dmi = mock(WindowManagerInternal.class); + LocalServices.addService(WindowManagerInternal.class, dmi); + + sContext.addMockSystemService(Context.APP_OPS_SERVICE, mock(AppOpsManager.class)); + + spyOn(sContext); + sContext.getTestablePermissions().setPermission( + android.Manifest.permission.SET_WALLPAPER_COMPONENT, + PackageManager.PERMISSION_GRANTED); + sContext.getTestablePermissions().setPermission( + android.Manifest.permission.SET_WALLPAPER, + PackageManager.PERMISSION_GRANTED); + doNothing().when(sContext).sendBroadcastAsUser(any(), any()); + + //Wallpaper components + final IWallpaperConnection.Stub wallpaperService = mock(IWallpaperConnection.Stub.class); + sImageWallpaperComponentName = ComponentName.unflattenFromString( + sContext.getResources().getString(R.string.image_wallpaper_component)); + // Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper. + sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext); + + if (sDefaultWallpaperComponent == null) { + sDefaultWallpaperComponent = sImageWallpaperComponentName; + doReturn(sImageWallpaperComponentName).when(() -> + WallpaperManager.getDefaultWallpaperComponent(any())); + } else { + sContext.addMockService(sDefaultWallpaperComponent, wallpaperService); + } + + sContext.addMockService(sImageWallpaperComponentName, wallpaperService); + } + + @AfterClass + public static void tearDownClass() { + if (sMockitoSession != null) { + sMockitoSession.finishMocking(); + sMockitoSession = null; + } + LocalServices.removeServiceForTest(WindowManagerInternal.class); + sImageWallpaperComponentName = null; + sDefaultWallpaperComponent = null; + reset(sContext); + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + sContext.addMockSystemService(DisplayManager.class, mDisplayManager); + + final Display mockDisplay = mock(Display.class); + doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension(); + doReturn(mockDisplay).when(mDisplayManager).getDisplay(anyInt()); + + final Display[] displays = new Display[]{mockDisplay}; + doReturn(displays).when(mDisplayManager).getDisplays(); + + spyOn(mIpm); + mService = new TestWallpaperManagerService(sContext); + spyOn(mService); + mService.systemReady(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(WallpaperManagerInternal.class); + + mTempDirs.clear(); + reset(mIpm); + mService = null; + } + + protected class TestWallpaperManagerService extends WallpaperManagerService { + private static final String TAG = "TestWallpaperManagerService"; + + TestWallpaperManagerService(Context context) { + super(context); + } + + @Override + File getWallpaperDir(int userId) { + File tempDir = mTempDirs.get(userId); + if (tempDir == null) { + try { + tempDir = mFolder.newFolder(String.valueOf(userId)); + mTempDirs.append(userId, tempDir); + } catch (IOException e) { + Log.e(TAG, "getWallpaperDir failed at userId= " + userId); + } + } + return tempDir; + } + + // Always return true for test + @Override + public boolean isWallpaperSupported(String callingPackage) { + return true; + } + + // Always return true for test + @Override + public boolean isSetWallpaperAllowed(String callingPackage) { + return true; + } + } + + /** + * Tests that internal basic data should be correct after boot up. + */ + @Test + public void testDataCorrectAfterBoot() { + mService.switchUser(UserHandle.USER_SYSTEM, null); + + final WallpaperData fallbackData = mService.mFallbackWallpaper; + assertEquals("Fallback wallpaper component should be ImageWallpaper.", + sImageWallpaperComponentName, fallbackData.wallpaperComponent); + + verifyLastWallpaperData(UserHandle.USER_SYSTEM, sDefaultWallpaperComponent); + verifyDisplayData(); + } + + /** + * Tests setWallpaperComponent and clearWallpaper should work as expected. + */ + @Test + public void testSetThenClearComponent() { + // Skip if there is no pre-defined default wallpaper component. + assumeThat(sDefaultWallpaperComponent, + not(CoreMatchers.equalTo(sImageWallpaperComponentName))); + + final int testUserId = UserHandle.USER_SYSTEM; + mService.switchUser(testUserId, null); + verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent); + verifyCurrentSystemData(testUserId); + + mService.setWallpaperComponent(sImageWallpaperComponentName); + verifyLastWallpaperData(testUserId, sImageWallpaperComponentName); + verifyCurrentSystemData(testUserId); + + mService.clearWallpaper(null, FLAG_SYSTEM, testUserId); + verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent); + verifyCurrentSystemData(testUserId); + } + + /** + * Tests internal data should be correct and no crash after switch user continuously. + */ + @Test + public void testSwitchMultipleUsers() throws Exception { + final int lastUserId = 5; + final ServiceInfo pi = mIpm.getServiceInfo(sDefaultWallpaperComponent, + PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, 0); + doReturn(pi).when(mIpm).getServiceInfo(any(), anyInt(), anyInt()); + + final Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); + final ParceledListSlice ris = + mIpm.queryIntentServices(intent, + intent.resolveTypeIfNeeded(sContext.getContentResolver()), + PackageManager.GET_META_DATA, 0); + doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyInt(), anyInt()); + doReturn(PackageManager.PERMISSION_GRANTED).when(mIpm).checkPermission( + eq(android.Manifest.permission.AMBIENT_WALLPAPER), any(), anyInt()); + + for (int userId = 0; userId <= lastUserId; userId++) { + mService.switchUser(userId, null); + verifyLastWallpaperData(userId, sDefaultWallpaperComponent); + verifyCurrentSystemData(userId); + } + verifyNoConnectionBeforeLastUser(lastUserId); + } + + /** + * Tests internal data should be correct and no crash after switch user + unlock user + * continuously. + * Simulating that the selected WallpaperService is not built-in. After switching users, the + * service should not be bound, but bound to the image wallpaper. After receiving the user + * unlock callback and can find the selected service for the user, the selected service should + * be bound. + */ + @Test + public void testSwitchThenUnlockMultipleUsers() throws Exception { + final int lastUserId = 5; + final ServiceInfo pi = mIpm.getServiceInfo(sDefaultWallpaperComponent, + PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, 0); + doReturn(pi).when(mIpm).getServiceInfo(any(), anyInt(), anyInt()); + + final Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); + final ParceledListSlice ris = + mIpm.queryIntentServices(intent, + intent.resolveTypeIfNeeded(sContext.getContentResolver()), + PackageManager.GET_META_DATA, 0); + doReturn(PackageManager.PERMISSION_GRANTED).when(mIpm).checkPermission( + eq(android.Manifest.permission.AMBIENT_WALLPAPER), any(), anyInt()); + + for (int userId = 1; userId <= lastUserId; userId++) { + mService.switchUser(userId, null); + verifyLastWallpaperData(userId, sImageWallpaperComponentName); + // Simulate user unlocked + doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyInt(), eq(userId)); + mService.onUnlockUser(userId); + verifyLastWallpaperData(userId, sDefaultWallpaperComponent); + verifyCurrentSystemData(userId); + } + verifyNoConnectionBeforeLastUser(lastUserId); + verifyDisplayData(); + } + + // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for + // non-current user must not bind to wallpaper service. + private void verifyNoConnectionBeforeLastUser(int lastUserId) { + for (int i = 0; i < lastUserId; i++) { + final WallpaperData userData = mService.getCurrentWallpaperData(FLAG_SYSTEM, i); + assertNull("No user data connection left", userData.connection); + } + } + + private void verifyLastWallpaperData(int lastUserId, ComponentName expectedComponent) { + final WallpaperData lastData = mService.mLastWallpaper; + assertNotNull("Last wallpaper must not be null", lastData); + assertEquals("Last wallpaper component must be equals.", expectedComponent, + lastData.wallpaperComponent); + assertEquals("The user id in last wallpaper should be the last switched user", + lastUserId, lastData.userId); + assertNotNull("Must exist user data connection on last wallpaper data", + lastData.connection); + } + + private void verifyCurrentSystemData(int userId) { + final WallpaperData lastData = mService.mLastWallpaper; + final WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, userId); + assertEquals("Last wallpaper should be equals to current system wallpaper", + lastData, wallpaper); + } + + private void verifyDisplayData() { + mService.forEachDisplayData(data -> { + assertTrue("Display width must larger than maximum screen size", + data.mWidth >= DISPLAY_SIZE_DIMENSION); + assertTrue("Display height must larger than maximum screen size", + data.mHeight >= DISPLAY_SIZE_DIMENSION); + }); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java index bd3d9ab2220d..3852b9fec001 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java @@ -17,6 +17,7 @@ package com.android.server.pm; import android.content.Context; import android.content.pm.ModuleInfo; +import android.content.pm.PackageManager; import android.test.InstrumentationTestCase; import com.android.frameworks.servicestests.R; @@ -28,7 +29,7 @@ public class ModuleInfoProviderTest extends InstrumentationTestCase { public void testSuccessfulParse() { ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata); - List<ModuleInfo> mi = provider.getInstalledModules(0); + List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL); assertEquals(2, mi.size()); Collections.sort(mi, (ModuleInfo m1, ModuleInfo m2) -> @@ -49,18 +50,18 @@ public class ModuleInfoProviderTest extends InstrumentationTestCase { public void testParseFailure_incorrectTopLevelElement() { ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata1); - assertEquals(0, provider.getInstalledModules(0).size()); + assertEquals(0, provider.getInstalledModules(PackageManager.MATCH_ALL).size()); } public void testParseFailure_incorrectModuleElement() { ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata2); - assertEquals(0, provider.getInstalledModules(0).size()); + assertEquals(0, provider.getInstalledModules(PackageManager.MATCH_ALL).size()); } public void testParse_unknownAttributesIgnored() { ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata); - List<ModuleInfo> mi = provider.getInstalledModules(0); + List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL); assertEquals(2, mi.size()); ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0); diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java index 5ae043402526..2290ef79da78 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java @@ -18,7 +18,10 @@ package com.android.server.pm; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; +import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_PLAY_AUDIO; +import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.opToName; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -39,7 +42,6 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; import android.content.res.Resources; -import android.media.AudioAttributes; import android.os.BaseBundle; import android.os.Bundle; import android.os.Handler; @@ -553,28 +555,42 @@ public class SuspendPackagesTest { } @Test - public void testAudioOpBlockedOnSuspend() throws Exception { + public void testCameraBlockedOnSuspend() throws Exception { + assertOpBlockedOnSuspend(OP_CAMERA); + } + + @Test + public void testPlayAudioBlockedOnSuspend() throws Exception { + assertOpBlockedOnSuspend(OP_PLAY_AUDIO); + } + + @Test + public void testRecordAudioBlockedOnSuspend() throws Exception { + assertOpBlockedOnSuspend(OP_RECORD_AUDIO); + } + + private void assertOpBlockedOnSuspend(int code) throws Exception { final IAppOpsService iAppOps = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); final CountDownLatch latch = new CountDownLatch(1); final IAppOpsCallback watcher = new IAppOpsCallback.Stub() { @Override public void opChanged(int op, int uid, String packageName) { - if (op == OP_PLAY_AUDIO && packageName.equals(TEST_APP_PACKAGE_NAME)) { + if (op == code && packageName.equals(TEST_APP_PACKAGE_NAME)) { latch.countDown(); } } }; - iAppOps.startWatchingMode(OP_PLAY_AUDIO, TEST_APP_PACKAGE_NAME, watcher); + iAppOps.startWatchingMode(code, TEST_APP_PACKAGE_NAME, watcher); final int testPackageUid = mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0); - int audioOpMode = iAppOps.checkAudioOperation(OP_PLAY_AUDIO, - AudioAttributes.USAGE_UNKNOWN, testPackageUid, TEST_APP_PACKAGE_NAME); - assertEquals("Audio muted for unsuspended package", MODE_ALLOWED, audioOpMode); + int opMode = iAppOps.checkOperation(code, testPackageUid, TEST_APP_PACKAGE_NAME); + assertEquals("Op " + opToName(code) + " disallowed for unsuspended package", MODE_ALLOWED, + opMode); suspendTestPackage(null, null, null); assertTrue("AppOpsWatcher did not callback", latch.await(5, TimeUnit.SECONDS)); - audioOpMode = iAppOps.checkAudioOperation(OP_PLAY_AUDIO, - AudioAttributes.USAGE_UNKNOWN, testPackageUid, TEST_APP_PACKAGE_NAME); - assertEquals("Audio not muted for suspended package", MODE_IGNORED, audioOpMode); + opMode = iAppOps.checkOperation(code, testPackageUid, TEST_APP_PACKAGE_NAME); + assertEquals("Op " + opToName(code) + " allowed for suspended package", MODE_IGNORED, + opMode); iAppOps.stopWatchingMode(watcher); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 34cc0c742005..8393ae0c3aec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -354,7 +354,7 @@ public class ActivityStarterTests extends ActivityTestsBase { doReturn(stack).when(mRootActivityContainer) .getLaunchStack(any(), any(), any(), anyBoolean()); doReturn(stack).when(mRootActivityContainer) - .getLaunchStack(any(), any(), any(), anyBoolean(), any()); + .getLaunchStack(any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt()); } // Set up mock package manager internal and make sure no unmocked methods are called @@ -501,7 +501,6 @@ public class ActivityStarterTests extends ActivityTestsBase { final ActivityStarter starter = prepareStarter(0); final LockTaskController lockTaskController = mService.getLockTaskController(); - doReturn(true).when(lockTaskController).isInLockTaskMode(); doReturn(true).when(lockTaskController).isLockTaskModeViolation(any()); final int result = starter.setReason("testTaskModeViolation").execute(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 1731f7cdd59c..d311dfc60675 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -139,6 +139,8 @@ class ActivityTestsBase { private int mScreenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; private boolean mLaunchTaskBehind; private int mConfigChanges; + private int mLaunchedFromPid; + private int mLaunchedFromUid; ActivityBuilder(ActivityTaskManagerService service) { mService = service; @@ -214,6 +216,16 @@ class ActivityTestsBase { return this; } + ActivityBuilder setLaunchedFromPid(int pid) { + mLaunchedFromPid = pid; + return this; + } + + ActivityBuilder setLaunchedFromUid(int uid) { + mLaunchedFromUid = uid; + return this; + } + ActivityRecord build() { if (mComponent == null) { final int id = sCurrentActivityId++; @@ -250,10 +262,11 @@ class ActivityTestsBase { } final ActivityRecord activity = new ActivityRecord(mService, null /* caller */, - 0 /* launchedFromPid */, 0, null, intent, null, - aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */, - 0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */, - mService.mStackSupervisor, options, null /* sourceRecord */); + mLaunchedFromPid /* launchedFromPid */, mLaunchedFromUid /* launchedFromUid */, + null, intent, null, aInfo /*aInfo*/, new Configuration(), null /* resultTo */, + null /* resultWho */, 0 /* reqCode */, false /*componentSpecified*/, + false /* rootVoiceInteraction */, mService.mStackSupervisor, options, + null /* sourceRecord */); spyOn(activity); if (mTaskRecord != null) { // fullscreen value is normally read from resources in ctor, so for testing we need diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index c67b860b656e..aa97de72e507 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.TYPE_VIRTUAL; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; @@ -61,6 +62,7 @@ import android.content.res.Resources; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.util.Pair; +import android.view.DisplayInfo; import androidx.test.filters.MediumTest; @@ -820,6 +822,41 @@ public class RootActivityContainerTests extends ActivityTestsBase { } /** + * Test that {@link RootActivityContainer#getLaunchStack} with the real caller id will get the + * expected stack when requesting the activity launch on the secondary display. + */ + @Test + public void testGetLaunchStackWithRealCallerId() { + // Create a non-system owned virtual display. + final DisplayInfo info = new DisplayInfo(); + mSupervisor.mService.mContext.getDisplay().getDisplayInfo(info); + info.type = TYPE_VIRTUAL; + info.ownerUid = 100; + final TestActivityDisplay secondaryDisplay = TestActivityDisplay.create(mSupervisor, info); + mRootActivityContainer.addChild(secondaryDisplay, POSITION_TOP); + + // Create an activity with specify the original launch pid / uid. + final ActivityRecord r = new ActivityBuilder(mService).setLaunchedFromPid(200) + .setLaunchedFromUid(200).build(); + + // Simulate ActivityStarter to find a launch stack for requesting the activity to launch + // on the secondary display with realCallerId. + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(secondaryDisplay.mDisplayId); + options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); + doReturn(true).when(mSupervisor).canPlaceEntityOnDisplay(secondaryDisplay.mDisplayId, + 300 /* test realCallerPid */, 300 /* test realCallerUid */, r.info); + final ActivityStack result = mRootActivityContainer.getLaunchStack(r, options, + null /* task */, true /* onTop */, null, 300 /* test realCallerPid */, + 300 /* test realCallerUid */); + + // Assert that the stack is returned as expected. + assertNotNull(result); + assertEquals("The display ID of the stack should same as secondary display ", + secondaryDisplay.mDisplayId, result.mDisplayId); + } + + /** * Mock {@link RootActivityContainer#resolveHomeActivity} for returning consistent activity * info for test cases (the original implementation will resolve from the real package manager). */ diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 8eeaf8d8f012..b4495787bb80 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -108,6 +108,19 @@ public class CarrierConfigManager { "call_forwarding_visibility_bool"; /** + * Boolean indicating if carrier supports call forwarding option "When unreachable". + * + * {@code true}: Call forwarding option "When unreachable" is supported. + * {@code false}: Call forwarding option "When unreachable" is not supported. Option will be + * greyed out in the UI. + * + * By default this value is true. + * @hide + */ + public static final String KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL = + "call_forwarding_when_unreachable_supported_bool"; + + /** * Boolean indicating if the "Caller ID" item is visible in the Additional Settings menu. * true means visible. false means gone. * @hide @@ -3250,6 +3263,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL, true); sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL, true); sDefaults.putBoolean(KEY_CALL_FORWARDING_VISIBILITY_BOOL, true); + sDefaults.putBoolean(KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL, true); sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL, true); sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL, true); sDefaults.putBoolean(KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL, false); diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index ab31ed7389a3..79f5095010e8 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -765,6 +765,78 @@ public class PackageWatchdogTest { assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_B); } + /** + * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered + * an invalid durationMs. + */ + @Test + public void testInvalidMonitoringDuration_beforeExpiry() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1); + // Note: Don't move too close to the expiration time otherwise the handler will be thrashed + // by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very + // small timeouts. + moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS - 100); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); + + // We should receive APP_A since the observer hasn't expired + assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A); + } + + /** + * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered + * an invalid durationMs. + */ + @Test + public void testInvalidMonitoringDuration_afterExpiry() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1); + moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); + + // We should receive nothing since the observer has expired + assertThat(observer.mHealthCheckFailedPackages).isEmpty(); + } + + /** Test we are notified when enough failures are triggered within any window. */ + @Test + public void testFailureTriggerWindow() { + adoptShellPermissions( + Manifest.permission.WRITE_DEVICE_CONFIG, + Manifest.permission.READ_DEVICE_CONFIG); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT, + Integer.toString(3), /*makeDefault*/false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS, + Integer.toString(1000), /*makeDefault*/false); + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE); + // Raise 2 failures at t=0 and t=900 respectively + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); + mTestLooper.dispatchAll(); + moveTimeForwardAndDispatch(900); + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); + mTestLooper.dispatchAll(); + + // Raise 2 failures at t=1100 + moveTimeForwardAndDispatch(200); + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); + mTestLooper.dispatchAll(); + + // We should receive APP_A since there are 3 failures within 1000ms window + assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A); + } + private void adoptShellPermissions(String... permissions) { InstrumentationRegistry .getInstrumentation() diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 5e06818d7a13..e36668e5a043 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -53,9 +53,9 @@ using ::android::ConfigDescription; using ::android::ResTable_config; using ::android::StringPiece; using ::android::base::ReadFileToString; -using ::android::base::WriteStringToFile; using ::android::base::StringAppendF; using ::android::base::StringPrintf; +using ::android::base::WriteStringToFile; namespace aapt { @@ -300,29 +300,7 @@ class Optimizer { OptimizeContext* context_; }; -bool ExtractObfuscationWhitelistFromConfig(const std::string& path, OptimizeContext* context, - OptimizeOptions* options) { - std::string contents; - if (!ReadFileToString(path, &contents, true)) { - context->GetDiagnostics()->Error(DiagMessage() - << "failed to parse whitelist from config file: " << path); - return false; - } - for (StringPiece resource_name : util::Tokenize(contents, ',')) { - options->table_flattener_options.whitelisted_resources.insert( - resource_name.to_string()); - } - return true; -} - -bool ExtractConfig(const std::string& path, OptimizeContext* context, - OptimizeOptions* options) { - std::string content; - if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) { - context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading whitelist"); - return false; - } - +bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOptions* options) { size_t line_no = 0; for (StringPiece line : util::Tokenize(content, '\n')) { line_no++; @@ -351,15 +329,24 @@ bool ExtractConfig(const std::string& path, OptimizeContext* context, for (StringPiece directive : util::Tokenize(directives, ',')) { if (directive == "remove") { options->resources_blacklist.insert(resource_name.ToResourceName()); - } else if (directive == "no_obfuscate") { - options->table_flattener_options.whitelisted_resources.insert( - resource_name.entry.to_string()); + } else if (directive == "no_collapse" || directive == "no_obfuscate") { + options->table_flattener_options.name_collapse_exemptions.insert( + resource_name.ToResourceName()); } } } return true; } +bool ExtractConfig(const std::string& path, IAaptContext* context, OptimizeOptions* options) { + std::string content; + if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) { + context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading config file"); + return false; + } + return ParseConfig(content, context, options); +} + bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk, OptimizeOptions* out_options) { const xml::XmlResource* manifest = apk->GetManifest(); @@ -467,15 +454,6 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { } } - if (options_.table_flattener_options.collapse_key_stringpool) { - if (whitelist_path_) { - std::string& path = whitelist_path_.value(); - if (!ExtractObfuscationWhitelistFromConfig(path, &context, &options_)) { - return 1; - } - } - } - if (resources_config_path_) { std::string& path = resources_config_path_.value(); if (!ExtractConfig(path, &context, &options_)) { diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h index 0be7dad18380..5070ccc8afbf 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -78,10 +78,6 @@ class OptimizeCommand : public Command { "All the resources that would be unused on devices of the given densities will be \n" "removed from the APK.", &target_densities_); - AddOptionalFlag("--whitelist-path", - "Path to the whitelist.cfg file containing whitelisted resources \n" - "whose names should not be altered in final resource tables.", - &whitelist_path_); AddOptionalFlag("--resources-config-path", "Path to the resources.cfg file containing the list of resources and \n" "directives to each resource. \n" @@ -104,11 +100,13 @@ class OptimizeCommand : public Command { "Enables encoding sparse entries using a binary search tree.\n" "This decreases APK size at the cost of resource retrieval performance.", &options_.table_flattener_options.use_sparse_entries); - AddOptionalSwitch("--enable-resource-obfuscation", - "Enables obfuscation of key string pool to single value", + AddOptionalSwitch("--collapse-resource-names", + "Collapses resource names to a single value in the key string pool. Resources can \n" + "be exempted using the \"no_collapse\" directive in a file specified by " + "--resources-config-path.", &options_.table_flattener_options.collapse_key_stringpool); - AddOptionalSwitch("--enable-resource-path-shortening", - "Enables shortening of the path of the resources inside the APK.", + AddOptionalSwitch("--shorten-resource-paths", + "Shortens the paths of resources inside the APK.", &options_.shorten_resource_paths); AddOptionalFlag("--resource-path-shortening-map", "Path to output the map of old resource paths to shortened paths.", @@ -125,7 +123,6 @@ class OptimizeCommand : public Command { const std::string &file_path); Maybe<std::string> config_path_; - Maybe<std::string> whitelist_path_; Maybe<std::string> resources_config_path_; Maybe<std::string> target_densities_; std::vector<std::string> configs_; diff --git a/tools/aapt2/cmd/Optimize_test.cpp b/tools/aapt2/cmd/Optimize_test.cpp new file mode 100644 index 000000000000..ac681e85b3d6 --- /dev/null +++ b/tools/aapt2/cmd/Optimize_test.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019 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. + */ + +#include "Optimize.h" + +#include "AppInfo.h" +#include "Diagnostics.h" +#include "LoadedApk.h" +#include "Resource.h" +#include "test/Test.h" + +using testing::Contains; +using testing::Eq; + +namespace aapt { + +bool ParseConfig(const std::string&, IAaptContext*, OptimizeOptions*); + +using OptimizeTest = CommandTestFixture; + +TEST_F(OptimizeTest, ParseConfigWithNoCollapseExemptions) { + const std::string& content = R"( +string/foo#no_collapse +dimen/bar#no_collapse +)"; + aapt::test::Context context; + OptimizeOptions options; + ParseConfig(content, &context, &options); + + const std::set<ResourceName>& name_collapse_exemptions = + options.table_flattener_options.name_collapse_exemptions; + + ASSERT_THAT(name_collapse_exemptions.size(), Eq(2)); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo"))); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar"))); +} + +TEST_F(OptimizeTest, ParseConfigWithNoObfuscateExemptions) { + const std::string& content = R"( +string/foo#no_obfuscate +dimen/bar#no_obfuscate +)"; + aapt::test::Context context; + OptimizeOptions options; + ParseConfig(content, &context, &options); + + const std::set<ResourceName>& name_collapse_exemptions = + options.table_flattener_options.name_collapse_exemptions; + + ASSERT_THAT(name_collapse_exemptions.size(), Eq(2)); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo"))); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar"))); +} + +} // namespace aapt diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index b9321174100b..58e232c33985 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -228,14 +228,15 @@ class PackageFlattener { public: PackageFlattener(IAaptContext* context, ResourceTablePackage* package, const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries, - bool collapse_key_stringpool, const std::set<std::string>& whitelisted_resources) + bool collapse_key_stringpool, + const std::set<ResourceName>& name_collapse_exemptions) : context_(context), diag_(context->GetDiagnostics()), package_(package), shared_libs_(shared_libs), use_sparse_entries_(use_sparse_entries), collapse_key_stringpool_(collapse_key_stringpool), - whitelisted_resources_(whitelisted_resources) { + name_collapse_exemptions_(name_collapse_exemptions) { } bool FlattenPackage(BigBuffer* buffer) { @@ -652,11 +653,12 @@ class PackageFlattener { for (ResourceEntry* entry : sorted_entries) { uint32_t local_key_index; + ResourceName resource_name({}, type->type, entry->name); if (!collapse_key_stringpool_ || - whitelisted_resources_.find(entry->name) != whitelisted_resources_.end()) { + name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) { local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index(); } else { - // resource isn't whitelisted, add it as obfuscated value + // resource isn't exempt from collapse, add it as obfuscated value local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index(); } // Group values by configuration. @@ -712,7 +714,7 @@ class PackageFlattener { StringPool type_pool_; StringPool key_pool_; bool collapse_key_stringpool_; - const std::set<std::string>& whitelisted_resources_; + const std::set<ResourceName>& name_collapse_exemptions_; }; } // namespace @@ -760,7 +762,7 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { PackageFlattener flattener(context, package.get(), &table->included_packages_, options_.use_sparse_entries, options_.collapse_key_stringpool, - options_.whitelisted_resources); + options_.name_collapse_exemptions); if (!flattener.FlattenPackage(&package_buffer)) { return false; } diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h index 73c17295556b..4360db190146 100644 --- a/tools/aapt2/format/binary/TableFlattener.h +++ b/tools/aapt2/format/binary/TableFlattener.h @@ -19,6 +19,7 @@ #include "android-base/macros.h" +#include "Resource.h" #include "ResourceTable.h" #include "process/IResourceTableConsumer.h" #include "util/BigBuffer.h" @@ -41,8 +42,8 @@ struct TableFlattenerOptions { // have name indices that point to this single value bool collapse_key_stringpool = false; - // Set of whitelisted resource names to avoid altering in key stringpool - std::set<std::string> whitelisted_resources; + // Set of resources to avoid collapsing to a single entry in key stringpool. + std::set<ResourceName> name_collapse_exemptions; // Map from original resource paths to shortened resource paths. std::map<std::string, std::string> shortened_path_map; diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index a9409235e07a..8fbdd7f27041 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -518,7 +518,7 @@ TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) { ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result)); } -TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) { +TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) @@ -572,7 +572,7 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) { ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); } -TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { +TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) @@ -591,21 +591,22 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { TableFlattenerOptions options; options.collapse_key_stringpool = true; - options.whitelisted_resources.insert("test"); - options.whitelisted_resources.insert("three"); + options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one")); + options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test")); ResTable res_table; ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table)); - EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); - EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {}, - Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + // Note that this resource is also named "one", but it's a different type, so gets obfuscated. EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, ResTable_config::CONFIG_VERSION)); |