1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.app; 18 19 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; 20 21 import android.annotation.Nullable; 22 import android.annotation.StringRes; 23 import android.annotation.UnsupportedAppUsage; 24 import android.app.Activity; 25 import android.app.ActivityTaskManager; 26 import android.app.ActivityThread; 27 import android.app.AppGlobals; 28 import android.app.admin.DevicePolicyManager; 29 import android.content.Intent; 30 import android.content.pm.ActivityInfo; 31 import android.content.pm.IPackageManager; 32 import android.content.pm.PackageManager; 33 import android.content.pm.ResolveInfo; 34 import android.content.pm.UserInfo; 35 import android.metrics.LogMaker; 36 import android.os.Bundle; 37 import android.os.RemoteException; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.util.Slog; 41 import android.widget.Toast; 42 43 import com.android.internal.annotations.VisibleForTesting; 44 import com.android.internal.logging.MetricsLogger; 45 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 46 47 import java.util.Arrays; 48 import java.util.HashSet; 49 import java.util.List; 50 import java.util.Set; 51 52 /** 53 * This is used in conjunction with 54 * {@link DevicePolicyManager#addCrossProfileIntentFilter} to enable intents to 55 * be passed in and out of a managed profile. 56 */ 57 public class IntentForwarderActivity extends Activity { 58 @UnsupportedAppUsage 59 public static String TAG = "IntentForwarderActivity"; 60 61 public static String FORWARD_INTENT_TO_PARENT 62 = "com.android.internal.app.ForwardIntentToParent"; 63 64 public static String FORWARD_INTENT_TO_MANAGED_PROFILE 65 = "com.android.internal.app.ForwardIntentToManagedProfile"; 66 67 private static final Set<String> ALLOWED_TEXT_MESSAGE_SCHEMES 68 = new HashSet<>(Arrays.asList("sms", "smsto", "mms", "mmsto")); 69 70 private static final String TEL_SCHEME = "tel"; 71 72 private Injector mInjector; 73 74 private MetricsLogger mMetricsLogger; 75 76 @Override onCreate(Bundle savedInstanceState)77 protected void onCreate(Bundle savedInstanceState) { 78 super.onCreate(savedInstanceState); 79 mInjector = createInjector(); 80 81 Intent intentReceived = getIntent(); 82 String className = intentReceived.getComponent().getClassName(); 83 final int targetUserId; 84 final int userMessageId; 85 if (className.equals(FORWARD_INTENT_TO_PARENT)) { 86 userMessageId = com.android.internal.R.string.forward_intent_to_owner; 87 targetUserId = getProfileParent(); 88 89 getMetricsLogger().write( 90 new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) 91 .setSubtype(MetricsEvent.PARENT_PROFILE)); 92 } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { 93 userMessageId = com.android.internal.R.string.forward_intent_to_work; 94 targetUserId = getManagedProfile(); 95 96 getMetricsLogger().write( 97 new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) 98 .setSubtype(MetricsEvent.MANAGED_PROFILE)); 99 } else { 100 Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly"); 101 userMessageId = -1; 102 targetUserId = UserHandle.USER_NULL; 103 } 104 if (targetUserId == UserHandle.USER_NULL) { 105 // This covers the case where there is no parent / managed profile. 106 finish(); 107 return; 108 } 109 110 final int callingUserId = getUserId(); 111 final Intent newIntent = canForward(intentReceived, targetUserId); 112 if (newIntent != null) { 113 if (Intent.ACTION_CHOOSER.equals(newIntent.getAction())) { 114 Intent innerIntent = newIntent.getParcelableExtra(Intent.EXTRA_INTENT); 115 // At this point, innerIntent is not null. Otherwise, canForward would have returned 116 // false. 117 innerIntent.prepareToLeaveUser(callingUserId); 118 innerIntent.fixUris(callingUserId); 119 } else { 120 newIntent.prepareToLeaveUser(callingUserId); 121 } 122 123 final ResolveInfo ri = mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, 124 targetUserId); 125 try { 126 startActivityAsCaller(newIntent, null, null, false, targetUserId); 127 } catch (RuntimeException e) { 128 int launchedFromUid = -1; 129 String launchedFromPackage = "?"; 130 try { 131 launchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid( 132 getActivityToken()); 133 launchedFromPackage = ActivityTaskManager.getService().getLaunchedFromPackage( 134 getActivityToken()); 135 } catch (RemoteException ignored) { 136 } 137 138 Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package " 139 + launchedFromPackage + ", while running in " 140 + ActivityThread.currentProcessName(), e); 141 } 142 143 if (shouldShowDisclosure(ri, intentReceived)) { 144 mInjector.showToast(userMessageId, Toast.LENGTH_LONG); 145 } 146 } else { 147 Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user " 148 + callingUserId + " to user " + targetUserId); 149 } 150 finish(); 151 } 152 shouldShowDisclosure(@ullable ResolveInfo ri, Intent intent)153 private boolean shouldShowDisclosure(@Nullable ResolveInfo ri, Intent intent) { 154 if (ri == null || ri.activityInfo == null) { 155 return true; 156 } 157 if (ri.activityInfo.applicationInfo.isSystemApp() 158 && (isDialerIntent(intent) || isTextMessageIntent(intent))) { 159 return false; 160 } 161 return !isTargetResolverOrChooserActivity(ri.activityInfo); 162 } 163 isTextMessageIntent(Intent intent)164 private boolean isTextMessageIntent(Intent intent) { 165 return (Intent.ACTION_SENDTO.equals(intent.getAction()) || isViewActionIntent(intent)) 166 && ALLOWED_TEXT_MESSAGE_SCHEMES.contains(intent.getScheme()); 167 } 168 isDialerIntent(Intent intent)169 private boolean isDialerIntent(Intent intent) { 170 return Intent.ACTION_DIAL.equals(intent.getAction()) 171 || Intent.ACTION_CALL.equals(intent.getAction()) 172 || Intent.ACTION_CALL_PRIVILEGED.equals(intent.getAction()) 173 || Intent.ACTION_CALL_EMERGENCY.equals(intent.getAction()) 174 || (isViewActionIntent(intent) && TEL_SCHEME.equals(intent.getScheme())); 175 } 176 isViewActionIntent(Intent intent)177 private boolean isViewActionIntent(Intent intent) { 178 return Intent.ACTION_VIEW.equals(intent.getAction()) 179 && intent.hasCategory(Intent.CATEGORY_BROWSABLE); 180 } 181 isTargetResolverOrChooserActivity(ActivityInfo activityInfo)182 private boolean isTargetResolverOrChooserActivity(ActivityInfo activityInfo) { 183 if (!"android".equals(activityInfo.packageName)) { 184 return false; 185 } 186 return ResolverActivity.class.getName().equals(activityInfo.name) 187 || ChooserActivity.class.getName().equals(activityInfo.name); 188 } 189 190 /** 191 * Check whether the intent can be forwarded to target user. Return the intent used for 192 * forwarding if it can be forwarded, {@code null} otherwise. 193 */ canForward(Intent incomingIntent, int targetUserId)194 Intent canForward(Intent incomingIntent, int targetUserId) { 195 Intent forwardIntent = new Intent(incomingIntent); 196 forwardIntent.addFlags( 197 Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 198 sanitizeIntent(forwardIntent); 199 200 Intent intentToCheck = forwardIntent; 201 if (Intent.ACTION_CHOOSER.equals(forwardIntent.getAction())) { 202 // The EXTRA_INITIAL_INTENTS may not be allowed to be forwarded. 203 if (forwardIntent.hasExtra(Intent.EXTRA_INITIAL_INTENTS)) { 204 Slog.wtf(TAG, "An chooser intent with extra initial intents cannot be forwarded to" 205 + " a different user"); 206 return null; 207 } 208 if (forwardIntent.hasExtra(Intent.EXTRA_REPLACEMENT_EXTRAS)) { 209 Slog.wtf(TAG, "A chooser intent with replacement extras cannot be forwarded to a" 210 + " different user"); 211 return null; 212 } 213 intentToCheck = forwardIntent.getParcelableExtra(Intent.EXTRA_INTENT); 214 if (intentToCheck == null) { 215 Slog.wtf(TAG, "Cannot forward a chooser intent with no extra " 216 + Intent.EXTRA_INTENT); 217 return null; 218 } 219 } 220 if (forwardIntent.getSelector() != null) { 221 intentToCheck = forwardIntent.getSelector(); 222 } 223 String resolvedType = intentToCheck.resolveTypeIfNeeded(getContentResolver()); 224 sanitizeIntent(intentToCheck); 225 try { 226 if (mInjector.getIPackageManager(). 227 canForwardTo(intentToCheck, resolvedType, getUserId(), targetUserId)) { 228 return forwardIntent; 229 } 230 } catch (RemoteException e) { 231 Slog.e(TAG, "PackageManagerService is dead?"); 232 } 233 return null; 234 } 235 236 /** 237 * Returns the userId of the managed profile for this device or UserHandle.USER_NULL if there is 238 * no managed profile. 239 * 240 * TODO: Remove the assumption that there is only one managed profile 241 * on the device. 242 */ getManagedProfile()243 private int getManagedProfile() { 244 List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId()); 245 for (UserInfo userInfo : relatedUsers) { 246 if (userInfo.isManagedProfile()) return userInfo.id; 247 } 248 Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE 249 + " has been called, but there is no managed profile"); 250 return UserHandle.USER_NULL; 251 } 252 253 /** 254 * Returns the userId of the profile parent or UserHandle.USER_NULL if there is 255 * no parent. 256 */ getProfileParent()257 private int getProfileParent() { 258 UserInfo parent = mInjector.getUserManager().getProfileParent(UserHandle.myUserId()); 259 if (parent == null) { 260 Slog.wtf(TAG, FORWARD_INTENT_TO_PARENT 261 + " has been called, but there is no parent"); 262 return UserHandle.USER_NULL; 263 } 264 return parent.id; 265 } 266 267 /** 268 * Sanitize the intent in place. 269 */ sanitizeIntent(Intent intent)270 private void sanitizeIntent(Intent intent) { 271 // Apps should not be allowed to target a specific package/ component in the target user. 272 intent.setPackage(null); 273 intent.setComponent(null); 274 } 275 getMetricsLogger()276 protected MetricsLogger getMetricsLogger() { 277 if (mMetricsLogger == null) { 278 mMetricsLogger = new MetricsLogger(); 279 } 280 return mMetricsLogger; 281 } 282 283 @VisibleForTesting createInjector()284 protected Injector createInjector() { 285 return new InjectorImpl(); 286 } 287 288 private class InjectorImpl implements Injector { 289 290 @Override getIPackageManager()291 public IPackageManager getIPackageManager() { 292 return AppGlobals.getPackageManager(); 293 } 294 295 @Override getUserManager()296 public UserManager getUserManager() { 297 return getSystemService(UserManager.class); 298 } 299 300 @Override getPackageManager()301 public PackageManager getPackageManager() { 302 return IntentForwarderActivity.this.getPackageManager(); 303 } 304 305 @Override resolveActivityAsUser(Intent intent, int flags, int userId)306 public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { 307 return getPackageManager().resolveActivityAsUser(intent, flags, userId); 308 } 309 310 @Override showToast(int messageId, int duration)311 public void showToast(int messageId, int duration) { 312 Toast.makeText(IntentForwarderActivity.this, getString(messageId), duration).show(); 313 } 314 } 315 316 public interface Injector { getIPackageManager()317 IPackageManager getIPackageManager(); 318 getUserManager()319 UserManager getUserManager(); 320 getPackageManager()321 PackageManager getPackageManager(); 322 resolveActivityAsUser(Intent intent, int flags, int userId)323 ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId); 324 showToast(@tringRes int messageId, int duration)325 void showToast(@StringRes int messageId, int duration); 326 } 327 } 328