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