summaryrefslogtreecommitdiff
path: root/telephony/java/com/android/internal/telephony/CarrierAppUtils.java
blob: a1bea4d417f97021bb2dce8a3b6c0662548108b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.internal.telephony;

import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemConfig;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Utilities for handling carrier applications.
 * @hide
 */
public final class CarrierAppUtils {
    private static final String TAG = "CarrierAppUtils";

    private static final boolean DEBUG = false; // STOPSHIP if true

    private CarrierAppUtils() {}

    /**
     * Handle preinstalled carrier apps which should be disabled until a matching SIM is inserted.
     *
     * Evaluates the list of applications in
     * {@link SystemConfig#getDisabledUntilUsedPreinstalledCarrierApps()}. We want to disable each
     * such application which is present on the system image until the user inserts a SIM which
     * causes that application to gain carrier privilege (indicating a "match"), without interfering
     * with the user if they opt to enable/disable the app explicitly.
     *
     * So, for each such app, we either disable until used IFF the app is not carrier privileged AND
     * in the default state (e.g. not explicitly DISABLED/DISABLED_BY_USER/ENABLED), or we enable if
     * the app is carrier privileged and in either the default state or DISABLED_UNTIL_USED.
     *
     * In addition, there is a list of carrier-associated applications in
     * {@link SystemConfig#getDisabledUntilUsedPreinstalledCarrierAssociatedApps}. Each app in this
     * list is associated with a carrier app. When the given carrier app is enabled/disabled per the
     * above, the associated applications are enabled/disabled to match.
     *
     * When enabling a carrier app we also grant it default permissions.
     *
     * This method is idempotent and is safe to be called at any time; it should be called once at
     * system startup prior to any application running, as well as any time the set of carrier
     * privileged apps may have changed.
     */
    public synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage,
            IPackageManager packageManager, TelephonyManager telephonyManager,
            ContentResolver contentResolver, int userId) {
        if (DEBUG) {
            Slog.d(TAG, "disableCarrierAppsUntilPrivileged");
        }
        SystemConfig config = SystemConfig.getInstance();
        ArraySet<String> systemCarrierAppsDisabledUntilUsed =
                config.getDisabledUntilUsedPreinstalledCarrierApps();
        ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
                config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
        disableCarrierAppsUntilPrivileged(callingPackage, packageManager, telephonyManager,
                contentResolver, userId, systemCarrierAppsDisabledUntilUsed,
                systemCarrierAssociatedAppsDisabledUntilUsed);
    }

    /**
     * Like {@link #disableCarrierAppsUntilPrivileged(String, IPackageManager, TelephonyManager,
     * ContentResolver, int)}, but assumes that no carrier apps have carrier privileges.
     *
     * This prevents a potential race condition on first boot - since the app's default state is
     * enabled, we will initially disable it when the telephony stack is first initialized as it has
     * not yet read the carrier privilege rules. However, since telephony is initialized later on
     * late in boot, the app being disabled may have already been started in response to certain
     * broadcasts. The app will continue to run (briefly) after being disabled, before the Package
     * Manager can kill it, and this can lead to crashes as the app is in an unexpected state.
     */
    public synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage,
            IPackageManager packageManager, ContentResolver contentResolver, int userId) {
        if (DEBUG) {
            Slog.d(TAG, "disableCarrierAppsUntilPrivileged");
        }
        SystemConfig config = SystemConfig.getInstance();
        ArraySet<String> systemCarrierAppsDisabledUntilUsed =
                config.getDisabledUntilUsedPreinstalledCarrierApps();


        ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
                config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
        disableCarrierAppsUntilPrivileged(callingPackage, packageManager,
                null /* telephonyManager */, contentResolver, userId,
                systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed);
    }

    // Must be public b/c framework unit tests can't access package-private methods.
    @VisibleForTesting
    public static void disableCarrierAppsUntilPrivileged(String callingPackage,
            IPackageManager packageManager, @Nullable TelephonyManager telephonyManager,
            ContentResolver contentResolver, int userId,
            ArraySet<String> systemCarrierAppsDisabledUntilUsed,
            ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) {
        List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(packageManager,
                userId, systemCarrierAppsDisabledUntilUsed);
        if (candidates == null || candidates.isEmpty()) {
            return;
        }

        Map<String, List<ApplicationInfo>> associatedApps = getDefaultCarrierAssociatedAppsHelper(
                packageManager,
                userId,
                systemCarrierAssociatedAppsDisabledUntilUsed);

        List<String> enabledCarrierPackages = new ArrayList<>();

        boolean hasRunOnce = Settings.Secure.getIntForUser(
                contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1;

        try {
            for (ApplicationInfo ai : candidates) {
                String packageName = ai.packageName;
                boolean hasPrivileges = telephonyManager != null &&
                        telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) ==
                                TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;

                // add hiddenUntilInstalled flag for carrier apps and associated apps
                packageManager.setSystemAppHiddenUntilInstalled(packageName, true);
                List<ApplicationInfo> associatedAppList = associatedApps.get(packageName);
                if (associatedAppList != null) {
                    for (ApplicationInfo associatedApp : associatedAppList) {
                        packageManager.setSystemAppHiddenUntilInstalled(
                                associatedApp.packageName,
                                true
                        );
                    }
                }

                if (hasPrivileges) {
                    // Only update enabled state for the app on /system. Once it has been
                    // updated we shouldn't touch it.
                    if (!ai.isUpdatedSystemApp()
                            && (ai.enabledSetting ==
                            PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                            || ai.enabledSetting ==
                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
                            || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
                        Slog.i(TAG, "Update state(" + packageName + "): ENABLED for user "
                                + userId);
                        packageManager.setSystemAppInstallState(
                                packageName,
                                true /*installed*/,
                                userId);
                        packageManager.setApplicationEnabledSetting(
                                packageName,
                                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                                PackageManager.DONT_KILL_APP,
                                userId,
                                callingPackage);
                    }

                    // Also enable any associated apps for this carrier app.
                    if (associatedAppList != null) {
                        for (ApplicationInfo associatedApp : associatedAppList) {
                            if (associatedApp.enabledSetting ==
                                    PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                                    || associatedApp.enabledSetting ==
                                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
                                    || (associatedApp.flags
                                    & ApplicationInfo.FLAG_INSTALLED) == 0) {
                                Slog.i(TAG, "Update associated state(" + associatedApp.packageName
                                        + "): ENABLED for user " + userId);
                                packageManager.setSystemAppInstallState(
                                        associatedApp.packageName,
                                        true /*installed*/,
                                        userId);
                                packageManager.setApplicationEnabledSetting(
                                        associatedApp.packageName,
                                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                                        PackageManager.DONT_KILL_APP,
                                        userId,
                                        callingPackage);
                            }
                        }
                    }

                    // Always re-grant default permissions to carrier apps w/ privileges.
                    enabledCarrierPackages.add(ai.packageName);
                } else {  // No carrier privileges
                    // Only update enabled state for the app on /system. Once it has been
                    // updated we shouldn't touch it.
                    if (!ai.isUpdatedSystemApp()
                            && ai.enabledSetting ==
                            PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                            && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
                        Slog.i(TAG, "Update state(" + packageName
                                + "): DISABLED_UNTIL_USED for user " + userId);
                        packageManager.setSystemAppInstallState(
                                packageName,
                                false /*installed*/,
                                userId);
                    }

                    // Also disable any associated apps for this carrier app if this is the first
                    // run. We avoid doing this a second time because it is brittle to rely on the
                    // distinction between "default" and "enabled".
                    if (!hasRunOnce) {
                        if (associatedAppList != null) {
                            for (ApplicationInfo associatedApp : associatedAppList) {
                                if (associatedApp.enabledSetting
                                        == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                                        && (associatedApp.flags
                                        & ApplicationInfo.FLAG_INSTALLED) != 0) {
                                    Slog.i(TAG,
                                            "Update associated state(" + associatedApp.packageName
                                                    + "): DISABLED_UNTIL_USED for user " + userId);
                                    packageManager.setSystemAppInstallState(
                                            associatedApp.packageName,
                                            false /*installed*/,
                                            userId);
                                }
                            }
                        }
                    }
                }
            }

            // Mark the execution so we do not disable apps again.
            if (!hasRunOnce) {
                Settings.Secure.putIntForUser(
                        contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId);
            }

            if (!enabledCarrierPackages.isEmpty()) {
                // Since we enabled at least one app, ensure we grant default permissions to those
                // apps.
                String[] packageNames = new String[enabledCarrierPackages.size()];
                enabledCarrierPackages.toArray(packageNames);
                packageManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId);
            }
        } catch (RemoteException e) {
            Slog.w(TAG, "Could not reach PackageManager", e);
        }
    }

    /**
     * Returns the list of "default" carrier apps.
     *
     * This is the subset of apps returned by
     * {@link #getDefaultCarrierAppCandidates(IPackageManager, int)} which currently have carrier
     * privileges per the SIM(s) inserted in the device.
     */
    public static List<ApplicationInfo> getDefaultCarrierApps(IPackageManager packageManager,
            TelephonyManager telephonyManager, int userId) {
        // Get all system apps from the default list.
        List<ApplicationInfo> candidates = getDefaultCarrierAppCandidates(packageManager, userId);
        if (candidates == null || candidates.isEmpty()) {
            return null;
        }

        // Filter out apps without carrier privileges.
        // Iterate from the end to avoid creating an Iterator object and because we will be removing
        // elements from the list as we pass through it.
        for (int i = candidates.size() - 1; i >= 0; i--) {
            ApplicationInfo ai = candidates.get(i);
            String packageName = ai.packageName;
            boolean hasPrivileges =
                    telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) ==
                            TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
            if (!hasPrivileges) {
                candidates.remove(i);
            }
        }

        return candidates;
    }

    /**
     * Returns the list of "default" carrier app candidates.
     *
     * These are the apps subject to the hiding/showing logic in
     * {@link CarrierAppUtils#disableCarrierAppsUntilPrivileged(String, IPackageManager,
     * TelephonyManager, ContentResolver, int)}, as well as the apps which should have default
     * permissions granted, when a matching SIM is inserted.
     *
     * Whether or not the app is actually considered a default app depends on whether the app has
     * carrier privileges as determined by the SIMs in the device.
     */
    public static List<ApplicationInfo> getDefaultCarrierAppCandidates(
            IPackageManager packageManager, int userId) {
        ArraySet<String> systemCarrierAppsDisabledUntilUsed =
                SystemConfig.getInstance().getDisabledUntilUsedPreinstalledCarrierApps();
        return getDefaultCarrierAppCandidatesHelper(packageManager, userId,
                systemCarrierAppsDisabledUntilUsed);
    }

    private static List<ApplicationInfo> getDefaultCarrierAppCandidatesHelper(
            IPackageManager packageManager,
            int userId,
            ArraySet<String> systemCarrierAppsDisabledUntilUsed) {
        if (systemCarrierAppsDisabledUntilUsed == null) {
            return null;
        }

        int size = systemCarrierAppsDisabledUntilUsed.size();
        if (size == 0) {
            return null;
        }

        List<ApplicationInfo> apps = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i);
            ApplicationInfo ai =
                    getApplicationInfoIfSystemApp(packageManager, userId, packageName);
            if (ai != null) {
                apps.add(ai);
            }
        }
        return apps;
    }

    private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper(
            IPackageManager packageManager,
            int userId,
            ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) {
        int size = systemCarrierAssociatedAppsDisabledUntilUsed.size();
        Map<String, List<ApplicationInfo>> associatedApps = new ArrayMap<>(size);
        for (int i = 0; i < size; i++) {
            String carrierAppPackage = systemCarrierAssociatedAppsDisabledUntilUsed.keyAt(i);
            List<String> associatedAppPackages =
                    systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i);
            for (int j = 0; j < associatedAppPackages.size(); j++) {
                ApplicationInfo ai =
                        getApplicationInfoIfSystemApp(
                                packageManager, userId, associatedAppPackages.get(j));
                // Only update enabled state for the app on /system. Once it has been updated we
                // shouldn't touch it.
                if (ai != null && !ai.isUpdatedSystemApp()) {
                    List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage);
                    if (appList == null) {
                        appList = new ArrayList<>();
                        associatedApps.put(carrierAppPackage, appList);
                    }
                    appList.add(ai);
                }
            }
        }
        return associatedApps;
    }

    @Nullable
    private static ApplicationInfo getApplicationInfoIfSystemApp(
            IPackageManager packageManager,
            int userId,
            String packageName) {
        try {
            ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
                    PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
                    | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId);
            if (ai != null && ai.isSystemApp()) {
                return ai;
            }
        } catch (RemoteException e) {
            Slog.w(TAG, "Could not reach PackageManager", e);
        }
        return null;
    }
}