summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Kondik <steve@cyngn.com>2013-10-20 23:48:00 -0700
committerSteve Kondik <steve@cyngn.com>2016-09-02 02:23:51 -0700
commite3a4937849ffe916bb1faf3bf9ea579f21432b0e (patch)
tree67bdd6fbfbd1cf32a8d27a9ceb78fa0c15999077
parent66a4ac05d851f7ca19e89dcc4b695abbf94d27a8 (diff)
downloadandroid_frameworks_base-staging/cm-14.0-caf.tar.gz
android_frameworks_base-staging/cm-14.0-caf.tar.bz2
android_frameworks_base-staging/cm-14.0-caf.zip
appops: Privacy Guard for Nstaging/cm-14.0-caf
* Squashed commit of all PG-related features including superuser Authors: - Sam Mortimer - Danesh Mondegarian - Jorge Ruesga - Diogo Ferreira - Roman Birg - Adnan Begovic - Chirayu Desai - Lars Greiss - Steve Kondik - CodeAurora AppOps: track op persistence by name instead of id On XML write, include the op name. On XML read, map the name back to op id (if it exists). Persistent AppOp state now follows the op name instead numeric id. This allows upgrades between versions of android that have different code<>name mappings. AppOpsService: Add MODE_ASK support to AppOps. Add support for new mode(MODE_ASK) in AppOpsService to show a permission dialog box to user to confirm user permission before allowing or ignoring that operation. All strict operations (defined in AppOpsManager) are going to be in MODE_ASK by default. Operations will be moved to MODE_ALLOWED or MODE_IGNORED according to user's choice. AppOps: Add support for AppOps policy file Add support to read AppOps policy file. AppOps policy file can be used to configure Ops default policy. [1/2] frameworks/base: Privacy Guard PS2: * add missing call to updatePrivacyGuardNotificationLocked() * remove ic_privacy_guard and ic_privacy_guard_off * use stat_notify_privacy_guard drawable from Björn * rebase PS3: * exclude MODE_IGNORED from causing getPrivacyGuardSettingForPackage() returning true. multiuser restrictions on calls/sms otherwise cause privacyguard to appear to be on when really it isn't. * rebase Pulled together from: Author: Danesh M <daneshm90@gmail.com> Date: Thu Sep 5 19:23:15 2013 -0700 Privacy Guard : Reimplement backed by AppOps Re-implements privacy guard to use appops. Author: Jorge Ruesga <jorge@ruesga.com> Date: Sat Jan 4 01:34:39 2014 +0100 privacyguard: Share privacy package name between activity stacks AOSP 4.4 now has a stack supervisor with differents activity stacks for home and focus activities. Every ActivityStack instance has a ref to this supervisor. Just share the privacy guard package name throught the supervisor. Patchset 2: Fix formatting JIRA: CYAN-2874 Issue: https://jira.cyanogenmod.org/browse/CYAN-2874 Author: Steve Kondik <shade@chemlab.org> Date: Sun Feb 23 19:23:31 2014 +0100 Use MODE_ASK as the default ops mode in Privacy Guard * Prompting the user is far more useful as a default behavior. The choice will be remembered after the first request. Author: Steve Kondik <shade@chemlab.org> Date: Tue Feb 25 13:11:12 2014 +0100 Relocate and cleanup new app ops features and PGify it [2/2] Framework AppOps: Add per Op allowed and ignored counters Port from cm-10.2 and cm-11.0 Author: Sam Mortimer <sam@mortimer.me.uk> Date: Wed Oct 2 22:06:42 2013 -0700 [2/2] AppOps: Add per Op allowed and ignored counters wifi: Confirm user permission before toggling wifi Check user permissions before enabling/disabling wifi. AppOps: Add Bluetooth enable control into AppOps Check user permission before enabling bluetooth. AppOps: Add BOOT_COMPLETED operation Add BOOT_COMPLETED operation in AppOps. This operation is used to allow user to control auto start of applications and services at bootup. AppOps: Add nfc enable control into AppOps. Check user permission before enabling nfc. appops: Add support for SU This patch adds support for superuser app-ops control. The end-goal is to better prepare superuser for the multi-user experience while also replacing the superuser app with existing components. su: Add an indicator when a 'su' session is active This adds an indicator to the statusbar that is visible whenever a root session is active, akin to the fine location icon. AppOps: fix wifi scan op There's no direct permission tied to it and fix the op-to-switch entry. AppOps: add a default constructor Frameworks: Redirect appops strings to string references adapted to reflect new permission indices and codes appops: Disassociate appops requests from notification broadcasts The AppOpsService is essentially a manager for a set of counters and permissions. Each operation request has the potential to change the state and, as such, access to such state is synchronized. We are whitnessing deadlocks caused by the broadcast and, in fact, while we want to notify superuser changes eventually, it does not have to be synchronous with the app ops request. This patch uses the request to schedule the notification on a handler, leaving the locking semantics of appops intact. add missing app ops to string array Also added op codes to prevent further mismatches. Add new OP_RUN_IN_BACKGROUND to list AppOps: fix deadlock issue when showing dialog WindowManagerService need call PowerManagerService to release wakelock. The Notifier in PowerManagerService need call AppOpsService to notify holding wakelock ops is finished. Meanwhile, AppOpsService may need call WindowManagerService to show dialog. This scenario will lead to deadlock issue. To move showing dialog action out of lock section to fix this issue. Since only UI work is moved out of lock area, it is supposed to be safe. AppOps: relax some system appops for systemui Grant wifi, bt, boot_complete, nfc, and data changes for systemui. Also remove the bluetooth permission tied to the bluetooth_change op - it is not always directly tied to BLUETOOTH or BLUETOOTH_ADMIN, so we can't force one there. cyanogen: Add missing RUN_IN_BACKGROUND where required Revert "AppOps: add a default constructor" * Not needed after http://review.cyanogenmod.org/126381 This reverts commit 19e0ce0755d191204ac3bd3106b779898d5daadd. appops: Implement concept of delayedcount. High frequency request ops will be delayed until their ignore count ceiling is met. This is to mitigate the overloading the main activity manager service handler and having watchdog kill our service. Google play services likes to share its uid with numerous packages to avoid having to grant permissions from the users perspective and thus is the worst example of overloading this queue -- so, to not encourage bad behavior, we move them to the back of the line. NOTE: these values are magic, and may need tuning. Ideally we'd want a ringbuffer or token bucket here to do proper rate limiting. appops: Respect screen interactivity before creating dialogs. If the device's screen is currently off, do not queue ask runnables who cannot be interacted with. Since these events are gating mechanisms for closing an IPC loop, they need to happen when the user is interacting with the device. Likewise, on screen off, clear the queue of every op as they become unnecessary. Change-Id: Ia2bbb56509bedb4aa7272e53cb67a4d94fec450d Allow disabling the privacy guard notification - port from cm-12.1 (2/3) Change-Id: Iab0288f50685220c8be0c11ea5075f91ec1bbe32 Launch app privacy settings when tapping on PG notification (1/2) Based on https://github.com/SlimRoms/frameworks_base/commit/97ccae06cd0ad1aa366c3a70e8e744277c409b06 JIRA: CYAN-6077 Change-Id: I8632e8c944c1d5d7ad2fb2a2276bae5fe2d4a0a0
-rw-r--r--core/java/android/app/AppOpsManager.java364
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java7
-rw-r--r--core/java/android/bluetooth/IBluetoothManager.aidl2
-rw-r--r--core/java/android/os/UserManager.java13
-rw-r--r--core/java/android/provider/Settings.java13
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl8
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--core/res/res/drawable/stat_notify_privacy_guard.xml11
-rw-r--r--core/res/res/layout/permission_confirmation_dialog.xml69
-rw-r--r--core/res/res/values/cm_arrays.xml145
-rw-r--r--core/res/res/values/cm_strings.xml80
-rw-r--r--core/res/res/values/cm_symbols.xml17
-rwxr-xr-xcore/res/res/values/config.xml2
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_su.xml15
-rw-r--r--packages/SystemUI/res/values/cm_strings.xml3
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java6
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java137
-rw-r--r--services/core/java/com/android/server/AppOpsPolicy.java446
-rw-r--r--services/core/java/com/android/server/AppOpsService.java562
-rw-r--r--services/core/java/com/android/server/BasePermissionDialog.java84
-rw-r--r--services/core/java/com/android/server/BluetoothManagerService.java13
-rw-r--r--services/core/java/com/android/server/PermissionDialog.java145
-rw-r--r--services/core/java/com/android/server/PermissionDialogReqQueue.java88
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java66
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java31
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java5
-rw-r--r--services/core/java/com/android/server/am/UserController.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java19
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java2
-rw-r--r--services/core/java/com/android/server/pm/UserRestrictionsUtils.java6
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java13
33 files changed, 2368 insertions, 61 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 8e9e284700c..66ab8ec0662 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1,5 +1,7 @@
/*
* Copyright (C) 2012 The Android Open Source Project
+ * Copyright (c) 2013-2016, The CyanogenMod Project
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +30,7 @@ import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.SystemProperties;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -69,6 +72,10 @@ public class AppOpsManager {
* will do this for you).
*/
+ /** {@hide} */
+ public static final String ACTION_SU_SESSION_CHANGED =
+ "android.intent.action.SU_SESSION_CHANGED";
+
final Context mContext;
final IAppOpsService mService;
final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers
@@ -103,9 +110,17 @@ public class AppOpsManager {
*/
public static final int MODE_DEFAULT = 3;
+ /**
+ * @hide Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}:
+ * AppOps Service should show a dialog box on screen to get user
+ * permission.
+ */
+ public static final int MODE_ASK = 4;
+
// when adding one of these:
// - increment _NUM_OP
- // - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault
+ // - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault,
+ // sOpDefaultStrictMode, sOpToOpString, sOpStrictMode.
// - add descriptive strings to Settings/res/values/arrays.xml
// - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
@@ -239,8 +254,20 @@ public class AppOpsManager {
public static final int OP_GET_ACCOUNTS = 62;
/** @hide Control whether an application is allowed to run in the background. */
public static final int OP_RUN_IN_BACKGROUND = 63;
+ /** @hide Wifi state change **/
+ public static final int OP_CHANGE_WIFI_STATE = 64;
+ /** @hide */
+ public static final int OP_BLUETOOTH_CHANGE = 65;
+ /** @hide */
+ public static final int OP_BOOT_COMPLETED = 66;
/** @hide */
- public static final int _NUM_OP = 64;
+ public static final int OP_NFC_CHANGE = 67;
+ /** @hide */
+ public static final int OP_DATA_CONNECT_CHANGE = 68;
+ /** @hide */
+ public static final int OP_SU = 69;
+ /** @hide */
+ public static final int _NUM_OP = 70;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -338,6 +365,19 @@ public class AppOpsManager {
/** @hide Get device accounts. */
public static final String OPSTR_GET_ACCOUNTS
= "android:get_accounts";
+ /** @hide **/
+ private static final String OPSTR_WIFI_CHANGE =
+ "android:wifi_change";
+ private static final String OPSTR_BLUETOOTH_CHANGE =
+ "android:bluetooth_change";
+ private static final String OPSTR_BOOT_COMPLETED =
+ "android:boot_completed";
+ private static final String OPSTR_NFC_CHANGE =
+ "android:nfc_change";
+ private static final String OPSTR_DATA_CONNECT_CHANGE =
+ "android:data_connect_change";
+ private static final String OPSTR_SU =
+ "android:su";
private static final int[] RUNTIME_PERMISSIONS_OPS = {
// Contacts
@@ -395,7 +435,7 @@ public class AppOpsManager {
OP_WRITE_CALL_LOG,
OP_READ_CALENDAR,
OP_WRITE_CALENDAR,
- OP_COARSE_LOCATION,
+ OP_WIFI_SCAN,
OP_POST_NOTIFICATION,
OP_COARSE_LOCATION,
OP_CALL_PHONE,
@@ -449,6 +489,12 @@ public class AppOpsManager {
OP_TURN_SCREEN_ON,
OP_GET_ACCOUNTS,
OP_RUN_IN_BACKGROUND,
+ OP_CHANGE_WIFI_STATE,
+ OP_BLUETOOTH_CHANGE,
+ OP_BOOT_COMPLETED,
+ OP_NFC_CHANGE,
+ OP_DATA_CONNECT_CHANGE,
+ OP_SU
};
/**
@@ -520,6 +566,12 @@ public class AppOpsManager {
null,
OPSTR_GET_ACCOUNTS,
null,
+ OPSTR_WIFI_CHANGE,
+ OPSTR_BLUETOOTH_CHANGE,
+ OPSTR_BOOT_COMPLETED,
+ OPSTR_NFC_CHANGE,
+ OPSTR_DATA_CONNECT_CHANGE,
+ OPSTR_SU,
};
/**
@@ -591,6 +643,12 @@ public class AppOpsManager {
"TURN_ON_SCREEN",
"GET_ACCOUNTS",
"RUN_IN_BACKGROUND",
+ "WIFI_CHANGE",
+ "BLUETOOTH_CHANGE",
+ "BOOT_COMPLETED",
+ "NFC_CHANGE",
+ "DATA_CONNECT_CHANGE",
+ "SU",
};
/**
@@ -608,7 +666,7 @@ public class AppOpsManager {
android.Manifest.permission.WRITE_CALL_LOG,
android.Manifest.permission.READ_CALENDAR,
android.Manifest.permission.WRITE_CALENDAR,
- android.Manifest.permission.ACCESS_WIFI_STATE,
+ null, // no permission for wifi scan available
null, // no permission required for notifications
null, // neighboring cells shares the coarse location perm
android.Manifest.permission.CALL_PHONE,
@@ -662,6 +720,12 @@ public class AppOpsManager {
null, // no permission for turning the screen on
Manifest.permission.GET_ACCOUNTS,
null, // no permission for running in background
+ Manifest.permission.CHANGE_WIFI_STATE,
+ null,
+ Manifest.permission.RECEIVE_BOOT_COMPLETED,
+ Manifest.permission.NFC,
+ Manifest.permission.MODIFY_PHONE_STATE,
+ null,
};
/**
@@ -734,6 +798,12 @@ public class AppOpsManager {
null, // TURN_ON_SCREEN
null, // GET_ACCOUNTS
null, // RUN_IN_BACKGROUND
+ null, //WIFI_CHANGE
+ null, //BLUETOOTH_CHANGE
+ null, //BOOT_COMPLETED
+ null, //NFC_CHANGE
+ null, //DATA_CONNECT_CHANGE
+ UserManager.DISALLOW_SU, //SU TODO: this should really be investigated.
};
/**
@@ -805,6 +875,12 @@ public class AppOpsManager {
false, // TURN_ON_SCREEN
false, // GET_ACCOUNTS
false, // RUN_IN_BACKGROUND
+ true, // WIFI_CHANGE
+ true, // BLUETOOTH_CHANGE
+ true, // BOOT_COMPLETED
+ true, // NFC_CHANGE
+ true, //DATA_CONNECT_CHANGE
+ false, //SU
};
/**
@@ -875,6 +951,165 @@ public class AppOpsManager {
AppOpsManager.MODE_ALLOWED, // OP_TURN_ON_SCREEN
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED, // OP_RUN_IN_BACKGROUND
+ AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE
+ AppOpsManager.MODE_ALLOWED, // OP_BLUETOOTH_CHANGE
+ AppOpsManager.MODE_ALLOWED, // OP_BOOT_COMPLETED
+ AppOpsManager.MODE_ALLOWED, // OP_NFC_CHANGE
+ AppOpsManager.MODE_ALLOWED,
+ AppOpsManager.MODE_ASK, // OP_SU
+ };
+
+ /**
+ * This specifies the default mode for each strict operation.
+ */
+
+ private static int[] sOpDefaultStrictMode = new int[] {
+ AppOpsManager.MODE_ASK, // OP_COARSE_LOCATION
+ AppOpsManager.MODE_ASK, // OP_FINE_LOCATION
+ AppOpsManager.MODE_ASK, // OP_GPS
+ AppOpsManager.MODE_ALLOWED, // OP_VIBRATE
+ AppOpsManager.MODE_ASK, // OP_READ_CONTACTS
+ AppOpsManager.MODE_ASK, // OP_WRITE_CONTACTS
+ AppOpsManager.MODE_ASK, // OP_READ_CALL_LOG
+ AppOpsManager.MODE_ASK, // OP_WRITE_CALL_LOG
+ AppOpsManager.MODE_ALLOWED, // OP_READ_CALENDAR
+ AppOpsManager.MODE_ALLOWED, // OP_WRITE_CALENDAR
+ AppOpsManager.MODE_ASK, // OP_WIFI_SCAN
+ AppOpsManager.MODE_ALLOWED, // OP_POST_NOTIFICATION
+ AppOpsManager.MODE_ALLOWED, // OP_NEIGHBORING_CELLS
+ AppOpsManager.MODE_ASK, // OP_CALL_PHONE
+ AppOpsManager.MODE_ASK, // OP_READ_SMS
+ AppOpsManager.MODE_ASK, // OP_WRITE_SMS
+ AppOpsManager.MODE_ASK, // OP_RECEIVE_SMS
+ AppOpsManager.MODE_ALLOWED, // OP_RECEIVE_EMERGECY_SMS
+ AppOpsManager.MODE_ASK, // OP_RECEIVE_MMS
+ AppOpsManager.MODE_ALLOWED, // OP_RECEIVE_WAP_PUSH
+ AppOpsManager.MODE_ASK, // OP_SEND_SMS
+ AppOpsManager.MODE_ALLOWED, // OP_READ_ICC_SMS
+ AppOpsManager.MODE_ALLOWED, // OP_WRITE_ICC_SMS
+ AppOpsManager.MODE_ALLOWED, // OP_WRITE_SETTINGS
+ AppOpsManager.MODE_ALLOWED, // OP_SYSTEM_ALERT_WINDOW
+ AppOpsManager.MODE_ALLOWED, // OP_ACCESS_NOTIFICATIONS
+ AppOpsManager.MODE_ASK, // OP_CAMERA
+ AppOpsManager.MODE_ASK, // OP_RECORD_AUDIO
+ AppOpsManager.MODE_ALLOWED, // OP_PLAY_AUDIO
+ AppOpsManager.MODE_ALLOWED, // OP_READ_CLIPBOARD
+ AppOpsManager.MODE_ALLOWED, // OP_WRITE_CLIPBOARD
+ AppOpsManager.MODE_ALLOWED, // OP_TAKE_MEDIA_BUTTONS
+ AppOpsManager.MODE_ALLOWED, // OP_TAKE_AUDIO_FOCUS
+ AppOpsManager.MODE_ALLOWED, // OP_AUDIO_MASTER_VOLUME
+ AppOpsManager.MODE_ALLOWED, // OP_AUDIO_VOICE_VOLUME
+ AppOpsManager.MODE_ALLOWED, // OP_AUDIO_RING_VOLUME
+ AppOpsManager.MODE_ALLOWED, // OP_AUDIO_MEDIA_VOLUME
+ AppOpsManager.MODE_ALLOWED, // OP_AUDIO_ALARM_VOLUME
+ AppOpsManager.MODE_ALLOWED, // OP_AUDIO_NOTIFICATION_VOLUME
+ AppOpsManager.MODE_ALLOWED, // OP_AUDIO_BLUETOOTH_VOLUME
+ AppOpsManager.MODE_ALLOWED, // OP_WAKE_LOCK
+ AppOpsManager.MODE_ALLOWED, // OP_MONITOR_LOCATION
+ AppOpsManager.MODE_ASK, // OP_MONITOR_HIGH_POWER_LOCATION
+ AppOpsManager.MODE_DEFAULT, // OP_GET_USAGE_STATS
+ AppOpsManager.MODE_ALLOWED, // OP_MUTE_MICROPHONE
+ AppOpsManager.MODE_ALLOWED, // OP_TOAST_WINDOW
+ AppOpsManager.MODE_IGNORED, // OP_PROJECT_MEDIA
+ AppOpsManager.MODE_IGNORED, // OP_ACTIVATE_VPN
+ AppOpsManager.MODE_ALLOWED, // OP WALLPAPER
+ AppOpsManager.MODE_ALLOWED, // OP_ASSIST_STRUCTURE
+ AppOpsManager.MODE_ALLOWED, // OP_ASSIST_SCREENSHOT
+ AppOpsManager.MODE_ALLOWED, // OP_READ_PHONE_STATE
+ AppOpsManager.MODE_ALLOWED, // OP_ADD_VOICEMAIL
+ AppOpsManager.MODE_ALLOWED, // OP_USE_SIP
+ AppOpsManager.MODE_ALLOWED, // OP_PROCESS_OUTGOING_CALLS
+ AppOpsManager.MODE_ALLOWED, // OP_USE_FINGERPRINT
+ AppOpsManager.MODE_ALLOWED, // OP_BODY_SENSORS
+ AppOpsManager.MODE_ALLOWED, // OP_READ_CELL_BROADCASTS
+ AppOpsManager.MODE_ERRORED, // OP_MOCK_LOCATION
+ AppOpsManager.MODE_ALLOWED, // OP_READ_EXTERNAL_STORAGE
+ AppOpsManager.MODE_ALLOWED, // OP_WRITE_EXTERNAL_STORAGE
+ AppOpsManager.MODE_ALLOWED, // OP_TURN_ON_SCREEN
+ AppOpsManager.MODE_ALLOWED, // OP_GET_ACCOUNTS
+ AppOpsManager.MODE_ALLOWED, // MODE_RUN_IN_BACKGROUND
+ AppOpsManager.MODE_ASK, // OP_CHANGE_WIFI_STATE
+ AppOpsManager.MODE_ASK, // OP_BLUETOOTH_CHANGE
+ AppOpsManager.MODE_ALLOWED, // OP_BOOT_COMPLETED
+ AppOpsManager.MODE_ASK, // OP_NFC_CHANGE
+ AppOpsManager.MODE_ASK, // OP_DATA_CONNECT_CHANGE
+ AppOpsManager.MODE_ASK, // OP_SU
+ };
+
+ /**
+ * This specifies if operation is in strict mode.
+ */
+ private final static boolean[] sOpStrictMode = new boolean[] {
+ true, // OP_COARSE_LOCATION
+ true, // OP_FINE_LOCATION
+ true, // OP_GPS
+ false, // OP_VIBRATE
+ true, // OP_READ_CONTACTS
+ true, // OP_WRITE_CONTACTS
+ true, // OP_READ_CALL_LOG
+ true, // OP_WRITE_CALL_LOG
+ false, // OP_READ_CALENDAR
+ false, // OP_WRITE_CALENDAR
+ true, // OP_WIFI_SCAN
+ false, // OP_POST_NOTIFICATION
+ false, // OP_NEIGHBORING_CELLS
+ true, // OP_CALL_PHONE
+ true, // OP_READ_SMS
+ true, // OP_WRITE_SMS
+ false, // OP_RECEIVE_SMS
+ false, // OP_RECEIVE_EMERGECY_SMS
+ true, // OP_RECEIVE_MMS
+ false, // OP_RECEIVE_WAP_PUSH
+ true, // OP_SEND_SMS
+ false, // OP_READ_ICC_SMS
+ false, // OP_WRITE_ICC_SMS
+ false, // OP_WRITE_SETTINGS
+ false, // OP_SYSTEM_ALERT_WINDOW
+ false, // OP_ACCESS_NOTIFICATIONS
+ true, // OP_CAMERA
+ true, // OP_RECORD_AUDIO
+ false, // OP_PLAY_AUDIO
+ false, // OP_READ_CLIPBOARD
+ false, // OP_WRITE_CLIPBOARD
+ false, // OP_TAKE_MEDIA_BUTTONS
+ false, // OP_TAKE_AUDIO_FOCUS
+ false, // OP_AUDIO_MASTER_VOLUME
+ false, // OP_AUDIO_VOICE_VOLUME
+ false, // OP_AUDIO_RING_VOLUME
+ false, // OP_AUDIO_MEDIA_VOLUME
+ false, // OP_AUDIO_ALARM_VOLUME
+ false, // OP_AUDIO_NOTIFICATION_VOLUME
+ false, // OP_AUDIO_BLUETOOTH_VOLUME
+ false, // OP_WAKE_LOCK
+ false, // OP_MONITOR_LOCATION
+ true, // OP_MONITOR_HIGH_POWER_LOCATION
+ false, // OP_GET_USAGE_STATS
+ false, // OP_MUTE_MICROPHONE
+ false, // OP_TOAST_WINDOW
+ false, // OP_PROJECT_MEDIA
+ false, // OP_ACTIVATE_VPN
+ true, // OP WALLPAPER
+ false, //ASSIST_STRUCTURE
+ false, //ASSIST_SCREENSHOT
+ false, //READ_PHONE_STATE
+ false, //ADD_VOICEMAIL
+ false, // USE_SIP
+ false, // PROCESS_OUTGOING_CALLS
+ false, // USE_FINGERPRINT
+ false, // BODY_SENSORS
+ false, // READ_CELL_BROADCASTS
+ false, // MOCK_LOCATION
+ true, // READ_EXTERNAL_STORAGE
+ true, // WRITE_EXTERNAL_STORAGE
+ false, // TURN_ON_SCREEN
+ false, // GET_ACCOUNTS
+ false, // RUN_IN_BACKGROUND
+ true, // OP_CHANGE_WIFI_STATE
+ true, // OP_BLUETOOTH_CHANGE
+ false, // OP_BOOT_COMPLETED
+ true, // OP_NFC_CHANGE
+ true, // OP_DATA_CONNECT_CHANGE
+ true, // OP_SU
};
/**
@@ -949,6 +1184,12 @@ public class AppOpsManager {
false,
false,
false,
+ false, // OP_CHANGE_WIFI_STATE
+ false, // OP_BLUETOOTH_CHANGE
+ false, // OP_BOOT_COMPLETED
+ false, // OP_NFC_CHANGE
+ false, // OP_DATA_CONNECT_CHANGE
+ false, // OP_SU
};
/**
@@ -961,6 +1202,8 @@ public class AppOpsManager {
*/
private static HashMap<String, Integer> sRuntimePermToOp = new HashMap<>();
+ private static HashMap<String, Integer> sNameToOp = new HashMap<String, Integer>();
+
static {
if (sOpToSwitch.length != _NUM_OP) {
throw new IllegalStateException("sOpToSwitch length " + sOpToSwitch.length
@@ -982,6 +1225,10 @@ public class AppOpsManager {
throw new IllegalStateException("sOpDefaultMode length " + sOpDefaultMode.length
+ " should be " + _NUM_OP);
}
+ if (sOpDefaultStrictMode.length != _NUM_OP) {
+ throw new IllegalStateException("sOpDefaultStrictMode length " + sOpDefaultStrictMode.length
+ + " should be " + _NUM_OP);
+ }
if (sOpDisableReset.length != _NUM_OP) {
throw new IllegalStateException("sOpDisableReset length " + sOpDisableReset.length
+ " should be " + _NUM_OP);
@@ -994,6 +1241,10 @@ public class AppOpsManager {
throw new IllegalStateException("sOpAllowSYstemRestrictionsBypass length "
+ sOpRestrictions.length + " should be " + _NUM_OP);
}
+ if (sOpStrictMode.length != _NUM_OP) {
+ throw new IllegalStateException("sOpStrictMode length "
+ + sOpStrictMode.length + " should be " + _NUM_OP);
+ }
for (int i=0; i<_NUM_OP; i++) {
if (sOpToString[i] != null) {
sOpStrToOp.put(sOpToString[i], i);
@@ -1004,6 +1255,9 @@ public class AppOpsManager {
sRuntimePermToOp.put(sOpPerms[op], op);
}
}
+ for (int i=0; i<_NUM_OP; i++) {
+ sNameToOp.put(sOpNames[i], i);
+ }
}
/**
@@ -1036,6 +1290,15 @@ public class AppOpsManager {
}
/**
+ * Map a non-localized name for the operation back to the Op number
+ * @hide
+ */
+ public static int nameToOp(String name) {
+ Integer val = sNameToOp.get(name);
+ return val != null ? val : OP_NONE;
+ }
+
+ /**
* Retrieve the permission associated with an operation, or null if there is not one.
* @hide
*/
@@ -1075,7 +1338,9 @@ public class AppOpsManager {
* Retrieve the default mode for the operation.
* @hide
*/
- public static int opToDefaultMode(int op) {
+ public static int opToDefaultMode(int op, boolean isStrict) {
+ if (isStrict)
+ return sOpDefaultStrictMode[op];
return sOpDefaultMode[op];
}
@@ -1162,9 +1427,11 @@ public class AppOpsManager {
private final int mDuration;
private final int mProxyUid;
private final String mProxyPackageName;
+ private final int mAllowedCount;
+ private final int mIgnoredCount;
public OpEntry(int op, int mode, long time, long rejectTime, int duration,
- int proxyUid, String proxyPackage) {
+ int proxyUid, String proxyPackage, int allowedCount, int ignoredCount) {
mOp = op;
mMode = mode;
mTime = time;
@@ -1172,6 +1439,8 @@ public class AppOpsManager {
mDuration = duration;
mProxyUid = proxyUid;
mProxyPackageName = proxyPackage;
+ mAllowedCount = allowedCount;
+ mIgnoredCount = ignoredCount;
}
public int getOp() {
@@ -1206,6 +1475,14 @@ public class AppOpsManager {
return mProxyPackageName;
}
+ public int getAllowedCount() {
+ return mAllowedCount;
+ }
+
+ public int getIgnoredCount() {
+ return mIgnoredCount;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -1220,6 +1497,8 @@ public class AppOpsManager {
dest.writeInt(mDuration);
dest.writeInt(mProxyUid);
dest.writeString(mProxyPackageName);
+ dest.writeInt(mAllowedCount);
+ dest.writeInt(mIgnoredCount);
}
OpEntry(Parcel source) {
@@ -1230,6 +1509,8 @@ public class AppOpsManager {
mDuration = source.readInt();
mProxyUid = source.readInt();
mProxyPackageName = source.readString();
+ mAllowedCount = source.readInt();
+ mIgnoredCount = source.readInt();
}
public static final Creator<OpEntry> CREATOR = new Creator<OpEntry>() {
@@ -1860,4 +2141,75 @@ public class AppOpsManager {
public void finishOp(int op) {
finishOp(op, Process.myUid(), mContext.getOpPackageName());
}
+
+ /** @hide */
+ public static boolean isStrictEnable() {
+ return SystemProperties.getBoolean("persist.sys.strict_op_enable", false);
+ }
+
+ /**
+ * Check if op in strict mode
+ * @hide
+ */
+ public static boolean isStrictOp(int code) {
+ return sOpStrictMode[code];
+ }
+
+
+ /** @hide */
+ public static int stringToMode(String permission) {
+ if ("allowed".equalsIgnoreCase(permission)) {
+ return AppOpsManager.MODE_ALLOWED;
+ } else if ("ignored".equalsIgnoreCase(permission)) {
+ return AppOpsManager.MODE_IGNORED;
+ } else if ("ask".equalsIgnoreCase(permission)) {
+ return AppOpsManager.MODE_ASK;
+ }
+ return AppOpsManager.MODE_ERRORED;
+ }
+
+ /** @hide */
+ public static int stringOpToOp (String op) {
+ Integer val = sOpStrToOp.get(op);
+ if (val == null) {
+ val = OP_NONE;
+ }
+ return val;
+ }
+
+ /** @hide */
+ public boolean isControlAllowed(int op, String packageName) {
+ boolean isShow = true;
+ try {
+ isShow = mService.isControlAllowed(op, packageName);
+ } catch (RemoteException e) {
+ }
+ return isShow;
+ }
+
+ /** @hide */
+ public boolean getPrivacyGuardSettingForPackage(int uid, String packageName) {
+ try {
+ return mService.getPrivacyGuardSettingForPackage(uid, packageName);
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /** @hide */
+ public void setPrivacyGuardSettingForPackage(int uid, String packageName,
+ boolean state) {
+ try {
+ mService.setPrivacyGuardSettingForPackage(uid, packageName, state);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /** @hide */
+ public void resetCounters() {
+ try {
+ mService.resetCounters();
+ } catch (RemoteException e) {
+ }
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 9c9b8340aca..035a508ef2f 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1,6 +1,8 @@
/*
* Copyright (C) 2009-2016 The Android Open Source Project
* Copyright (C) 2015 Samsung LSI
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +25,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
+import android.app.ActivityThread;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
@@ -777,7 +780,7 @@ public final class BluetoothAdapter {
try {
if (DBG) Log.d(TAG, "Calling enableBLE");
mManagerService.updateBleAppCount(mToken, true);
- return mManagerService.enable();
+ return mManagerService.enable(ActivityThread.currentPackageName());
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
@@ -906,7 +909,7 @@ public final class BluetoothAdapter {
return true;
}
try {
- return mManagerService.enable();
+ return mManagerService.enable(ActivityThread.currentPackageName());
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl
index 5b7d29a0587..bd8c6c9687f 100644
--- a/core/java/android/bluetooth/IBluetoothManager.aidl
+++ b/core/java/android/bluetooth/IBluetoothManager.aidl
@@ -33,7 +33,7 @@ interface IBluetoothManager
void registerStateChangeCallback(in IBluetoothStateChangeCallback callback);
void unregisterStateChangeCallback(in IBluetoothStateChangeCallback callback);
boolean isEnabled();
- boolean enable();
+ boolean enable(String callingPackage);
boolean enableNoAutoConnect();
boolean disable(boolean persist);
IBluetoothGatt getBluetoothGatt();
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bcc8d463e51..388403809d6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2016 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -673,6 +674,18 @@ public class UserManager {
public static final String EXTRA_USER_ACCOUNT_OPTIONS
= "android.os.extra.USER_ACCOUNT_OPTIONS";
+ /**
+ * Specifies if the user is not allowed to use SU commands.
+ * The default value is <code>false</code>.
+ *
+ * <p/>Key for user restrictions.
+ * <p/>Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ * @hide
+ */
+ public static final String DISALLOW_SU = "no_su";
+
/** @hide */
public static final int PIN_VERIFICATION_FAILED_INCORRECT = -3;
/** @hide */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3d54132e02c..2f59fab6f54 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -748,6 +748,19 @@ public final class Settings {
/**
* @hide
+ * Activity Action: Show the "app ops" details screen.
+ * <p>
+ * Input: The Intent's data URI specifies the application package name
+ * to be shown, with the "package" scheme. That is "package:com.my.app".
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_APP_OPS_DETAILS_SETTINGS =
+ "android.settings.APP_OPS_DETAILS_SETTINGS";
+
+ /**
+ * @hide
* Activity Action: Show the "app ops" settings screen.
* <p>
* Input: Nothing.
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 3a31b370a41..861392ec1dd 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -47,4 +47,12 @@ interface IAppOpsService {
void setUserRestrictions(in Bundle restrictions, IBinder token, int userHandle);
void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, in String[] exceptionPackages);
void removeUser(int userHandle);
+ boolean isControlAllowed(int code, String packageName);
+
+ // Privacy guard methods
+ boolean getPrivacyGuardSettingForPackage(int uid, String packageName);
+ void setPrivacyGuardSettingForPackage(int uid, String packageName, boolean state);
+
+ // AppOps accounting
+ void resetCounters();
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2325d8786be..7656b954b9c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -82,6 +82,7 @@
<protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
<protected-broadcast android:name="android.intent.action.USER_INITIALIZE" />
<protected-broadcast android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION" />
+ <protected-broadcast android:name="android.intent.action.SU_SESSION_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" />
diff --git a/core/res/res/drawable/stat_notify_privacy_guard.xml b/core/res/res/drawable/stat_notify_privacy_guard.xml
new file mode 100644
index 00000000000..bcf3a7f3180
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_privacy_guard.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M11.999,11.488c0.017,0.148,0.013,0.301-0.017,0.455c-0.155,0.804-0.932,1.33-1.736,1.175 c-0.804-0.155-1.33-0.933-1.175-1.736c0.03-0.154,0.083-0.297,0.154-0.428L11.999,11.488Z M16.775,10.953l-2.774,0.535 c-0.017,0.148-0.013,0.301,0.017,0.455c0.155,0.804,0.932,1.33,1.736,1.175c0.804-0.155,1.33-0.933,1.175-1.736 C16.899,11.228,16.846,11.084,16.775,10.953z M21.539,8.978c-0.132,2.629-0.803,5.264-2.162,7.534 c-1.186,1.991-2.889,3.675-4.905,4.82c-0.459,0.259-0.932,0.494-1.427,0.676c-0.061,0.029-0.122-0.014-0.18-0.032 c-1.047-0.415-2.019-1.006-2.909-1.693c-1.807-1.397-3.239-3.263-4.151-5.356c-0.725-1.651-1.137-3.429-1.297-5.222 C4.33,7.77,4.442,5.816,4.73,3.896c0.601,0.053,1.207,0.031,1.807-0.017c1.309-0.115,2.596-0.41,3.849-0.799 c0.888-0.283,1.766-0.604,2.611-0.999c1.13,0.523,2.311,0.935,3.512,1.26c1.549,0.412,3.157,0.666,4.763,0.562 c0.116,0.846,0.216,1.695,0.261,2.548c0.002,0.241,0.042,0.481,0.037,0.723C21.574,7.776,21.594,8.379,21.539,8.978z M17.957,10.132 c0-0.787-0.345-1.322-1.002-1.675V6.396c0-0.179-0.145-0.324-0.324-0.324c-0.179,0-0.324,0.145-0.324,0.324v1.799 c-0.821-0.248-1.933-0.34-3.307-0.34V7.86h0V7.856c-0.155,0-0.305,0.002-0.453,0.004h-0.001c-1.075,0.018-1.973,0.101-2.677,0.286 v-1.75c0-0.179-0.145-0.324-0.324-0.324c-0.179,0-0.324,0.145-0.324,0.324v1.975C8.449,8.72,8.043,9.277,8.043,10.132v1.943v0.504 v0.554c0,1.702,1.613,2.225,4.503,2.272v0.003h0.179c0.09,0.001,0.182,0.001,0.275,0.001v-0.001h0v0.001 c0.093,0,0.184-0.001,0.275-0.001h0.013c2.995-0.03,4.67-0.54,4.67-2.275v-0.554v-0.504V10.132z"
+ android:fillColor="#FFFFFFFF" />
+</vector>
diff --git a/core/res/res/layout/permission_confirmation_dialog.xml b/core/res/res/layout/permission_confirmation_dialog.xml
new file mode 100644
index 00000000000..ab5b9fa3a5c
--- /dev/null
+++ b/core/res/res/layout/permission_confirmation_dialog.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright (c) 2013, The Linux Foundation. All rights reserved.
+** Not a Contribution.
+**
+** Copyright 2012 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.
+*/
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/parentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dip"
+ android:layout_marginRight="8dip"
+ android:orientation="vertical">
+
+ <TextView android:id="@+id/permission_text"
+ style="?android:attr/textAppearanceMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="20dip"
+ android:paddingRight="20dip"
+ android:paddingTop="16dip"
+ android:paddingBottom="16dip" />
+
+ <TableLayout android:id="@+id/permission_remember_layout"
+ android:shrinkColumns="1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="16dip"
+ android:paddingRight="16dip">
+
+ <TableRow
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" >
+ <RelativeLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="12dip"
+ android:paddingLeft="8dip" >
+ <CheckBox android:id="@+id/permission_remember_choice_checkbox"
+ android:paddingTop="11dip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </RelativeLayout>
+ <TextView android:id="@+id/permission_remember_choice_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="18dip"
+ android:text="@string/permission_remember_choice" />
+ </TableRow>
+
+ </TableLayout>
+
+</LinearLayout>
diff --git a/core/res/res/values/cm_arrays.xml b/core/res/res/values/cm_arrays.xml
index 33fce45da12..a211f563be2 100644
--- a/core/res/res/values/cm_arrays.xml
+++ b/core/res/res/values/cm_arrays.xml
@@ -39,4 +39,149 @@
<item>recovery</item>
<item>bootloader</item>
</string-array>
+
+ <!-- Do not translate. App ops request strings -->
+ <string-array name="app_ops_labels" translatable="false">
+ <!-- OP_COARSE_LOCATION -->
+ <item>@string/app_ops_access_location</item>
+ <!-- OP_FINE_LOCATION -->
+ <item>@string/app_ops_access_location</item>
+ <!-- OP_GPS -->
+ <item>@string/app_ops_access_location</item>
+ <!-- OP_VIBRATE -->
+ <item>@string/app_ops_use_vibrate</item>
+ <!-- OP_READ_CONTACTS -->
+ <item>@string/app_ops_read_contacts</item>
+ <!-- OP_WRITE_CONTACTS -->
+ <item>@string/app_ops_modify_contacts</item>
+ <!-- OP_READ_CALL_LOG -->
+ <item>@string/app_ops_read_call_log</item>
+ <!-- OP_WRITE_CALL_LOG -->
+ <item>@string/app_ops_modify_call_log</item>
+ <!-- OP_READ_CALENDAR -->
+ <item>@string/app_ops_read_calendar</item>
+ <!-- OP_WRITE_CALENDAR -->
+ <item>@string/app_ops_modify_calendar</item>
+ <!-- OP_WIFI_SCAN -->
+ <item>@string/app_ops_scan_wifi</item>
+ <!-- OP_POST_NOTIFICATION -->
+ <item>@string/app_ops_post_notification</item>
+ <!-- OP_NEIGHBORING_CELLS -->
+ <item>@string/app_ops_access_location</item>
+ <!-- OP_CALL_PHONE -->
+ <item>@string/app_ops_make_phone_call</item>
+ <!-- OP_READ_SMS -->
+ <item>@string/app_ops_read_sms</item>
+ <!-- OP_WRITE_SMS -->
+ <item>@string/app_ops_write_sms</item>
+ '<!-- OP_RECEIVE_SMS -->
+ <item>@string/app_ops_receive_sms</item>
+ <!-- OP_RECEIVE_EMERGENCY_SMS -->
+ <item>@string/app_ops_receive_sms</item>
+ <!-- OP_RECEIVE_MMS -->
+ <item>@string/app_ops_receive_sms</item>
+ <!-- OP_RECEIVE_WAP_PUSH -->
+ <item>@string/app_ops_receive_sms</item>
+ <!-- OP_SEND_SMS -->
+ <item>@string/app_ops_send_sms</item>
+ <!-- OP_READ_ICC_SMS -->
+ <item>@string/app_ops_read_sms</item>
+ <!-- OP_WRITE_ICC_SMS -->
+ <item>@string/app_ops_write_sms</item>
+ <!-- OP_WRITE_SETTINGS -->
+ <item>@string/app_ops_modify_settings</item>
+ <!-- OP_SYSTEM_ALERT_WINDOW -->
+ <item>@string/app_ops_draw_on_top</item>
+ <!-- OP_ACCESS_NOTIFICATIONS -->
+ <item>@string/app_ops_access_notifications</item>
+ <!-- OP_CAMERA -->
+ <item>@string/app_ops_access_camera</item>
+ <!-- OP_RECORD_AUDIO -->
+ <item>@string/app_ops_record_audio</item>
+ <!-- OP_PLAY_AUDIO -->
+ <item>@string/app_ops_play_audio</item>
+ <!-- OP_READ_CLIPBOARD -->
+ <item>@string/app_ops_read_clipboard</item>
+ <!-- OP_WRITE_CLIPBOARD -->
+ <item>@string/app_ops_modify_clipboard</item>
+ <!-- OP_TAKE_MEDIA_BUTTONS -->
+ <item>@string/app_ops_use_media_buttons</item>
+ <!-- OP_TAKE_AUDIO_FOCUS -->
+ <item>@string/app_ops_use_audio_focus</item>
+ <!-- OP_AUDIO_MASTER_VOLUME -->
+ <item>@string/app_ops_use_master_volume</item>
+ <!-- OP_AUDIO_VOICE_VOLUME -->
+ <item>@string/app_ops_use_voice_volume</item>
+ <!-- OP_AUDIO_RING_VOLUME -->
+ <item>@string/app_ops_use_ring_volume</item>
+ <!-- OP_AUDIO_MEDIA_VOLUME -->
+ <item>@string/app_ops_use_media_volume</item>
+ <!-- OP_AUDIO_ALARM_VOLUME -->
+ <item>@string/app_ops_use_alarm_volume</item>
+ <!-- OP_AUDIO_NOTIFICATION_VOLUME -->
+ <item>@string/app_ops_use_notification_volume</item>
+ <!-- OP_AUDIO_BLUETOOTH_VOLUME -->
+ <item>@string/app_ops_use_bluetooth_volume</item>
+ <!-- OP_WAKE_LOCK -->
+ <item>@string/app_ops_keep_device_awake</item>
+ <!-- OP_MONITOR_LOCATION -->
+ <item>@string/app_ops_access_location</item>
+ <!-- OP_MONITOR_HIGH_POWER_LOCATION -->
+ <item>@string/app_ops_access_location</item>
+ <!-- OP_GET_USAGE_STATS -->
+ <item>@string/app_ops_get_usage_stats</item>
+ <!-- OP_MUTE_MICROPHONE -->
+ <item>@string/app_ops_mute_unmute_microphone</item>
+ <!-- OP_TOAST_WINDOW -->
+ <item>@string/app_ops_toast_window</item>
+ <!-- OP_PROJECT_MEDIA -->
+ <item>@string/app_ops_project_media</item>
+ <!-- OP_ACTIVATE_VPN -->
+ <item>@string/app_ops_activate_vpn</item>
+ <!-- OP_WRITE_WALLPAPER -->
+ <item>@string/app_ops_change_wallpaper</item>
+ <!-- OP_ASSIST_STRUCTURE -->
+ <item>@string/app_ops_assist_structure</item>
+ <!-- OP_ASSIST_SCREENSHOT -->
+ <item>@string/app_ops_assist_screenshot</item>
+ <!-- OP_READ_PHONE_STATE -->
+ <item>@string/app_ops_read_phone_state</item>
+ <!-- OP_ADD_VOICEMAIL -->
+ <item>@string/app_ops_add_voicemail</item>
+ <!-- OP_USE_SIP -->
+ <item>@string/app_ops_make_phone_call</item>
+ <!-- OP_PROCESS_OUTGOING_CALLS -->
+ <item>@string/app_ops_make_phone_call</item>
+ <!-- OP_USE_FINGERPRINT -->
+ <item>@string/app_ops_use_fingerprint</item>
+ <!-- OP_BODY_SENSORS -->
+ <item>@string/app_ops_use_body_sensors</item>
+ <!-- OP_READ_CELL_BROADCASTS -->
+ <item>@string/app_ops_read_cell_broadcasts</item>
+ <!-- OP_MOCK_LOCATION -->
+ <item>@string/app_ops_mock_location</item>
+ <!-- OP_READ_EXTERNAL_STORAGE -->
+ <item>@string/app_ops_read_external_storage</item>
+ <!-- OP_WRITE_EXTERNAL_STORAGE -->
+ <item>@string/app_ops_write_external_storage</item>
+ <!-- OP_TURN_SCREEN_ON -->
+ <item>@string/app_ops_turn_on_screen</item>
+ <!-- OP_GET_ACCOUNTS -->
+ <item>@string/app_ops_get_accounts</item>
+ <!-- OP_RUN_IN_BACKGROUND -->
+ <item>@string/app_ops_run_in_background</item>
+ <!-- OP_CHANGE_WIFI_STATE -->
+ <item>@string/app_ops_wifi_change</item>
+ <!-- OP_BLUETOOTH_CHANGE -->
+ <item>@string/app_ops_toggle_bluetooth</item>
+ <!-- OP_BOOT_COMPLETED -->
+ <item>@string/app_ops_start_at_bootup</item>
+ <!-- OP_NFC_CHANGE -->
+ <item>@string/app_ops_toggle_nfc</item>
+ <!-- OP_DATA_CONNECT_CHANGE -->
+ <item>@string/app_ops_toggle_mobile_data</item>
+ <!-- OP_SU -->
+ <item>@string/app_ops_su</item>
+ </string-array>
+
</resources>
diff --git a/core/res/res/values/cm_strings.xml b/core/res/res/values/cm_strings.xml
index 411701464ea..b828697a7b4 100644
--- a/core/res/res/values/cm_strings.xml
+++ b/core/res/res/values/cm_strings.xml
@@ -74,4 +74,84 @@
<!-- ADB notification message-->
<string name="adb_active_generic_notification_message">Touch to disable debugging.</string>
+ <!-- Privacy Guard -->
+ <string name="permlab_changePrivacyGuardState">enable or disable Privacy Guard</string>
+ <string name="permdesc_changePrivacyGuardState">Allows the app to change whether another app runs with Privacy Guard. When an app is running with Privacy Guard, it will not have access to personal data such as contacts, call logs, or messages.</string>
+ <string name="privacy_guard_notification">Privacy Guard active</string>
+ <string name="privacy_guard_notification_detail"><xliff:g id="app">%1$s</xliff:g> will not be able to access personal data</string>
+ <string name="privacy_guard_dialog_title">Privacy Guard</string>
+ <string name="privacy_guard_dialog_summary"><xliff:g id="app">%1$s</xliff:g> would like to <xliff:g id="op">%2$s</xliff:g>.</string>
+
+ <!-- Text of the checkbox for the permission confirmation dialog to remember the user's choice. [CHAR LIMIT=40] -->
+ <string name="permission_remember_choice">Remember</string>
+ <string name="permission">Permission</string>
+
+ <!-- App ops requests -->
+ <string name="app_ops_access_camera">access the camera</string>
+ <string name="app_ops_access_location">access your location</string>
+ <string name="app_ops_access_notifications">read your notifications</string>
+ <string name="app_ops_activate_vpn">activate a VPN</string>
+ <string name="app_ops_auto_start">start at power up</string>
+ <string name="app_ops_delete_call_log">delete your call log</string>
+ <string name="app_ops_delete_contacts">delete your contacts</string>
+ <string name="app_ops_delete_mms">delete your MMS messages</string>
+ <string name="app_ops_delete_sms">delete your SMS messages</string>
+ <string name="app_ops_draw_on_top">draw windows on top</string>
+ <string name="app_ops_get_usage_stats">get app usage stats</string>
+ <string name="app_ops_keep_device_awake">keep your device awake</string>
+ <string name="app_ops_make_phone_call">make a phone call</string>
+ <string name="app_ops_modify_calendar">update your calendar</string>
+ <string name="app_ops_modify_call_log">update the call log</string>
+ <string name="app_ops_modify_clipboard">modify the clipboard</string>
+ <string name="app_ops_modify_contacts">update your contacts</string>
+ <string name="app_ops_modify_settings">update system settings</string>
+ <string name="app_ops_mute_unmute_microphone">mute/unmute the microphone</string>
+ <string name="app_ops_play_audio">play audio</string>
+ <string name="app_ops_post_notification">post a notification</string>
+ <string name="app_ops_project_media">project media</string>
+ <string name="app_ops_read_calendar">read your calendar</string>
+ <string name="app_ops_read_call_log">read the call log</string>
+ <string name="app_ops_read_clipboard">read the clipboard</string>
+ <string name="app_ops_read_contacts">read your contacts</string>
+ <string name="app_ops_read_mms">read your MMS messages</string>
+ <string name="app_ops_read_sms">read your SMS messages</string>
+ <string name="app_ops_receive_sms">receive an SMS message</string>
+ <string name="app_ops_record_audio">record audio</string>
+ <string name="app_ops_send_mms">send an MMS message</string>
+ <string name="app_ops_send_sms">send an SMS message</string>
+ <string name="app_ops_start_at_bootup">start at power up</string>
+ <string name="app_ops_toast_window">display toast messages</string>
+ <string name="app_ops_toggle_bluetooth">toggle Bluetooth</string>
+ <string name="app_ops_toggle_mobile_data">toggle mobile data</string>
+ <string name="app_ops_toggle_nfc">toggle NFC</string>
+ <string name="app_ops_toggle_wifi">toggle WiFi</string>
+ <string name="app_ops_use_alarm_volume">control alarm volume</string>
+ <string name="app_ops_use_audio_focus">control the audio focus</string>
+ <string name="app_ops_use_bluetooth_volume">control the Bluetooth volume</string>
+ <string name="app_ops_use_master_volume">control the master volume</string>
+ <string name="app_ops_use_media_buttons">use the media buttons</string>
+ <string name="app_ops_use_media_volume">control the media volume</string>
+ <string name="app_ops_use_notification_volume">control the notification volume</string>
+ <string name="app_ops_use_ring_volume">control the ringtone volume</string>
+ <string name="app_ops_use_vibrate">use haptic feedback</string>
+ <string name="app_ops_use_voice_volume">control the voice call volume</string>
+ <string name="app_ops_write_mms">write an MMS message</string>
+ <string name="app_ops_write_sms">write an SMS message</string>
+ <string name="app_ops_use_fingerprint">use fingerprint</string>
+ <string name="app_ops_add_voicemail">add a voicemail</string>
+ <string name="app_ops_read_phone_state">access phone state</string>
+ <string name="app_ops_scan_wifi">Scan WiFi Networks</string>
+ <string name="app_ops_change_wallpaper">change the wallpaper</string>
+ <string name="app_ops_assist_structure">use assist structure</string>
+ <string name="app_ops_assist_screenshot">take a screenshot</string>
+ <string name="app_ops_use_body_sensors">use body sensors</string>
+ <string name="app_ops_read_cell_broadcasts">read cell broadcasts</string>
+ <string name="app_ops_mock_location">mock your location</string>
+ <string name="app_ops_read_external_storage">read external storage</string>
+ <string name="app_ops_write_external_storage">write external storage</string>
+ <string name="app_ops_turn_on_screen">turn the screen on</string>
+ <string name="app_ops_get_accounts">get device accounts</string>
+ <string name="app_ops_run_in_background">run in background</string>
+ <string name="app_ops_wifi_change">change WiFI state</string>
+ <string name="app_ops_su">get Superuser access</string>
</resources>
diff --git a/core/res/res/values/cm_symbols.xml b/core/res/res/values/cm_symbols.xml
index e01f173d15d..49e5a9eeb2e 100644
--- a/core/res/res/values/cm_symbols.xml
+++ b/core/res/res/values/cm_symbols.xml
@@ -39,4 +39,21 @@
<java-symbol type="array" name="config_disabledComponents" />
<java-symbol type="array" name="config_forceEnabledComponents" />
+ <!-- Privacy Guard -->
+ <java-symbol type="drawable" name="stat_notify_privacy_guard" />
+ <java-symbol type="string" name="privacy_guard_notification" />
+ <java-symbol type="string" name="privacy_guard_notification_detail" />
+ <java-symbol type="string" name="privacy_guard_dialog_title" />
+ <java-symbol type="string" name="privacy_guard_dialog_summary" />
+ <java-symbol type="id" name="permission_text" />
+ <java-symbol type="id" name="permission_remember_layout" />
+ <java-symbol type="id" name="permission_remember_choice_checkbox" />
+ <java-symbol type="id" name="permission_remember_choice_text" />
+ <java-symbol type="string" name="allow" />
+ <java-symbol type="string" name="deny" />
+ <java-symbol type="string" name="permission" />
+ <java-symbol type="layout" name="permission_confirmation_dialog" />
+ <java-symbol type="array" name="app_ops_labels" />
+ <java-symbol type="string" name="status_bar_su" />
+
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a73e0a79a9d..a64df61f01d 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -53,6 +53,7 @@
<item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_clock</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_su</xliff:g></item>
</string-array>
<string translatable="false" name="status_bar_rotate">rotate</string>
@@ -81,6 +82,7 @@
<string translatable="false" name="status_bar_alarm_clock">alarm_clock</string>
<string translatable="false" name="status_bar_secure">secure</string>
<string translatable="false" name="status_bar_clock">clock</string>
+ <string translatable="false" name="status_bar_su">su</string>
<!-- Flag indicating whether the surface flinger has limited
alpha compositing functionality in hardware. If set, the window
diff --git a/packages/SystemUI/res/drawable/stat_sys_su.xml b/packages/SystemUI/res/drawable/stat_sys_su.xml
new file mode 100644
index 00000000000..e013c135958
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_su.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="2.5dp"
+ android:insetRight="2.5dp">
+ <vector
+ android:width="23dp"
+ android:height="18dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12.49,15.934h-2.373L9.24,21H6.966l0.89-5.066H5.051v-2.089h3.163l0.63-3.584H5.977V8.148h3.238 l0.902-5.141h2.261l-0.902,5.141h2.373l0.914-5.141h2.261l-0.902,5.141h2.719v2.113h-3.089l-0.63,3.584h2.78v2.089h-3.139L13.874,21 H11.6L12.49,15.934Z M10.488,13.845h2.36l0.63-3.584h-2.373L10.488,13.845z" />
+ </vector>
+</inset>
diff --git a/packages/SystemUI/res/values/cm_strings.xml b/packages/SystemUI/res/values/cm_strings.xml
index e99c950a3d6..436cd83978a 100644
--- a/packages/SystemUI/res/values/cm_strings.xml
+++ b/packages/SystemUI/res/values/cm_strings.xml
@@ -2,6 +2,7 @@
<!--
/**
* Copyright (c) 2009, The Android Open Source Project
+ * Copyright (c) 2015, The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,4 +29,6 @@
<string name="led_notification_title">Light settings</string>
<string name="led_notification_text">LED light enabled by settings</string>
+ <!-- Content description of the superuser tile -->
+ <string name="accessibility_su_active">Superuser session active</string>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 10ba8d49bb8..ff39a6d2c19 100755
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -175,6 +175,7 @@ import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.SuControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.WeatherControllerImpl;
@@ -329,6 +330,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
LightStatusBarController mLightStatusBarController;
protected LockscreenWallpaper mLockscreenWallpaper;
WeatherControllerImpl mWeatherController;
+ SuControllerImpl mSuController;
int mNaturalBarHeight = -1;
@@ -813,7 +815,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController, mCastController,
mHotspotController, mUserInfoController, mBluetoothController,
- mRotationLockController, mNetworkController.getDataSaverController());
+ mRotationLockController, mNetworkController.getDataSaverController(),
+ mSuController);
mIconPolicy.setCurrentUserSetup(mUserSetup);
mSettingsObserver.onChange(false); // set up
@@ -1016,6 +1019,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mZenModeController = mVolumeComponent.getZenController();
}
mCastController = new CastControllerImpl(mContext);
+ mSuController = new SuControllerImpl(mContext);
initSignalCluster(mStatusBarView);
initSignalCluster(mKeyguardStatusBar);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index f5a5a93c297..904313ccf03 100755
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -48,6 +48,7 @@ import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.SuController;
import cyanogenmod.providers.CMSettings;
@@ -71,6 +72,7 @@ public class PhoneStatusBarPolicy implements Callback, RotationLockController.Ro
private final String mSlotRotate;
private final String mSlotHeadset;
private final String mSlotDataSaver;
+ private final String mSlotSu;
private final Context mContext;
private final Handler mHandler = new Handler();
@@ -83,6 +85,7 @@ public class PhoneStatusBarPolicy implements Callback, RotationLockController.Ro
private final RotationLockController mRotationLockController;
private final DataSaverController mDataSaver;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final SuController mSuController;
// Assume it's all good unless we hear otherwise. We don't always seem
// to get broadcasts that it *is* there.
@@ -103,7 +106,7 @@ public class PhoneStatusBarPolicy implements Callback, RotationLockController.Ro
public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController,
CastController cast, HotspotController hotspot, UserInfoController userInfoController,
BluetoothController bluetooth, RotationLockController rotationLockController,
- DataSaverController dataSaver) {
+ DataSaverController dataSaver, SuController su) {
mContext = context;
mIconController = iconController;
mCast = cast;
@@ -115,6 +118,7 @@ public class PhoneStatusBarPolicy implements Callback, RotationLockController.Ro
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mRotationLockController = rotationLockController;
mDataSaver = dataSaver;
+ mSuController = su;
mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -128,7 +132,7 @@ public class PhoneStatusBarPolicy implements Callback, RotationLockController.Ro
mSlotRotate = context.getString(com.android.internal.R.string.status_bar_rotate);
mSlotHeadset = context.getString(com.android.internal.R.string.status_bar_headset);
mSlotDataSaver = context.getString(com.android.internal.R.string.status_bar_data_saver);
-
+ mSlotSu = context.getString(com.android.internal.R.string.status_bar_su);
mRotationLockController.addRotationLockControllerCallback(this);
// listen for broadcasts
@@ -182,6 +186,11 @@ public class PhoneStatusBarPolicy implements Callback, RotationLockController.Ro
mIconController.setIconVisibility(mSlotHotspot, mHotspot.isHotspotEnabled());
mHotspot.addCallback(mHotspotCallback);
+ // su
+ mIconController.setIcon(mSlotSu, R.drawable.stat_sys_su, null);
+ mIconController.setIconVisibility(mSlotSu, false);
+ mSuController.addCallback(mSuCallback);
+
// managed profile
mIconController.setIcon(mSlotManagedProfile, R.drawable.stat_sys_managed_profile_status,
mContext.getString(R.string.accessibility_managed_profile));
@@ -458,6 +467,10 @@ public class PhoneStatusBarPolicy implements Callback, RotationLockController.Ro
}
};
+ private void updateSu() {
+ mIconController.setIconVisibility(mSlotSu, mSuController.hasActiveSessions());
+ }
+
private final CastController.Callback mCastCallback = new CastController.Callback() {
@Override
public void onCastDevicesChanged() {
@@ -549,4 +562,12 @@ public class PhoneStatusBarPolicy implements Callback, RotationLockController.Ro
mIconController.setIconVisibility(mSlotCast, false);
}
};
+
+ private final SuController.Callback mSuCallback = new SuController.Callback() {
+ @Override
+ public void onSuSessionsChanged() {
+ updateSu();
+ }
+ };
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java
new file mode 100644
index 00000000000..5f1e52e0bf2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.systemui.statusbar.policy;
+
+public interface SuController {
+ void addCallback(Callback callback);
+ void removeCallback(Callback callback);
+ boolean hasActiveSessions();
+
+ public interface Callback {
+ void onSuSessionsChanged();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java
new file mode 100644
index 00000000000..1ba334a3e7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.systemui.statusbar.policy;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A controller to manage changes to superuser-related states and update the views accordingly.
+ */
+public class SuControllerImpl implements SuController {
+ private static final String TAG = "SuControllerImpl";
+
+ private static final int[] mSuOpArray = new int[] {AppOpsManager.OP_SU};
+
+ private ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+
+ private Context mContext;
+
+ private AppOpsManager mAppOpsManager;
+
+ private boolean mHasActiveSuSessions;
+
+ public SuControllerImpl(Context context) {
+ mContext = context;
+
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(AppOpsManager.ACTION_SU_SESSION_CHANGED);
+ context.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "Got change");
+ String action = intent.getAction();
+ if (AppOpsManager.ACTION_SU_SESSION_CHANGED.equals(action)) {
+ updateActiveSuSessions();
+ }
+ }
+ }, UserHandle.ALL, intentFilter, null, new Handler());
+
+ updateActiveSuSessions();
+ }
+
+ @Override
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
+ fireCallback(callback);
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ @Override
+ public boolean hasActiveSessions() {
+ return mHasActiveSuSessions;
+ }
+
+ private void fireCallback(Callback callback) {
+ callback.onSuSessionsChanged();
+ }
+
+ private void fireCallbacks() {
+ for (Callback callback : mCallbacks) {
+ callback.onSuSessionsChanged();
+ }
+ }
+
+ /**
+ * Returns true if a su session is active
+ */
+ private boolean hasActiveSuSessions() {
+ List<AppOpsManager.PackageOps> packages
+ = mAppOpsManager.getPackagesForOps(mSuOpArray);
+ // AppOpsManager can return null when there is no requested data.
+ if (packages != null) {
+ final int numPackages = packages.size();
+ for (int packageInd = 0; packageInd < numPackages; packageInd++) {
+ AppOpsManager.PackageOps packageOp = packages.get(packageInd);
+ List<AppOpsManager.OpEntry> opEntries = packageOp.getOps();
+ if (opEntries != null) {
+ final int numOps = opEntries.size();
+ for (int opInd = 0; opInd < numOps; opInd++) {
+ AppOpsManager.OpEntry opEntry = opEntries.get(opInd);
+ if (opEntry.getOp() == AppOpsManager.OP_SU) {
+ if (opEntry.isRunning()) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void updateActiveSuSessions() {
+ boolean hadActiveSuSessions = mHasActiveSuSessions;
+ mHasActiveSuSessions = hasActiveSuSessions();
+ if (mHasActiveSuSessions != hadActiveSuSessions) {
+ fireCallbacks();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/AppOpsPolicy.java b/services/core/java/com/android/server/AppOpsPolicy.java
new file mode 100644
index 00000000000..d51983f744f
--- /dev/null
+++ b/services/core/java/com/android/server/AppOpsPolicy.java
@@ -0,0 +1,446 @@
+/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.server;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+
+public class AppOpsPolicy {
+ static final String TAG = "AppOpsPolicy";
+ static final boolean DEBUG = false;
+ final File mFile;
+ final Context mContext;
+ public static final int CONTROL_SHOW = 0;
+
+ public static final int CONTROL_NOSHOW = 1;
+
+ public static final int CONTROL_UNKNOWN = 2;
+
+ // Rate limiting thresholds for ask operations
+ public static final int RATE_LIMIT_OP_COUNT = 3;
+ public static final int RATE_LIMIT_OPS_TOTAL_PKG_COUNT = 4;
+ public static final int RATE_LIMIT_OP_DELAY_CEILING = 10;
+
+ public static int stringToControl(String show) {
+ if ("true".equalsIgnoreCase(show)) {
+ return CONTROL_SHOW;
+ } else if ("false".equalsIgnoreCase(show)) {
+ return CONTROL_NOSHOW;
+ }
+ return CONTROL_UNKNOWN;
+ }
+
+ HashMap<String, PolicyPkg> mPolicy = new HashMap<String, PolicyPkg>();
+
+ public AppOpsPolicy(File file, Context context) {
+ super();
+ mFile = file;
+ mContext = context;
+ }
+
+ public final static class PolicyPkg extends SparseArray<PolicyOp> {
+ public String packageName;
+ public int mode;
+ public int show;
+ public String type;
+
+ public PolicyPkg(String packageName, int mode, int show, String type) {
+ this.packageName = packageName;
+ this.mode = mode;
+ this.show = show;
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return "PolicyPkg [packageName=" + packageName + ", mode=" + mode
+ + ", show=" + show + ", type=" + type + "]";
+ }
+
+ }
+
+ public final static class PolicyOp {
+ public int op;
+ public int mode;
+ public int show;
+
+ public PolicyOp(int op, int mode, int show) {
+ this.op = op;
+ this.mode = mode;
+ this.show = show;
+ }
+
+ @Override
+ public String toString() {
+ return "PolicyOp [op=" + op + ", mode=" + mode + ", show=" + show
+ + "]";
+ }
+ }
+
+ void readPolicy() {
+ FileInputStream stream;
+ synchronized (mFile) {
+ try {
+ stream = new FileInputStream(mFile);
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "App ops policy file (" + mFile.getPath()
+ + ") not found; Skipping.");
+ return;
+ }
+ boolean success = false;
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+ int type;
+ success = true;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ ;
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("no start tag found");
+ }
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG
+ || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("user-app")
+ || tagName.equals("system-app")) {
+ readDefaultPolicy(parser, tagName);
+ } else if (tagName.equals("application")) {
+ readApplicationPolicy(parser);
+ } else {
+ Slog.w(TAG, "Unknown element under <appops-policy>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ } catch (IllegalStateException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IndexOutOfBoundsException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } finally {
+ if (!success) {
+ mPolicy.clear();
+ }
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ private void readDefaultPolicy(XmlPullParser parser, String packageName)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ if (!"user-app".equalsIgnoreCase(packageName)
+ && !"system-app".equalsIgnoreCase(packageName)) {
+ return;
+ }
+ int mode = AppOpsManager.stringToMode(parser.getAttributeValue(null,
+ "permission"));
+ int show = stringToControl(parser.getAttributeValue(null, "show"));
+ if (mode == AppOpsManager.MODE_ERRORED && show == CONTROL_UNKNOWN) {
+ return;
+ }
+ PolicyPkg pkg = this.mPolicy.get(packageName);
+ if (pkg == null) {
+ pkg = new PolicyPkg(packageName, mode, show, packageName);
+ this.mPolicy.put(packageName, pkg);
+ } else {
+ Slog.w(TAG, "Duplicate policy found for package: " + packageName
+ + " of type: " + packageName);
+ pkg.mode = mode;
+ pkg.show = show;
+ }
+ }
+
+ private void readApplicationPolicy(XmlPullParser parser)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("pkg")) {
+ readPkgPolicy(parser);
+ } else {
+ Slog.w(TAG,
+ "Unknown element under <application>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readPkgPolicy(XmlPullParser parser)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ String packageName = parser.getAttributeValue(null, "name");
+ if (packageName == null)
+ return;
+ String appType = parser.getAttributeValue(null, "type");
+ if (appType == null)
+ return;
+ int mode = AppOpsManager.stringToMode(parser.getAttributeValue(null,
+ "permission"));
+ int show = stringToControl(parser.getAttributeValue(null, "show"));
+ String key = packageName + "." + appType;
+ PolicyPkg pkg = this.mPolicy.get(key);
+ if (pkg == null) {
+ pkg = new PolicyPkg(packageName, mode, show, appType);
+ this.mPolicy.put(key, pkg);
+ } else {
+ Slog.w(TAG, "Duplicate policy found for package: " + packageName
+ + " of type: " + appType);
+ pkg.mode = mode;
+ pkg.show = show;
+ }
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("op")) {
+ readOpPolicy(parser, pkg);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: " + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readOpPolicy(XmlPullParser parser, PolicyPkg pkg)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ if (pkg == null) {
+ return;
+ }
+ String opName = parser.getAttributeValue(null, "name");
+ if (opName == null) {
+ Slog.w(TAG, "Op name is null");
+ return;
+ }
+ int code = AppOpsManager.stringOpToOp(opName);
+ if (code == AppOpsManager.OP_NONE) {
+ Slog.w(TAG, "Unknown Op: " + opName);
+ return;
+ }
+ int mode = AppOpsManager.stringToMode(parser.getAttributeValue(null,
+ "permission"));
+ int show = stringToControl(parser.getAttributeValue(null, "show"));
+ if (mode == AppOpsManager.MODE_ERRORED && show == CONTROL_UNKNOWN) {
+ return;
+ }
+ PolicyOp op = pkg.get(code);
+ if (op == null) {
+ op = new PolicyOp(code, mode, show);
+ pkg.put(code, op);
+ } else {
+ Slog.w(TAG, "Duplicate policy found for package: "
+ + pkg.packageName + " type: " + pkg.type + " op: " + op.op);
+ op.mode = mode;
+ op.show = show;
+ }
+ }
+
+ void debugPoilcy() {
+ Iterator<Map.Entry<String, PolicyPkg>> iterator = mPolicy.entrySet()
+ .iterator();
+ while (iterator.hasNext()) {
+ String key = iterator.next().getKey();
+ if (DEBUG)
+ Slog.d(TAG, "Key: " + key);
+ PolicyPkg pkg = mPolicy.get(key);
+ if (pkg == null) {
+ if (DEBUG)
+ Slog.d(TAG, "Pkg is null for key: " + key);
+ continue;
+ }
+ if (DEBUG)
+ Slog.d(TAG, pkg.toString());
+ for (int i = 0; i < pkg.size(); i++) {
+ PolicyOp op = pkg.valueAt(i);
+ if (DEBUG)
+ Slog.d(TAG, op.toString());
+ }
+ }
+ }
+
+ private String getAppType(String packageName) {
+ String appType = null;
+ ApplicationInfo appInfo = null;
+ if (mContext != null) {
+ try {
+ appInfo = mContext.getPackageManager().getApplicationInfo(
+ packageName, 0);
+ } catch (NameNotFoundException e) {
+ appInfo = null;
+ }
+ if (appInfo != null) {
+ if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ appType = "system-app";
+ } else {
+ appType = "user-app";
+ }
+ }
+ } else {
+ Slog.e(TAG, "Context is null");
+ }
+ return appType;
+ }
+
+ public boolean isControlAllowed(int code, String packageName) {
+ boolean isShow = true;
+ int show = CONTROL_UNKNOWN;
+ PolicyPkg pkg;
+ String key;
+ String type;
+
+ if (mPolicy == null) {
+ return isShow;
+ }
+
+ type = getAppType(packageName);
+ if (type != null) {
+ key = type;
+ pkg = mPolicy.get(key);
+ if (pkg != null && pkg.show != CONTROL_UNKNOWN) {
+ show = pkg.show;
+ }
+ }
+ key = packageName;
+ if (type != null) {
+ key = key + "." + type;
+ }
+ pkg = mPolicy.get(key);
+ if (pkg != null) {
+ if (pkg.show != CONTROL_UNKNOWN) {
+ show = pkg.show;
+ }
+ PolicyOp op = pkg.get(code);
+ if (op != null) {
+ if (op.show != CONTROL_UNKNOWN) {
+ show = op.show;
+ }
+ }
+ }
+ if (show == CONTROL_NOSHOW) {
+ isShow = false;
+ }
+ return isShow;
+ }
+
+ public int getDefualtMode(int code, String packageName) {
+ int mode = AppOpsManager.MODE_ERRORED;
+ PolicyPkg pkg;
+ String key;
+ String type;
+
+ if (mPolicy == null) {
+ return mode;
+ }
+ if (DEBUG)
+ Slog.d(TAG, "Default mode requested for op=" + code + " package="
+ + packageName);
+ type = getAppType(packageName);
+ if (type != null) {
+ // Get value based on 'type'
+ key = type;
+ pkg = mPolicy.get(key);
+ if (pkg != null && pkg.mode != AppOpsManager.MODE_ERRORED) {
+ if (DEBUG)
+ Slog.d(TAG, "Setting value based on type: " + pkg);
+ mode = pkg.mode;
+ }
+ }
+ // Get value based on 'pkg'.
+ key = packageName;
+ if (type != null) {
+ key = key + "." + type;
+ }
+ pkg = mPolicy.get(key);
+ if (pkg != null) {
+ if (pkg.mode != AppOpsManager.MODE_ERRORED) {
+ if (DEBUG)
+ Slog.d(TAG, "Setting value based on packageName: " + pkg);
+ mode = pkg.mode;
+ }
+ // Get value base on 'op'
+ PolicyOp op = pkg.get(code);
+ if (op != null) {
+ if (op.mode != AppOpsManager.MODE_ERRORED) {
+ if (DEBUG)
+ Slog.d(TAG, "Setting value based on op: " + op);
+ mode = op.mode;
+ }
+ }
+ }
+ if (DEBUG)
+ Slog.d(TAG, "Returning mode=" + mode);
+ return mode;
+ }
+}
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index b5b0cd86f39..1e029a47269 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -1,4 +1,7 @@
/*
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -37,7 +40,11 @@ import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
@@ -47,6 +54,8 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -71,6 +80,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import com.android.server.PermissionDialogReqQueue.PermissionDialogReq;
import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
@@ -84,9 +94,24 @@ public class AppOpsService extends IAppOpsService.Stub {
// Write at most every 30 minutes.
static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
+ // Location of policy file.
+ static final String DEFAULT_POLICY_FILE = "/system/etc/appops_policy.xml";
+
Context mContext;
final AtomicFile mFile;
final Handler mHandler;
+ final Looper mLooper;
+ final boolean mStrictEnable;
+ AppOpsPolicy mPolicy;
+ private PowerManager mPowerManager;
+
+ private static final int[] PRIVACY_GUARD_OP_STATES = new int[] {
+ AppOpsManager.OP_COARSE_LOCATION,
+ AppOpsManager.OP_READ_CALL_LOG,
+ AppOpsManager.OP_READ_CONTACTS,
+ AppOpsManager.OP_READ_CALENDAR,
+ AppOpsManager.OP_READ_SMS
+ };
boolean mWriteScheduled;
boolean mFastWriteScheduled;
@@ -113,6 +138,15 @@ public class AppOpsService extends IAppOpsService.Stub {
*/
private final ArrayMap<IBinder, ClientRestrictionState> mOpUserRestrictions = new ArrayMap<>();
+ private Runnable mSuSessionChangedRunner = new Runnable() {
+ @Override
+ public void run() {
+ mContext.sendBroadcastAsUser(new Intent(AppOpsManager.ACTION_SU_SESSION_CHANGED),
+ UserHandle.ALL);
+ }
+ };
+
+
private static final class UidState {
public final int uid;
public ArrayMap<String, Ops> pkgOps;
@@ -156,12 +190,21 @@ public class AppOpsService extends IAppOpsService.Stub {
public long time;
public long rejectTime;
public int nesting;
-
- public Op(int _uid, String _packageName, int _op) {
+ public int noteOpCount;
+ public int startOpCount;
+ public PermissionDialogReqQueue dialogReqQueue;
+ final ArrayList<IBinder> clientTokens;
+ public int allowedCount;
+ public int ignoredCount;
+ public int delayedCount;
+
+ public Op(int _uid, String _packageName, int _op, int _mode) {
uid = _uid;
packageName = _packageName;
op = _op;
- mode = AppOpsManager.opToDefaultMode(op);
+ mode = _mode;
+ dialogReqQueue = new PermissionDialogReqQueue();
+ clientTokens = new ArrayList<IBinder>();
}
}
@@ -233,17 +276,27 @@ public class AppOpsService extends IAppOpsService.Stub {
}
mClients.remove(mAppToken);
}
+
+ // We cannot broadcast on the synchronized block above because the broadcast might
+ // trigger another appop call that eventually arrives here from a different thread,
+ // causing a deadlock.
+ for (int i=mStartedOps.size()-1; i>=0; i--) {
+ broadcastOpIfNeeded(mStartedOps.get(i).op);
+ }
}
}
public AppOpsService(File storagePath, Handler handler) {
mFile = new AtomicFile(storagePath);
mHandler = handler;
+ mLooper = Looper.myLooper();
+ mStrictEnable = AppOpsManager.isStrictEnable();
readState();
}
public void publish(Context context) {
mContext = context;
+ readPolicy();
ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
}
@@ -320,8 +373,45 @@ public class AppOpsService extends IAppOpsService.Stub {
|| mountMode == Zygote.MOUNT_EXTERNAL_WRITE;
}
});
+
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mIntentReceiver, filter);
}
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ synchronized (this) {
+ for (int i = mUidStates.size() - 1; i >= 0; i--) {
+ UidState uidState = mUidStates.valueAt(i);
+
+ ArrayMap<String, Ops> packages = uidState.pkgOps;
+ if (packages == null) {
+ continue;
+ }
+
+ Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, Ops> ent = it.next();
+ Ops pkgOps = ent.getValue();
+ for (int j = pkgOps.size() - 1; j >= 0; j--) {
+ Op curOp = pkgOps.valueAt(j);
+ if (DEBUG)
+ Log.d(TAG, "Ignoring " + curOp.packageName + " request "
+ + curOp.op);
+ curOp.dialogReqQueue.ignore();
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+
public void packageRemoved(int uid, String packageName) {
synchronized (this) {
UidState uidState = mUidStates.get(uid);
@@ -379,7 +469,7 @@ public class AppOpsService extends IAppOpsService.Stub {
Op curOp = pkgOps.valueAt(j);
resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
curOp.rejectTime, curOp.duration, curOp.proxyUid,
- curOp.proxyPackageName));
+ curOp.proxyPackageName, curOp.allowedCount, curOp.ignoredCount));
}
} else {
for (int j=0; j<ops.length; j++) {
@@ -390,7 +480,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
curOp.rejectTime, curOp.duration, curOp.proxyUid,
- curOp.proxyPackageName));
+ curOp.proxyPackageName, curOp.allowedCount, curOp.ignoredCount));
}
}
}
@@ -486,7 +576,8 @@ public class AppOpsService extends IAppOpsService.Stub {
code = AppOpsManager.opToSwitch(code);
synchronized (this) {
- final int defaultMode = AppOpsManager.opToDefaultMode(code);
+ final int defaultMode = AppOpsManager.opToDefaultMode(code,
+ AppOpsManager.isStrictOp(code));
UidState uidState = getUidStateLocked(uid, false);
if (uidState == null) {
@@ -616,7 +707,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
repCbs.addAll(cbs);
}
- if (mode == AppOpsManager.opToDefaultMode(op.op)) {
+ if (mode == getDefaultMode(code, uid, packageName)) {
// If going into the default mode, prune this op
// if there is nothing else interesting in it.
pruneOp(op, uid, packageName);
@@ -755,9 +846,11 @@ public class AppOpsService extends IAppOpsService.Stub {
Ops pkgOps = ent.getValue();
for (int j=pkgOps.size()-1; j>=0; j--) {
Op curOp = pkgOps.valueAt(j);
+ int defaultMode = getDefaultMode(curOp.op, curOp.uid,
+ curOp.packageName);
if (AppOpsManager.opAllowsReset(curOp.op)
- && curOp.mode != AppOpsManager.opToDefaultMode(curOp.op)) {
- curOp.mode = AppOpsManager.opToDefaultMode(curOp.op);
+ && curOp.mode != defaultMode) {
+ curOp.mode = defaultMode;
changed = true;
callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
mOpModeWatchers.get(curOp.op));
@@ -888,7 +981,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
Op op = getOpLocked(code, uid, resolvedPackageName, false);
if (op == null) {
- return AppOpsManager.opToDefaultMode(code);
+ return getDefaultMode(code, uid, packageName);
}
return op.mode;
}
@@ -1016,6 +1109,7 @@ public class AppOpsService extends IAppOpsService.Stub {
private int noteOperationUnchecked(int code, int uid, String packageName,
int proxyUid, String proxyPackageName) {
+ PermissionDialogReq req = null;
synchronized (this) {
Ops ops = getOpsRawLocked(uid, packageName, true);
if (ops == null) {
@@ -1025,6 +1119,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
Op op = getOpLocked(ops, code, true);
if (isOpRestricted(uid, code, packageName)) {
+ op.ignoredCount++;
return AppOpsManager.MODE_IGNORED;
}
if (op.duration == -1) {
@@ -1047,26 +1142,94 @@ public class AppOpsService extends IAppOpsService.Stub {
}
} else {
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
- if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName);
+ if (switchOp.mode != AppOpsManager.MODE_ALLOWED
+ && switchOp.mode != AppOpsManager.MODE_ASK) {
+ if (DEBUG)
+ Log.d(TAG, "noteOperation: reject #" + op.mode
+ + " for code " + switchCode + " (" + code
+ + ") uid " + uid + " package " + packageName);
op.rejectTime = System.currentTimeMillis();
+ op.ignoredCount++;
return switchOp.mode;
+ } else if (switchOp.mode == AppOpsManager.MODE_ASK) {
+ if (Looper.myLooper() == mLooper) {
+ Log.e(TAG,
+ "noteOperation: This method will deadlock if called from the main thread. (Code: "
+ + code
+ + " uid: "
+ + uid
+ + " package: "
+ + packageName + ")");
+ return switchOp.mode;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Package " + op.packageName + " has " + op.noteOpCount
+ + " requests and " + op.startOpCount + " start requests with "
+ + op.ignoredCount + " ignored at " + op.time +
+ " with a duration of "
+ + op.duration + " while being delayed " + op.delayedCount +
+ " times");
+ Log.d(TAG, "Total pkops for " + ops.packageName + " "
+ + ops.uidState.pkgOps.size());
+ }
+
+ // First drop all request events if the device is not interactive, next
+ // check what the global pkg ops count for the package,
+ // then check op scoped count. High frequency request ops will be delayed until
+ // their delay count ceiling is met. This is to mitigate the overloading the
+ // main activity manager service handler and having watchdog kill our service.
+ // Google play services likes to share its uid with numerous packages to avoid
+ // having to grant permissions from the users perspective and thus is the worst
+ // example of overloading this queue -- so, to not encourage bad behavior,
+ // we move them to the back of the line. NOTE: these values are magic, and may need
+ // tuning. Ideally we'd want a ringbuffer or token bucket here to do proper rate
+ // limiting.
+ final boolean isInteractive = mPowerManager.isInteractive();
+ if (isInteractive &&
+ (ops.uidState.pkgOps.size() < AppOpsPolicy.RATE_LIMIT_OPS_TOTAL_PKG_COUNT
+ && op.noteOpCount < AppOpsPolicy.RATE_LIMIT_OP_COUNT
+ || op.delayedCount > AppOpsPolicy.RATE_LIMIT_OP_DELAY_CEILING)) {
+
+ // Reset delayed count, most ops will never need this
+ if (op.delayedCount > 0) {
+ if (DEBUG) Log.d(TAG, "Resetting delayed count for " + op.packageName);
+ op.delayedCount = 0;
+ }
+
+ op.noteOpCount++;
+ req = askOperationLocked(code, uid, packageName, switchOp);
+ } else {
+ if (isInteractive) {
+ op.delayedCount++;
+ }
+ op.ignoredCount++;
+ return AppOpsManager.MODE_IGNORED;
+ }
}
}
- if (DEBUG) Log.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
- + " package " + packageName);
- op.time = System.currentTimeMillis();
- op.rejectTime = 0;
- op.proxyUid = proxyUid;
- op.proxyPackageName = proxyPackageName;
- return AppOpsManager.MODE_ALLOWED;
+ if (req == null) {
+ if (DEBUG) Log.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
+ + " package " + packageName);
+ op.time = System.currentTimeMillis();
+ op.rejectTime = 0;
+ op.proxyUid = proxyUid;
+ op.proxyPackageName = proxyPackageName;
+ broadcastOpIfNeeded(code);
+ op.allowedCount++;
+ return AppOpsManager.MODE_ALLOWED;
+ }
}
+
+ int result = req.get();
+ broadcastOpIfNeeded(code);
+ return result;
}
@Override
- public int startOperation(IBinder token, int code, int uid, String packageName) {
+ public int startOperation(IBinder token, int code, int uid,
+ String packageName) {
+ final PermissionDialogReq req;
verifyIncomingUid(uid);
verifyIncomingOp(code);
String resolvedPackageName = resolvePackageName(uid, packageName);
@@ -1083,6 +1246,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
Op op = getOpLocked(ops, code, true);
if (isOpRestricted(uid, code, resolvedPackageName)) {
+ op.ignoredCount++;
return AppOpsManager.MODE_IGNORED;
}
final int switchCode = AppOpsManager.opToSwitch(code);
@@ -1098,26 +1262,51 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
- if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG) Log.d(TAG, "startOperation: reject #" + op.mode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + resolvedPackageName);
+ if (switchOp.mode != AppOpsManager.MODE_ALLOWED
+ && switchOp.mode != AppOpsManager.MODE_ASK) {
+ if (DEBUG)
+ Log.d(TAG, "startOperation: reject #" + op.mode
+ + " for code " + switchCode + " (" + code
+ + ") uid " + uid + " package " + resolvedPackageName);
op.rejectTime = System.currentTimeMillis();
+ op.ignoredCount++;
return switchOp.mode;
+ } else if (switchOp.mode == AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG)
+ Log.d(TAG, "startOperation: allowing code " + code
+ + " uid " + uid + " package " + resolvedPackageName);
+ if (op.nesting == 0) {
+ op.time = System.currentTimeMillis();
+ op.rejectTime = 0;
+ op.duration = -1;
+ op.allowedCount++;
+ }
+ op.nesting++;
+ if (client.mStartedOps != null) {
+ client.mStartedOps.add(op);
+ }
+ broadcastOpIfNeeded(code);
+ return AppOpsManager.MODE_ALLOWED;
+ } else {
+ if (Looper.myLooper() == mLooper) {
+ Log.e(TAG,
+ "startOperation: This method will deadlock if called from the main thread. (Code: "
+ + code
+ + " uid: "
+ + uid
+ + " package: "
+ + resolvedPackageName + ")");
+ return switchOp.mode;
+ }
+ op.startOpCount++;
+ IBinder clientToken = client.mAppToken;
+ op.clientTokens.add(clientToken);
+ req = askOperationLocked(code, uid, resolvedPackageName, switchOp);
}
- if (DEBUG) Log.d(TAG, "startOperation: allowing code " + code + " uid " + uid
- + " package " + resolvedPackageName);
- if (op.nesting == 0) {
- op.time = System.currentTimeMillis();
- op.rejectTime = 0;
- op.duration = -1;
- }
- op.nesting++;
- if (client.mStartedOps != null) {
- client.mStartedOps.add(op);
- }
- return AppOpsManager.MODE_ALLOWED;
}
+ int result = req.get();
+ broadcastOpIfNeeded(code);
+ return result;
}
@Override
@@ -1145,6 +1334,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
finishOperationLocked(op);
}
+ broadcastOpIfNeeded(code);
}
@Override
@@ -1172,6 +1362,10 @@ public class AppOpsService extends IAppOpsService.Stub {
}
private void verifyIncomingUid(int uid) {
+ if (Binder.getCallingUid() == 0) {
+ // Allow root to delegate uid operations.
+ return;
+ }
if (uid == Binder.getCallingUid()) {
return;
}
@@ -1294,12 +1488,14 @@ public class AppOpsService extends IAppOpsService.Stub {
}
private Op getOpLocked(Ops ops, int code, boolean edit) {
+ int mode;
Op op = ops.get(code);
if (op == null) {
if (!edit) {
return null;
}
- op = new Op(ops.uidState.uid, ops.packageName, code);
+ mode = getDefaultMode(code, ops.uidState.uid, ops.packageName);
+ op = new Op(ops.uidState.uid, ops.packageName, code, mode);
ops.put(code, op);
}
if (edit) {
@@ -1486,10 +1682,32 @@ public class AppOpsService extends IAppOpsService.Stub {
String tagName = parser.getName();
if (tagName.equals("op")) {
- Op op = new Op(uid, pkgName, Integer.parseInt(parser.getAttributeValue(null, "n")));
+ int code = Integer
+ .parseInt(parser.getAttributeValue(null, "n"));
+ // use op name string if it exists
+ String codeNameStr = parser.getAttributeValue(null, "ns");
+ if (codeNameStr != null) {
+ // returns OP_NONE if it could not be mapped
+ code = AppOpsManager.nameToOp(codeNameStr);
+ }
+ // skip op codes that are out of bounds
+ if (code == AppOpsManager.OP_NONE
+ || code >= AppOpsManager._NUM_OP) {
+ continue;
+ }
+ Op op = new Op(uid, pkgName, code, AppOpsManager.MODE_ERRORED);
String mode = parser.getAttributeValue(null, "m");
if (mode != null) {
op.mode = Integer.parseInt(mode);
+ } else {
+ String sDefualtMode = parser.getAttributeValue(null, "dm");
+ int defaultMode;
+ if (sDefualtMode != null) {
+ defaultMode = Integer.parseInt(sDefualtMode);
+ } else {
+ defaultMode = getDefaultMode(code, uid, pkgName);
+ }
+ op.mode = defaultMode;
}
String time = parser.getAttributeValue(null, "t");
if (time != null) {
@@ -1511,7 +1729,14 @@ public class AppOpsService extends IAppOpsService.Stub {
if (proxyPackageName != null) {
op.proxyPackageName = proxyPackageName;
}
-
+ String allowed = parser.getAttributeValue(null, "ac");
+ if (allowed != null) {
+ op.allowedCount = Integer.parseInt(allowed);
+ }
+ String ignored = parser.getAttributeValue(null, "ic");
+ if (ignored != null) {
+ op.ignoredCount = Integer.parseInt(ignored);
+ }
UidState uidState = getUidStateLocked(uid, true);
if (uidState.pkgOps == null) {
uidState.pkgOps = new ArrayMap<>();
@@ -1598,8 +1823,13 @@ public class AppOpsService extends IAppOpsService.Stub {
AppOpsManager.OpEntry op = ops.get(j);
out.startTag(null, "op");
out.attribute(null, "n", Integer.toString(op.getOp()));
- if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
+ out.attribute(null, "ns", AppOpsManager.opToName(op.getOp()));
+ int defaultMode = getDefaultMode(op.getOp(),
+ pkg.getUid(), pkg.getPackageName());
+ if (op.getMode() != defaultMode) {
out.attribute(null, "m", Integer.toString(op.getMode()));
+ } else {
+ out.attribute(null, "dm", Integer.toString(defaultMode));
}
long time = op.getTime();
if (time != 0) {
@@ -1621,6 +1851,14 @@ public class AppOpsService extends IAppOpsService.Stub {
if (proxyPackageName != null) {
out.attribute(null, "pp", proxyPackageName);
}
+ int allowed = op.getAllowedCount();
+ if (allowed != 0) {
+ out.attribute(null, "ac", Integer.toString(allowed));
+ }
+ int ignored = op.getIgnoredCount();
+ if (ignored != 0) {
+ out.attribute(null, "ic", Integer.toString(ignored));
+ }
out.endTag(null, "op");
}
out.endTag(null, "uid");
@@ -2273,7 +2511,174 @@ public class AppOpsService extends IAppOpsService.Stub {
private void checkSystemUid(String function) {
int uid = Binder.getCallingUid();
if (uid != Process.SYSTEM_UID) {
- throw new SecurityException(function + " must by called by the system");
+ throw new SecurityException(function
+ + " must by called by the system");
+ }
+ }
+
+ final class AskRunnable implements Runnable {
+ final int code;
+ final int uid;
+ final String packageName;
+ final Op op;
+ final PermissionDialogReq request;
+
+ public AskRunnable(int code, int uid, String packageName, Op op,
+ PermissionDialogReq request) {
+ super();
+ this.code = code;
+ this.uid = uid;
+ this.packageName = packageName;
+ this.op = op;
+ this.request = request;
+ }
+
+ @Override
+ public void run() {
+ PermissionDialog permDialog = null;
+ synchronized (AppOpsService.this) {
+ Log.e(TAG, "Creating dialog box");
+ op.dialogReqQueue.register(request);
+ if (op.dialogReqQueue.getDialog() == null) {
+ permDialog = new PermissionDialog(mContext,
+ AppOpsService.this, code, uid, packageName);
+ op.dialogReqQueue.setDialog(permDialog);
+ }
+ }
+ if (permDialog != null) {
+ permDialog.show();
+ }
+ }
+ }
+
+ private PermissionDialogReq askOperationLocked(int code, int uid,
+ String packageName, Op op) {
+ PermissionDialogReq request = new PermissionDialogReq();
+ mHandler.post(new AskRunnable(code, uid, packageName, op, request));
+ return request;
+ }
+
+ private int getDefaultMode(int code, int uid, String packageName) {
+ int mode = AppOpsManager.opToDefaultMode(code,
+ isStrict(code, uid, packageName));
+ if (AppOpsManager.isStrictOp(code) && mPolicy != null) {
+ int policyMode = mPolicy.getDefualtMode(code, packageName);
+ if (policyMode != AppOpsManager.MODE_ERRORED) {
+ mode = policyMode;
+ }
+ }
+ return mode;
+ }
+
+ private boolean isStrict(int code, int uid, String packageName) {
+ if (!mStrictEnable)
+ return false;
+
+ return UserHandle.isApp(uid);
+ }
+
+ private void printOperationLocked(Op op, int mode, String operation) {
+ if(op != null) {
+ int switchCode = AppOpsManager.opToSwitch(op.op);
+ if (mode == AppOpsManager.MODE_IGNORED) {
+ if (DEBUG) Log.d(TAG, operation + ": reject #" + mode + " for code "
+ + switchCode + " (" + op.op + ") uid " + op.uid + " package "
+ + op.packageName);
+ } else if (mode == AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG) Log.d(TAG, operation + ": allowing code " + op.op + " uid "
+ + op.uid
+ + " package " + op.packageName);
+ }
+ }
+ }
+
+ private void recordOperationLocked(int code, int uid, String packageName,
+ int mode) {
+ Op op = getOpLocked(code, uid, packageName, false);
+ if(op != null) {
+ if(op.noteOpCount != 0)
+ printOperationLocked(op, mode, "noteOperartion");
+ if(op.startOpCount != 0)
+ printOperationLocked(op, mode, "startOperation");
+ if (mode == AppOpsManager.MODE_IGNORED) {
+ op.rejectTime = System.currentTimeMillis();
+ } else if (mode == AppOpsManager.MODE_ALLOWED) {
+ if(op.noteOpCount != 0) {
+ op.time = System.currentTimeMillis();
+ op.rejectTime = 0;
+ }
+ if(op.startOpCount != 0) {
+ if(op.nesting == 0) {
+ op.time = System.currentTimeMillis();
+ op.rejectTime = 0;
+ op.duration = -1;
+ }
+ op.nesting = op.nesting + op.startOpCount;
+ while(op.clientTokens.size() != 0) {
+ IBinder clientToken = op.clientTokens.get(0);
+ ClientState client = mClients.get(clientToken);
+ if (client != null) {
+ if (client.mStartedOps != null) {
+ client.mStartedOps.add(op);
+ }
+ }
+ op.clientTokens.remove(0);
+ }
+ }
+ }
+ op.clientTokens.clear();
+ op.startOpCount = 0;
+ op.noteOpCount = 0;
+ }
+ }
+
+ public void notifyOperation(int code, int uid, String packageName,
+ int mode, boolean remember) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ ArrayList<Callback> repCbs = null;
+ int switchCode = AppOpsManager.opToSwitch(code);
+ synchronized (this) {
+ recordOperationLocked(code, uid, packageName, mode);
+ Op op = getOpLocked(switchCode, uid, packageName, true);
+ if (op != null) {
+ // Send result to all waiting client
+ if (op.dialogReqQueue.getDialog() != null) {
+ op.dialogReqQueue.notifyAll(mode);
+ op.dialogReqQueue.setDialog(null);
+ }
+ if (remember && op.mode != mode) {
+ op.mode = mode;
+ ArrayList<Callback> cbs = mOpModeWatchers.get(switchCode);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArrayList<Callback>();
+ }
+ repCbs.addAll(cbs);
+ }
+ cbs = mPackageModeWatchers.get(packageName);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArrayList<Callback>();
+ }
+ repCbs.addAll(cbs);
+ }
+ if (mode == getDefaultMode(op.op, op.uid, op.packageName)) {
+ // If going into the default mode, prune this op
+ // if there is nothing else interesting in it.
+ pruneOp(op, uid, packageName);
+ }
+ scheduleWriteLocked();
+ }
+ }
+ }
+ if (repCbs != null) {
+ for (int i = 0; i < repCbs.size(); i++) {
+ try {
+ repCbs.get(i).mCallback.opChanged(switchCode, uid, packageName);
+ } catch (RemoteException e) {
+ }
+ }
}
}
@@ -2430,4 +2835,75 @@ public class AppOpsService extends IAppOpsService.Stub {
return true;
}
}
+
+ private void broadcastOpIfNeeded(int op) {
+ switch (op) {
+ case AppOpsManager.OP_SU:
+ mHandler.post(mSuSessionChangedRunner);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void readPolicy() {
+ if (mStrictEnable) {
+ mPolicy = new AppOpsPolicy(new File(DEFAULT_POLICY_FILE), mContext);
+ mPolicy.readPolicy();
+ mPolicy.debugPoilcy();
+ } else {
+ mPolicy = null;
+ }
+ }
+
+ public boolean isControlAllowed(int code, String packageName) {
+ boolean isShow = true;
+ if (mPolicy != null) {
+ isShow = mPolicy.isControlAllowed(code, packageName);
+ }
+ return isShow;
+ }
+
+ @Override
+ public boolean getPrivacyGuardSettingForPackage(int uid, String packageName) {
+ for (int op : PRIVACY_GUARD_OP_STATES) {
+ int switchOp = AppOpsManager.opToSwitch(op);
+ int mode = checkOperation(op, uid, packageName);
+ if (mode != AppOpsManager.MODE_ALLOWED && mode != AppOpsManager.MODE_IGNORED) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void setPrivacyGuardSettingForPackage(int uid, String packageName, boolean state) {
+ for (int op : PRIVACY_GUARD_OP_STATES) {
+ int switchOp = AppOpsManager.opToSwitch(op);
+ setMode(switchOp, uid, packageName, state
+ ? AppOpsManager.MODE_ASK : AppOpsManager.MODE_ALLOWED);
+ }
+ }
+
+ @Override
+ public void resetCounters() {
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ synchronized (this) {
+ for (int i=0; i<mUidStates.size(); i++) {
+ final UidState uidState = mUidStates.valueAt(i);
+ for (Map.Entry<String, Ops> ent : uidState.pkgOps.entrySet()) {
+ String packageName = ent.getKey();
+ Ops pkgOps = ent.getValue();
+ for (int j=0; j<pkgOps.size(); j++) {
+ Op curOp = pkgOps.valueAt(j);
+ curOp.allowedCount = 0;
+ curOp.ignoredCount = 0;
+ }
+ }
+ }
+ // ensure the counter reset persists
+ scheduleWriteLocked();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/BasePermissionDialog.java b/services/core/java/com/android/server/BasePermissionDialog.java
new file mode 100644
index 00000000000..e3dbcdabcd1
--- /dev/null
+++ b/services/core/java/com/android/server/BasePermissionDialog.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Copyright (C) 2006 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.server;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+import android.widget.Button;
+
+import com.android.internal.R;
+
+public class BasePermissionDialog extends AlertDialog {
+ public BasePermissionDialog(Context context) {
+ super(context, com.android.internal.R.style.Theme_Dialog_AppError);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ WindowManager.LayoutParams attrs = getWindow().getAttributes();
+ attrs.setTitle("Permission Dialog");
+ getWindow().setAttributes(attrs);
+ setIconAttribute(R.attr.alertDialogIcon);
+ }
+
+ public void onStart() {
+ super.onStart();
+ setEnabled(false);
+ mHandler.sendMessage(mHandler.obtainMessage(0));
+ }
+
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mConsuming) {
+ // Slog.i(TAG, "Consuming: " + event);
+ return true;
+ }
+ // Slog.i(TAG, "Dispatching: " + event);
+ return super.dispatchKeyEvent(event);
+ }
+
+ private void setEnabled(boolean enabled) {
+ Button b = (Button) findViewById(R.id.button1);
+ if (b != null) {
+ b.setEnabled(enabled);
+ }
+ b = (Button) findViewById(R.id.button2);
+ if (b != null) {
+ b.setEnabled(enabled);
+ }
+ b = (Button) findViewById(R.id.button3);
+ if (b != null) {
+ b.setEnabled(enabled);
+ }
+ }
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ if (msg.what == 0) {
+ mConsuming = false;
+ setEnabled(true);
+ }
+ }
+ };
+
+ private boolean mConsuming = true;
+}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index f04311b6e8f..79bb98068cc 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -1,4 +1,7 @@
/*
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,6 +21,7 @@ package com.android.server;
import android.Manifest;
import android.app.ActivityManager;
+import android.app.AppOpsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.IBluetooth;
@@ -617,7 +621,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
return true;
}
- public boolean enable() {
+ public boolean enable(String callingPackage) {
if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
(!checkIfCallerIsForegroundUser())) {
Slog.w(TAG,"enable(): not allowed for non-active and non system user");
@@ -631,6 +635,13 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
" mBinding = " + mBinding + " mState = " + mState);
}
+ AppOpsManager appOps = (AppOpsManager) mContext
+ .getSystemService(Context.APP_OPS_SERVICE);
+ int callingUid = Binder.getCallingUid();
+ if (appOps.noteOp(AppOpsManager.OP_BLUETOOTH_CHANGE, callingUid,
+ callingPackage) != AppOpsManager.MODE_ALLOWED)
+ return false;
+
synchronized(mReceiver) {
mQuietEnableExternal = false;
mEnableExternal = true;
diff --git a/services/core/java/com/android/server/PermissionDialog.java b/services/core/java/com/android/server/PermissionDialog.java
new file mode 100644
index 00000000000..fd676b5f893
--- /dev/null
+++ b/services/core/java/com/android/server/PermissionDialog.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Copyright (C) 2006 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.server;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+public class PermissionDialog extends BasePermissionDialog {
+ private final static String TAG = "PermissionDialog";
+
+ private final AppOpsService mService;
+ private final String mPackageName;
+ private final int mCode;
+ private View mView;
+ private CheckBox mChoice;
+ private int mUid;
+ final CharSequence[] mOpLabels;
+ private Context mContext;
+
+ // Event 'what' codes
+ static final int ACTION_ALLOWED = 0x2;
+ static final int ACTION_IGNORED = 0x4;
+ static final int ACTION_IGNORED_TIMEOUT = 0x8;
+
+ // 15s timeout, then we automatically dismiss the permission
+ // dialog. Otherwise, it may cause watchdog timeout sometimes.
+ static final long DISMISS_TIMEOUT = 1000 * 15 * 1;
+
+ public PermissionDialog(Context context, AppOpsService service,
+ int code, int uid, String packageName) {
+ super(context);
+
+ mContext = context;
+ Resources res = context.getResources();
+
+ mService = service;
+ mCode = code;
+ mPackageName = packageName;
+ mUid = uid;
+ mOpLabels = res.getTextArray(
+ com.android.internal.R.array.app_ops_labels);
+
+ setCancelable(false);
+
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ res.getString(com.android.internal.R.string.allow), mHandler.obtainMessage(ACTION_ALLOWED));
+
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ res.getString(com.android.internal.R.string.deny), mHandler.obtainMessage(ACTION_IGNORED));
+
+ setTitle(res.getString(com.android.internal.R.string.privacy_guard_dialog_title));
+ WindowManager.LayoutParams attrs = getWindow().getAttributes();
+ attrs.setTitle("Permission info: " + getAppName(mPackageName));
+ attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR
+ | WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ getWindow().setAttributes(attrs);
+
+ mView = getLayoutInflater().inflate(
+ com.android.internal.R.layout.permission_confirmation_dialog,
+ null);
+ TextView tv = (TextView) mView.findViewById(
+ com.android.internal.R.id.permission_text);
+ mChoice = (CheckBox) mView.findViewById(
+ com.android.internal.R.id.permission_remember_choice_checkbox);
+ String name = getAppName(mPackageName);
+ if(name == null)
+ name = mPackageName;
+ tv.setText(mContext.getString(com.android.internal.R.string.privacy_guard_dialog_summary,
+ name, mOpLabels[mCode]));
+ setView(mView);
+
+ // After the timeout, pretend the user clicked the quit button
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(ACTION_IGNORED_TIMEOUT), DISMISS_TIMEOUT);
+ }
+
+ public void ignore() {
+ mHandler.sendMessage(mHandler.obtainMessage(ACTION_IGNORED_TIMEOUT));
+ }
+
+ private String getAppName(String packageName) {
+ ApplicationInfo appInfo = null;
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ appInfo = pm.getApplicationInfo(packageName,
+ PackageManager.GET_DISABLED_COMPONENTS
+ | PackageManager.GET_UNINSTALLED_PACKAGES);
+ } catch (final NameNotFoundException e) {
+ return null;
+ }
+ if(appInfo != null) {
+ return (String)pm.getApplicationLabel(appInfo);
+ }
+ return null;
+ }
+
+ private final Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ int mode;
+ boolean remember = mChoice.isChecked();
+ switch(msg.what) {
+ case ACTION_ALLOWED:
+ mode = AppOpsManager.MODE_ALLOWED;
+ break;
+ case ACTION_IGNORED:
+ mode = AppOpsManager.MODE_IGNORED;
+ break;
+ default:
+ mode = AppOpsManager.MODE_IGNORED;
+ remember = false;
+ }
+ mService.notifyOperation(mCode, mUid, mPackageName, mode,
+ remember);
+ dismiss();
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/PermissionDialogReqQueue.java b/services/core/java/com/android/server/PermissionDialogReqQueue.java
new file mode 100644
index 00000000000..5b602e33a60
--- /dev/null
+++ b/services/core/java/com/android/server/PermissionDialogReqQueue.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Copyright (C) 2006 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.server;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PermissionDialogReqQueue {
+ public PermissionDialog getDialog() {
+ return mDialog;
+ }
+
+ public void setDialog(PermissionDialog mDialog) {
+ this.mDialog = mDialog;
+ }
+
+ public final static class PermissionDialogReq {
+ public void set(int res) {
+ synchronized (this) {
+ mHasResult = true;
+ mResult = res;
+ notifyAll();
+ }
+ }
+
+ public int get() {
+ synchronized (this) {
+ while (!mHasResult) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ return mResult;
+ }
+
+ boolean mHasResult = false;
+ int mResult;
+ }
+
+ private PermissionDialog mDialog;
+ private List<PermissionDialogReq> resultList;
+
+ public PermissionDialogReqQueue() {
+ mDialog = null;
+ resultList = new ArrayList<PermissionDialogReq>();
+ }
+
+ public void register(PermissionDialogReq res) {
+ synchronized (this) {
+ resultList.add(res);
+ }
+ }
+
+ public void notifyAll(int mode) {
+ synchronized (this) {
+ while (resultList.size() != 0) {
+ PermissionDialogReq res = resultList.get(0);
+ res.set(mode);
+ resultList.remove(0);
+ }
+ }
+ }
+
+ public void ignore() {
+ if (mDialog != null) {
+ mDialog.ignore();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 86806b9aa3f..418c1e2c0e2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1549,6 +1549,9 @@ public final class ActivityManagerService extends ActivityManagerNative
static final int VR_MODE_APPLY_IF_NEEDED_MSG = 69;
static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 70;
+ static final int POST_PRIVACY_NOTIFICATION_MSG = 90;
+ static final int CANCEL_PRIVACY_NOTIFICATION_MSG = 91;
+
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
static final int FIRST_COMPAT_MODE_MSG = 300;
@@ -2313,6 +2316,69 @@ public final class ActivityManagerService extends ActivityManagerNative
// it is finished we make sure it is reset to its default.
mUserIsMonkey = false;
} break;
+ case POST_PRIVACY_NOTIFICATION_MSG: {
+ INotificationManager inm = NotificationManager.getService();
+ if (inm == null) {
+ return;
+ }
+
+ ActivityRecord root = (ActivityRecord)msg.obj;
+ ProcessRecord process = root.app;
+ if (process == null) {
+ return;
+ }
+
+ try {
+ Context context = mContext.createPackageContext(process.info.packageName, 0);
+ String text = mContext.getString(R.string.privacy_guard_notification_detail,
+ context.getApplicationInfo().loadLabel(context.getPackageManager()));
+ String title = mContext.getString(R.string.privacy_guard_notification);
+
+ Intent infoIntent = new Intent(Settings.ACTION_APP_OPS_DETAILS_SETTINGS,
+ Uri.fromParts("package", root.packageName, null));
+
+ Notification notification = new Notification();
+ notification.icon = com.android.internal.R.drawable.stat_notify_privacy_guard;
+ notification.when = 0;
+ notification.flags = Notification.FLAG_ONGOING_EVENT;
+ notification.priority = Notification.PRIORITY_LOW;
+ notification.defaults = 0;
+ notification.sound = null;
+ notification.vibrate = null;
+ notification.setLatestEventInfo(mContext,
+ title, text,
+ PendingIntent.getActivityAsUser(mContext, 0, infoIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null,
+ new UserHandle(root.userId)));
+
+ try {
+ int[] outId = new int[1];
+ inm.enqueueNotificationWithTag("android", "android", null,
+ R.string.privacy_guard_notification,
+ notification, outId, root.userId);
+ } catch (RuntimeException e) {
+ Slog.w(ActivityManagerService.TAG,
+ "Error showing notification for privacy guard", e);
+ } catch (RemoteException e) {
+ }
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "Unable to create context for privacy guard notification", e);
+ }
+ } break;
+ case CANCEL_PRIVACY_NOTIFICATION_MSG: {
+ INotificationManager inm = NotificationManager.getService();
+ if (inm == null) {
+ return;
+ }
+ try {
+ inm.cancelNotificationWithTag("android", null,
+ R.string.privacy_guard_notification, msg.arg1);
+ } catch (RuntimeException e) {
+ Slog.w(ActivityManagerService.TAG,
+ "Error canceling notification for service", e);
+ } catch (RemoteException e) {
+ }
+ } break;
case APP_BOOST_DEACTIVATE_MSG: {
synchronized(ActivityManagerService.this) {
if (mIsBoosted) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 58290e705ce..c22290bc132 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -90,6 +90,7 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.IActivityController;
import android.app.ResultInfo;
import android.content.ComponentName;
@@ -131,6 +132,8 @@ import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
import com.android.server.wm.TaskGroup;
import com.android.server.wm.WindowManagerService;
+import cyanogenmod.providers.CMSettings;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -1447,6 +1450,8 @@ final class ActivityStack {
// When resuming an activity, require it to call requestVisibleBehind() again.
setVisibleBehindActivity(null);
}
+
+ updatePrivacyGuardNotificationLocked(next);
}
private void setVisible(ActivityRecord r, boolean visible) {
@@ -2664,6 +2669,32 @@ final class ActivityStack {
updateTaskMovement(task, true);
}
+ private final void updatePrivacyGuardNotificationLocked(ActivityRecord next) {
+
+ String privacyGuardPackageName = mStackSupervisor.mPrivacyGuardPackageName;
+ if (privacyGuardPackageName != null && privacyGuardPackageName.equals(next.packageName)) {
+ return;
+ }
+
+ boolean privacy = mService.mAppOpsService.getPrivacyGuardSettingForPackage(
+ next.app.uid, next.packageName);
+ boolean privacyNotification = (CMSettings.Secure.getInt(
+ mService.mContext.getContentResolver(),
+ CMSettings.Secure.PRIVACY_GUARD_NOTIFICATION, 1) == 1);
+
+ if (privacyGuardPackageName != null && !privacy) {
+ Message msg = mService.mHandler.obtainMessage(
+ ActivityManagerService.CANCEL_PRIVACY_NOTIFICATION_MSG, next.userId);
+ msg.sendToTarget();
+ mStackSupervisor.mPrivacyGuardPackageName = null;
+ } else if (privacy && privacyNotification) {
+ Message msg = mService.mHandler.obtainMessage(
+ ActivityManagerService.POST_PRIVACY_NOTIFICATION_MSG, next);
+ msg.sendToTarget();
+ mStackSupervisor.mPrivacyGuardPackageName = next.packageName;
+ }
+ }
+
final void startActivityLocked(ActivityRecord r, boolean newTask, boolean keepCurTransition,
ActivityOptions options) {
TaskRecord rTask = r.task;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 30ea8df5bd2..9c8414f692d 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -369,6 +369,11 @@ public final class ActivityStackSupervisor implements DisplayListener {
boolean mSleepTimeout = false;
/**
+ * Is the privacy guard currently enabled? Shared between ActivityStacks
+ */
+ String mPrivacyGuardPackageName = null;
+
+ /**
* We don't want to allow the device to go to sleep while in the process
* of launching an activity. This is primarily to allow alarm intent
* receivers to launch an activity and get that to run before the device
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b685dd3f677..6286f368c35 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -246,7 +246,7 @@ final class UserController {
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mService.broadcastIntentLocked(null, null, intent, null, resultTo, 0, null, null,
new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
- AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
+ AppOpsManager.OP_BOOT_COMPLETED, null, true, false, MY_PID, SYSTEM_UID, userId);
}
// We need to delay unlocking managed profiles until the parent user
@@ -404,7 +404,7 @@ final class UserController {
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mService.broadcastIntentLocked(null, null, bootIntent, null, null, 0, null, null,
new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
- AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
+ AppOpsManager.OP_BOOT_COMPLETED, null, true, false, MY_PID, SYSTEM_UID, userId);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index aac619f9808..5297a955658 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -109,6 +109,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.ResourcesManager;
import android.app.admin.IDevicePolicyManager;
@@ -225,6 +226,8 @@ import android.util.Xml;
import android.util.jar.StrictJarFile;
import android.view.Display;
+import cyanogenmod.providers.CMSettings;
+
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IMediaContainerService;
@@ -970,6 +973,8 @@ public class PackageManagerService extends IPackageManager.Stub {
ArrayList<ComponentName> mDisabledComponentsList;
+ private AppOpsManager mAppOps;
+
// Set of pending broadcasts for aggregating enable/disable of components.
static class PendingPackageBroadcasts {
// for each user id, a map of <package name -> components within that package>
@@ -1955,6 +1960,18 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ if (!update && !isSystemApp(res.pkg)) {
+ boolean privacyGuard = CMSettings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ CMSettings.Secure.PRIVACY_GUARD_DEFAULT,
+ 0, UserHandle.USER_CURRENT) == 1;
+ if (privacyGuard) {
+ mAppOps.setPrivacyGuardSettingForPackage(
+ res.pkg.applicationInfo.uid,
+ res.pkg.applicationInfo.packageName, true);
+ }
+ }
+
// Log current value of "unknown sources" setting
EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,
getUnknownSourcesSettings());
@@ -2277,6 +2294,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+
String separateProcesses = SystemProperties.get("debug.separate_processes");
if (separateProcesses != null && separateProcesses.length() > 0) {
if ("*".equals(separateProcesses)) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d8a1c779f77..7b8aec8fbc6 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1043,6 +1043,7 @@ public class UserManagerService extends IUserManager.Stub {
mGuestRestrictions.putBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+ mGuestRestrictions.putBoolean(UserManager.DISALLOW_SU, true);
}
}
}
@@ -3386,6 +3387,7 @@ public class UserManagerService extends IUserManager.Stub {
if (user != null && !user.isAdmin()) {
setUserRestriction(UserManager.DISALLOW_SMS, true, user.id);
setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, true, user.id);
+ setUserRestriction(UserManager.DISALLOW_SU, true, user.id);
}
return user;
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 38a3f421264..7edb189be48 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -103,7 +103,8 @@ public class UserRestrictionsUtils {
UserManager.DISALLOW_RUN_IN_BACKGROUND,
UserManager.DISALLOW_DATA_ROAMING,
UserManager.DISALLOW_SET_USER_ICON,
- UserManager.DISALLOW_SET_WALLPAPER
+ UserManager.DISALLOW_SET_WALLPAPER,
+ UserManager.DISALLOW_SU
});
/**
@@ -129,7 +130,8 @@ public class UserRestrictionsUtils {
UserManager.DISALLOW_FUN,
UserManager.DISALLOW_SAFE_BOOT,
UserManager.DISALLOW_CREATE_WINDOWS,
- UserManager.DISALLOW_DATA_ROAMING
+ UserManager.DISALLOW_DATA_ROAMING,
+ UserManager.DISALLOW_SU
);
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7140abf653a..d2dba65b79e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1,4 +1,7 @@
/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,6 +25,7 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.ActivityThread;
import android.content.ContentResolver;
+import android.app.AppOpsManager;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
@@ -4602,11 +4606,18 @@ public class TelephonyManager {
public void setDataEnabled(int subId, boolean enable) {
try {
Log.d(TAG, "setDataEnabled: enabled=" + enable);
+ AppOpsManager appOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
+ if (enable) {
+ if (appOps.noteOp(AppOpsManager.OP_DATA_CONNECT_CHANGE) != AppOpsManager.MODE_ALLOWED) {
+ Log.w(TAG, "Permission denied by user.");
+ return;
+ }
+ }
ITelephony telephony = getITelephony();
if (telephony != null)
telephony.setDataEnabled(subId, enable);
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#setDataEnabled", e);
+ Log.e(TAG, "Error calling setDataEnabled", e);
}
}