From 64439c1e8c93d9e929f2f9d901d1f523bc060aaf Mon Sep 17 00:00:00 2001 From: arangelov Date: Tue, 31 Jul 2018 14:18:47 +0100 Subject: No disclaimer when switching profile for system SMS and dialer apps. This CL prevents the Toast message from showing when the following conditions are met: 1. App is system app 2. Intent action is either ACTION_DIAL or ACTION_CALL or if it is ACTION_SENDTO, only skip if data scheme is one of "sms:", "smsto:", "mms:" or "mmsto:". Bug: 111228250 Test: atest FrameworksCoreTests:IntentForwarderActivityTest Change-Id: Idef71ff2928e9e3d72bad4ba8ff17f9306e91d25 --- .../internal/app/IntentForwarderActivity.java | 80 ++++++-- .../internal/app/IntentForwarderActivityTest.java | 205 +++++++++++++++++++-- 2 files changed, 246 insertions(+), 39 deletions(-) diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 398d08791b5c..4728124c17c5 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -16,14 +16,21 @@ package com.android.internal.app; +import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; + +import android.annotation.Nullable; +import android.annotation.StringRes; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.admin.DevicePolicyManager; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.os.Bundle; import android.os.RemoteException; @@ -31,12 +38,11 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; import android.widget.Toast; - import com.android.internal.annotations.VisibleForTesting; - +import java.util.Arrays; +import java.util.HashSet; import java.util.List; - -import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; +import java.util.Set; /** * This is used in conjunction with @@ -44,7 +50,6 @@ import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; * be passed in and out of a managed profile. */ public class IntentForwarderActivity extends Activity { - public static String TAG = "IntentForwarderActivity"; public static String FORWARD_INTENT_TO_PARENT @@ -53,6 +58,9 @@ public class IntentForwarderActivity extends Activity { public static String FORWARD_INTENT_TO_MANAGED_PROFILE = "com.android.internal.app.ForwardIntentToManagedProfile"; + private static final Set ALLOWED_TEXT_MESSAGE_SCHEME + = new HashSet<>(Arrays.asList("sms", "smsto", "mms", "mmsto")); + private Injector mInjector; @Override @@ -93,19 +101,8 @@ public class IntentForwarderActivity extends Activity { newIntent.prepareToLeaveUser(callingUserId); } - final android.content.pm.ResolveInfo ri = - mInjector.getPackageManager().resolveActivityAsUser( - newIntent, - MATCH_DEFAULT_ONLY, - targetUserId); - - // Don't show the disclosure if next activity is ResolverActivity or ChooserActivity - // as those will already have shown work / personal as neccesary etc. - final boolean shouldShowDisclosure = ri == null || ri.activityInfo == null || - !"android".equals(ri.activityInfo.packageName) || - !(ResolverActivity.class.getName().equals(ri.activityInfo.name) - || ChooserActivity.class.getName().equals(ri.activityInfo.name)); - + final ResolveInfo ri = mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, + targetUserId); try { startActivityAsCaller(newIntent, null, false, targetUserId); } catch (RuntimeException e) { @@ -124,8 +121,8 @@ public class IntentForwarderActivity extends Activity { + ActivityThread.currentProcessName(), e); } - if (shouldShowDisclosure) { - Toast.makeText(this, getString(userMessageId), Toast.LENGTH_LONG).show(); + if (shouldShowDisclosure(ri, intentReceived)) { + mInjector.showToast(userMessageId, Toast.LENGTH_LONG); } } else { Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user " @@ -134,6 +131,35 @@ public class IntentForwarderActivity extends Activity { finish(); } + private boolean shouldShowDisclosure(@Nullable ResolveInfo ri, Intent intent) { + if (ri == null || ri.activityInfo == null) { + return true; + } + if (ri.activityInfo.applicationInfo.isSystemApp() + && (isDialerIntent(intent) || isTextMessageIntent(intent))) { + return false; + } + return !isTargetResolverOrChooserActivity(ri.activityInfo); + } + + private boolean isTextMessageIntent(Intent intent) { + return Intent.ACTION_SENDTO.equals(intent.getAction()) && intent.getData() != null + && ALLOWED_TEXT_MESSAGE_SCHEME.contains(intent.getData().getScheme()); + } + + private boolean isDialerIntent(Intent intent) { + return Intent.ACTION_DIAL.equals(intent.getAction()) + || Intent.ACTION_CALL.equals(intent.getAction()); + } + + private boolean isTargetResolverOrChooserActivity(ActivityInfo activityInfo) { + if (!"android".equals(activityInfo.packageName)) { + return false; + } + return ResolverActivity.class.getName().equals(activityInfo.name) + || ChooserActivity.class.getName().equals(activityInfo.name); + } + /** * Check whether the intent can be forwarded to target user. Return the intent used for * forwarding if it can be forwarded, {@code null} otherwise. @@ -241,6 +267,16 @@ public class IntentForwarderActivity extends Activity { public PackageManager getPackageManager() { return IntentForwarderActivity.this.getPackageManager(); } + + @Override + public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { + return getPackageManager().resolveActivityAsUser(intent, flags, userId); + } + + @Override + public void showToast(int messageId, int duration) { + Toast.makeText(IntentForwarderActivity.this, getString(messageId), duration).show(); + } } public interface Injector { @@ -249,5 +285,9 @@ public class IntentForwarderActivity extends Activity { UserManager getUserManager(); PackageManager getPackageManager(); + + ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId); + + void showToast(@StringRes int messageId, int duration); } } diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java index b18fa747557d..c165b6be525e 100644 --- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java @@ -16,46 +16,50 @@ package com.android.internal.app; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; +import android.net.Uri; import android.os.Bundle; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.support.test.InstrumentationRegistry; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; -import android.util.Log; - +import java.util.ArrayList; +import java.util.List; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.ArrayList; -import java.util.List; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @RunWith(AndroidJUnit4.class) public class IntentForwarderActivityTest { + private static final ComponentName FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME = new ComponentName( "android", @@ -77,22 +81,26 @@ public class IntentForwarderActivityTest { private static IntentForwarderActivity.Injector sInjector; private static ComponentName sComponentName; + private static String sActivityName; + private static String sPackageName; @Mock private IPackageManager mIPm; @Mock private PackageManager mPm; @Mock private UserManager mUserManager; + @Mock private ApplicationInfo mApplicationInfo; @Rule public ActivityTestRule mActivityRule = new ActivityTestRule<>(IntentForwarderWrapperActivity.class, true, false); private Context mContext; + public static final String PHONE_NUMBER = "123-456-789"; @Before public void setup() { MockitoAnnotations.initMocks(this); mContext = InstrumentationRegistry.getTargetContext(); - sInjector = new TestInjector(); + sInjector = spy(new TestInjector()); } @Test @@ -252,6 +260,149 @@ public class IntentForwarderActivityTest { assertEquals(MANAGED_PROFILE_INFO.id, activity.mUserIdActivityLaunchedIn); } + @Test + public void shouldSkipDisclosure_notWhitelisted() throws RemoteException { + setupShouldSkipDisclosureTest(); + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class) + .setAction(Intent.ACTION_SEND) + .setType(TYPE_PLAIN_TEXT); + + mActivityRule.launchActivity(intent); + + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + verify(sInjector).showToast(anyInt(), anyInt()); + } + + @Test + public void shouldSkipDisclosure_withResolverActivity() throws RemoteException { + setupShouldSkipDisclosureTest(); + sActivityName = ResolverActivity.class.getName(); + sPackageName = "android"; + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class) + .setAction(Intent.ACTION_SEND) + .setType(TYPE_PLAIN_TEXT); + + mActivityRule.launchActivity(intent); + + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyInt(), anyInt()); + } + + @Test + public void shouldSkipDisclosure_callIntent_call() throws RemoteException { + setupShouldSkipDisclosureTest(); + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class) + .setAction(Intent.ACTION_CALL); + + mActivityRule.launchActivity(intent); + + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyInt(), anyInt()); + } + + @Test + public void shouldSkipDisclosure_callIntent_dial() throws RemoteException { + setupShouldSkipDisclosureTest(); + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class) + .setAction(Intent.ACTION_DIAL); + + mActivityRule.launchActivity(intent); + + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyInt(), anyInt()); + } + + @Test + public void shouldSkipDisclosure_callIntent_notCallOrDial() throws RemoteException { + setupShouldSkipDisclosureTest(); + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class) + .setAction(Intent.ACTION_ALARM_CHANGED); + + mActivityRule.launchActivity(intent); + + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + verify(sInjector).showToast(anyInt(), anyInt()); + } + + @Test + public void shouldSkipDisclosure_textMessageIntent_sms() throws RemoteException { + setupShouldSkipDisclosureTest(); + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class) + .setAction(Intent.ACTION_SENDTO) + .setData(Uri.fromParts("sms", PHONE_NUMBER, null)); + + mActivityRule.launchActivity(intent); + + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyInt(), anyInt()); + } + + @Test + public void shouldSkipDisclosure_textMessageIntent_smsto() throws RemoteException { + setupShouldSkipDisclosureTest(); + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class) + .setAction(Intent.ACTION_SENDTO) + .setData(Uri.fromParts("smsto", PHONE_NUMBER, null)); + + mActivityRule.launchActivity(intent); + + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyInt(), anyInt()); + } + + @Test + public void shouldSkipDisclosure_textMessageIntent_mms() throws RemoteException { + setupShouldSkipDisclosureTest(); + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class) + .setAction(Intent.ACTION_SENDTO) + .setData(Uri.fromParts("mms", PHONE_NUMBER, null)); + + mActivityRule.launchActivity(intent); + + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyInt(), anyInt()); + } + + @Test + public void shouldSkipDisclosure_textMessageIntent_mmsto() throws RemoteException { + setupShouldSkipDisclosureTest(); + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class) + .setAction(Intent.ACTION_SENDTO) + .setData(Uri.fromParts("mmsto", PHONE_NUMBER, null)); + + mActivityRule.launchActivity(intent); + + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyInt(), anyInt()); + } + + @Test + public void shouldSkipDisclosure_textMessageIntent_invalidUri() throws RemoteException { + setupShouldSkipDisclosureTest(); + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class) + .setAction(Intent.ACTION_SENDTO) + .setData(Uri.fromParts("invalid", PHONE_NUMBER, null)); + + mActivityRule.launchActivity(intent); + + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + verify(sInjector).showToast(anyInt(), anyInt()); + } + + private void setupShouldSkipDisclosureTest() throws RemoteException { + sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME; + sActivityName = "MyTestActivity"; + sPackageName = "test.package.name"; + when(mApplicationInfo.isSystemApp()).thenReturn(true); + // Managed profile exists. + List profiles = new ArrayList<>(); + profiles.add(CURRENT_USER_INFO); + profiles.add(MANAGED_PROFILE_INFO); + when(mUserManager.getProfiles(anyInt())).thenReturn(profiles); + // Intent can be forwarded. + when(mIPm.canForwardTo( + any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true); + } public static class IntentForwarderWrapperActivity extends IntentForwarderActivity { private Intent mStartActivityIntent; @@ -276,7 +427,7 @@ public class IntentForwarderActivityTest { } } - class TestInjector implements IntentForwarderActivity.Injector { + public class TestInjector implements IntentForwarderActivity.Injector { @Override public IPackageManager getIPackageManager() { @@ -292,5 +443,21 @@ public class IntentForwarderActivityTest { public PackageManager getPackageManager() { return mPm; } + + @Override + public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.packageName = sPackageName; + activityInfo.name = sActivityName; + activityInfo.applicationInfo = mApplicationInfo; + + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = activityInfo; + + return resolveInfo; + } + + @Override + public void showToast(int messageId, int duration) {} } } \ No newline at end of file -- cgit v1.2.3-59-g8ed1b