• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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