diff --git a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java new file mode 100644 index 0000000000000..31c38220ccc21 --- /dev/null +++ b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 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.internal.app; + +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.R; + +/** + * A dialog shown to the user when they try to launch an app that is not allowed on a virtual + * device. The intent to start this activity must be created with the static factory method provided + * below. + */ +public class BlockedAppStreamingActivity extends AlertActivity { + + private static final String TAG = "BlockedAppStreamingActivity"; + private static final String PACKAGE_NAME = "com.android.internal.app"; + private static final String EXTRA_BLOCKED_ACTIVITY_INFO = + PACKAGE_NAME + ".extra.BLOCKED_ACTIVITY_INFO"; + private static final String EXTRA_STREAMED_DEVICE = PACKAGE_NAME + ".extra.STREAMED_DEVICE"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + CharSequence appLabel = null; + ActivityInfo activityInfo = intent.getParcelableExtra(EXTRA_BLOCKED_ACTIVITY_INFO); + if (activityInfo != null) { + appLabel = activityInfo.loadLabel(getPackageManager()); + } + + if (TextUtils.isEmpty(appLabel)) { + Slog.wtf(TAG, "Invalid activity info: " + activityInfo); + finish(); + return; + } + + CharSequence streamedDeviceName = intent.getCharSequenceExtra(EXTRA_STREAMED_DEVICE); + if (!TextUtils.isEmpty(streamedDeviceName)) { + mAlertParams.mTitle = getString(R.string.app_streaming_blocked_title, appLabel); + mAlertParams.mMessage = + getString(R.string.app_streaming_blocked_message, streamedDeviceName); + } else { + mAlertParams.mTitle = getString(R.string.app_blocked_title); + mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel); + } + mAlertParams.mPositiveButtonText = getString(android.R.string.ok); + setupAlert(); + } + + /** + * Creates an intent that launches {@link BlockedAppStreamingActivity} when app streaming is + * blocked. + */ + public static Intent createIntent(ActivityInfo activityInfo, CharSequence streamedDeviceName) { + return new Intent() + .setClassName("android", BlockedAppStreamingActivity.class.getName()) + .putExtra(EXTRA_BLOCKED_ACTIVITY_INFO, activityInfo) + .putExtra(EXTRA_STREAMED_DEVICE, streamedDeviceName); + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 9dc2313651a52..2736ba630902c 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6591,6 +6591,12 @@ android:process=":ui"> + + + %1$s is not available right now. + + %1$s unavailable + + This can’t be accessed on your %1$s at this time. Try on your Android TV device instead. + + This can’t be accessed on your %1$s at this time. Try on your tablet instead. + + This can’t be accessed on your %1$s at this time. Try on your phone instead. + This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ea648dd104827..a79eec28e2482 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3288,6 +3288,9 @@ + + + diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index 75acf81a4a3cc..dafcc60fb651d 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -37,8 +37,11 @@ import android.util.Slog; import android.view.Display; import android.window.DisplayWindowPolicyController; +import com.android.internal.app.BlockedAppStreamingActivity; + import java.util.List; import java.util.Set; +import java.util.function.Consumer; /** @@ -48,6 +51,9 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { private static final String TAG = "VirtualDeviceManager"; + private static final ComponentName BLOCKED_APP_STREAMING_COMPONENT = + new ComponentName("android", BlockedAppStreamingActivity.class.getName()); + /** * If required, allow the secure activity to display on remote device since * {@link android.os.Build.VERSION_CODES#TIRAMISU}. @@ -61,6 +67,7 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { private final ArraySet mAllowedActivities; @Nullable private final ArraySet mBlockedActivities; + private Consumer mActivityBlockedCallback; @NonNull final ArraySet mRunningUids = new ArraySet<>(); @@ -81,10 +88,12 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @NonNull ArraySet allowedUsers, @Nullable Set allowedActivities, @Nullable Set blockedActivities, - @NonNull ActivityListener activityListener) { + @NonNull ActivityListener activityListener, + @NonNull Consumer activityBlockedCallback) { mAllowedUsers = allowedUsers; mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities); mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities); + mActivityBlockedCallback = activityBlockedCallback; setInterestedWindowFlags(windowFlags, systemWindowFlags); mActivityListener = activityListener; } @@ -96,6 +105,7 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { for (int i = 0; i < activityCount; i++) { final ActivityInfo aInfo = activities.get(i); if (!canContainActivity(aInfo, /* windowFlags= */ 0, /* systemWindowFlags= */ 0)) { + mActivityBlockedCallback.accept(aInfo); return false; } } @@ -105,7 +115,11 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @Override public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags, int systemWindowFlags) { - return canContainActivity(activityInfo, windowFlags, systemWindowFlags); + if (!canContainActivity(activityInfo, windowFlags, systemWindowFlags)) { + mActivityBlockedCallback.accept(activityInfo); + return false; + } + return true; } @Override @@ -140,22 +154,23 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) { return false; } + ComponentName activityComponent = activityInfo.getComponentName(); + if (BLOCKED_APP_STREAMING_COMPONENT.equals(activityComponent)) { + // The error dialog alerting users that streaming is blocked is always allowed. + return true; + } final UserHandle activityUser = UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid); if (!mAllowedUsers.contains(activityUser)) { Slog.d(TAG, "Virtual device activity not allowed from user " + activityUser); return false; } - if (mBlockedActivities != null - && mBlockedActivities.contains(activityInfo.getComponentName())) { - Slog.d(TAG, - "Virtual device blocking launch of " + activityInfo.getComponentName()); + if (mBlockedActivities != null && mBlockedActivities.contains(activityComponent)) { + Slog.d(TAG, "Virtual device blocking launch of " + activityComponent); return false; } - if (mAllowedActivities != null - && !mAllowedActivities.contains(activityInfo.getComponentName())) { - Slog.d(TAG, - activityInfo.getComponentName() + " is not in the allowed list."); + if (mAllowedActivities != null && !mAllowedActivities.contains(activityComponent)) { + Slog.d(TAG, activityComponent + " is not in the allowed list."); return false; } if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE, diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 47e218b48c093..5f3a030360f90 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -34,6 +34,8 @@ import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.PointF; import android.hardware.display.DisplayManager; @@ -57,6 +59,7 @@ import android.window.DisplayWindowPolicyController; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.BlockedAppStreamingActivity; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -441,7 +444,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub getAllowedUserHandles(), mParams.getAllowedActivities(), mParams.getBlockedActivities(), - createListenerAdapter(displayId)); + createListenerAdapter(displayId), + activityInfo -> onActivityBlocked(displayId, activityInfo)); mWindowPolicyControllers.put(displayId, dwpc); return dwpc; } @@ -464,6 +468,15 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + private void onActivityBlocked(int displayId, ActivityInfo activityInfo) { + Intent intent = BlockedAppStreamingActivity.createIntent( + activityInfo, mAssociationInfo.getDisplayName()); + mContext.startActivityAsUser( + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK), + ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(), + mContext.getUser()); + } + private ArraySet getAllowedUserHandles() { ArraySet result = new ArraySet<>(); DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);