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 android.app.Activity;
20 import android.app.ActivityManager;
21 import android.app.ActivityThread;
22 import android.app.AppGlobals;
23 import android.app.admin.DevicePolicyManager;
24 import android.content.Intent;
25 import android.content.pm.IPackageManager;
26 import android.content.pm.PackageManager;
27 import android.content.pm.UserInfo;
28 import android.os.Bundle;
29 import android.os.RemoteException;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.util.Slog;
33 import android.widget.Toast;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 import java.util.List;
38 
39 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
40 
41 /**
42  * This is used in conjunction with
43  * {@link DevicePolicyManager#addCrossProfileIntentFilter} to enable intents to
44  * be passed in and out of a managed profile.
45  */
46 public class IntentForwarderActivity extends Activity  {
47 
48     public static String TAG = "IntentForwarderActivity";
49 
50     public static String FORWARD_INTENT_TO_PARENT
51             = "com.android.internal.app.ForwardIntentToParent";
52 
53     public static String FORWARD_INTENT_TO_MANAGED_PROFILE
54             = "com.android.internal.app.ForwardIntentToManagedProfile";
55 
56     private Injector mInjector;
57 
58     @Override
onCreate(Bundle savedInstanceState)59     protected void onCreate(Bundle savedInstanceState) {
60         super.onCreate(savedInstanceState);
61         mInjector = createInjector();
62 
63         Intent intentReceived = getIntent();
64         String className = intentReceived.getComponent().getClassName();
65         final int targetUserId;
66         final int userMessageId;
67         if (className.equals(FORWARD_INTENT_TO_PARENT)) {
68             userMessageId = com.android.internal.R.string.forward_intent_to_owner;
69             targetUserId = getProfileParent();
70         } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
71             userMessageId = com.android.internal.R.string.forward_intent_to_work;
72             targetUserId = getManagedProfile();
73         } else {
74             Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly");
75             userMessageId = -1;
76             targetUserId = UserHandle.USER_NULL;
77         }
78         if (targetUserId == UserHandle.USER_NULL) {
79             // This covers the case where there is no parent / managed profile.
80             finish();
81             return;
82         }
83 
84         final int callingUserId = getUserId();
85         final Intent newIntent = canForward(intentReceived, targetUserId);
86         if (newIntent != null) {
87             if (Intent.ACTION_CHOOSER.equals(newIntent.getAction())) {
88                 Intent innerIntent = newIntent.getParcelableExtra(Intent.EXTRA_INTENT);
89                 // At this point, innerIntent is not null. Otherwise, canForward would have returned
90                 // false.
91                 innerIntent.prepareToLeaveUser(callingUserId);
92             } else {
93                 newIntent.prepareToLeaveUser(callingUserId);
94             }
95 
96             final android.content.pm.ResolveInfo ri =
97                     mInjector.getPackageManager().resolveActivityAsUser(
98                             newIntent,
99                             MATCH_DEFAULT_ONLY,
100                             targetUserId);
101 
102             // Don't show the disclosure if next activity is ResolverActivity or ChooserActivity
103             // as those will already have shown work / personal as neccesary etc.
104             final boolean shouldShowDisclosure = ri == null || ri.activityInfo == null ||
105                     !"android".equals(ri.activityInfo.packageName) ||
106                     !(ResolverActivity.class.getName().equals(ri.activityInfo.name)
107                             || ChooserActivity.class.getName().equals(ri.activityInfo.name));
108 
109             try {
110                 startActivityAsCaller(newIntent, null, false, targetUserId);
111             } catch (RuntimeException e) {
112                 int launchedFromUid = -1;
113                 String launchedFromPackage = "?";
114                 try {
115                     launchedFromUid = ActivityManager.getService().getLaunchedFromUid(
116                             getActivityToken());
117                     launchedFromPackage = ActivityManager.getService().getLaunchedFromPackage(
118                             getActivityToken());
119                 } catch (RemoteException ignored) {
120                 }
121 
122                 Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package "
123                         + launchedFromPackage + ", while running in "
124                         + ActivityThread.currentProcessName(), e);
125             }
126 
127             if (shouldShowDisclosure) {
128                 Toast.makeText(this, getString(userMessageId), Toast.LENGTH_LONG).show();
129             }
130         } else {
131             Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user "
132                     + callingUserId + " to user " + targetUserId);
133         }
134         finish();
135     }
136 
137     /**
138      * Check whether the intent can be forwarded to target user. Return the intent used for
139      * forwarding if it can be forwarded, {@code null} otherwise.
140      */
canForward(Intent incomingIntent, int targetUserId)141     Intent canForward(Intent incomingIntent, int targetUserId)  {
142         Intent forwardIntent = new Intent(incomingIntent);
143         forwardIntent.addFlags(
144                 Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
145         sanitizeIntent(forwardIntent);
146 
147         Intent intentToCheck = forwardIntent;
148         if (Intent.ACTION_CHOOSER.equals(forwardIntent.getAction())) {
149             // The EXTRA_INITIAL_INTENTS may not be allowed to be forwarded.
150             if (forwardIntent.hasExtra(Intent.EXTRA_INITIAL_INTENTS)) {
151                 Slog.wtf(TAG, "An chooser intent with extra initial intents cannot be forwarded to"
152                         + " a different user");
153                 return null;
154             }
155             if (forwardIntent.hasExtra(Intent.EXTRA_REPLACEMENT_EXTRAS)) {
156                 Slog.wtf(TAG, "A chooser intent with replacement extras cannot be forwarded to a"
157                         + " different user");
158                 return null;
159             }
160             intentToCheck = forwardIntent.getParcelableExtra(Intent.EXTRA_INTENT);
161             if (intentToCheck == null) {
162                 Slog.wtf(TAG, "Cannot forward a chooser intent with no extra "
163                         + Intent.EXTRA_INTENT);
164                 return null;
165             }
166         }
167         if (forwardIntent.getSelector() != null) {
168             intentToCheck = forwardIntent.getSelector();
169         }
170         String resolvedType = intentToCheck.resolveTypeIfNeeded(getContentResolver());
171         sanitizeIntent(intentToCheck);
172         try {
173             if (mInjector.getIPackageManager().
174                     canForwardTo(intentToCheck, resolvedType, getUserId(), targetUserId)) {
175                 return forwardIntent;
176             }
177         } catch (RemoteException e) {
178             Slog.e(TAG, "PackageManagerService is dead?");
179         }
180         return null;
181     }
182 
183     /**
184      * Returns the userId of the managed profile for this device or UserHandle.USER_NULL if there is
185      * no managed profile.
186      *
187      * TODO: Remove the assumption that there is only one managed profile
188      * on the device.
189      */
getManagedProfile()190     private int getManagedProfile() {
191         List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
192         for (UserInfo userInfo : relatedUsers) {
193             if (userInfo.isManagedProfile()) return userInfo.id;
194         }
195         Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE
196                 + " has been called, but there is no managed profile");
197         return UserHandle.USER_NULL;
198     }
199 
200     /**
201      * Returns the userId of the profile parent or UserHandle.USER_NULL if there is
202      * no parent.
203      */
getProfileParent()204     private int getProfileParent() {
205         UserInfo parent = mInjector.getUserManager().getProfileParent(UserHandle.myUserId());
206         if (parent == null) {
207             Slog.wtf(TAG, FORWARD_INTENT_TO_PARENT
208                     + " has been called, but there is no parent");
209             return UserHandle.USER_NULL;
210         }
211         return parent.id;
212     }
213 
214     /**
215      * Sanitize the intent in place.
216      */
sanitizeIntent(Intent intent)217     private void sanitizeIntent(Intent intent) {
218         // Apps should not be allowed to target a specific package/ component in the target user.
219         intent.setPackage(null);
220         intent.setComponent(null);
221     }
222 
223     @VisibleForTesting
createInjector()224     protected Injector createInjector() {
225         return new InjectorImpl();
226     }
227 
228     private class InjectorImpl implements Injector {
229 
230         @Override
getIPackageManager()231         public IPackageManager getIPackageManager() {
232             return AppGlobals.getPackageManager();
233         }
234 
235         @Override
getUserManager()236         public UserManager getUserManager() {
237             return getSystemService(UserManager.class);
238         }
239 
240         @Override
getPackageManager()241         public PackageManager getPackageManager() {
242             return IntentForwarderActivity.this.getPackageManager();
243         }
244     }
245 
246     public interface Injector {
getIPackageManager()247         IPackageManager getIPackageManager();
248 
getUserManager()249         UserManager getUserManager();
250 
getPackageManager()251         PackageManager getPackageManager();
252     }
253 }
254