1/*
2 * Copyright (C) 2011 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.usb;
18
19import android.annotation.NonNull;
20import android.app.PendingIntent;
21import android.content.ActivityNotFoundException;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.PackageManager;
26import android.content.pm.PackageManager.NameNotFoundException;
27import android.hardware.usb.UsbAccessory;
28import android.hardware.usb.UsbDevice;
29import android.hardware.usb.UsbManager;
30import android.os.Binder;
31import android.os.Process;
32import android.os.UserHandle;
33import android.util.Slog;
34import android.util.SparseBooleanArray;
35
36import com.android.internal.util.IndentingPrintWriter;
37
38import java.util.HashMap;
39
40class UsbUserSettingsManager {
41    private static final String TAG = "UsbUserSettingsManager";
42    private static final boolean DEBUG = false;
43
44    private final UserHandle mUser;
45    private final boolean mDisablePermissionDialogs;
46
47    private final Context mUserContext;
48    private final PackageManager mPackageManager;
49
50    // Temporary mapping USB device name to list of UIDs with permissions for the device
51    private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
52            new HashMap<>();
53    // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory
54    private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
55            new HashMap<>();
56
57    private final Object mLock = new Object();
58
59    public UsbUserSettingsManager(Context context, UserHandle user) {
60        if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
61
62        try {
63            mUserContext = context.createPackageContextAsUser("android", 0, user);
64        } catch (NameNotFoundException e) {
65            throw new RuntimeException("Missing android package");
66        }
67
68        mPackageManager = mUserContext.getPackageManager();
69
70        mUser = user;
71
72        mDisablePermissionDialogs = context.getResources().getBoolean(
73                com.android.internal.R.bool.config_disableUsbPermissionDialogs);
74    }
75
76    /**
77     * Remove all access permission for a device.
78     *
79     * @param device The device the permissions are for
80     */
81    void removeDevicePermissions(@NonNull UsbDevice device) {
82        synchronized (mLock) {
83            mDevicePermissionMap.remove(device.getDeviceName());
84        }
85    }
86
87    /**
88     * Remove all access permission for a accessory.
89     *
90     * @param accessory The accessory the permissions are for
91     */
92    void removeAccessoryPermissions(@NonNull UsbAccessory accessory) {
93        synchronized (mLock) {
94            mAccessoryPermissionMap.remove(accessory);
95        }
96    }
97
98
99    public boolean hasPermission(UsbDevice device) {
100        synchronized (mLock) {
101            int uid = Binder.getCallingUid();
102            if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
103                return true;
104            }
105            SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
106            if (uidList == null) {
107                return false;
108            }
109            return uidList.get(uid);
110        }
111    }
112
113    public boolean hasPermission(UsbAccessory accessory) {
114        synchronized (mLock) {
115            int uid = Binder.getCallingUid();
116            if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
117                return true;
118            }
119            SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
120            if (uidList == null) {
121                return false;
122            }
123            return uidList.get(uid);
124        }
125    }
126
127    public void checkPermission(UsbDevice device) {
128        if (!hasPermission(device)) {
129            throw new SecurityException("User has not given permission to device " + device);
130        }
131    }
132
133    public void checkPermission(UsbAccessory accessory) {
134        if (!hasPermission(accessory)) {
135            throw new SecurityException("User has not given permission to accessory " + accessory);
136        }
137    }
138
139    private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) {
140        final int uid = Binder.getCallingUid();
141
142        // compare uid with packageName to foil apps pretending to be someone else
143        try {
144            ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
145            if (aInfo.uid != uid) {
146                throw new IllegalArgumentException("package " + packageName +
147                        " does not match caller's uid " + uid);
148            }
149        } catch (PackageManager.NameNotFoundException e) {
150            throw new IllegalArgumentException("package " + packageName + " not found");
151        }
152
153        long identity = Binder.clearCallingIdentity();
154        intent.setClassName("com.android.systemui",
155                "com.android.systemui.usb.UsbPermissionActivity");
156        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
157        intent.putExtra(Intent.EXTRA_INTENT, pi);
158        intent.putExtra("package", packageName);
159        intent.putExtra(Intent.EXTRA_UID, uid);
160        try {
161            mUserContext.startActivityAsUser(intent, mUser);
162        } catch (ActivityNotFoundException e) {
163            Slog.e(TAG, "unable to start UsbPermissionActivity");
164        } finally {
165            Binder.restoreCallingIdentity(identity);
166        }
167    }
168
169    public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
170      Intent intent = new Intent();
171
172        // respond immediately if permission has already been granted
173      if (hasPermission(device)) {
174            intent.putExtra(UsbManager.EXTRA_DEVICE, device);
175            intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
176            try {
177                pi.send(mUserContext, 0, intent);
178            } catch (PendingIntent.CanceledException e) {
179                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
180            }
181            return;
182        }
183
184        // start UsbPermissionActivity so user can choose an activity
185        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
186        requestPermissionDialog(intent, packageName, pi);
187    }
188
189    public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) {
190        Intent intent = new Intent();
191
192        // respond immediately if permission has already been granted
193        if (hasPermission(accessory)) {
194            intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
195            intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
196            try {
197                pi.send(mUserContext, 0, intent);
198            } catch (PendingIntent.CanceledException e) {
199                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
200            }
201            return;
202        }
203
204        intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
205        requestPermissionDialog(intent, packageName, pi);
206    }
207
208    public void grantDevicePermission(UsbDevice device, int uid) {
209        synchronized (mLock) {
210            String deviceName = device.getDeviceName();
211            SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
212            if (uidList == null) {
213                uidList = new SparseBooleanArray(1);
214                mDevicePermissionMap.put(deviceName, uidList);
215            }
216            uidList.put(uid, true);
217        }
218    }
219
220    public void grantAccessoryPermission(UsbAccessory accessory, int uid) {
221        synchronized (mLock) {
222            SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
223            if (uidList == null) {
224                uidList = new SparseBooleanArray(1);
225                mAccessoryPermissionMap.put(accessory, uidList);
226            }
227            uidList.put(uid, true);
228        }
229    }
230
231    public void dump(IndentingPrintWriter pw) {
232        synchronized (mLock) {
233            pw.println("Device permissions:");
234            for (String deviceName : mDevicePermissionMap.keySet()) {
235                pw.print("  " + deviceName + ": ");
236                SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
237                int count = uidList.size();
238                for (int i = 0; i < count; i++) {
239                    pw.print(Integer.toString(uidList.keyAt(i)) + " ");
240                }
241                pw.println();
242            }
243            pw.println("Accessory permissions:");
244            for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) {
245                pw.print("  " + accessory + ": ");
246                SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
247                int count = uidList.size();
248                for (int i = 0; i < count; i++) {
249                    pw.print(Integer.toString(uidList.keyAt(i)) + " ");
250                }
251                pw.println();
252            }
253        }
254    }
255}
256