1/*
2 * Copyright (C) 2015 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 android.support.v4.content;
18
19import android.content.Context;
20import android.content.pm.PackageManager;
21import android.os.Binder;
22import android.os.Process;
23import android.support.annotation.IntDef;
24import android.support.annotation.NonNull;
25import android.support.v4.app.AppOpsManagerCompat;
26
27import java.lang.annotation.Retention;
28import java.lang.annotation.RetentionPolicy;
29
30/**
31 * This class provides permission check APIs that verify both the
32 * permission and the associated app op for this permission if
33 * such is defined.
34 * <p>
35 * In the new permission model permissions with protection level
36 * dangerous are runtime permissions. For apps targeting {@link android.os.Build.VERSION_CODES#M}
37 * and above the user may not grant such permissions or revoke
38 * them at any time. For apps targeting API lower than {@link android.os.Build.VERSION_CODES#M}
39 * these permissions are always granted as such apps do not expect
40 * permission revocations and would crash. Therefore, when the
41 * user disables a permission for a legacy app in the UI the
42 * platform disables the APIs guarded by this permission making
43 * them a no-op which is doing nothing or returning an empty
44 * result or default error.
45 * </p>
46 * <p>
47 * It is important that when you perform an operation on behalf of
48 * another app you use these APIs to check for permissions as the
49 * app may be a legacy app that does not participate in the new
50 * permission model for which the user had disabled the "permission"
51 * which is achieved by disallowing the corresponding app op.
52 * </p>
53 */
54public final class PermissionChecker {
55    /** Permission result: The permission is granted. */
56    public static final int PERMISSION_GRANTED =  PackageManager.PERMISSION_GRANTED;
57
58    /** Permission result: The permission is denied. */
59    public static final int PERMISSION_DENIED =  PackageManager.PERMISSION_DENIED;
60
61    /** Permission result: The permission is denied because the app op is not allowed. */
62    public static final int PERMISSION_DENIED_APP_OP =  PackageManager.PERMISSION_DENIED  - 1;
63
64    @IntDef({PERMISSION_GRANTED,
65            PERMISSION_DENIED,
66            PERMISSION_DENIED_APP_OP})
67    @Retention(RetentionPolicy.SOURCE)
68    public @interface PermissionResult {}
69
70    private PermissionChecker() {
71        /* do nothing */
72    }
73
74    /**
75     * Checks whether a given package in a UID and PID has a given permission
76     * and whether the app op that corresponds to this permission is allowed.
77     *
78     * @param context Context for accessing resources.
79     * @param permission The permission to check.
80     * @param pid The process id for which to check.
81     * @param uid The uid for which to check.
82     * @param packageName The package name for which to check. If null the
83     *     the first package for the calling UID will be used.
84     * @return The permission check result which is either {@link #PERMISSION_GRANTED}
85     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
86     */
87    public static int checkPermission(@NonNull Context context, @NonNull String permission,
88            int pid, int uid, String packageName) {
89        if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
90            return PERMISSION_DENIED;
91        }
92
93        String op = AppOpsManagerCompat.permissionToOp(permission);
94        if (op == null) {
95            return PERMISSION_GRANTED;
96        }
97
98        if (packageName == null) {
99            String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
100            if (packageNames == null || packageNames.length <= 0) {
101                return PERMISSION_DENIED;
102            }
103            packageName = packageNames[0];
104        }
105
106        if (AppOpsManagerCompat.noteProxyOp(context, op, packageName)
107                != AppOpsManagerCompat.MODE_ALLOWED) {
108            return PERMISSION_DENIED_APP_OP;
109        }
110
111        return PERMISSION_GRANTED;
112    }
113
114    /**
115     * Checks whether your app has a given permission and whether the app op
116     * that corresponds to this permission is allowed.
117     *
118     * @param context Context for accessing resources.
119     * @param permission The permission to check.
120     * @return The permission check result which is either {@link #PERMISSION_GRANTED}
121     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
122     */
123    public static int checkSelfPermission(@NonNull Context context,
124            @NonNull String permission) {
125        return checkPermission(context, permission, android.os.Process.myPid(),
126                android.os.Process.myUid(), context.getPackageName());
127    }
128
129    /**
130     * Checks whether the IPC you are handling has a given permission and whether
131     * the app op that corresponds to this permission is allowed.
132     *
133     * @param context Context for accessing resources.
134     * @param permission The permission to check.
135     * @param packageName The package name making the IPC. If null the
136     *     the first package for the calling UID will be used.
137     * @return The permission check result which is either {@link #PERMISSION_GRANTED}
138     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
139     */
140    public static int checkCallingPermission(@NonNull Context context,
141            @NonNull String permission, String packageName) {
142        if (Binder.getCallingPid() == Process.myPid()) {
143            return PackageManager.PERMISSION_DENIED;
144        }
145        return checkPermission(context, permission, Binder.getCallingPid(),
146                Binder.getCallingUid(), packageName);
147    }
148
149    /**
150     * Checks whether the IPC you are handling or your app has a given permission
151     * and whether the app op that corresponds to this permission is allowed.
152     *
153     * @param context Context for accessing resources.
154     * @param permission The permission to check.
155     * @return The permission check result which is either {@link #PERMISSION_GRANTED}
156     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
157     */
158    public static int checkCallingOrSelfPermission(@NonNull Context context,
159            @NonNull String permission) {
160        String packageName = (Binder.getCallingPid() == Process.myPid())
161                ? context.getPackageName() : null;
162        return checkPermission(context, permission, Binder.getCallingPid(),
163                Binder.getCallingUid(), packageName);
164    }
165}
166