1/*
2 * Copyright (C) 2016 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.server.am;
18
19import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
20import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
21import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
22import static android.app.PendingIntent.FLAG_IMMUTABLE;
23import static android.app.PendingIntent.FLAG_ONE_SHOT;
24import static android.app.admin.DevicePolicyManager.EXTRA_RESTRICTION;
25import static android.app.admin.DevicePolicyManager.POLICY_SUSPEND_PACKAGES;
26import static android.content.Context.KEYGUARD_SERVICE;
27import static android.content.Intent.EXTRA_INTENT;
28import static android.content.Intent.EXTRA_PACKAGE_NAME;
29import static android.content.Intent.EXTRA_TASK_ID;
30import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
31import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
32import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
33import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
34
35import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
36
37import android.app.ActivityOptions;
38import android.app.KeyguardManager;
39import android.app.admin.DevicePolicyManagerInternal;
40import android.content.Context;
41import android.content.IIntentSender;
42import android.content.Intent;
43import android.content.IntentSender;
44import android.content.pm.ActivityInfo;
45import android.content.pm.PackageManagerInternal;
46import android.content.pm.ResolveInfo;
47import android.content.pm.UserInfo;
48import android.os.Binder;
49import android.os.Bundle;
50import android.os.RemoteException;
51import android.os.UserHandle;
52import android.os.UserManager;
53
54import com.android.internal.annotations.VisibleForTesting;
55import com.android.internal.app.HarmfulAppWarningActivity;
56import com.android.internal.app.SuspendedAppActivity;
57import com.android.internal.app.UnlaunchableAppActivity;
58import com.android.server.LocalServices;
59
60/**
61 * A class that contains activity intercepting logic for {@link ActivityStarter#startActivityLocked}
62 * It's initialized via setStates and interception occurs via the intercept method.
63 *
64 * Note that this class is instantiated when {@link ActivityManagerService} gets created so there
65 * is no guarantee that other system services are already present.
66 */
67class ActivityStartInterceptor {
68
69    private final ActivityManagerService mService;
70    private final ActivityStackSupervisor mSupervisor;
71    private final Context mServiceContext;
72    private final UserController mUserController;
73
74    // UserManager cannot be final as it's not ready when this class is instantiated during boot
75    private UserManager mUserManager;
76
77    /*
78     * Per-intent states loaded from ActivityStarter than shouldn't be changed by any
79     * interception routines.
80     */
81    private int mRealCallingPid;
82    private int mRealCallingUid;
83    private int mUserId;
84    private int mStartFlags;
85    private String mCallingPackage;
86
87    /*
88     * Per-intent states that were load from ActivityStarter and are subject to modifications
89     * by the interception routines. After calling {@link #intercept} the caller should assign
90     * these values back to {@link ActivityStarter#startActivityLocked}'s local variables if
91     * {@link #intercept} returns true.
92     */
93    Intent mIntent;
94    int mCallingPid;
95    int mCallingUid;
96    ResolveInfo mRInfo;
97    ActivityInfo mAInfo;
98    String mResolvedType;
99    TaskRecord mInTask;
100    ActivityOptions mActivityOptions;
101
102    ActivityStartInterceptor(ActivityManagerService service, ActivityStackSupervisor supervisor) {
103        this(service, supervisor, service.mContext, service.mUserController);
104    }
105
106    @VisibleForTesting
107    ActivityStartInterceptor(ActivityManagerService service, ActivityStackSupervisor supervisor,
108            Context context, UserController userController) {
109        mService = service;
110        mSupervisor = supervisor;
111        mServiceContext = context;
112        mUserController = userController;
113    }
114
115    /**
116     * Effectively initialize the class before intercepting the start intent. The values set in this
117     * method should not be changed during intercept.
118     */
119    void setStates(int userId, int realCallingPid, int realCallingUid, int startFlags,
120            String callingPackage) {
121        mRealCallingPid = realCallingPid;
122        mRealCallingUid = realCallingUid;
123        mUserId = userId;
124        mStartFlags = startFlags;
125        mCallingPackage = callingPackage;
126    }
127
128    private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) {
129        Bundle activityOptions = deferCrossProfileAppsAnimationIfNecessary();
130        final IIntentSender target = mService.getIntentSenderLocked(
131                INTENT_SENDER_ACTIVITY, mCallingPackage, callingUid, mUserId, null /*token*/,
132                null /*resultCode*/, 0 /*requestCode*/,
133                new Intent[] { mIntent }, new String[] { mResolvedType },
134                flags, activityOptions);
135        return new IntentSender(target);
136    }
137
138    /**
139     * Intercept the launch intent based on various signals. If an interception happened the
140     * internal variables get assigned and need to be read explicitly by the caller.
141     *
142     * @return true if an interception occurred
143     */
144    boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType,
145            TaskRecord inTask, int callingPid, int callingUid, ActivityOptions activityOptions) {
146        mUserManager = UserManager.get(mServiceContext);
147
148        mIntent = intent;
149        mCallingPid = callingPid;
150        mCallingUid = callingUid;
151        mRInfo = rInfo;
152        mAInfo = aInfo;
153        mResolvedType = resolvedType;
154        mInTask = inTask;
155        mActivityOptions = activityOptions;
156
157        if (interceptSuspendedPackageIfNeeded()) {
158            // Skip the rest of interceptions as the package is suspended by device admin so
159            // no user action can undo this.
160            return true;
161        }
162        if (interceptQuietProfileIfNeeded()) {
163            // If work profile is turned off, skip the work challenge since the profile can only
164            // be unlocked when profile's user is running.
165            return true;
166        }
167        if (interceptHarmfulAppIfNeeded()) {
168            // If the app has a "harmful app" warning associated with it, we should ask to uninstall
169            // before issuing the work challenge.
170            return true;
171        }
172        return interceptWorkProfileChallengeIfNeeded();
173    }
174
175    /**
176     * If the activity option is the {@link ActivityOptions#ANIM_OPEN_CROSS_PROFILE_APPS} one,
177     * defer the animation until the original intent is started.
178     *
179     * @return the activity option used to start the original intent.
180     */
181    private Bundle deferCrossProfileAppsAnimationIfNecessary() {
182        if (mActivityOptions != null
183                && mActivityOptions.getAnimationType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
184            mActivityOptions = null;
185            return ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
186        }
187        return null;
188    }
189
190    private boolean interceptQuietProfileIfNeeded() {
191        // Do not intercept if the user has not turned off the profile
192        if (!mUserManager.isQuietModeEnabled(UserHandle.of(mUserId))) {
193            return false;
194        }
195
196        IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
197                FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT);
198
199        mIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(mUserId, target);
200        mCallingPid = mRealCallingPid;
201        mCallingUid = mRealCallingUid;
202        mResolvedType = null;
203
204        final UserInfo parent = mUserManager.getProfileParent(mUserId);
205        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, parent.id, 0, mRealCallingUid);
206        mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
207        return true;
208    }
209
210    private boolean interceptSuspendedByAdminPackage() {
211        DevicePolicyManagerInternal devicePolicyManager = LocalServices
212                .getService(DevicePolicyManagerInternal.class);
213        if (devicePolicyManager == null) {
214            return false;
215        }
216        mIntent = devicePolicyManager.createShowAdminSupportIntent(mUserId, true);
217        mIntent.putExtra(EXTRA_RESTRICTION, POLICY_SUSPEND_PACKAGES);
218
219        mCallingPid = mRealCallingPid;
220        mCallingUid = mRealCallingUid;
221        mResolvedType = null;
222
223        final UserInfo parent = mUserManager.getProfileParent(mUserId);
224        if (parent != null) {
225            mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, parent.id, 0,
226                    mRealCallingUid);
227        } else {
228            mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0,
229                    mRealCallingUid);
230        }
231        mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
232        return true;
233    }
234
235    private boolean interceptSuspendedPackageIfNeeded() {
236        // Do not intercept if the package is not suspended
237        if (mAInfo == null || mAInfo.applicationInfo == null ||
238                (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) {
239            return false;
240        }
241        final PackageManagerInternal pmi = mService.getPackageManagerInternalLocked();
242        if (pmi == null) {
243            return false;
244        }
245        final String suspendedPackage = mAInfo.applicationInfo.packageName;
246        final String suspendingPackage = pmi.getSuspendingPackage(suspendedPackage, mUserId);
247        if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
248            return interceptSuspendedByAdminPackage();
249        }
250        final String dialogMessage = pmi.getSuspendedDialogMessage(suspendedPackage, mUserId);
251        mIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(suspendedPackage,
252                suspendingPackage, dialogMessage, mUserId);
253        mCallingPid = mRealCallingPid;
254        mCallingUid = mRealCallingUid;
255        mResolvedType = null;
256        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid);
257        mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
258        return true;
259    }
260
261    private boolean interceptWorkProfileChallengeIfNeeded() {
262        final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
263        if (interceptingIntent == null) {
264            return false;
265        }
266        mIntent = interceptingIntent;
267        mCallingPid = mRealCallingPid;
268        mCallingUid = mRealCallingUid;
269        mResolvedType = null;
270        // If we are intercepting and there was a task, convert it into an extra for the
271        // ConfirmCredentials intent and unassign it, as otherwise the task will move to
272        // front even if ConfirmCredentials is cancelled.
273        if (mInTask != null) {
274            mIntent.putExtra(EXTRA_TASK_ID, mInTask.taskId);
275            mInTask = null;
276        }
277        if (mActivityOptions == null) {
278            mActivityOptions = ActivityOptions.makeBasic();
279        }
280
281        ActivityRecord homeActivityRecord = mSupervisor.getHomeActivity();
282        if (homeActivityRecord != null && homeActivityRecord.getTask() != null) {
283            // Showing credential confirmation activity in home task to avoid stopping multi-windowed
284            // mode after showing the full-screen credential confirmation activity.
285            mActivityOptions.setLaunchTaskId(homeActivityRecord.getTask().taskId);
286        }
287
288        final UserInfo parent = mUserManager.getProfileParent(mUserId);
289        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, parent.id, 0, mRealCallingUid);
290        mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
291        return true;
292    }
293
294    /**
295     * Creates an intent to intercept the current activity start with Confirm Credentials if needed.
296     *
297     * @return The intercepting intent if needed.
298     */
299    private Intent interceptWithConfirmCredentialsIfNeeded(ActivityInfo aInfo, int userId) {
300        if (!mUserController.shouldConfirmCredentials(userId)) {
301            return null;
302        }
303        // TODO(b/28935539): should allow certain activities to bypass work challenge
304        final IntentSender target = createIntentSenderForOriginalIntent(Binder.getCallingUid(),
305                FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE);
306        final KeyguardManager km = (KeyguardManager) mServiceContext
307                .getSystemService(KEYGUARD_SERVICE);
308        final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, userId);
309        if (newIntent == null) {
310            return null;
311        }
312        newIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
313                FLAG_ACTIVITY_TASK_ON_HOME);
314        newIntent.putExtra(EXTRA_PACKAGE_NAME, aInfo.packageName);
315        newIntent.putExtra(EXTRA_INTENT, target);
316        return newIntent;
317    }
318
319    private boolean interceptHarmfulAppIfNeeded() {
320        CharSequence harmfulAppWarning;
321        try {
322            harmfulAppWarning = mService.getPackageManager()
323                    .getHarmfulAppWarning(mAInfo.packageName, mUserId);
324        } catch (RemoteException ex) {
325            return false;
326        }
327
328        if (harmfulAppWarning == null) {
329            return false;
330        }
331
332        final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
333                FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE);
334
335        mIntent = HarmfulAppWarningActivity.createHarmfulAppWarningIntent(mServiceContext,
336                mAInfo.packageName, target, harmfulAppWarning);
337
338        mCallingPid = mRealCallingPid;
339        mCallingUid = mRealCallingUid;
340        mResolvedType = null;
341
342        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid);
343        mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
344        return true;
345    }
346}
347