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