/* 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; } }