diff options
Diffstat (limited to 'services/core/java/com/android/server')
57 files changed, 8932 insertions, 589 deletions
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 0000000..75bca05 --- /dev/null +++ b/services/core/java/com/android/server/AppOpsPolicy.java @@ -0,0 +1,441 @@ +/* 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; + + 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; + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index c373fb8..5d0b4f8 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"); @@ -35,7 +38,9 @@ import android.app.ActivityManager; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.Dialog; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; @@ -46,6 +51,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -68,6 +74,7 @@ import com.android.internal.os.Zygote; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; +import com.android.server.PermissionDialogReqQueue.PermissionDialogReq; import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; @@ -81,9 +88,23 @@ 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 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; @@ -105,6 +126,14 @@ public class AppOpsService extends IAppOpsService.Stub { final SparseArray<UidState> mUidStates = new SparseArray<>(); + private Runnable mSuSessionChangedRunner = new Runnable() { + @Override + public void run() { + mContext.sendBroadcastAsUser(new Intent(AppOpsManager.ACTION_SU_SESSION_CHANGED), + UserHandle.ALL); + } + }; + private final SparseArray<boolean[]> mOpRestrictions = new SparseArray<boolean[]>(); private static final class UidState { @@ -150,12 +179,20 @@ 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 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>(); } } @@ -227,17 +264,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()); } @@ -373,7 +420,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++) { @@ -384,7 +431,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)); } } } @@ -476,7 +523,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) { @@ -604,7 +652,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); @@ -731,9 +779,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, packageName, curOp.op, mOpModeWatchers.get(curOp.op)); @@ -854,7 +904,7 @@ public class AppOpsService extends IAppOpsService.Stub { } Op op = getOpLocked(code, uid, packageName, false); if (op == null) { - return AppOpsManager.opToDefaultMode(code); + return getDefaultMode(code, uid, packageName); } return op.mode; } @@ -945,6 +995,7 @@ public class AppOpsService extends IAppOpsService.Stub { private int noteOperationUnchecked(int code, int uid, String packageName, int proxyUid, String proxyPackageName) { + final PermissionDialogReq req; synchronized (this) { Ops ops = getOpsLocked(uid, packageName, true); if (ops == null) { @@ -954,6 +1005,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) { @@ -974,24 +1026,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, "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_ALLOWED) { + 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; + op.allowedCount++; + broadcastOpIfNeeded(code); + return AppOpsManager.MODE_ALLOWED; + + } else { + 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; + } + op.noteOpCount++; + req = askOperationLocked(code, uid, packageName, switchOp); } - 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; } + + 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); ClientState client = (ClientState)token; @@ -1004,6 +1083,7 @@ public class AppOpsService extends IAppOpsService.Stub { } Op op = getOpLocked(ops, code, true); if (isOpRestricted(uid, code, packageName)) { + op.ignoredCount++; return AppOpsManager.MODE_IGNORED; } final int switchCode = AppOpsManager.opToSwitch(code); @@ -1019,25 +1099,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 " + packageName); + 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 " + packageName); 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 " + packageName); + 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: " + + packageName + ")"); + return switchOp.mode; + } + op.startOpCount++; + IBinder clientToken = client.mAppToken; + op.clientTokens.add(clientToken); + req = askOperationLocked(code, uid, packageName, switchOp); } - if (DEBUG) Log.d(TAG, "startOperation: allowing code " + code + " uid " + uid - + " package " + packageName); - 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 @@ -1058,6 +1164,7 @@ public class AppOpsService extends IAppOpsService.Stub { } finishOperationLocked(op); } + broadcastOpIfNeeded(code); } @Override @@ -1082,6 +1189,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; } @@ -1116,6 +1227,9 @@ public class AppOpsService extends IAppOpsService.Stub { packageName = "root"; } else if (uid == Process.SHELL_UID) { packageName = "com.android.shell"; + } else if (uid == Process.SYSTEM_UID) { + if (packageName == null) + packageName = "android"; } return getOpsRawLocked(uid, packageName, edit); } @@ -1203,12 +1317,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) { @@ -1388,10 +1504,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) { @@ -1413,7 +1551,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<>(); @@ -1500,8 +1645,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) { @@ -1523,6 +1673,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"); @@ -1766,14 +1924,181 @@ 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, packageName); + } catch (RemoteException e) { + } + } } } private static String[] getPackagesForUid(int uid) { String[] packageNames = null; try { - packageNames= AppGlobals.getPackageManager().getPackagesForUid(uid); + packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid); } catch (RemoteException e) { /* ignore - local call */ } @@ -1782,4 +2107,75 @@ public class AppOpsService extends IAppOpsService.Stub { } return packageNames; } + + 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/AssetAtlasService.java b/services/core/java/com/android/server/AssetAtlasService.java index 4569dae..ff4456e 100644 --- a/services/core/java/com/android/server/AssetAtlasService.java +++ b/services/core/java/com/android/server/AssetAtlasService.java @@ -391,6 +391,11 @@ public class AssetAtlasService extends IAssetAtlas.Stub { } } + if (results.size() == 0) { + if (DEBUG_ATLAS) Log.w(LOG_TAG, "No atlas configuration found!"); + return null; + } + // Maximize the number of packed bitmaps, minimize the texture size Collections.sort(results, new Comparator<WorkerResult>() { @Override 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 0000000..e3dbcda --- /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/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index c3200fe..8a84246 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -29,6 +29,8 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.database.ContentObserver; import android.os.BatteryManager; import android.os.BatteryManagerInternal; import android.os.BatteryProperties; @@ -97,6 +99,9 @@ public final class BatteryService extends SystemService { private static final int BATTERY_SCALE = 100; // battery capacity is a percentage + // notification light maximum brightness value to use + private static final int LIGHT_BRIGHTNESS_MAXIMUM = 255; + // Used locally for determining when to make a last ditch effort to log // discharge stats before the device dies. private int mCriticalBatteryLevel; @@ -129,6 +134,12 @@ public final class BatteryService extends SystemService { private int mInvalidCharger; private int mLastInvalidCharger; + private boolean mAdjustableNotificationLedBrightness; + private int mNotificationLedBrightnessLevel = LIGHT_BRIGHTNESS_MAXIMUM; + + private boolean mMultipleNotificationLeds; + private boolean mMultipleLedsEnabled = false; + private int mLowBatteryWarningLevel; private int mLowBatteryCloseWarningLevel; private int mShutdownBatteryTemperature; @@ -144,6 +155,13 @@ public final class BatteryService extends SystemService { private boolean mUpdatesStopped; private Led mLed; + // Disable LED until SettingsObserver can be started + private boolean mLightEnabled = false; + private boolean mLedPulseEnabled; + private int mBatteryLowARGB; + private int mBatteryMediumARGB; + private int mBatteryFullARGB; + private boolean mMultiColorLed; private boolean mSentLowBatteryBroadcast = false; @@ -205,6 +223,9 @@ public final class BatteryService extends SystemService { false, obs, UserHandle.USER_ALL); updateBatteryWarningLevelLocked(); } + } else if (phase == PHASE_BOOT_COMPLETED) { + SettingsObserver observer = new SettingsObserver(new Handler()); + observer.observe(); } } @@ -261,8 +282,10 @@ public final class BatteryService extends SystemService { private void shutdownIfNoPowerLocked() { // shut down gracefully if our battery is critically low and we are not powered. + // or the battery voltage is decreasing (consumption rate higher than charging rate) // wait until the system has booted before attempting to display the shutdown dialog. - if (mBatteryProps.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) { + if (mBatteryProps.batteryLevel == 0 && (!isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY) || + mBatteryProps.batteryVoltage < mLastBatteryVoltage) ) { mHandler.post(new Runnable() { @Override public void run() { @@ -716,24 +739,31 @@ public final class BatteryService extends SystemService { } }; + private synchronized void updateLedPulse() { + mLed.updateLightsLocked(); + } + private final class Led { private final Light mBatteryLight; - private final int mBatteryLowARGB; - private final int mBatteryMediumARGB; - private final int mBatteryFullARGB; private final int mBatteryLedOn; private final int mBatteryLedOff; public Led(Context context, LightsManager lights) { mBatteryLight = lights.getLight(LightsManager.LIGHT_ID_BATTERY); - mBatteryLowARGB = context.getResources().getInteger( - com.android.internal.R.integer.config_notificationsBatteryLowARGB); - mBatteryMediumARGB = context.getResources().getInteger( - com.android.internal.R.integer.config_notificationsBatteryMediumARGB); - mBatteryFullARGB = context.getResources().getInteger( - com.android.internal.R.integer.config_notificationsBatteryFullARGB); + // Does the Device support changing battery LED colors? + mMultiColorLed = context.getResources().getBoolean( + com.android.internal.R.bool.config_multiColorBatteryLed); + + // Is the notification LED brightness changeable ? + mAdjustableNotificationLedBrightness = context.getResources().getBoolean( + com.android.internal.R.bool.config_adjustableNotificationLedBrightness); + + // Does the Device have multiple LEDs ? + mMultipleNotificationLeds = context.getResources().getBoolean( + com.android.internal.R.bool.config_multipleNotificationLeds); + mBatteryLedOn = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryLedOn); mBatteryLedOff = context.getResources().getInteger( @@ -772,21 +802,37 @@ public final class BatteryService extends SystemService { * Synchronize on BatteryService. */ public void updateLightsLocked() { + // mBatteryProps could be null on startup (called by SettingsObserver) + if (mBatteryProps == null) { + Slog.w(TAG, "updateLightsLocked: mBatteryProps is null; skipping"); + return; + } + final int level = mBatteryProps.batteryLevel; final int status = mBatteryProps.batteryStatus; - if (level < mLowBatteryWarningLevel) { + if (!mLightEnabled) { + // No lights if explicitly disabled + mBatteryLight.turnOff(); + } else if (level < mLowBatteryWarningLevel) { + mBatteryLight.setModes(mNotificationLedBrightnessLevel, + mMultipleLedsEnabled); if (status == BatteryManager.BATTERY_STATUS_CHARGING) { - // Solid red when battery is charging + // Battery is charging and low mBatteryLight.setColor(mBatteryLowARGB); - } else { - // Flash red when battery is low and not charging + } else if (mLedPulseEnabled) { + // Battery is low and not charging mBatteryLight.setFlashing(mBatteryLowARGB, Light.LIGHT_FLASH_TIMED, mBatteryLedOn, mBatteryLedOff); + } else { + // "Pulse low battery light" is disabled, no lights. + mBatteryLight.turnOff(); } } else if (status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL) { + mBatteryLight.setModes(mNotificationLedBrightnessLevel, + mMultipleLedsEnabled); if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) { - // Solid green when full or charging and nearly full + // Battery is full or charging and nearly full mBatteryLight.setColor(mBatteryFullARGB); } else { if (isHvdcpPresent()) { @@ -794,7 +840,7 @@ public final class BatteryService extends SystemService { mBatteryLight.setFlashing(mBatteryMediumARGB, Light.LIGHT_FLASH_TIMED, mBatteryLedOn, mBatteryLedOn); } else { - // Solid orange when charging and halfway full + // Battery is charging and halfway full mBatteryLight.setColor(mBatteryMediumARGB); } } @@ -869,4 +915,96 @@ public final class BatteryService extends SystemService { } } } + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + + // Battery light enabled + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.BATTERY_LIGHT_ENABLED), false, this, UserHandle.USER_ALL); + + // Low battery pulse + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.BATTERY_LIGHT_PULSE), false, this, UserHandle.USER_ALL); + + // Notification LED brightness + if (mAdjustableNotificationLedBrightness) { + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL), + false, this, UserHandle.USER_ALL); + } + + // Multiple LEDs enabled + if (mMultipleNotificationLeds) { + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE), + false, this, UserHandle.USER_ALL); + } + + // Light colors + if (mMultiColorLed) { + // Register observer if we have a multi color led + resolver.registerContentObserver( + Settings.System.getUriFor(Settings.System.BATTERY_LIGHT_LOW_COLOR), + false, this, UserHandle.USER_ALL); + resolver.registerContentObserver( + Settings.System.getUriFor(Settings.System.BATTERY_LIGHT_MEDIUM_COLOR), + false, this, UserHandle.USER_ALL); + resolver.registerContentObserver( + Settings.System.getUriFor(Settings.System.BATTERY_LIGHT_FULL_COLOR), + false, this, UserHandle.USER_ALL); + } + + update(); + } + + @Override public void onChange(boolean selfChange) { + update(); + } + + public void update() { + ContentResolver resolver = mContext.getContentResolver(); + Resources res = mContext.getResources(); + + // Battery light enabled + mLightEnabled = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_ENABLED, 1) != 0; + + // Low battery pulse + mLedPulseEnabled = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_PULSE, 1) != 0; + + // Light colors + mBatteryLowARGB = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_LOW_COLOR, res.getInteger( + com.android.internal.R.integer.config_notificationsBatteryLowARGB)); + mBatteryMediumARGB = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_MEDIUM_COLOR, res.getInteger( + com.android.internal.R.integer.config_notificationsBatteryMediumARGB)); + mBatteryFullARGB = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_FULL_COLOR, res.getInteger( + com.android.internal.R.integer.config_notificationsBatteryFullARGB)); + + // Notification LED brightness + if (mAdjustableNotificationLedBrightness) { + mNotificationLedBrightnessLevel = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL, + LIGHT_BRIGHTNESS_MAXIMUM); + } + + // Multiple LEDs enabled + if (mMultipleNotificationLeds) { + mMultipleLedsEnabled = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE, + mMultipleNotificationLeds ? 1 : 0) != 0; + } + + updateLedPulse(); + } + } } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index fd7b050..52a45f6 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.BluetoothProfile; @@ -619,7 +623,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return true; } - public boolean enable() { + public boolean enable(String callingPackage) { if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { Log.w(TAG,"enable(): not allowed for non-active and non system user"); @@ -633,6 +637,13 @@ class BluetoothManagerService extends IBluetoothManager.Stub { " mBinding = " + mBinding); } + 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/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 63d8c45..ecfd45d 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -629,13 +629,18 @@ public class ConnectivityService extends IConnectivityManager.Stub mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper()); // setup our unique device name - if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) { + String hostname = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.DEVICE_HOSTNAME); + if (TextUtils.isEmpty(hostname) && + TextUtils.isEmpty(SystemProperties.get("net.hostname"))) { String id = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); if (id != null && id.length() > 0) { String name = new String("android-").concat(id); SystemProperties.set("net.hostname", name); } + } else { + SystemProperties.set("net.hostname", hostname); } // read our default dns server ip @@ -4408,7 +4413,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (reapUnvalidatedNetworks == ReapUnvalidatedNetworks.REAP) { for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { - if (unneeded(nai)) { + if (unneeded(nai) && nai != newNetwork) { if (DBG) log("Reaping " + nai.name()); teardownUnneededNetwork(nai); } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 4e11070..9128280 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -89,6 +89,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; import android.text.style.SuggestionSpan; @@ -465,6 +466,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, userId); resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.STATUS_BAR_IME_SWITCHER), + false, new ContentObserver(mHandler) { + public void onChange(boolean selfChange) { + updateFromSettingsLocked(true); + } + }, userId); + mRegistered = true; } @@ -1074,8 +1083,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mStatusBar = statusBar; statusBar.setIconVisibility("ime", false); updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition); - mShowOngoingImeSwitcherForPhones = mRes.getBoolean( - com.android.internal.R.bool.show_ongoing_ime_switcher); if (mShowOngoingImeSwitcherForPhones) { mWindowManagerService.setOnHardKeyboardStatusChangeListener( mHardKeyboardListener); @@ -1912,6 +1919,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurMethodId = null; unbindCurrentMethodLocked(true, false); } + // code to disable the CM Phone IME switcher with config_show_cmIMESwitcher set = false + try { + mShowOngoingImeSwitcherForPhones = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.STATUS_BAR_IME_SWITCHER) == 1; + } catch (SettingNotFoundException e) { + mShowOngoingImeSwitcherForPhones = mRes.getBoolean( + com.android.internal.R.bool.config_show_cmIMESwitcher); + } // Here is not the perfect place to reset the switching controller. Ideally // mSwitchingController and mSettings should be able to share the same state. // TODO: Make sure that mSwitchingController and mSettings are sharing the diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index bc1383c..00e86de 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -64,6 +64,7 @@ import android.location.Geofence; import android.location.IGpsGeofenceHardware; import android.location.IGpsMeasurementsListener; import android.location.IGpsNavigationMessageListener; +import android.location.GeoFenceParams; import android.location.IGpsStatusListener; import android.location.IGpsStatusProvider; import android.location.ILocationListener; @@ -90,6 +91,28 @@ import android.provider.Settings; import android.util.Log; import android.util.Slog; +import com.android.internal.content.PackageMonitor; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; +import com.android.internal.os.BackgroundThread; +import com.android.server.location.FlpHardwareProvider; +import com.android.server.location.FusedProxy; +import com.android.server.location.GeocoderProxy; +import com.android.server.location.GeofenceProxy; +import com.android.server.location.GeofenceManager; +import com.android.server.location.GeoFencerBase; +import com.android.server.location.GeoFencerProxy; +import com.android.server.location.GpsLocationProvider; +import com.android.server.location.LocationBlacklist; +import com.android.server.location.LocationFudger; +import com.android.server.location.LocationProviderInterface; +import com.android.server.location.LocationProviderProxy; +import com.android.server.location.LocationRequestStatistics; +import com.android.server.location.LocationRequestStatistics.PackageProviderKey; +import com.android.server.location.LocationRequestStatistics.PackageStatistics; +import com.android.server.location.MockProvider; +import com.android.server.location.PassiveProvider; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -157,6 +180,8 @@ public class LocationManagerService extends ILocationManager.Stub { private String mComboNlpPackageName; private String mComboNlpReadyMarker; private String mComboNlpScreenMarker; + private String mGeoFencerPackageName; + private GeoFencerBase mGeoFencer; private PowerManager mPowerManager; private UserManager mUserManager; private GeocoderProxy mGeocodeProvider; @@ -512,6 +537,15 @@ public class LocationManagerService extends ILocationManager.Stub { Slog.e(TAG, "no geocoder provider found"); } + mGeoFencerPackageName = resources.getString( + com.android.internal.R.string.config_geofenceProvider); + if (mGeoFencerPackageName != null && + mPackageManager.resolveService(new Intent(mGeoFencerPackageName), 0) != null){ + mGeoFencer = GeoFencerProxy.getGeoFencerProxy(mContext, mGeoFencerPackageName); + } else { + mGeoFencer = null; + } + // bind to fused hardware provider if supported // in devices without support, requesting an instance of FlpHardwareProvider will raise an // exception, so make sure we only do that when supported @@ -1622,9 +1656,11 @@ public class LocationManagerService extends ILocationManager.Stub { checkLocationAccess(pid, uid, packageName, allowedResolutionLevel); synchronized (mLock) { - Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid, + Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid, packageName, workSource, hideFromAppOps); - requestLocationUpdatesLocked(sanitizedRequest, recevier, pid, uid, packageName); + if (receiver != null) { + requestLocationUpdatesLocked(sanitizedRequest, receiver, pid, uid, packageName); + } } } finally { Binder.restoreCallingIdentity(identity); @@ -1683,7 +1719,9 @@ public class LocationManagerService extends ILocationManager.Stub { // providers may use public location API's, need to clear identity long identity = Binder.clearCallingIdentity(); try { - removeUpdatesLocked(receiver); + if (receiver != null) { + removeUpdatesLocked(receiver); + } } finally { Binder.restoreCallingIdentity(identity); } @@ -1822,8 +1860,20 @@ public class LocationManagerService extends ILocationManager.Stub { } long identity = Binder.clearCallingIdentity(); try { - mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel, - uid, packageName); + if (mGeoFencer != null) { + long expiration; + if (sanitizedRequest.getExpireAt() == Long.MAX_VALUE) { + expiration = -1; // -1 means forever + } else { + expiration = sanitizedRequest.getExpireAt() - SystemClock.elapsedRealtime(); + } + mGeoFencer.add(new GeoFenceParams(uid, geofence.getLatitude(), + geofence.getLongitude(), geofence.getRadius(), + expiration, intent, packageName)); + } else { + mGeofenceManager.addFence(sanitizedRequest, geofence, intent, + allowedResolutionLevel, uid, packageName); + } } finally { Binder.restoreCallingIdentity(identity); } @@ -1839,7 +1889,11 @@ public class LocationManagerService extends ILocationManager.Stub { // geo-fence manager uses the public location API, need to clear identity long identity = Binder.clearCallingIdentity(); try { - mGeofenceManager.removeFence(geofence, intent); + if (mGeoFencer != null) { + mGeoFencer.remove(intent); + } else { + mGeofenceManager.removeFence(geofence, intent); + } } finally { Binder.restoreCallingIdentity(identity); } @@ -2731,6 +2785,10 @@ public class LocationManagerService extends ILocationManager.Stub { mGeofenceManager.dump(pw); + if (mGeoFencer != null) { + mGeoFencer.dump(pw, ""); + } + if (mEnabledProviders.size() > 0) { pw.println(" Enabled Providers:"); for (String i : mEnabledProviders) { diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java index 1653db9..23556c0 100644 --- a/services/core/java/com/android/server/MasterClearReceiver.java +++ b/services/core/java/com/android/server/MasterClearReceiver.java @@ -34,6 +34,9 @@ import java.io.IOException; public class MasterClearReceiver extends BroadcastReceiver { private static final String TAG = "MasterClear"; + /* {@hide} */ + public static final String EXTRA_WIPE_MEDIA = "wipe_media"; + @Override public void onReceive(final Context context, final Intent intent) { if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) { @@ -54,7 +57,8 @@ public class MasterClearReceiver extends BroadcastReceiver { @Override public void run() { try { - RecoverySystem.rebootWipeUserData(context, shutdown, reason); + boolean wipeMedia = intent.getBooleanExtra(EXTRA_WIPE_MEDIA, false); + RecoverySystem.rebootWipeUserData(context, shutdown, reason, wipeMedia); Log.wtf(TAG, "Still running after master clear?!"); } catch (IOException e) { Slog.e(TAG, "Can't perform master clear/factory reset", e); diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index ecfd042..95c6c0e 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -838,7 +838,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub timestampNanos = SystemClock.elapsedRealtimeNanos(); } boolean isActive = cooked[2].equals("active"); - notifyInterfaceClassActivity(Integer.parseInt(cooked[3]), + notifyInterfaceClassActivity(cooked[3] == null ? 0 : Integer.parseInt(cooked[3]), isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, timestampNanos, false); return 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 0000000..0f337b8 --- /dev/null +++ b/services/core/java/com/android/server/PermissionDialog.java @@ -0,0 +1,141 @@ +/* + * 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); + } + + 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 0000000..ee94427 --- /dev/null +++ b/services/core/java/com/android/server/PermissionDialogReqQueue.java @@ -0,0 +1,82 @@ +/* + * 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); + } + } + } +} diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index cd61347..2d94370 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -18,6 +18,7 @@ package com.android.server; import android.app.ActivityManager; import android.content.pm.FeatureInfo; +import android.content.pm.Signature; import android.os.*; import android.os.Process; import android.util.ArrayMap; @@ -99,6 +100,9 @@ public class SystemConfig { // URL-handling state upon factory reset. final ArraySet<String> mLinkedApps = new ArraySet<>(); + final ArrayMap<Signature, ArraySet<String>> mSignatureAllowances + = new ArrayMap<Signature, ArraySet<String>>(); + public static SystemConfig getInstance() { synchronized (SystemConfig.class) { if (sInstance == null) { @@ -144,6 +148,10 @@ public class SystemConfig { return mLinkedApps; } + public ArrayMap<Signature, ArraySet<String>> getSignatureAllowances() { + return mSignatureAllowances; + } + SystemConfig() { // Read configuration from system readPermissions(Environment.buildPath( @@ -290,6 +298,43 @@ public class SystemConfig { perms.add(perm); XmlUtils.skipCurrentTag(parser); + } else if ("allow-permission".equals(name)) { + String perm = parser.getAttributeValue(null, "name"); + if (perm == null) { + Slog.w(TAG, + "<allow-permission> without name at " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + continue; + } + String signature = parser.getAttributeValue(null, "signature"); + if (signature == null) { + Slog.w(TAG, + "<allow-permission> without signature at " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + continue; + } + Signature sig = null; + try { + sig = new Signature(signature); + } catch (IllegalArgumentException e) { + // sig will be null so we will log it below + } + if (sig != null) { + ArraySet<String> perms = mSignatureAllowances.get(sig); + if (perms == null) { + perms = new ArraySet<String>(); + mSignatureAllowances.put(sig, perms); + } + perms.add(perm); + } else { + Slog.w(TAG, + "<allow-permission> with bad signature at " + + parser.getPositionDescription()); + } + XmlUtils.skipCurrentTag(parser); + } else if ("library".equals(name) && !onlyFeatures) { String lname = parser.getAttributeValue(null, "name"); String lfile = parser.getAttributeValue(null, "file"); diff --git a/services/core/java/com/android/server/ThemeService.java b/services/core/java/com/android/server/ThemeService.java new file mode 100644 index 0000000..2553961 --- /dev/null +++ b/services/core/java/com/android/server/ThemeService.java @@ -0,0 +1,1244 @@ +/* + * 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.server; + +import android.Manifest; +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.WallpaperManager; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.ThemeUtils; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.IThemeProcessingListener; +import android.content.res.ThemeChangeRequest; +import android.content.res.ThemeConfig; +import android.content.res.IThemeChangeListener; +import android.content.res.IThemeService; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.media.RingtoneManager; +import android.os.Binder; +import android.os.Environment; +import android.os.FileUtils; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; +import android.provider.ThemesContract; +import android.provider.ThemesContract.MixnMatchColumns; +import android.provider.ThemesContract.ThemesColumns; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.R; +import com.android.internal.util.cm.ImageUtils; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import static android.content.pm.ThemeUtils.SYSTEM_THEME_PATH; +import static android.content.pm.ThemeUtils.THEME_BOOTANIMATION_PATH; +import static android.content.res.ThemeConfig.SYSTEM_DEFAULT; + +import java.util.List; + +/** + * {@hide} + */ +public class ThemeService extends IThemeService.Stub { + private static final String TAG = ThemeService.class.getName(); + + private static final boolean DEBUG = false; + + private static final String GOOGLE_SETUPWIZARD_PACKAGE = "com.google.android.setupwizard"; + private static final String CM_SETUPWIZARD_PACKAGE = "com.cyanogenmod.account"; + + private static final long MAX_ICON_CACHE_SIZE = 33554432L; // 32MB + private static final long PURGED_ICON_CACHE_SIZE = 25165824L; // 24 MB + + // Defines a min and max compatible api level for themes on this system. + private static final int MIN_COMPATIBLE_VERSION = 21; + + private HandlerThread mWorker; + private ThemeWorkerHandler mHandler; + private ResourceProcessingHandler mResourceProcessingHandler; + private Context mContext; + private PackageManager mPM; + private int mProgress; + private boolean mWallpaperChangedByUs = false; + private long mIconCacheSize = 0L; + private int mCurrentUserId = UserHandle.USER_OWNER; + + private boolean mIsThemeApplying = false; + + private final RemoteCallbackList<IThemeChangeListener> mClients = + new RemoteCallbackList<IThemeChangeListener>(); + + private final RemoteCallbackList<IThemeProcessingListener> mProcessingListeners = + new RemoteCallbackList<IThemeProcessingListener>(); + + final private ArrayList<String> mThemesToProcessQueue = new ArrayList<String>(0); + + private class ThemeWorkerHandler extends Handler { + private static final int MESSAGE_CHANGE_THEME = 1; + private static final int MESSAGE_APPLY_DEFAULT_THEME = 2; + private static final int MESSAGE_REBUILD_RESOURCE_CACHE = 3; + + public ThemeWorkerHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_CHANGE_THEME: + final ThemeChangeRequest request = (ThemeChangeRequest) msg.obj; + doApplyTheme(request, msg.arg1 == 1); + break; + case MESSAGE_APPLY_DEFAULT_THEME: + doApplyDefaultTheme(); + break; + case MESSAGE_REBUILD_RESOURCE_CACHE: + doRebuildResourceCache(); + break; + default: + Log.w(TAG, "Unknown message " + msg.what); + break; + } + } + } + + private class ResourceProcessingHandler extends Handler { + private static final int MESSAGE_QUEUE_THEME_FOR_PROCESSING = 3; + private static final int MESSAGE_DEQUEUE_AND_PROCESS_THEME = 4; + + public ResourceProcessingHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_QUEUE_THEME_FOR_PROCESSING: + String pkgName = (String) msg.obj; + synchronized (mThemesToProcessQueue) { + if (!mThemesToProcessQueue.contains(pkgName)) { + if (DEBUG) Log.d(TAG, "Adding " + pkgName + " for processing"); + mThemesToProcessQueue.add(pkgName); + if (mThemesToProcessQueue.size() == 1) { + this.sendEmptyMessage(MESSAGE_DEQUEUE_AND_PROCESS_THEME); + } + } + } + break; + case MESSAGE_DEQUEUE_AND_PROCESS_THEME: + synchronized (mThemesToProcessQueue) { + pkgName = mThemesToProcessQueue.get(0); + } + if (pkgName != null) { + if (DEBUG) Log.d(TAG, "Processing " + pkgName); + String name; + try { + PackageInfo pi = mPM.getPackageInfo(pkgName, 0); + name = getThemeName(pi); + } catch (PackageManager.NameNotFoundException e) { + name = null; + } + + int result = mPM.processThemeResources(pkgName); + if (result < 0) { + postFailedThemeInstallNotification(name != null ? name : pkgName); + } + sendThemeResourcesCachedBroadcast(pkgName, result); + + synchronized (mThemesToProcessQueue) { + mThemesToProcessQueue.remove(0); + if (mThemesToProcessQueue.size() > 0 && + !hasMessages(MESSAGE_DEQUEUE_AND_PROCESS_THEME)) { + this.sendEmptyMessage(MESSAGE_DEQUEUE_AND_PROCESS_THEME); + } + } + postFinishedProcessing(pkgName); + } + break; + default: + Log.w(TAG, "Unknown message " + msg.what); + break; + } + } + } + + public ThemeService(Context context) { + super(); + mContext = context; + mWorker = new HandlerThread("ThemeServiceWorker", Process.THREAD_PRIORITY_BACKGROUND); + mWorker.start(); + mHandler = new ThemeWorkerHandler(mWorker.getLooper()); + Log.i(TAG, "Spawned worker thread"); + + HandlerThread processingThread = new HandlerThread("ResourceProcessingThread", + Process.THREAD_PRIORITY_BACKGROUND); + processingThread.start(); + mResourceProcessingHandler = + new ResourceProcessingHandler(processingThread.getLooper()); + + // create the theme directory if it does not exist + ThemeUtils.createThemeDirIfNotExists(); + ThemeUtils.createFontDirIfNotExists(); + ThemeUtils.createAlarmDirIfNotExists(); + ThemeUtils.createNotificationDirIfNotExists(); + ThemeUtils.createRingtoneDirIfNotExists(); + ThemeUtils.createIconCacheDirIfNotExists(); + } + + public void systemRunning() { + // listen for wallpaper changes + IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); + mContext.registerReceiver(mWallpaperChangeReceiver, filter); + + filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); + mContext.registerReceiver(mUserChangeReceiver, filter); + + mPM = mContext.getPackageManager(); + + processInstalledThemes(); + + if (!isThemeApiUpToDate()) { + Log.d(TAG, "The system has been upgraded to a theme new api, " + + "checking if currently set theme is compatible"); + removeObsoleteThemeOverlayIfExists(); + updateThemeApi(); + } + } + + private void removeObsoleteThemeOverlayIfExists() { + // Get the current overlay theme so we can see it it's overlay should be unapplied + final IActivityManager am = ActivityManagerNative.getDefault(); + ThemeConfig config = null; + try { + if (am != null) { + config = am.getConfiguration().themeConfig; + } else { + Log.e(TAG, "ActivityManager getDefault() " + + "returned null, cannot remove obsolete theme"); + } + } catch(RemoteException e) { + Log.e(TAG, "Failed to get the theme config ", e); + } + if (config == null) return; // No need to unapply a theme if one isn't set + + // Populate the currentTheme map for the components we care about, we'll look + // at the compatibility of each pkg below. + HashMap<String, String> currentThemeMap = new HashMap<String, String>(); + currentThemeMap.put(ThemesColumns.MODIFIES_STATUS_BAR, config.getOverlayForStatusBar()); + currentThemeMap.put(ThemesColumns.MODIFIES_NAVIGATION_BAR, + config.getOverlayForNavBar()); + currentThemeMap.put(ThemesColumns.MODIFIES_OVERLAYS, config.getOverlayPkgName()); + + // Look at each component's theme (that we care about at least) and check compatibility + // of the pkg with the system. If it is not compatible then we will add it to a theme + // change request. + Map<String, String> defaults = ThemeUtils.getDefaultComponents(mContext); + ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); + for(Map.Entry<String, String> entry : currentThemeMap.entrySet()) { + String component = entry.getKey(); + String pkgName = entry.getValue(); + String defaultPkg = defaults.get(component); + + // Check that the default overlay theme is not currently set + if (defaultPkg.equals(pkgName)) { + Log.d(TAG, "Current overlay theme is same as default. " + + "Not doing anything for " + component); + continue; + } + + // No need to unapply a system theme since it is always compatible + if (ThemeConfig.SYSTEM_DEFAULT.equals(pkgName)) { + Log.d(TAG, "Current overlay theme for " + + component + " was system. no need to unapply"); + continue; + } + + if (!isThemeCompatibleWithUpgradedApi(pkgName)) { + Log.d(TAG, pkgName + "is incompatible with latest theme api for component " + + component + ", Applying " + defaultPkg); + builder.setComponent(component, pkgName); + } + } + + // Now actually unapply the incompatible themes + ThemeChangeRequest request = builder.build(); + if (!request.getThemeComponentsMap().isEmpty()) { + try { + requestThemeChange(request, true); + } catch(RemoteException e) { + // This cannot happen + } + } else { + Log.d(TAG, "Current theme is compatible with the system. Not unapplying anything"); + } + } + + private boolean isThemeCompatibleWithUpgradedApi(String pkgName) { + // Note this function does not cover the case of a downgrade. That case is out of scope and + // would require predicting whether the future API levels will be compatible or not. + boolean compatible = false; + try { + PackageInfo pi = mPM.getPackageInfo(pkgName, 0); + Log.d(TAG, "Comparing theme target: " + pi.applicationInfo.targetSdkVersion + + "to " + android.os.Build.VERSION.SDK_INT); + compatible = pi.applicationInfo.targetSdkVersion >= MIN_COMPATIBLE_VERSION; + } catch (NameNotFoundException e) { + Log.e(TAG, "Unable to get package info for " + pkgName, e); + } + return compatible; + } + + private boolean isThemeApiUpToDate() { + // We can't be 100% sure its an upgrade. If the field is undefined it + // could have been a factory reset. + final ContentResolver resolver = mContext.getContentResolver(); + int recordedApiLevel = android.os.Build.VERSION.SDK_INT; + try { + recordedApiLevel = Settings.Secure.getInt(resolver, + Settings.Secure.THEME_PREV_BOOT_API_LEVEL); + } catch (SettingNotFoundException e) { + recordedApiLevel = -1; + Log.d(TAG, "Previous api level not found. First time booting?"); + } + Log.d(TAG, "Prev api level was: " + recordedApiLevel + + ", api is now: " + android.os.Build.VERSION.SDK_INT); + + return recordedApiLevel == android.os.Build.VERSION.SDK_INT; + } + + private void updateThemeApi() { + final ContentResolver resolver = mContext.getContentResolver(); + boolean success = Settings.Secure.putInt(resolver, + Settings.Secure.THEME_PREV_BOOT_API_LEVEL, android.os.Build.VERSION.SDK_INT); + if (!success) { + Log.e(TAG, "Unable to store latest API level to secure settings"); + } + } + + private void doApplyTheme(ThemeChangeRequest request, boolean removePerAppTheme) { + synchronized(this) { + mProgress = 0; + } + + if (request == null || request.getNumChangesRequested() == 0) { + postFinish(true, request, 0); + return; + } + mIsThemeApplying = true; + long updateTime = System.currentTimeMillis(); + + incrementProgress(5); + + // TODO: provide progress updates that reflect the time needed for each component + final int progressIncrement = 75 / request.getNumChangesRequested(); + + if (request.getIconsThemePackageName() != null) { + updateIcons(request.getIconsThemePackageName()); + incrementProgress(progressIncrement); + } + + if (request.getWallpaperThemePackageName() != null) { + if (updateWallpaper(request.getWallpaperThemePackageName(), + request.getWallpaperId())) { + mWallpaperChangedByUs = true; + } + incrementProgress(progressIncrement); + } + + if (request.getLockWallpaperThemePackageName() != null) { + updateLockscreen(request.getLockWallpaperThemePackageName()); + incrementProgress(progressIncrement); + } + + Environment.setUserRequired(false); + if (request.getNotificationThemePackageName() != null) { + updateNotifications(request.getNotificationThemePackageName()); + incrementProgress(progressIncrement); + } + + if (request.getAlarmThemePackageName() != null) { + updateAlarms(request.getAlarmThemePackageName()); + incrementProgress(progressIncrement); + } + + if (request.getRingtoneThemePackageName() != null) { + updateRingtones(request.getRingtoneThemePackageName()); + incrementProgress(progressIncrement); + } + Environment.setUserRequired(true); + + if (request.getBootanimationThemePackageName() != null) { + updateBootAnim(request.getBootanimationThemePackageName()); + incrementProgress(progressIncrement); + } + + if (request.getFontThemePackageName() != null) { + updateFonts(request.getFontThemePackageName()); + incrementProgress(progressIncrement); + } + + try { + updateProvider(request, updateTime); + } catch(IllegalArgumentException e) { + // Safeguard against provider not being ready yet. + Log.e(TAG, "Not updating the theme provider since it is unavailable"); + } + + if (shouldUpdateConfiguration(request)) { + updateConfiguration(request, removePerAppTheme); + } + + killLaunchers(request); + + postFinish(true, request, updateTime); + mIsThemeApplying = false; + } + + private void doApplyDefaultTheme() { + final ContentResolver resolver = mContext.getContentResolver(); + final String defaultThemePkg = Settings.Secure.getString(resolver, + Settings.Secure.DEFAULT_THEME_PACKAGE); + if (!TextUtils.isEmpty(defaultThemePkg)) { + String defaultThemeComponents = Settings.Secure.getString(resolver, + Settings.Secure.DEFAULT_THEME_COMPONENTS); + List<String> components; + if (TextUtils.isEmpty(defaultThemeComponents)) { + components = ThemeUtils.getAllComponents(); + } else { + components = new ArrayList<String>( + Arrays.asList(defaultThemeComponents.split("\\|"))); + } + ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); + for (String component : components) { + builder.setComponent(component, defaultThemePkg); + } + try { + requestThemeChange(builder.build(), true); + } catch (RemoteException e) { + Log.w(TAG, "Unable to set default theme", e); + } + } + } + + private void doRebuildResourceCache() { + FileUtils.deleteContents(new File(ThemeUtils.RESOURCE_CACHE_DIR)); + processInstalledThemes(); + } + + private void updateProvider(ThemeChangeRequest request, long updateTime) { + ContentValues values = new ContentValues(); + values.put(MixnMatchColumns.COL_UPDATE_TIME, updateTime); + Map<String, String> componentMap = request.getThemeComponentsMap(); + for (String component : componentMap.keySet()) { + values.put(ThemesContract.MixnMatchColumns.COL_VALUE, componentMap.get(component)); + String where = ThemesContract.MixnMatchColumns.COL_KEY + "=?"; + String[] selectionArgs = { MixnMatchColumns.componentToMixNMatchKey(component) }; + if (selectionArgs[0] == null) { + continue; // No equivalence between mixnmatch and theme + } + + // Add component ID for multiwallpaper + if (ThemesColumns.MODIFIES_LAUNCHER.equals(component)) { + values.put(MixnMatchColumns.COL_COMPONENT_ID, request.getWallpaperId()); + } + + mContext.getContentResolver().update(MixnMatchColumns.CONTENT_URI, values, where, + selectionArgs); + } + } + + private boolean updateIcons(String pkgName) { + ThemeUtils.clearIconCache(); + try { + if (pkgName.equals(SYSTEM_DEFAULT)) { + mPM.updateIconMaps(null); + } else { + mPM.updateIconMaps(pkgName); + } + } catch (Exception e) { + Log.w(TAG, "Changing icons failed", e); + return false; + } + return true; + } + + private boolean updateFonts(String pkgName) { + //Clear the font dir + FileUtils.deleteContents(new File(ThemeUtils.SYSTEM_THEME_FONT_PATH)); + + if (!pkgName.equals(SYSTEM_DEFAULT)) { + //Get Font Assets + Context themeCtx; + String[] assetList; + try { + themeCtx = mContext.createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY); + AssetManager assetManager = themeCtx.getAssets(); + assetList = assetManager.list("fonts"); + } catch (Exception e) { + Log.e(TAG, "There was an error getting assets for pkg " + pkgName, e); + return false; + } + if (assetList == null || assetList.length == 0) { + Log.e(TAG, "Could not find any font assets"); + return false; + } + + //Copy font assets to font dir + for(String asset : assetList) { + InputStream is = null; + OutputStream os = null; + try { + is = ThemeUtils.getInputStreamFromAsset(themeCtx, + "file:///android_asset/fonts/" + asset); + File outFile = new File(ThemeUtils.SYSTEM_THEME_FONT_PATH, asset); + FileUtils.copyToFile(is, outFile); + FileUtils.setPermissions(outFile, + FileUtils.S_IRWXU|FileUtils.S_IRGRP|FileUtils.S_IRWXO, -1, -1); + } catch (Exception e) { + Log.e(TAG, "There was an error installing the new fonts for pkg " + pkgName, e); + return false; + } finally { + ThemeUtils.closeQuietly(is); + ThemeUtils.closeQuietly(os); + } + } + } + + //Notify zygote that themes need a refresh + SystemProperties.set("sys.refresh_theme", "1"); + return true; + } + + private boolean updateBootAnim(String pkgName) { + if (SYSTEM_DEFAULT.equals(pkgName)) { + clearBootAnimation(); + return true; + } + + try { + final ApplicationInfo ai = mPM.getApplicationInfo(pkgName, 0); + applyBootAnimation(ai.sourceDir); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Changing boot animation failed", e); + return false; + } + return true; + } + + private boolean updateAlarms(String pkgName) { + return updateAudible(ThemeUtils.SYSTEM_THEME_ALARM_PATH, "alarms", + RingtoneManager.TYPE_ALARM, pkgName); + } + + private boolean updateNotifications(String pkgName) { + return updateAudible(ThemeUtils.SYSTEM_THEME_NOTIFICATION_PATH, "notifications", + RingtoneManager.TYPE_NOTIFICATION, pkgName); + } + + private boolean updateRingtones(String pkgName) { + return updateAudible(ThemeUtils.SYSTEM_THEME_RINGTONE_PATH, "ringtones", + RingtoneManager.TYPE_RINGTONE, pkgName); + } + + private boolean updateAudible(String dirPath, String assetPath, int type, String pkgName) { + //Clear the dir + ThemeUtils.clearAudibles(mContext, dirPath); + if (pkgName.equals(SYSTEM_DEFAULT)) { + if (!ThemeUtils.setDefaultAudible(mContext, type)) { + Log.e(TAG, "There was an error installing the default audio file"); + return false; + } + return true; + } + + PackageInfo pi = null; + try { + pi = mPM.getPackageInfo(pkgName, 0); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to update audible " + dirPath, e); + return false; + } + + //Get theme Assets + Context themeCtx; + String[] assetList; + try { + themeCtx = mContext.createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY); + AssetManager assetManager = themeCtx.getAssets(); + assetList = assetManager.list(assetPath); + } catch (Exception e) { + Log.e(TAG, "There was an error getting assets for pkg " + pkgName, e); + return false; + } + if (assetList == null || assetList.length == 0) { + Log.e(TAG, "Could not find any audio assets"); + return false; + } + + // TODO: right now we just load the first file but this will need to be changed + // in the future if multiple audio files are supported. + final String asset = assetList[0]; + if (!ThemeUtils.isValidAudible(asset)) return false; + + InputStream is = null; + OutputStream os = null; + try { + is = ThemeUtils.getInputStreamFromAsset(themeCtx, "file:///android_asset/" + + assetPath + File.separator + asset); + File outFile = new File(dirPath, asset); + FileUtils.copyToFile(is, outFile); + FileUtils.setPermissions(outFile, + FileUtils.S_IRWXU|FileUtils.S_IRGRP|FileUtils.S_IRWXO,-1, -1); + ThemeUtils.setAudible(mContext, outFile, type, pi.themeInfo.name); + } catch (Exception e) { + Log.e(TAG, "There was an error installing the new audio file for pkg " + pkgName, e); + return false; + } finally { + ThemeUtils.closeQuietly(is); + ThemeUtils.closeQuietly(os); + } + return true; + } + + private boolean updateLockscreen(String pkgName) { + boolean success; + success = setCustomLockScreenWallpaper(pkgName); + + // TODO: uncomment once Keyguard wallpaper is re-implemented + /* + if (success) { + mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_KEYGUARD_WALLPAPER_CHANGED), + UserHandle.ALL); + } + */ + return success; + } + + private boolean setCustomLockScreenWallpaper(String pkgName) { + // TODO: uncomment once Keyguard wallpaper is re-implemented + /* + WallpaperManager wm = WallpaperManager.getInstance(mContext); + try { + if (SYSTEM_DEFAULT.equals(pkgName) || TextUtils.isEmpty(pkgName)) { + wm.clearKeyguardWallpaper(); + } else { + InputStream in = ImageUtils.getCroppedKeyguardStream(pkgName, mContext); + if (in != null) { + wm.setKeyguardStream(in); + ThemeUtils.closeQuietly(in); + } + } + } catch (Exception e) { + Log.e(TAG, "There was an error setting lockscreen wp for pkg " + pkgName, e); + return false; + } + */ + return true; + } + + private boolean updateWallpaper(String pkgName, long id) { + WallpaperManager wm = WallpaperManager.getInstance(mContext); + if (SYSTEM_DEFAULT.equals(pkgName)) { + try { + wm.clear(); + } catch (IOException e) { + return false; + } + } else if (TextUtils.isEmpty(pkgName)) { + try { + wm.clear(false); + } catch (IOException e) { + return false; + } + } else { + InputStream in = null; + try { + in = ImageUtils.getCroppedWallpaperStream(pkgName, id, mContext); + if (in != null) + wm.setStream(in); + } catch (Exception e) { + return false; + } finally { + ThemeUtils.closeQuietly(in); + } + } + return true; + } + + private boolean updateConfiguration(ThemeChangeRequest request, + boolean removePerAppThemes) { + final IActivityManager am = ActivityManagerNative.getDefault(); + if (am != null) { + final long token = Binder.clearCallingIdentity(); + try { + Configuration config = am.getConfiguration(); + ThemeConfig.Builder themeBuilder = createBuilderFrom(config, request, null, + removePerAppThemes); + ThemeConfig newConfig = themeBuilder.build(); + + config.themeConfig = newConfig; + am.updateConfiguration(config); + } catch (RemoteException e) { + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + return true; + } + + private boolean updateConfiguration(ThemeConfig themeConfig) { + final IActivityManager am = ActivityManagerNative.getDefault(); + if (am != null) { + final long token = Binder.clearCallingIdentity(); + try { + Configuration config = am.getConfiguration(); + + config.themeConfig = themeConfig; + am.updateConfiguration(config); + } catch (RemoteException e) { + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + return true; + } + + private boolean shouldUpdateConfiguration(ThemeChangeRequest request) { + return request.getOverlayThemePackageName() != null || + request.getFontThemePackageName() != null || + request.getIconsThemePackageName() != null || + request.getStatusBarThemePackageName() != null || + request.getNavBarThemePackageName() != null || + request.getPerAppOverlays().size() > 0; + } + + private static ThemeConfig.Builder createBuilderFrom(Configuration config, + ThemeChangeRequest request, String pkgName, boolean removePerAppThemes) { + ThemeConfig.Builder builder = new ThemeConfig.Builder(config.themeConfig); + + if (removePerAppThemes) removePerAppThemesFromConfig(builder, config.themeConfig); + + if (request.getIconsThemePackageName() != null) { + builder.defaultIcon(pkgName == null ? request.getIconsThemePackageName() : pkgName); + } + + if (request.getOverlayThemePackageName() != null) { + builder.defaultOverlay(pkgName == null ? + request.getOverlayThemePackageName() : pkgName); + } + + if (request.getFontThemePackageName() != null) { + builder.defaultFont(pkgName == null ? request.getFontThemePackageName() : pkgName); + } + + if (request.getStatusBarThemePackageName() != null) { + builder.overlay(ThemeConfig.SYSTEMUI_STATUS_BAR_PKG, pkgName == null ? + request.getStatusBarThemePackageName() : pkgName); + } + + if (request.getNavBarThemePackageName() != null) { + builder.overlay(ThemeConfig.SYSTEMUI_NAVBAR_PKG, pkgName == null ? + request.getNavBarThemePackageName() : pkgName); + } + + // check for any per app overlays being applied + Map<String, String> appOverlays = request.getPerAppOverlays(); + for (String appPkgName : appOverlays.keySet()) { + if (appPkgName != null) { + builder.overlay(appPkgName, appOverlays.get(appPkgName)); + } + } + + builder.setLastThemeChangeRequestType(request.getReqeustType()); + + return builder; + } + + private static void removePerAppThemesFromConfig(ThemeConfig.Builder builder, + ThemeConfig themeConfig) { + if (themeConfig != null) { + Map<String, ThemeConfig.AppTheme> themes = themeConfig.getAppThemes(); + for (String appPkgName : themes.keySet()) { + if (ThemeUtils.isPerAppThemeComponent(appPkgName)) { + builder.overlay(appPkgName, null); + } + } + } + } + + // Kill the current Home process, they tend to be evil and cache + // drawable references in all apps + private void killLaunchers(ThemeChangeRequest request) { + if (request.getOverlayThemePackageName() == null + && request.getIconsThemePackageName() == null) { + return; + } + + final ActivityManager am = + (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); + + Intent homeIntent = new Intent(); + homeIntent.setAction(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + + List<ResolveInfo> infos = mPM.queryIntentActivities(homeIntent, 0); + List<ResolveInfo> themeChangeInfos = mPM.queryBroadcastReceivers( + new Intent(ThemeUtils.ACTION_THEME_CHANGED), 0); + for(ResolveInfo info : infos) { + if (info.activityInfo != null && info.activityInfo.applicationInfo != null && + !isSetupActivity(info) && !handlesThemeChanges( + info.activityInfo.applicationInfo.packageName, themeChangeInfos)) { + String pkgToStop = info.activityInfo.applicationInfo.packageName; + Log.d(TAG, "Force stopping " + pkgToStop + " for theme change"); + try { + am.forceStopPackage(pkgToStop); + } catch(Exception e) { + Log.e(TAG, "Unable to force stop package, did you forget platform signature?", + e); + } + } + } + } + + private boolean isSetupActivity(ResolveInfo info) { + return GOOGLE_SETUPWIZARD_PACKAGE.equals(info.activityInfo.packageName) || + CM_SETUPWIZARD_PACKAGE.equals(info.activityInfo.packageName); + } + + private boolean handlesThemeChanges(String pkgName, List<ResolveInfo> infos) { + if (infos != null && infos.size() > 0) { + for (ResolveInfo info : infos) { + if (info.activityInfo.applicationInfo.packageName.equals(pkgName)) { + return true; + } + } + } + return false; + } + + private void postProgress() { + int N = mClients.beginBroadcast(); + for(int i=0; i < N; i++) { + IThemeChangeListener listener = mClients.getBroadcastItem(0); + try { + listener.onProgress(mProgress); + } catch(RemoteException e) { + Log.w(TAG, "Unable to post progress to client listener", e); + } + } + mClients.finishBroadcast(); + } + + private void postFinish(boolean isSuccess, ThemeChangeRequest request, long updateTime) { + synchronized(this) { + mProgress = 0; + } + + int N = mClients.beginBroadcast(); + for(int i=0; i < N; i++) { + IThemeChangeListener listener = mClients.getBroadcastItem(0); + try { + listener.onFinish(isSuccess); + } catch(RemoteException e) { + Log.w(TAG, "Unable to post progress to client listener", e); + } + } + mClients.finishBroadcast(); + + // if successful, broadcast that the theme changed + if (isSuccess) { + broadcastThemeChange(request, updateTime); + } + } + + private void postFinishedProcessing(String pkgName) { + int N = mProcessingListeners.beginBroadcast(); + for(int i=0; i < N; i++) { + IThemeProcessingListener listener = mProcessingListeners.getBroadcastItem(0); + try { + listener.onFinishedProcessing(pkgName); + } catch(RemoteException e) { + Log.w(TAG, "Unable to post progress to listener", e); + } + } + mProcessingListeners.finishBroadcast(); + } + + private void broadcastThemeChange(ThemeChangeRequest request, long updateTime) { + Map<String, String> componentMap = request.getThemeComponentsMap(); + if (componentMap == null || componentMap.size() == 0) return; + + final Intent intent = new Intent(ThemeUtils.ACTION_THEME_CHANGED); + ArrayList componentsArrayList = new ArrayList(componentMap.keySet()); + intent.putStringArrayListExtra(ThemeUtils.EXTRA_COMPONENTS, componentsArrayList); + intent.putExtra(ThemeUtils.EXTRA_REQUEST_TYPE, request.getReqeustType().ordinal()); + intent.putExtra(ThemeUtils.EXTRA_UPDATE_TIME, updateTime); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } + + private void incrementProgress(int increment) { + synchronized(this) { + mProgress += increment; + if (mProgress > 100) mProgress = 100; + } + postProgress(); + } + + @Override + public void requestThemeChangeUpdates(IThemeChangeListener listener) throws RemoteException { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_THEME_MANAGER, null); + mClients.register(listener); + } + + @Override + public void removeUpdates(IThemeChangeListener listener) throws RemoteException { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_THEME_MANAGER, null); + mClients.unregister(listener); + } + + @Override + public void requestThemeChange(ThemeChangeRequest request, boolean removePerAppThemes) + throws RemoteException { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_THEME_MANAGER, null); + Message msg; + + /** + * Since the ThemeService handles compiling theme resource we need to make sure that any + * of the components we are trying to apply are either already processed or put to the + * front of the queue and handled before the theme change takes place. + * + * TODO: create a callback that can be sent to any ThemeChangeListeners to notify them that + * the theme will be applied once the processing is done. + */ + synchronized (mThemesToProcessQueue) { + Map<String, String> componentMap = request.getThemeComponentsMap(); + for (Object key : componentMap.keySet()) { + if (ThemesColumns.MODIFIES_OVERLAYS.equals(key) || + ThemesColumns.MODIFIES_NAVIGATION_BAR.equals(key) || + ThemesColumns.MODIFIES_STATUS_BAR.equals(key) || + ThemesColumns.MODIFIES_ICONS.equals(key)) { + String pkgName = (String) componentMap.get(key); + if (mThemesToProcessQueue.indexOf(pkgName) > 0) { + mThemesToProcessQueue.remove(pkgName); + mThemesToProcessQueue.add(0, pkgName); + // We want to make sure these resources are taken care of first so + // send the dequeue message and place it in the front of the queue + msg = mResourceProcessingHandler.obtainMessage( + ResourceProcessingHandler.MESSAGE_DEQUEUE_AND_PROCESS_THEME); + mResourceProcessingHandler.sendMessageAtFrontOfQueue(msg); + } + } + } + } + msg = Message.obtain(); + msg.what = ThemeWorkerHandler.MESSAGE_CHANGE_THEME; + msg.obj = request; + msg.arg1 = removePerAppThemes ? 1 : 0; + mHandler.sendMessage(msg); + } + + @Override + public void applyDefaultTheme() { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_THEME_MANAGER, null); + Message msg = Message.obtain(); + msg.what = ThemeWorkerHandler.MESSAGE_APPLY_DEFAULT_THEME; + mHandler.sendMessage(msg); + } + + @Override + public boolean isThemeApplying() throws RemoteException { + mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_THEME_MANAGER, null); + return mIsThemeApplying; + } + + @Override + public int getProgress() throws RemoteException { + mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_THEME_MANAGER, null); + synchronized(this) { + return mProgress; + } + } + + @Override + public boolean cacheComposedIcon(Bitmap icon, String fileName) throws RemoteException { + final long token = Binder.clearCallingIdentity(); + boolean success; + FileOutputStream os; + final File cacheDir = new File(ThemeUtils.SYSTEM_THEME_ICON_CACHE_DIR); + if (cacheDir.listFiles().length == 0) { + mIconCacheSize = 0; + } + try { + File outFile = new File(cacheDir, fileName); + os = new FileOutputStream(outFile); + icon.compress(Bitmap.CompressFormat.PNG, 90, os); + os.close(); + FileUtils.setPermissions(outFile, + FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH, + -1, -1); + mIconCacheSize += outFile.length(); + if (mIconCacheSize > MAX_ICON_CACHE_SIZE) { + purgeIconCache(); + } + success = true; + } catch (Exception e) { + success = false; + Log.w(TAG, "Unable to cache icon " + fileName, e); + } + Binder.restoreCallingIdentity(token); + return success; + } + + @Override + public boolean processThemeResources(String themePkgName) throws RemoteException { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_THEME_MANAGER, null); + try { + mPM.getPackageInfo(themePkgName, 0); + } catch (PackageManager.NameNotFoundException e) { + // Package doesn't exist so nothing to process + return false; + } + // Obtain a message and send it to the handler to process this theme + Message msg = mResourceProcessingHandler.obtainMessage( + ResourceProcessingHandler.MESSAGE_QUEUE_THEME_FOR_PROCESSING, 0, 0, themePkgName); + mResourceProcessingHandler.sendMessage(msg); + return true; + } + + @Override + public boolean isThemeBeingProcessed(String themePkgName) throws RemoteException { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_THEME_MANAGER, null); + synchronized (mThemesToProcessQueue) { + return mThemesToProcessQueue.contains(themePkgName); + } + } + + @Override + public void registerThemeProcessingListener(IThemeProcessingListener listener) + throws RemoteException { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_THEME_MANAGER, null); + mProcessingListeners.register(listener); + } + + @Override + public void unregisterThemeProcessingListener(IThemeProcessingListener listener) + throws RemoteException { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_THEME_MANAGER, null); + mProcessingListeners.unregister(listener); + } + + @Override + public void rebuildResourceCache() throws RemoteException { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_THEME_MANAGER, null); + mHandler.sendEmptyMessage(ThemeWorkerHandler.MESSAGE_REBUILD_RESOURCE_CACHE); + } + + private void purgeIconCache() { + Log.d(TAG, "Purging icon cahe of size " + mIconCacheSize); + File cacheDir = new File(ThemeUtils.SYSTEM_THEME_ICON_CACHE_DIR); + File[] files = cacheDir.listFiles(); + Arrays.sort(files, mOldestFilesFirstComparator); + for (File f : files) { + if (!f.isDirectory()) { + final long size = f.length(); + if(f.delete()) mIconCacheSize -= size; + } + if (mIconCacheSize <= PURGED_ICON_CACHE_SIZE) break; + } + } + + private boolean applyBootAnimation(String themePath) { + boolean success = false; + try { + ZipFile zip = new ZipFile(new File(themePath)); + ZipEntry ze = zip.getEntry(THEME_BOOTANIMATION_PATH); + if (ze != null) { + clearBootAnimation(); + BufferedInputStream is = new BufferedInputStream(zip.getInputStream(ze)); + final String bootAnimationPath = SYSTEM_THEME_PATH + File.separator + + "bootanimation.zip"; + ThemeUtils.copyAndScaleBootAnimation(mContext, is, bootAnimationPath); + FileUtils.setPermissions(bootAnimationPath, + FileUtils.S_IRWXU|FileUtils.S_IRGRP|FileUtils.S_IROTH, -1, -1); + } + zip.close(); + success = true; + } catch (Exception e) { + Log.w(TAG, "Unable to load boot animation for " + themePath, e); + } + + return success; + } + + private void clearBootAnimation() { + File anim = new File(SYSTEM_THEME_PATH + File.separator + "bootanimation.zip"); + if (anim.exists()) + anim.delete(); + } + + private BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (!mWallpaperChangedByUs) { + // In case the mixnmatch table has a mods_launcher entry, we'll clear it + ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); + builder.setWallpaper(""); + updateProvider(builder.build(), System.currentTimeMillis()); + } else { + mWallpaperChangedByUs = false; + } + } + }; + + private BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userHandle >= 0 && userHandle != mCurrentUserId) { + mCurrentUserId = userHandle; + ThemeConfig config = ThemeConfig.getBootThemeForUser(mContext.getContentResolver(), + userHandle); + if (DEBUG) { + Log.d(TAG, + "Changing theme for user " + userHandle + " to " + config.toString()); + } + ThemeChangeRequest request = new ThemeChangeRequest.Builder(config).build(); + try { + requestThemeChange(request, true); + } catch (RemoteException e) { + Log.e(TAG, "Unable to change theme for user change", e); + } + } + } + }; + + private Comparator<File> mOldestFilesFirstComparator = new Comparator<File>() { + @Override + public int compare(File lhs, File rhs) { + return (int) (lhs.lastModified() - rhs.lastModified()); + } + }; + + private void processInstalledThemes() { + final String defaultTheme = ThemeUtils.getDefaultThemePackageName(mContext); + Message msg; + // Make sure the default theme is the first to get processed! + if (!ThemeConfig.SYSTEM_DEFAULT.equals(defaultTheme)) { + msg = mHandler.obtainMessage( + ResourceProcessingHandler.MESSAGE_QUEUE_THEME_FOR_PROCESSING, + 0, 0, defaultTheme); + mResourceProcessingHandler.sendMessage(msg); + } + // Iterate over all installed packages and queue up the ones that are themes or icon packs + List<PackageInfo> packages = mPM.getInstalledPackages(0); + for (PackageInfo info : packages) { + if (!defaultTheme.equals(info.packageName) && + (info.isThemeApk || info.isLegacyIconPackApk)) { + msg = mHandler.obtainMessage( + ResourceProcessingHandler.MESSAGE_QUEUE_THEME_FOR_PROCESSING, + 0, 0, info.packageName); + mResourceProcessingHandler.sendMessage(msg); + } + } + } + + private void sendThemeResourcesCachedBroadcast(String themePkgName, int resultCode) { + final Intent intent = new Intent(Intent.ACTION_THEME_RESOURCES_CACHED); + intent.putExtra(Intent.EXTRA_THEME_PACKAGE_NAME, themePkgName); + intent.putExtra(Intent.EXTRA_THEME_RESULT, resultCode); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } + + /** + * Posts a notification to let the user know the theme was not installed. + * @param name + */ + private void postFailedThemeInstallNotification(String name) { + NotificationManager nm = + (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + Notification notice = new Notification.Builder(mContext) + .setAutoCancel(true) + .setOngoing(false) + .setContentTitle( + mContext.getString(R.string.theme_install_error_title)) + .setContentText(String.format(mContext.getString( + R.string.theme_install_error_message), + name)) + .setSmallIcon(android.R.drawable.stat_notify_error) + .setWhen(System.currentTimeMillis()) + .build(); + nm.notify(name.hashCode(), notice); + } + + private String getThemeName(PackageInfo pi) { + if (pi.themeInfo != null) { + return pi.themeInfo.name; + } else if (pi.isLegacyIconPackApk) { + return pi.applicationInfo.name; + } + + return null; + } +} diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java index e0e6070..1b82ed6 100644 --- a/services/core/java/com/android/server/WiredAccessoryManager.java +++ b/services/core/java/com/android/server/WiredAccessoryManager.java @@ -66,6 +66,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { private static final String NAME_H2W = "h2w"; private static final String NAME_USB_AUDIO = "usb_audio"; + private static final String NAME_SAMSUNG_USB_AUDIO = "dock"; private static final String NAME_HDMI_AUDIO = "hdmi_audio"; private static final String NAME_HDMI = "hdmi"; @@ -330,7 +331,8 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { FileReader file = new FileReader(uei.getSwitchStatePath()); int len = file.read(buffer, 0, 1024); file.close(); - curState = Integer.valueOf((new String(buffer, 0, len)).trim()); + curState = validateSwitchState( + Integer.valueOf((new String(buffer, 0, len)).trim())); if (curState > 0) { updateStateLocked(uei.getDevPath(), uei.getDevName(), curState); @@ -345,14 +347,23 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { } // At any given time accessories could be inserted - // one on the board, one on the dock and one on HDMI: - // observe three UEVENTs + // one on the board, one on the dock, one on the + // samsung dock and one on HDMI: + // observe all UEVENTs that have valid switch supported + // by the Kernel for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); startObserving("DEVPATH="+uei.getDevPath()); } } + private int validateSwitchState(int state) { + // Some drivers, namely HTC headset ones, add additional bits to + // the switch state. As we only are able to deal with the states + // 0, 1 and 2, mask out all the other bits + return state & 0x3; + } + private List<UEventInfo> makeObservedUEventList() { List<UEventInfo> retVal = new ArrayList<UEventInfo>(); UEventInfo uei; @@ -375,6 +386,15 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { Slog.w(TAG, "This kernel does not have usb audio support"); } + // Monitor Samsung USB audio + uei = new UEventInfo(NAME_SAMSUNG_USB_AUDIO, BIT_USB_HEADSET_DGTL, + BIT_USB_HEADSET_ANLG, 0); + if (uei.checkSwitchExists()) { + retVal.add(uei); + } else { + Slog.w(TAG, "This kernel does not have samsung usb dock audio support"); + } + // Monitor HDMI // // If the kernel has support for the "hdmi_audio" switch, use that. It will be @@ -405,7 +425,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { try { String devPath = event.get("DEVPATH"); String name = event.get("SWITCH_NAME"); - int state = Integer.parseInt(event.get("SWITCH_STATE")); + int state = validateSwitchState(Integer.parseInt(event.get("SWITCH_STATE"))); synchronized (mLock) { updateStateLocked(devPath, name, state); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2f3cdf7..fb65a15 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1,6 +1,8 @@ /* * Copyright (C) 2006-2008 The Android Open Source Project - * + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. + * Copyright (c) 2010-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. * You may obtain a copy of the License at @@ -52,6 +54,7 @@ import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetManager; import android.content.pm.PermissionInfo; import android.content.res.Resources; +import android.content.res.ThemeConfig; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; @@ -71,8 +74,8 @@ import android.util.ArraySet; import android.util.DebugUtils; import android.util.SparseIntArray; import android.view.Display; -import android.util.BoostFramework; +import android.view.InflateException; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.AssistUtils; @@ -171,6 +174,7 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.content.pm.ThemeUtils; import android.content.pm.UserInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PathPermission; @@ -179,6 +183,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.ThemeConfig; import android.net.Proxy; import android.net.ProxyInfo; import android.net.Uri; @@ -389,6 +394,8 @@ public final class ActivityManagerService extends ActivityManagerNative // How many bytes to write into the dropbox log before truncating static final int DROPBOX_MAX_SIZE = 256 * 1024; + static final String PROP_REFRESH_THEME = "sys.refresh_theme"; + // Access modes for handleIncomingUser. static final int ALLOW_NON_FULL = 0; static final int ALLOW_NON_FULL_IN_PROFILE = 1; @@ -1012,6 +1019,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean mLaunchWarningShown = false; Context mContext; + Context mUiContext; int mFactoryTest; @@ -1363,6 +1371,8 @@ public final class ActivityManagerService extends ActivityManagerNative static final int REPORT_TIME_TRACKER_MSG = 55; static final int REPORT_USER_SWITCH_COMPLETE_MSG = 56; static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 57; + static final int POST_PRIVACY_NOTIFICATION_MSG = 58; + static final int CANCEL_PRIVACY_NOTIFICATION_MSG = 59; static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -1435,7 +1445,7 @@ public final class ActivityManagerService extends ActivityManagerNative return; } if (mShowDialogs && !mSleeping && !mShuttingDown) { - Dialog d = new AppErrorDialog(mContext, + Dialog d = new AppErrorDialog(getUiContext(), ActivityManagerService.this, res, proc); d.show(); proc.crashDialog = d; @@ -1470,7 +1480,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (mShowDialogs) { Dialog d = new AppNotRespondingDialog(ActivityManagerService.this, - mContext, proc, (ActivityRecord)data.get("activity"), + getUiContext(), proc, (ActivityRecord)data.get("activity"), msg.arg1 != 0); d.show(); proc.anrDialog = d; @@ -1496,7 +1506,7 @@ public final class ActivityManagerService extends ActivityManagerNative } AppErrorResult res = (AppErrorResult) data.get("result"); if (mShowDialogs && !mSleeping && !mShuttingDown) { - Dialog d = new StrictModeViolationDialog(mContext, + Dialog d = new StrictModeViolationDialog(getUiContext(), ActivityManagerService.this, res, proc); d.show(); proc.crashDialog = d; @@ -1510,7 +1520,7 @@ public final class ActivityManagerService extends ActivityManagerNative } break; case SHOW_FACTORY_ERROR_MSG: { Dialog d = new FactoryErrorDialog( - mContext, msg.getData().getCharSequence("msg")); + getUiContext(), msg.getData().getCharSequence("msg")); d.show(); ensureBootCompleted(); } break; @@ -1521,7 +1531,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (!app.waitedForDebugger) { Dialog d = new AppWaitingForDebuggerDialog( ActivityManagerService.this, - mContext, app); + getUiContext(), app); app.waitDialog = d; app.waitedForDebugger = true; d.show(); @@ -2048,6 +2058,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_APPLICATION_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; } } }; @@ -2596,6 +2669,15 @@ public final class ActivityManagerService extends ActivityManagerNative -1, Process.SYSTEM_UID, UserHandle.USER_ALL); } + private Context getUiContext() { + synchronized (this) { + if (mUiContext == null && mBooted) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + return mUiContext != null ? mUiContext : mContext; + } + } + /** * Initialize the application bind args. These are passed to each * process when the bindApplication() IPC is sent to the process. They're @@ -3320,6 +3402,13 @@ public final class ActivityManagerService extends ActivityManagerNative debugFlags |= Zygote.DEBUG_ENABLE_ASSERT; } + //Check if zygote should refresh its fonts + boolean refreshTheme = false; + if (SystemProperties.getBoolean(PROP_REFRESH_THEME, false)) { + SystemProperties.set(PROP_REFRESH_THEME, "false"); + refreshTheme = true; + } + String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi; if (requiredAbi == null) { requiredAbi = Build.SUPPORTED_ABIS[0]; @@ -3344,7 +3433,7 @@ public final class ActivityManagerService extends ActivityManagerNative Process.ProcessStartResult startResult = Process.start(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet, - app.info.dataDir, entryPointArgs); + app.info.dataDir, refreshTheme, entryPointArgs); checkTime(startTime, "startProcess: returned from zygote!"); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); @@ -3366,17 +3455,6 @@ public final class ActivityManagerService extends ActivityManagerNative checkTime(startTime, "startProcess: building log message"); StringBuilder buf = mStringBuilder; buf.setLength(0); - if(hostingType.equals("activity")) - { - BoostFramework mPerf = null; - if (null == mPerf) { - mPerf = new BoostFramework(); - } - if (mPerf != null) { - mPerf.perfIOPrefetchStart(startResult.pid,app.processName); - } - } - buf.append("Start proc "); buf.append(startResult.pid); buf.append(':'); @@ -3866,7 +3944,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (sourceRecord == null) { throw new SecurityException("Called with bad activity token: " + resultTo); } - if (!sourceRecord.info.packageName.equals("android")) { + if (!sourceRecord.info.packageName.equals("android") && + !sourceRecord.info.packageName.equals("org.cyanogenmod.resolver")) { throw new SecurityException( "Must be called from an activity that is declared in the android package"); } @@ -5115,7 +5194,7 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void run() { synchronized (ActivityManagerService.this) { - final Dialog d = new LaunchWarningWindow(mContext, cur, next); + final Dialog d = new LaunchWarningWindow(getUiContext(), cur, next); d.show(); mUiHandler.postDelayed(new Runnable() { @Override @@ -6315,14 +6394,16 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void keyguardGoingAway(boolean disableWindowAnimations, - boolean keyguardGoingToNotificationShade) { + boolean keyguardGoingToNotificationShade, + boolean keyguardShowingMedia) { enforceNotIsolatedCaller("keyguardGoingAway"); final long token = Binder.clearCallingIdentity(); try { synchronized (this) { if (DEBUG_LOCKSCREEN) logLockScreen(""); mWindowManager.keyguardGoingAway(disableWindowAnimations, - keyguardGoingToNotificationShade); + keyguardGoingToNotificationShade, + keyguardShowingMedia); if (mLockScreenShown == LOCK_SCREEN_SHOWN) { mLockScreenShown = LOCK_SCREEN_HIDDEN; updateSleepIfNeededLocked(); @@ -6388,6 +6469,13 @@ public final class ActivityManagerService extends ActivityManagerNative } }, dumpheapFilter); + ThemeUtils.registerThemeChangeReceiver(mContext, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }); + // Let system services know. mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED); @@ -6439,7 +6527,7 @@ public final class ActivityManagerService extends ActivityManagerNative }, 0, null, null, new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED}, - AppOpsManager.OP_NONE, null, true, false, + AppOpsManager.OP_BOOT_COMPLETED, null, true, false, MY_PID, Process.SYSTEM_UID, userId); } } @@ -10244,7 +10332,13 @@ public final class ActivityManagerService extends ActivityManagerNative if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) { app.persistent = true; - app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ; + + // The Adj score defines an order of processes to be killed. + // If a process is shared by multiple apps, maxAdj must be set by the highest + // prioritized app to avoid being killed. + if (app.maxAdj >= ProcessList.PERSISTENT_PROC_ADJ) { + app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ; + } } if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) { mPersistentStartingProcesses.add(app); @@ -10682,6 +10776,7 @@ public final class ActivityManagerService extends ActivityManagerNative public void requestBugReport() { enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport"); + mContext.sendBroadcast(new Intent("android.intent.action.BUGREPORT_STARTED")); SystemProperties.set("ctl.start", "bugreport"); } @@ -10759,9 +10854,15 @@ public final class ActivityManagerService extends ActivityManagerNative return true; } } + final int anrPid = proc.pid; mHandler.post(new Runnable() { @Override public void run() { + if (anrPid != proc.pid) { + Slog.i(TAG, "Ignoring stale ANR (occurred in " + anrPid + + ", but current pid is " + proc.pid + ")"); + return; + } appNotResponding(proc, activity, parent, aboveSystem, annotation); } }); @@ -11984,6 +12085,28 @@ public final class ActivityManagerService extends ActivityManagerNative } } + private void sendAppFailureBroadcast(String pkgName) { + Intent intent = new Intent(Intent.ACTION_APP_FAILURE, + (pkgName != null)? Uri.fromParts("package", pkgName, null) : null); + mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT_OR_SELF); + } + + /** + * A possible theme crash is one that throws one of the following exceptions + * {@link android.content.res.Resources.NotFoundException} + * {@link android.view.InflateException} + * + * @param exceptionClassName + * @return True if exceptionClassName is one of the above exceptions + */ + private boolean isPossibleThemeCrash(String exceptionClassName) { + if (Resources.NotFoundException.class.getName().equals(exceptionClassName) || + InflateException.class.getName().equals(exceptionClassName)) { + return true; + } + return false; + } + private boolean handleAppCrashLocked(ProcessRecord app, String reason, String shortMsg, String longMsg, String stackTrace) { long now = SystemClock.uptimeMillis(); @@ -11994,6 +12117,9 @@ public final class ActivityManagerService extends ActivityManagerNative } else { crashTime = null; } + + if (isPossibleThemeCrash(shortMsg)) sendAppFailureBroadcast(app.info.packageName); + if (crashTime != null && now < crashTime+ProcessList.MIN_CRASH_INTERVAL) { // This process loses! Slog.w(TAG, "Process " + app.info.processName @@ -12793,6 +12919,9 @@ public final class ActivityManagerService extends ActivityManagerNative || (!allUids && app.uid != callingUid)) { continue; } + if (app.processName.equals("system")) { + continue; + } if ((app.thread != null) && (!app.crashing && !app.notResponding)) { // Generate process state info for running application ActivityManager.RunningAppProcessInfo currApp = @@ -17290,6 +17419,9 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(this) { ci = new Configuration(mConfiguration); ci.userSetLocale = false; + if (ci.themeConfig == null) { + ci.themeConfig = ThemeConfig.getBootTheme(mContext.getContentResolver()); + } } return ci; } @@ -17379,6 +17511,11 @@ public final class ActivityManagerService extends ActivityManagerNative values.locale)); } + if (values.themeConfig != null) { + saveThemeResourceLocked(values.themeConfig, + !values.themeConfig.equals(mConfiguration.themeConfig)); + } + mConfigurationSeq++; if (mConfigurationSeq <= 0) { mConfigurationSeq = 1; @@ -17434,6 +17571,10 @@ public final class ActivityManagerService extends ActivityManagerNative null, AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL); if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) { + // if locale changed, time format may have changed + final int is24Hour = android.text.format.DateFormat.is24HourFormat(mContext) ? 1 : 0; + mHandler.sendMessage(mHandler.obtainMessage(UPDATE_TIME, is24Hour, 0)); + // now send general broadcast intent = new Intent(Intent.ACTION_LOCALE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); if (!mProcessesReady) { @@ -17531,6 +17672,14 @@ public final class ActivityManagerService extends ActivityManagerNative return srec.launchedFromPackage; } + private void saveThemeResourceLocked(ThemeConfig t, boolean isDiff){ + if(isDiff) { + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Configuration.THEME_PKG_CONFIGURATION_PERSISTENCE_PROPERTY, t.toJson(), + UserHandle.USER_CURRENT); + } + } + // ========================================================= // LIFETIME MANAGEMENT // ========================================================= @@ -20268,7 +20417,7 @@ public final class ActivityManagerService extends ActivityManagerNative broadcastIntentLocked(null, null, intent, null, null, 0, null, null, new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED}, - AppOpsManager.OP_NONE, null, true, false, MY_PID, Process.SYSTEM_UID, + AppOpsManager.OP_BOOT_COMPLETED, null, true, false, MY_PID, Process.SYSTEM_UID, userId); } } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index a254451..1f1ed6f 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -40,6 +40,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.IActivityController; import android.app.ResultInfo; import android.app.ActivityManager.RunningTaskInfo; @@ -66,8 +67,10 @@ import android.service.voice.IVoiceInteractionSession; import android.util.EventLog; import android.util.Slog; import android.view.Display; -import android.util.BoostFramework; -import com.android.internal.app.ActivityTrigger; + +import com.android.server.LocalServices; + +import cyanogenmod.power.PerformanceManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -153,10 +156,6 @@ final class ActivityStack { final WindowManagerService mWindowManager; private final RecentTasks mRecentTasks; - public BoostFramework mPerf = null; - public boolean mIsAnimationBoostEnabled = false; - public int aBoostTimeOut = 0; - public int aBoostParamVal[]; /** * The back history of all previous (and possibly still * running) activities. It contains #TaskRecord objects. @@ -270,9 +269,9 @@ final class ActivityStack { } } - final Handler mHandler; + private final PerformanceManagerInternal mPerf; - static final ActivityTrigger mActivityTrigger = new ActivityTrigger(); + final Handler mHandler; final class ActivityStackHandler extends Handler { @@ -370,14 +369,7 @@ final class ActivityStack { mCurrentUser = mService.mCurrentUserId; mRecentTasks = recentTasks; mOverrideConfig = Configuration.EMPTY; - mIsAnimationBoostEnabled = mService.mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enablePerfBoostForAnimation); - if (mIsAnimationBoostEnabled) { - aBoostTimeOut = mService.mContext.getResources().getInteger( - com.android.internal.R.integer.animationboost_timeout_param); - aBoostParamVal = mService.mContext.getResources().getIntArray( - com.android.internal.R.array.animationboost_param_value); - } + mPerf = LocalServices.getService(PerformanceManagerInternal.class); } boolean okToShowLocked(ActivityRecord r) { @@ -1146,6 +1138,8 @@ final class ActivityStack { // When resuming an activity, require it to call requestVisibleBehind() again. mActivityContainer.mActivityDisplay.setVisibleBehindActivity(null); } + + updatePrivacyGuardNotificationLocked(next); } private void setVisible(ActivityRecord r, boolean visible) { @@ -1698,8 +1692,11 @@ final class ActivityStack { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next); - mActivityTrigger.activityResumeTrigger(next.intent, next.info, next.appInfo); - + // Some activities may want to alter the system power management + if (mStackSupervisor.mPerf != null) { + mStackSupervisor.mPerf.activityResumed(next.intent); + } + // If we are currently pausing an activity, then don't do anything // until that is done. if (!mStackSupervisor.allPausedActivitiesComplete()) { @@ -1821,9 +1818,6 @@ final class ActivityStack { // that the previous one will be hidden soon. This way it can know // to ignore it when computing the desired screen orientation. boolean anim = true; - if (mIsAnimationBoostEnabled == true && mPerf == null) { - mPerf = new BoostFramework(); - } if (prev != null) { if (prev.finishing) { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, @@ -1835,8 +1829,10 @@ final class ActivityStack { mWindowManager.prepareAppTransition(prev.task == next.task ? AppTransition.TRANSIT_ACTIVITY_CLOSE : AppTransition.TRANSIT_TASK_CLOSE, false); - if(prev.task != next.task && mPerf != null) { - mPerf.perfLockAcquire(aBoostTimeOut, aBoostParamVal); + if (prev.task != next.task) { + if (mStackSupervisor.mPerf != null) { + mStackSupervisor.mPerf.cpuBoost(2000 * 1000); + } } } mWindowManager.setAppWillBeHidden(prev.appToken); @@ -1853,8 +1849,10 @@ final class ActivityStack { : next.mLaunchTaskBehind ? AppTransition.TRANSIT_TASK_OPEN_BEHIND : AppTransition.TRANSIT_TASK_OPEN, false); - if(prev.task != next.task && mPerf != null) { - mPerf.perfLockAcquire(aBoostTimeOut, aBoostParamVal); + if (prev.task != next.task) { + if (mStackSupervisor.mPerf != null) { + mStackSupervisor.mPerf.cpuBoost(2000 * 1000); + } } } } @@ -2098,6 +2096,29 @@ 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); + + if (privacyGuardPackageName != null && !privacy) { + Message msg = mService.mHandler.obtainMessage( + ActivityManagerService.CANCEL_PRIVACY_NOTIFICATION_MSG, next.userId); + msg.sendToTarget(); + mStackSupervisor.mPrivacyGuardPackageName = null; + } else if (privacy) { + 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 doResume, boolean keepCurTransition, Bundle options) { TaskRecord rTask = r.task; @@ -2168,7 +2189,6 @@ final class ActivityStack { task.setFrontOfTask(); r.putInHistory(); - mActivityTrigger.activityStartTrigger(r.intent, r.info, r.appInfo); if (!isHomeStack() || numActivities() > 0) { // We want to show the starting preview window if we are // switching to a new task, or the next activity's process is diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index a4c8882..81cb623 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -101,13 +101,12 @@ import android.util.ArraySet; import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; -import android.util.BoostFramework; - import android.util.SparseIntArray; import android.view.Display; import android.view.DisplayInfo; import android.view.InputEvent; import android.view.Surface; + import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; @@ -119,6 +118,8 @@ import com.android.server.LocalServices; import com.android.server.am.ActivityStack.ActivityState; import com.android.server.wm.WindowManagerService; +import cyanogenmod.power.PerformanceManagerInternal; + import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; @@ -161,15 +162,6 @@ public final class ActivityStackSupervisor implements DisplayListener { static final int RESUME_TOP_ACTIVITY_MSG = FIRST_SUPERVISOR_STACK_MSG + 2; static final int SLEEP_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 3; static final int LAUNCH_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 4; - public BoostFramework mPerf_iop = null; - public BoostFramework mPerfBoost = null; - public BoostFramework mPerfPack = null; - public boolean mIsPerfBoostEnabled = false; - public boolean mIsperfDisablepackingEnable = false; - public int lBoostTimeOut = 0; - public int lDisPackTimeOut = 0; - public int lBoostCpuParamVal[]; - public int lBoostPackParamVal[]; static final int HANDLE_DISPLAY_ADDED = FIRST_SUPERVISOR_STACK_MSG + 5; static final int HANDLE_DISPLAY_CHANGED = FIRST_SUPERVISOR_STACK_MSG + 6; static final int HANDLE_DISPLAY_REMOVED = FIRST_SUPERVISOR_STACK_MSG + 7; @@ -282,6 +274,15 @@ public final class ActivityStackSupervisor implements DisplayListener { * setWindowManager is called. **/ private boolean mLeanbackOnlyDevice; + PowerManager mPm; + + PerformanceManagerInternal mPerf; + + /** + * 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 @@ -351,35 +352,27 @@ public final class ActivityStackSupervisor implements DisplayListener { mService = service; mRecentTasks = recentTasks; mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper()); - /* Is perf lock for cpu-boost enabled during App 1st launch */ - mIsPerfBoostEnabled = mService.mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enableCpuBoostForAppLaunch); - mIsperfDisablepackingEnable = mService.mContext.getResources().getBoolean( - com.android.internal.R.bool.config_disablePacking); + } - if(mIsPerfBoostEnabled) { - lBoostTimeOut = mService.mContext.getResources().getInteger( - com.android.internal.R.integer.launchboost_timeout_param); - lBoostCpuParamVal = mService.mContext.getResources().getIntArray( - com.android.internal.R.array.launchboost_param_value); + private void launchBoost() { + if (mPerf == null) { + mPerf = LocalServices.getService(PerformanceManagerInternal.class); + } + if (mPerf == null) { + Slog.e(TAG, "PerformanceManager not ready!"); + } else { + mPerf.launchBoost(); } - - if(mIsperfDisablepackingEnable) { - lDisPackTimeOut = mService.mContext.getResources().getInteger( - com.android.internal.R.integer.disablepacking_timeout_param); - lBoostPackParamVal = mService.mContext.getResources().getIntArray( - com.android.internal.R.array.launchboost_packing_param_value); - } } - + /** * At the time when the constructor runs, the power manager has not yet been * initialized. So we initialize our wakelocks afterwards. */ void initPowerManagement() { - PowerManager pm = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE); - mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); - mLaunchingActivity = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*launch*"); + mPm = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE); + mGoingToSleep = mPm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); + mLaunchingActivity = mPm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*launch*"); mLaunchingActivity.setReferenceCounted(false); } @@ -1457,6 +1450,8 @@ public final class ActivityStackSupervisor implements DisplayListener { Display.DEFAULT_DISPLAY : mFocusedStack.mDisplayId) : (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY : container.mActivityDisplay.mDisplayId))); + /* Acquire perf lock during new app launch */ + launchBoost(); } ActivityRecord sourceRecord = null; @@ -1509,6 +1504,11 @@ public final class ActivityStackSupervisor implements DisplayListener { if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { // We couldn't find a class that can handle the given Intent. // That's the end of that! + final Uri data = intent.getData(); + final String strData = data != null ? data.toSafeString() : null; + EventLog.writeEvent(EventLogTags.AM_INTENT_NOT_RESOLVED, callingPackage, + intent.getAction(), intent.getType(), strData, intent.getFlags()); + err = ActivityManager.START_INTENT_NOT_RESOLVED; } @@ -2797,12 +2797,10 @@ public final class ActivityStackSupervisor implements DisplayListener { } void findTaskToMoveToFrontLocked(TaskRecord task, int flags, Bundle options, String reason) { - - ActivityRecord top_activity; - top_activity = task.stack.topRunningActivityLocked(null); + ActivityRecord top = task.stack.topRunningActivityLocked(null); /* App is launching from recent apps and it's a new process */ - if(top_activity != null && top_activity.state == ActivityState.DESTROYED) { - acquireAppLaunchPerfLock(); + if(top != null && top.state == ActivityState.DESTROYED) { + launchBoost(); } if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { @@ -3069,23 +3067,6 @@ public final class ActivityStackSupervisor implements DisplayListener { resumeTopActivitiesLocked(); } - void acquireAppLaunchPerfLock() { - /* Acquire perf lock during new app launch */ - if (mIsperfDisablepackingEnable == true && mPerfPack == null) { - mPerfPack = new BoostFramework(); - } - if (mPerfPack != null) { - mPerfPack.perfLockAcquire(lDisPackTimeOut, lBoostPackParamVal); - } - - if (mIsPerfBoostEnabled == true && mPerfBoost == null) { - mPerfBoost = new BoostFramework(); - } - if (mPerfBoost != null) { - mPerfBoost.perfLockAcquire(lBoostTimeOut, lBoostCpuParamVal); - } - } - ActivityRecord findTaskLocked(ActivityRecord r) { if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { @@ -3103,33 +3084,16 @@ public final class ActivityStackSupervisor implements DisplayListener { } final ActivityRecord ar = stack.findTaskLocked(r); if (ar != null) { - if(ar.state == ActivityState.DESTROYED ) { - /*It's a new app launch */ - acquireAppLaunchPerfLock(); - - // Strat IOP - if (mPerf_iop == null) { - mPerf_iop = new BoostFramework(); - } - if (mPerf_iop != null) { - mPerf_iop.perfIOPrefetchStart(-1,r.packageName); - } + if (ar.state == ActivityState.DESTROYED) { + launchBoost(); } return ar; } } } - /* Acquire perf lock during new app launch */ - acquireAppLaunchPerfLock(); - //Start IOP - if (mPerf_iop == null) { - mPerf_iop = new BoostFramework(); - } - if (mPerf_iop != null) { - mPerf_iop.perfIOPrefetchStart(-1,r.packageName); - } - if (DEBUG_TASKS) Slog.d(TAG_TASKS, "No task found"); + launchBoost(); + return null; } diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index 9a645df..33fa714 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -78,6 +78,8 @@ option java_package com.android.server.am 30036 am_provider_lost_process (User|1|5),(Package Name|3),(UID|1|5),(Name|3) # The activity manager gave up on a new process taking too long to start 30037 am_process_start_timeout (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3) +# The activity manager was unable to resolve the intent to an Activity +30038 am_intent_not_resolved (Calling Package Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5) # Unhandled exception 30039 am_crash (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5) diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index cde2d7c..1d72fd9 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -59,6 +59,7 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioManagerInternal; import android.media.AudioPort; +import android.media.AudioRecord; import android.media.AudioRoutesInfo; import android.media.IAudioFocusDispatcher; import android.media.IAudioRoutesObserver; @@ -405,6 +406,15 @@ public class AudioService extends IAudioService.Stub { * @see System#MUTE_STREAMS_AFFECTED */ private int mMuteAffectedStreams; + /** @see #handleHotwordInput **/ + private Object mHotwordInputLock = new Object(); + + /** The package name of the application that is + * currently using the HOTWORD input. + */ + // protected by mHotwordInputLock + private String mHotwordAudioInputPackage; + /** * NOTE: setVibrateSetting(), getVibrateSetting(), shouldVibrate() are deprecated. * mVibrateSetting is just maintained during deprecation period but vibration policy is @@ -542,7 +552,6 @@ public class AudioService extends IAudioService.Stub { // Devices for which the volume is fixed and VolumePanel slider should be disabled int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | - AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_HDMI_ARC | AudioSystem.DEVICE_OUT_SPDIF | AudioSystem.DEVICE_OUT_AUX_LINE; @@ -554,6 +563,9 @@ public class AudioService extends IAudioService.Stub { private boolean mDockAudioMediaEnabled = true; + private boolean mForceAnalogDeskDock; + private boolean mForceAnalogCarDock; + private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; // Used when safe volume warning message display is requested by setStreamVolume(). In this @@ -657,6 +669,11 @@ public class AudioService extends IAudioService.Stub { mUseFixedVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); + mForceAnalogDeskDock = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_forceAnalogDeskDock); + mForceAnalogCarDock = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_forceAnalogCarDock); + // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[] // array initialized by updateStreamVolumeAlias() updateStreamVolumeAlias(false /*updateVolumes*/, TAG); @@ -1601,6 +1618,46 @@ public class AudioService extends IAudioService.Stub { sendVolumeUpdate(streamType, oldIndex, index, flags); } + /** + * Retrieve the package name of the application that currently controls + * the {@link android.media.MediaRecorder.AudioSource#HOTWORD} input. + * @return The package name of the application that controls the input + * or null if no package currently controls it. + */ + public String getCurrentHotwordInputPackageName() { + return mHotwordAudioInputPackage; + } + + /** + * Handle the change of state of the HOTWORD input. + * + * When the {@link android.media.MediaRecorder.AudioSource#HOTWORD} input is + * in use, send a broadcast to alert the new state and store the name of the current + * package that controls the input in mHotwordAudioInputPackage. + * @param listening Whether the input is being listened to. + */ + public void handleHotwordInput(boolean listening) { + synchronized (mHotwordInputLock) { + Intent broadcastIntent = new Intent(Intent.ACTION_HOTWORD_INPUT_CHANGED); + String[] packages = mContext.getPackageManager().getPackagesForUid( + Binder.getCallingUid()); + if (packages.length > 0) { + if (listening) { + mHotwordAudioInputPackage = packages[0]; + } + broadcastIntent.putExtra(Intent.EXTRA_CURRENT_PACKAGE_NAME, packages[0]); + } + broadcastIntent.putExtra(Intent.EXTRA_HOTWORD_INPUT_STATE, + listening ? AudioRecord.RECORDSTATE_RECORDING : + AudioRecord.RECORDSTATE_STOPPED); + // Set the currently listening package to null if listening has stopped. + if (!listening) { + mHotwordAudioInputPackage = null; + } + sendBroadcastToAll(broadcastIntent, Manifest.permission.CAPTURE_AUDIO_HOTWORD); + } + } + /** @see AudioManager#forceVolumeControlStream(int) */ public void forceVolumeControlStream(int streamType, IBinder cb) { synchronized(mForceControlStreamLock) { @@ -1653,11 +1710,15 @@ public class AudioService extends IAudioService.Stub { } private void sendBroadcastToAll(Intent intent) { + sendBroadcastToAll(intent, null); + } + + private void sendBroadcastToAll(Intent intent, String receiverPermission) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final long ident = Binder.clearCallingIdentity(); try { - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, receiverPermission); } finally { Binder.restoreCallingIdentity(ident); } @@ -5158,10 +5219,12 @@ public class AudioService extends IAudioService.Stub { int config; switch (dockState) { case Intent.EXTRA_DOCK_STATE_DESK: - config = AudioSystem.FORCE_BT_DESK_DOCK; + config = mForceAnalogDeskDock ? AudioSystem.FORCE_ANALOG_DOCK : + AudioSystem.FORCE_BT_DESK_DOCK; break; case Intent.EXTRA_DOCK_STATE_CAR: - config = AudioSystem.FORCE_BT_CAR_DOCK; + config = mForceAnalogCarDock ? AudioSystem.FORCE_ANALOG_DOCK : + AudioSystem.FORCE_BT_CAR_DOCK; break; case Intent.EXTRA_DOCK_STATE_LE_DESK: config = AudioSystem.FORCE_ANALOG_DOCK; diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 1ead64d..89569ec 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -131,6 +131,7 @@ public class Nat464Xlat extends BaseNetworkObserver { } try { + mNMService.unregisterObserver(this); mNMService.registerObserver(this); } catch(RemoteException e) { Slog.e(TAG, "startClat: Can't register interface observer for clat on " + mNetwork); @@ -239,16 +240,12 @@ public class Nat464Xlat extends BaseNetworkObserver { } @Override - public void interfaceLinkStateChanged(String iface, boolean up) { + public void addressUpdated(String iface, LinkAddress clatAddress) { // Called by the InterfaceObserver on its own thread, so can race with stop(). - if (isStarted() && up && mIface.equals(iface)) { + if (isStarted() && mIface.equals(iface) && clatAddress != null) { Slog.i(TAG, "interface " + iface + " is up, mIsRunning " + mIsRunning + "->true"); if (!mIsRunning) { - LinkAddress clatAddress = getLinkAddress(iface); - if (clatAddress == null) { - return; - } mIsRunning = true; maybeSetIpv6NdOffload(mBaseIface, false); LinkProperties lp = new LinkProperties(mNetwork.linkProperties); diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index f106b75..9ead529 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -41,6 +41,7 @@ import android.os.Binder; import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.SystemProperties; import android.telephony.TelephonyManager; @@ -872,6 +873,10 @@ public class Tethering extends BaseNetworkObserver { if (tm != null) { secureSetting = tm.getTetherApnRequired(); } + // Allow override of TETHER_DUN_REQUIRED via prop + int prop = SystemProperties.getInt("persist.sys.dun.override", -1); + secureSetting = ((prop < 3) && (prop >= 0)) ? prop : secureSetting; + synchronized (mPublicSync) { // 2 = not set, 0 = DUN not required, 1 = DUN required if (secureSetting != 2) { diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index e15bca6..575701a 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -22,6 +22,8 @@ import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; +import android.content.Context; +import android.content.res.Resources; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -193,11 +195,18 @@ class AutomaticBrightnessController { private int mBrightnessAdjustmentSampleOldBrightness; private float mBrightnessAdjustmentSampleOldGamma; - public AutomaticBrightnessController(Callbacks callbacks, Looper looper, + // Night mode color temperature adjustments + private final LiveDisplayController mLiveDisplay; + + private final Context mContext; + + public AutomaticBrightnessController(Context context, Callbacks callbacks, Looper looper, SensorManager sensorManager, Spline autoBrightnessSpline, int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor, int lightSensorRate, long brighteningLightDebounceConfig, - long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig) { + long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig, + LiveDisplayController ldc) { + mContext = context; mCallbacks = callbacks; mTwilight = LocalServices.getService(TwilightManager.class); mSensorManager = sensorManager; @@ -210,6 +219,7 @@ class AutomaticBrightnessController { mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; + mLiveDisplay = ldc; mHandler = new AutomaticBrightnessHandler(looper); mAmbientLightRingBuffer = new AmbientLightRingBuffer(mLightSensorRate); @@ -478,6 +488,9 @@ class AutomaticBrightnessController { } } + // Update LiveDisplay with the current lux + mLiveDisplay.updateLiveDisplay(mAmbientLux); + if (USE_TWILIGHT_ADJUSTMENT) { TwilightState state = mTwilight.getCurrentState(); if (state != null && state.isNight()) { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index b2ab797..81c3791 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -287,6 +287,8 @@ public final class DisplayManagerService extends SystemService { mOnlyCore = onlyCore; } + mDisplayPowerController.systemReady(); + mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 452378f..45065b8 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -19,6 +19,7 @@ package com.android.server.display; import com.android.internal.app.IBatteryStats; import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; +import com.android.server.lights.LightsManager; import android.animation.Animator; import android.animation.ObjectAnimator; @@ -124,6 +125,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Battery stats. private final IBatteryStats mBatteryStats; + // The lights service. + private final LightsManager mLights; + // The sensor manager. private final SensorManager mSensorManager; @@ -247,6 +251,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The controller for the automatic brightness level. private AutomaticBrightnessController mAutomaticBrightnessController; + // The controller for LiveDisplay + private final LiveDisplayController mLiveDisplayController; + // Animators. private ObjectAnimator mColorFadeOnAnimator; private ObjectAnimator mColorFadeOffAnimator; @@ -262,11 +269,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mCallbacks = callbacks; mBatteryStats = BatteryStatsService.getService(); + mLights = LocalServices.getService(LightsManager.class); mSensorManager = sensorManager; mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class); mBlanker = blanker; mContext = context; + mLiveDisplayController = new LiveDisplayController(context, handler.getLooper()); + final Resources resources = context.getResources(); final int screenBrightnessSettingMinimum = clampAbsoluteBrightness(resources.getInteger( com.android.internal.R.integer.config_screenBrightnessSettingMinimum)); @@ -343,12 +353,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (bottom < screenBrightnessRangeMinimum) { screenBrightnessRangeMinimum = bottom; } - mAutomaticBrightnessController = new AutomaticBrightnessController(this, + mAutomaticBrightnessController = new AutomaticBrightnessController(mContext, this, handler.getLooper(), sensorManager, screenAutoBrightnessSpline, lightSensorWarmUpTimeConfig, screenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate, brighteningLightDebounce, darkeningLightDebounce, - autoBrightnessResetAmbientLuxAfterWarmUp); + autoBrightnessResetAmbientLuxAfterWarmUp, mLiveDisplayController); } } @@ -573,6 +583,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } else { setProximitySensorEnabled(false); mWaitingForNegativeProximity = false; + mProximity = PROXIMITY_UNKNOWN; } if (mScreenOffBecauseOfProximity && mProximity != PROXIMITY_POSITIVE) { @@ -595,6 +606,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Use zero brightness when screen is off. if (state == Display.STATE_OFF) { brightness = PowerManager.BRIGHTNESS_OFF; + mLights.getLight(LightsManager.LIGHT_ID_BUTTONS).setBrightness(brightness); + mLights.getLight(LightsManager.LIGHT_ID_KEYBOARD).setBrightness(brightness); + } + + // Disable button lights when dozing + if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) { + mLights.getLight(LightsManager.LIGHT_ID_BUTTONS).setBrightness(PowerManager.BRIGHTNESS_OFF); + mLights.getLight(LightsManager.LIGHT_ID_KEYBOARD).setBrightness(PowerManager.BRIGHTNESS_OFF); } // Configure auto-brightness. @@ -692,6 +711,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } + // Update LiveDisplay now + mLiveDisplayController.updateLiveDisplay(); + // Determine whether the display is ready for use in the newly requested state. // Note that we do not wait for the brightness ramp animation to complete before // reporting the display is ready because we only need to ensure the screen is in the @@ -1129,6 +1151,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController.dump(pw); } + mLiveDisplayController.dump(pw); } private static String proximityToString(int state) { @@ -1190,6 +1213,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return MathUtils.constrain(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON); } + void systemReady() { + mLiveDisplayController.systemReady(); + } + private final class DisplayControllerHandler extends Handler { public DisplayControllerHandler(Looper looper) { super(looper, null, true /*async*/); diff --git a/services/core/java/com/android/server/display/LiveDisplayController.java b/services/core/java/com/android/server/display/LiveDisplayController.java new file mode 100644 index 0000000..1ed81da --- /dev/null +++ b/services/core/java/com/android/server/display/LiveDisplayController.java @@ -0,0 +1,823 @@ +/* + * 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. + * 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.display; + +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Parcel; +import android.os.PowerManagerInternal; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.util.MathUtils; +import android.util.Slog; + +import com.android.server.LocalServices; +import com.android.server.accessibility.DisplayAdjustmentUtils; +import com.android.server.twilight.TwilightListener; +import com.android.server.twilight.TwilightManager; +import com.android.server.twilight.TwilightState; + +import cyanogenmod.hardware.CMHardwareManager; + +import java.io.PrintWriter; + +public class LiveDisplayController { + + private static final String TAG = "LiveDisplay"; + + private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 1; + + private static final int OFF_TEMPERATURE = 6500; + + public static final int MODE_OFF = 0; + public static final int MODE_NIGHT = 1; + public static final int MODE_AUTO = 2; + public static final int MODE_OUTDOOR = 3; + public static final int MODE_DAY = 4; + + private int mColorTemperature = OFF_TEMPERATURE; + private float mCurrentLux = 0.0f; + + private int mHintCounter; + private int mMode; + + private boolean mOutdoorMode; + private boolean mColorEnhancement; + private boolean mLowPower; + + private final Context mContext; + private final Handler mHandler; + + private CMHardwareManager mHardware; + + private int mDayTemperature; + private int mNightTemperature; + + private boolean mUseOutdoorMode; + private boolean mUseColorEnhancement; + private boolean mUseLowPower; + + private final float[] mColorAdjustment = new float[] { 1.0f, 1.0f, 1.0f }; + private final float[] mRGB = new float[] { 0.0f, 0.0f, 0.0f }; + + private TwilightManager mTwilightManager; + private boolean mSunset = false; + + private SettingsObserver mObserver; + + private ValueAnimator mAnimator; + + private int mDefaultDayTemperature; + private int mDefaultNightTemperature; + private int mDefaultOutdoorLux; + + private boolean mInitialized = false; + + private static final int MSG_UPDATE_LIVE_DISPLAY = 1; + + // Display postprocessing can have power impact. Disable it if powersave mode is on. + private boolean mLowPerformance = false; + private PowerManagerInternal.LowPowerModeListener mLowPowerModeListener = + new PowerManagerInternal.LowPowerModeListener() { + @Override + public void onLowPowerModeChanged(boolean enabled) { + mLowPerformance = enabled; + updateLiveDisplay(mCurrentLux); + } + }; + + LiveDisplayController(Context context, Looper looper) { + mContext = context; + mHandler = new LiveDisplayHandler(looper); + } + + void systemReady() { + mHardware = CMHardwareManager.getInstance(mContext); + + mDefaultDayTemperature = mContext.getResources().getInteger( + com.android.internal.R.integer.config_dayColorTemperature); + mDefaultNightTemperature = mContext.getResources().getInteger( + com.android.internal.R.integer.config_nightColorTemperature); + mDefaultOutdoorLux = mContext.getResources().getInteger( + com.android.internal.R.integer.config_outdoorAmbientLux); + + // Counter used to determine when we should tell the user about this feature. + // If it's not used after 3 sunsets, we'll show the hint once. + mHintCounter = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.LIVE_DISPLAY_HINTED, + -3, + UserHandle.USER_CURRENT); + + mUseOutdoorMode = + mHardware.isSupported(CMHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT); + + mUseLowPower = + mHardware.isSupported(CMHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT); + if (mUseLowPower) { + mLowPower = mHardware.get(CMHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT); + } + + mUseColorEnhancement = + mHardware.isSupported(CMHardwareManager.FEATURE_COLOR_ENHANCEMENT); + if (mUseColorEnhancement) { + mColorEnhancement = + mHardware.get(CMHardwareManager.FEATURE_COLOR_ENHANCEMENT); + } + + updateSettings(); + + mObserver = new SettingsObserver(); + mObserver.register(true); + + PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); + pmi.registerLowPowerModeObserver(mLowPowerModeListener); + mLowPerformance = pmi.getLowPowerModeEnabled(); + + mTwilightManager = LocalServices.getService(TwilightManager.class); + mTwilightManager.registerListener(mTwilightListener, mHandler); + + mInitialized = true; + } + + private void updateSettings() { + mDayTemperature = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.DISPLAY_TEMPERATURE_DAY, + mDefaultDayTemperature, + UserHandle.USER_CURRENT); + mNightTemperature = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.DISPLAY_TEMPERATURE_NIGHT, + mDefaultNightTemperature, + UserHandle.USER_CURRENT); + mMode = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.DISPLAY_TEMPERATURE_MODE, + MODE_OFF, + UserHandle.USER_CURRENT); + + // Clear the hint forever + if (mMode != MODE_OFF) { + saveUserHint(1); + } + + // Manual color adjustment will be set as a space separated string of float values + String colorAdjustmentTemp = Settings.System.getStringForUser(mContext.getContentResolver(), + Settings.System.DISPLAY_COLOR_ADJUSTMENT, + UserHandle.USER_CURRENT); + String[] colorAdjustment = colorAdjustmentTemp == null ? + null : colorAdjustmentTemp.split(" "); + if (colorAdjustment == null || colorAdjustment.length != 3) { + colorAdjustment = new String[] { "1.0", "1.0", "1.0" }; + } + try { + mColorAdjustment[0] = Float.parseFloat(colorAdjustment[0]); + mColorAdjustment[1] = Float.parseFloat(colorAdjustment[1]); + mColorAdjustment[2] = Float.parseFloat(colorAdjustment[2]); + } catch (NumberFormatException e) { + Slog.e(TAG, e.getMessage(), e); + mColorAdjustment[0] = 1.0f; + mColorAdjustment[1] = 1.0f; + mColorAdjustment[2] = 1.0f; + } + + updateLiveDisplay(mCurrentLux); + } + + private final class SettingsObserver extends ContentObserver { + private final Uri DISPLAY_TEMPERATURE_DAY_URI = + Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_DAY); + private final Uri DISPLAY_TEMPERATURE_NIGHT_URI = + Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_NIGHT); + private final Uri DISPLAY_TEMPERATURE_MODE_URI = + Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_MODE); + private final Uri DISPLAY_AUTO_OUTDOOR_MODE_URI = + Settings.System.getUriFor(Settings.System.DISPLAY_AUTO_OUTDOOR_MODE); + private final Uri DISPLAY_LOW_POWER_URI = + Settings.System.getUriFor(Settings.System.DISPLAY_LOW_POWER); + private final Uri DISPLAY_COLOR_ENHANCE_URI = + Settings.System.getUriFor(Settings.System.DISPLAY_COLOR_ENHANCE); + private final Uri DISPLAY_COLOR_ADJUSTMENT_URI = + Settings.System.getUriFor(Settings.System.DISPLAY_COLOR_ADJUSTMENT); + public SettingsObserver() { + super(mHandler); + } + + public void register(boolean register) { + final ContentResolver cr = mContext.getContentResolver(); + if (register) { + cr.registerContentObserver(DISPLAY_TEMPERATURE_DAY_URI, false, this, UserHandle.USER_ALL); + cr.registerContentObserver(DISPLAY_TEMPERATURE_NIGHT_URI, false, this, UserHandle.USER_ALL); + cr.registerContentObserver(DISPLAY_TEMPERATURE_MODE_URI, false, this, UserHandle.USER_ALL); + cr.registerContentObserver(DISPLAY_AUTO_OUTDOOR_MODE_URI, false, this, UserHandle.USER_ALL); + cr.registerContentObserver(DISPLAY_LOW_POWER_URI, false, this, UserHandle.USER_ALL); + cr.registerContentObserver(DISPLAY_COLOR_ENHANCE_URI, false, this, UserHandle.USER_ALL); + cr.registerContentObserver(DISPLAY_COLOR_ADJUSTMENT_URI, false, this, UserHandle.USER_ALL); + } else { + cr.unregisterContentObserver(this); + } + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + updateSettings(); + } + } + + public void updateLiveDisplay() { + updateLiveDisplay(mCurrentLux); + } + + synchronized void updateLiveDisplay(float lux) { + mCurrentLux = lux; + mHandler.removeMessages(MSG_UPDATE_LIVE_DISPLAY); + mHandler.sendEmptyMessage(MSG_UPDATE_LIVE_DISPLAY); + } + + private synchronized void updateColorTemperature(TwilightState twilight) { + int temperature = mDayTemperature; + if (mMode == MODE_OFF || mLowPerformance) { + temperature = OFF_TEMPERATURE; + } else if (mMode == MODE_NIGHT) { + temperature = mNightTemperature; + } else if (mMode == MODE_AUTO) { + temperature = getTwilightK(twilight); + } + + if (mAnimator != null) { + mAnimator.cancel(); + } + mAnimator = ValueAnimator.ofInt(mColorTemperature, temperature); + mAnimator.setDuration(Math.abs(mColorTemperature - temperature) / 2); + mAnimator.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setDisplayTemperature((Integer)animation.getAnimatedValue()); + } + }); + mAnimator.start(); + } + + private synchronized void setDisplayTemperature(int temperature) { + mColorTemperature = temperature; + + final float[] rgb = temperatureToRGB(temperature); + + if (!mLowPerformance) { + rgb[0] *= mColorAdjustment[0]; + rgb[1] *= mColorAdjustment[1]; + rgb[2] *= mColorAdjustment[2]; + } + + if (rgb[0] == mRGB[0] && rgb[1] == mRGB[1] && rgb[2] == mRGB[2]) { + // no changes + return; + } + + System.arraycopy(rgb, 0, mRGB, 0, 3); + + Slog.d(TAG, "Adjust display temperature to " + temperature + + "K [r=" + rgb[0] + " g=" + rgb[1] + " b=" + rgb[2] + "]"); + + if (mHardware.isSupported(CMHardwareManager.FEATURE_DISPLAY_COLOR_CALIBRATION)) { + // Clear this out in case of an upgrade + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.LIVE_DISPLAY_COLOR_MATRIX, + null, + UserHandle.USER_CURRENT); + + int max = mHardware.getDisplayColorCalibrationMax(); + mHardware.setDisplayColorCalibration(new int[] { + (int) Math.ceil(rgb[0] * max), + (int) Math.ceil(rgb[1] * max), + (int) Math.ceil(rgb[2] * max) + }); + screenRefresh(); + } else { + String colorMatrixStr = null; + if (rgb[0] != 1.0f || rgb[1] != 1.0f || rgb[2] != 1.0f) { + final Float[] colorMatrix = new Float[] { + rgb[0], 0.0f, 0.0f, 0.0f, + 0.0f, rgb[1], 0.0f, 0.0f, + 0.0f, 0.0f, rgb[2], 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + colorMatrixStr = TextUtils.join(" ", colorMatrix); + } + + // For GPU color transform, go thru DisplayAdjustmentUtils in + // order to coexist with accessibility settings + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.LIVE_DISPLAY_COLOR_MATRIX, + colorMatrixStr, + UserHandle.USER_CURRENT); + + DisplayAdjustmentUtils.applyAdjustments(mContext, UserHandle.USER_CURRENT); + } + } + + /** + * Outdoor mode is optionally enabled when ambient lux > 10000 and it's daytime + * Melt faces! + * + * TODO: Use the camera or RGB sensor to determine if it's really sunlight + */ + private synchronized void updateOutdoorMode(TwilightState twilight) { + if (!mUseOutdoorMode) { + return; + } + + boolean value = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.DISPLAY_AUTO_OUTDOOR_MODE, + 1, + UserHandle.USER_CURRENT) == 1; + + boolean enabled = !mLowPerformance && + ((mMode == MODE_OUTDOOR) || + (value && mMode == MODE_AUTO && + twilight != null && !twilight.isNight() && + mCurrentLux > mDefaultOutdoorLux)); + + if (enabled == mOutdoorMode) { + return; + } + + mHardware.set(CMHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT, enabled); + mOutdoorMode = enabled; + } + + /** + * Color enhancement is optional, but can look bad with night mode + */ + private synchronized void updateColorEnhancement(TwilightState twilight) { + if (!mUseColorEnhancement) { + return; + } + + boolean value = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.DISPLAY_COLOR_ENHANCE, + 1, + UserHandle.USER_CURRENT) == 1; + + boolean enabled = !mLowPerformance && value && + !(mMode == MODE_NIGHT || + (mMode == MODE_AUTO && twilight != null && twilight.isNight())); + + if (enabled == mColorEnhancement) { + return; + } + + mHardware.set(CMHardwareManager.FEATURE_COLOR_ENHANCEMENT, enabled); + mColorEnhancement = enabled; + } + + /** + * Adaptive backlight / low power mode. Turn it off when under very bright light. + */ + private synchronized void updateLowPowerMode() { + if (!mUseLowPower) { + return; + } + + boolean value = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.DISPLAY_LOW_POWER, + 1, + UserHandle.USER_CURRENT) == 1; + + boolean enabled = value && (mCurrentLux < mDefaultOutdoorLux); + + if (enabled == mLowPower) { + return; + } + + mHardware.set(CMHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT, enabled); + mLowPower = enabled; + } + + /** + * Convert a color temperature value (in Kelvin) to a RGB units as floats. + * This can be used in a transform matrix or hardware gamma control. + * + * @param tempK + * @return + */ + private static float[] temperatureToRGB(int degreesK) { + int k = MathUtils.constrain(degreesK, 1000, 20000); + float a = (k % 100) / 100.0f; + int i = ((k - 1000)/ 100) * 3; + + return new float[] { interp(i, a), interp(i+1, a), interp(i+2, a) }; + } + + private static float interp(int i, float a) { + return MathUtils.lerp((float)sColorTable[i], (float)sColorTable[i+3], a); + } + + /** + * Where is the sun anyway? This calculation determines day or night, and scales + * the value around sunset/sunrise for a smooth transition. + * + * @param now + * @param sunset + * @param sunrise + * @return float between 0 and 1 + */ + private static float adj(long now, long sunset, long sunrise) { + if (sunset < 0 || sunrise < 0 + || now < sunset || now > sunrise) { + return 1.0f; + } + + if (now < sunset + TWILIGHT_ADJUSTMENT_TIME) { + return MathUtils.lerp(1.0f, 0.0f, + (float)(now - sunset) / TWILIGHT_ADJUSTMENT_TIME); + } + + if (now > sunrise - TWILIGHT_ADJUSTMENT_TIME) { + return MathUtils.lerp(1.0f, 0.0f, + (float)(sunrise - now) / TWILIGHT_ADJUSTMENT_TIME); + } + + return 0.0f; + } + + /** + * Determine the color temperature we should use for the display based on + * the position of the sun. + * + * @param state + * @return color temperature in Kelvin + */ + private int getTwilightK(TwilightState state) { + float adjustment = 1.0f; + + if (state != null) { + final long now = System.currentTimeMillis(); + adjustment = adj(now, state.getYesterdaySunset(), state.getTodaySunrise()) * + adj(now, state.getTodaySunset(), state.getTomorrowSunrise()); + } + + return (int)MathUtils.lerp(mNightTemperature, mDayTemperature, adjustment); + } + + /** + * Tell SurfaceFlinger to repaint the screen. This is called after updating + * hardware registers for display calibration to have an immediate effect. + */ + private static void screenRefresh() { + try { + final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); + if (flinger != null) { + final Parcel data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + flinger.transact(1004, data, null, 0); + data.recycle(); + } + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to refresh screen", ex); + } + } + + private void saveUserHint(int value) { + if (mHintCounter == value) { + return; + } + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.LIVE_DISPLAY_HINTED, + value, + UserHandle.USER_CURRENT); + mHintCounter = value; + } + + /** + * Show a friendly notification to the user about the potential benefits of decreasing + * blue light at night. Do this only once if the feature has not been used after + * three sunsets. It would be great to enable this by default, but we don't want + * the change of screen color to be considered a "bug" by a user who doesn't + * understand what's happening. + * + * @param state + */ + private void updateUserHint(TwilightState state) { + // check if we should send the hint only once after sunset + if (state == null || mHintCounter == 1) { + return; + } + boolean transition = state.isNight() && !mSunset; + mSunset = state.isNight(); + if (!transition) { + return; + } + + if (mHintCounter <= 0) { + mHintCounter++; + saveUserHint(mHintCounter); + } + if (mHintCounter == 0) { + //show the notification and don't come back here + final Intent intent = new Intent("android.settings.LIVEDISPLAY_SETTINGS"); + PendingIntent result = PendingIntent.getActivity( + mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + Notification.Builder builder = new Notification.Builder(mContext) + .setContentTitle(mContext.getResources().getString( + com.android.internal.R.string.live_display_title)) + .setContentText(mContext.getResources().getString( + com.android.internal.R.string.live_display_hint)) + .setSmallIcon(com.android.internal.R.drawable.ic_livedisplay_notif) + .setStyle(new Notification.BigTextStyle().bigText(mContext.getResources() + .getString(com.android.internal.R.string.live_display_hint))) + .setContentIntent(result); + + NotificationManager nm = + (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE); + nm.notifyAsUser(null, 1, builder.build(), UserHandle.CURRENT); + + saveUserHint(1); + } + } + + private final TwilightListener mTwilightListener = new TwilightListener() { + @Override + public void onTwilightStateChanged() { + updateLiveDisplay(mCurrentLux); + } + }; + + private final class LiveDisplayHandler extends Handler { + public LiveDisplayHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_LIVE_DISPLAY: + if (!mInitialized) { + break; + } + TwilightState twilight = mTwilightManager.getCurrentState(); + + updateColorTemperature(twilight); + updateOutdoorMode(twilight); + updateColorEnhancement(twilight); + updateLowPowerMode(); + updateUserHint(twilight); + + boolean transition = mMode == MODE_AUTO && + mColorTemperature != mDayTemperature && + mColorTemperature != mNightTemperature; + if (transition) { + // fire again in a minute + sendEmptyMessageDelayed(MSG_UPDATE_LIVE_DISPLAY, + DateUtils.MINUTE_IN_MILLIS); + } + break; + } + } + } + + public void dump(PrintWriter pw) { + pw.println(); + pw.println("LiveDisplay Controller Configuration:"); + pw.println(" mDayTemperature=" + mDayTemperature); + pw.println(" mNightTemperature=" + mNightTemperature); + pw.println(); + pw.println("LiveDisplay Controller State:"); + pw.println(" mMode=" + (mLowPerformance ? "disabled in powersave mode" : mMode)); + pw.println(" mSunset=" + mSunset); + pw.println(" mColorTemperature=" + mColorTemperature); + pw.println(" mColorAdjustment=[r: " + mColorAdjustment[0] + " g:" + mColorAdjustment[1] + + " b:" + mColorAdjustment[2] + "]"); + pw.println(" mRGB=[r:" + mRGB[0] + " g:" + mRGB[1] + " b:" + mRGB[2] + "]"); + pw.println(" mOutdoorMode=" + (mUseOutdoorMode ? mOutdoorMode : "N/A")); + pw.println(" mColorEnhancement=" + (mUseColorEnhancement ? mColorEnhancement : "N/A")); + pw.println(" mLowPower=" + (mUseLowPower ? mLowPower : "N/A")); + } + + /** + * This table is a modified version of the original blackbody chart, found here: + * http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html + * + * Created by Ingo Thiel. + */ + private static final double[] sColorTable = new double[] { + 1.00000000, 0.18172716, 0.00000000, + 1.00000000, 0.25503671, 0.00000000, + 1.00000000, 0.30942099, 0.00000000, + 1.00000000, 0.35357379, 0.00000000, + 1.00000000, 0.39091524, 0.00000000, + 1.00000000, 0.42322816, 0.00000000, + 1.00000000, 0.45159884, 0.00000000, + 1.00000000, 0.47675916, 0.00000000, + 1.00000000, 0.49923747, 0.00000000, + 1.00000000, 0.51943421, 0.00000000, + 1.00000000, 0.54360078, 0.08679949, + 1.00000000, 0.56618736, 0.14065513, + 1.00000000, 0.58734976, 0.18362641, + 1.00000000, 0.60724493, 0.22137978, + 1.00000000, 0.62600248, 0.25591950, + 1.00000000, 0.64373109, 0.28819679, + 1.00000000, 0.66052319, 0.31873863, + 1.00000000, 0.67645822, 0.34786758, + 1.00000000, 0.69160518, 0.37579588, + 1.00000000, 0.70602449, 0.40267128, + 1.00000000, 0.71976951, 0.42860152, + 1.00000000, 0.73288760, 0.45366838, + 1.00000000, 0.74542112, 0.47793608, + 1.00000000, 0.75740814, 0.50145662, + 1.00000000, 0.76888303, 0.52427322, + 1.00000000, 0.77987699, 0.54642268, + 1.00000000, 0.79041843, 0.56793692, + 1.00000000, 0.80053332, 0.58884417, + 1.00000000, 0.81024551, 0.60916971, + 1.00000000, 0.81957693, 0.62893653, + 1.00000000, 0.82854786, 0.64816570, + 1.00000000, 0.83717703, 0.66687674, + 1.00000000, 0.84548188, 0.68508786, + 1.00000000, 0.85347859, 0.70281616, + 1.00000000, 0.86118227, 0.72007777, + 1.00000000, 0.86860704, 0.73688797, + 1.00000000, 0.87576611, 0.75326132, + 1.00000000, 0.88267187, 0.76921169, + 1.00000000, 0.88933596, 0.78475236, + 1.00000000, 0.89576933, 0.79989606, + 1.00000000, 0.90198230, 0.81465502, + 1.00000000, 0.90963069, 0.82838210, + 1.00000000, 0.91710889, 0.84190889, + 1.00000000, 0.92441842, 0.85523742, + 1.00000000, 0.93156127, 0.86836903, + 1.00000000, 0.93853986, 0.88130458, + 1.00000000, 0.94535695, 0.89404470, + 1.00000000, 0.95201559, 0.90658983, + 1.00000000, 0.95851906, 0.91894041, + 1.00000000, 0.96487079, 0.93109690, + 1.00000000, 0.97107439, 0.94305985, + 1.00000000, 0.97713351, 0.95482993, + 1.00000000, 0.98305189, 0.96640795, + 1.00000000, 0.98883326, 0.97779486, + 1.00000000, 0.99448139, 0.98899179, + 1.00000000, 1.00000000, 1.00000000, + 0.98947904, 0.99348723, 1.00000000, + 0.97940448, 0.98722715, 1.00000000, + 0.96975025, 0.98120637, 1.00000000, + 0.96049223, 0.97541240, 1.00000000, + 0.95160805, 0.96983355, 1.00000000, + 0.94303638, 0.96443333, 1.00000000, + 0.93480451, 0.95923080, 1.00000000, + 0.92689056, 0.95421394, 1.00000000, + 0.91927697, 0.94937330, 1.00000000, + 0.91194747, 0.94470005, 1.00000000, + 0.90488690, 0.94018594, 1.00000000, + 0.89808115, 0.93582323, 1.00000000, + 0.89151710, 0.93160469, 1.00000000, + 0.88518247, 0.92752354, 1.00000000, + 0.87906581, 0.92357340, 1.00000000, + 0.87315640, 0.91974827, 1.00000000, + 0.86744421, 0.91604254, 1.00000000, + 0.86191983, 0.91245088, 1.00000000, + 0.85657444, 0.90896831, 1.00000000, + 0.85139976, 0.90559011, 1.00000000, + 0.84638799, 0.90231183, 1.00000000, + 0.84153180, 0.89912926, 1.00000000, + 0.83682430, 0.89603843, 1.00000000, + 0.83225897, 0.89303558, 1.00000000, + 0.82782969, 0.89011714, 1.00000000, + 0.82353066, 0.88727974, 1.00000000, + 0.81935641, 0.88452017, 1.00000000, + 0.81530175, 0.88183541, 1.00000000, + 0.81136180, 0.87922257, 1.00000000, + 0.80753191, 0.87667891, 1.00000000, + 0.80380769, 0.87420182, 1.00000000, + 0.80018497, 0.87178882, 1.00000000, + 0.79665980, 0.86943756, 1.00000000, + 0.79322843, 0.86714579, 1.00000000, + 0.78988728, 0.86491137, 1.00000000, + 0.78663296, 0.86273225, 1.00000000, + 0.78346225, 0.86060650, 1.00000000, + 0.78037207, 0.85853224, 1.00000000, + 0.77735950, 0.85650771, 1.00000000, + 0.77442176, 0.85453121, 1.00000000, + 0.77155617, 0.85260112, 1.00000000, + 0.76876022, 0.85071588, 1.00000000, + 0.76603147, 0.84887402, 1.00000000, + 0.76336762, 0.84707411, 1.00000000, + 0.76076645, 0.84531479, 1.00000000, + 0.75822586, 0.84359476, 1.00000000, + 0.75574383, 0.84191277, 1.00000000, + 0.75331843, 0.84026762, 1.00000000, + 0.75094780, 0.83865816, 1.00000000, + 0.74863017, 0.83708329, 1.00000000, + 0.74636386, 0.83554194, 1.00000000, + 0.74414722, 0.83403311, 1.00000000, + 0.74197871, 0.83255582, 1.00000000, + 0.73985682, 0.83110912, 1.00000000, + 0.73778012, 0.82969211, 1.00000000, + 0.73574723, 0.82830393, 1.00000000, + 0.73375683, 0.82694373, 1.00000000, + 0.73180765, 0.82561071, 1.00000000, + 0.72989845, 0.82430410, 1.00000000, + 0.72802807, 0.82302316, 1.00000000, + 0.72619537, 0.82176715, 1.00000000, + 0.72439927, 0.82053539, 1.00000000, + 0.72263872, 0.81932722, 1.00000000, + 0.72091270, 0.81814197, 1.00000000, + 0.71922025, 0.81697905, 1.00000000, + 0.71756043, 0.81583783, 1.00000000, + 0.71593234, 0.81471775, 1.00000000, + 0.71433510, 0.81361825, 1.00000000, + 0.71276788, 0.81253878, 1.00000000, + 0.71122987, 0.81147883, 1.00000000, + 0.70972029, 0.81043789, 1.00000000, + 0.70823838, 0.80941546, 1.00000000, + 0.70678342, 0.80841109, 1.00000000, + 0.70535469, 0.80742432, 1.00000000, + 0.70395153, 0.80645469, 1.00000000, + 0.70257327, 0.80550180, 1.00000000, + 0.70121928, 0.80456522, 1.00000000, + 0.69988894, 0.80364455, 1.00000000, + 0.69858167, 0.80273941, 1.00000000, + 0.69729688, 0.80184943, 1.00000000, + 0.69603402, 0.80097423, 1.00000000, + 0.69479255, 0.80011347, 1.00000000, + 0.69357196, 0.79926681, 1.00000000, + 0.69237173, 0.79843391, 1.00000000, + 0.69119138, 0.79761446, 1.00000000, + 0.69003044, 0.79680814, 1.00000000, + 0.68888844, 0.79601466, 1.00000000, + 0.68776494, 0.79523371, 1.00000000, + 0.68665951, 0.79446502, 1.00000000, + 0.68557173, 0.79370830, 1.00000000, + 0.68450119, 0.79296330, 1.00000000, + 0.68344751, 0.79222975, 1.00000000, + 0.68241029, 0.79150740, 1.00000000, + 0.68138918, 0.79079600, 1.00000000, + 0.68038380, 0.79009531, 1.00000000, + 0.67939381, 0.78940511, 1.00000000, + 0.67841888, 0.78872517, 1.00000000, + 0.67745866, 0.78805526, 1.00000000, + 0.67651284, 0.78739518, 1.00000000, + 0.67558112, 0.78674472, 1.00000000, + 0.67466317, 0.78610368, 1.00000000, + 0.67375872, 0.78547186, 1.00000000, + 0.67286748, 0.78484907, 1.00000000, + 0.67198916, 0.78423512, 1.00000000, + 0.67112350, 0.78362984, 1.00000000, + 0.67027024, 0.78303305, 1.00000000, + 0.66942911, 0.78244457, 1.00000000, + 0.66859988, 0.78186425, 1.00000000, + 0.66778228, 0.78129191, 1.00000000, + 0.66697610, 0.78072740, 1.00000000, + 0.66618110, 0.78017057, 1.00000000, + 0.66539706, 0.77962127, 1.00000000, + 0.66462376, 0.77907934, 1.00000000, + 0.66386098, 0.77854465, 1.00000000, + 0.66310852, 0.77801705, 1.00000000, + 0.66236618, 0.77749642, 1.00000000, + 0.66163375, 0.77698261, 1.00000000, + 0.66091106, 0.77647551, 1.00000000, + 0.66019791, 0.77597498, 1.00000000, + 0.65949412, 0.77548090, 1.00000000, + 0.65879952, 0.77499315, 1.00000000, + 0.65811392, 0.77451161, 1.00000000, + 0.65743716, 0.77403618, 1.00000000, + 0.65676908, 0.77356673, 1.00000000, + 0.65610952, 0.77310316, 1.00000000, + 0.65545831, 0.77264537, 1.00000000, + 0.65481530, 0.77219324, 1.00000000, + 0.65418036, 0.77174669, 1.00000000, + 0.65355332, 0.77130560, 1.00000000, + 0.65293404, 0.77086988, 1.00000000, + 0.65232240, 0.77043944, 1.00000000, + 0.65171824, 0.77001419, 1.00000000, + 0.65112144, 0.76959404, 1.00000000, + 0.65053187, 0.76917889, 1.00000000, + 0.64994941, 0.76876866, 1.00000000, + 0.64937392, 0.76836326, 1.00000000 + }; +} diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 8813a61..7d1dbe1 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -18,6 +18,7 @@ package com.android.server.dreams; import static android.Manifest.permission.BIND_DREAM_SERVICE; +import android.view.WindowManagerPolicy; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.SystemService; @@ -84,6 +85,7 @@ public final class DreamManagerService extends SystemService { private boolean mCurrentDreamIsWaking; private int mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN; private int mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT; + private int mLidState = WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; public DreamManagerService(Context context) { super(context); @@ -150,6 +152,12 @@ public final class DreamManagerService extends SystemService { } } + private boolean isDozingInternal() { + synchronized (mLock) { + return mCurrentDreamIsDozing; + } + } + private void requestDreamInternal() { // Ask the power manager to nap. It will eventually call back into // startDream() if/when it is appropriate to start dreaming. @@ -219,7 +227,8 @@ public final class DreamManagerService extends SystemService { } synchronized (mLock) { - if (mCurrentDreamToken == token && mCurrentDreamCanDoze) { + if (mCurrentDreamToken == token && mCurrentDreamCanDoze + && mLidState != WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED) { mCurrentDreamDozeScreenState = screenState; mCurrentDreamDozeScreenBrightness = screenBrightness; mPowerManagerInternal.setDozeOverrideFromDreamManager( @@ -232,6 +241,40 @@ public final class DreamManagerService extends SystemService { } } + private int getLidStateInternal() { + return mLidState; + } + + private void setLidStateInternal(int state) { + synchronized (mLock) { + mLidState = state; + } + switch (state) { + case WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT: + // do nothing + break; + case WindowManagerPolicy.WindowManagerFuncs.LID_OPEN: + synchronized (mLock) { + mPowerManagerInternal.setDozeOverrideFromDreamManager( + Display.STATE_UNKNOWN, PowerManager.BRIGHTNESS_DEFAULT); + } + break; + case WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED: + // mimicing logic from stopDozingInternal(), stop any thing when the lid is closed. + synchronized (mLock) { + if (mCurrentDreamIsDozing) { + mCurrentDreamIsDozing = false; + if (mDozeWakeLock.isHeld()) { + mDozeWakeLock.release(); + } + mPowerManagerInternal.setDozeOverrideFromDreamManager( + Display.STATE_OFF, PowerManager.BRIGHTNESS_OFF); + } + } + break; + } + } + private void stopDozingInternal(IBinder token) { if (DEBUG) { Slog.d(TAG, "Dream requested to stop dozing: " + token); @@ -530,6 +573,18 @@ public final class DreamManagerService extends SystemService { } @Override // Binder call + public boolean isDozing() { + checkPermission(android.Manifest.permission.READ_DREAM_STATE); + + final long ident = Binder.clearCallingIdentity(); + try { + return isDozingInternal(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call public void dream() { checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); @@ -621,6 +676,30 @@ public final class DreamManagerService extends SystemService { Binder.restoreCallingIdentity(ident); } } + + @Override + public void setLidState(int lidState) { + checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + + final long ident = Binder.clearCallingIdentity(); + try { + setLidStateInternal(lidState); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public int getLidState() { + checkPermission(Manifest.permission.READ_DREAM_STATE); + + final long ident = Binder.clearCallingIdentity(); + try { + return getLidStateInternal(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } } private final class LocalService extends DreamManagerInternal { @@ -638,6 +717,11 @@ public final class DreamManagerService extends SystemService { public boolean isDreaming() { return isDreamingInternal(); } + + @Override + public boolean isDozing() { + return isDozingInternal(); + } } private final Runnable mSystemPropertiesChanged = new Runnable() { diff --git a/services/core/java/com/android/server/gesture/GestureInputFilter.java b/services/core/java/com/android/server/gesture/GestureInputFilter.java new file mode 100644 index 0000000..e40761f --- /dev/null +++ b/services/core/java/com/android/server/gesture/GestureInputFilter.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2013 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.server.gesture; + +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.content.Context; +import android.hardware.input.InputManager; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Slog; +import android.view.Display; +import android.view.GestureDetector; +import android.view.GestureDetector.OnDoubleTapListener; +import android.view.IInputFilter; +import android.view.IInputFilterHost; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.MotionEvent; +import android.view.OrientationEventListener; +import android.view.ViewConfiguration; +import android.view.WindowManager; +import java.io.PrintWriter; + +/** + * A simple input filter that listens for gesture sensor events and converts + * them to input events to be injected into the input stream. + */ +public class GestureInputFilter implements IInputFilter, GestureDetector.OnGestureListener, OnDoubleTapListener { + + private static final String TAG = "GestureInputFilter"; + private static final boolean DEBUG = false; + + private IInputFilterHost mHost = null; + + private GestureDetector mGestureDetector; + private InputManager mInputManager; + private OrientationEventListener mOrientationListener; + private final int mScreenWidth, mScreenHeight; + private float mGesturePadWidth, mGesturePadHeight; + private int mTouchSlop, mOrientation; + private Context mContext; + private PendingIntent mLongPressPendingIntent; + private PendingIntent mDoubleClickPendingIntent; + + public GestureInputFilter(Context context) { + mInputManager = InputManager.getInstance(); + mContext = context; + for (int id : mInputManager.getInputDeviceIds()) { + InputDevice inputDevice = mInputManager.getInputDevice(id); + if ((inputDevice.getSources() & InputDevice.SOURCE_GESTURE_SENSOR) + == mInputManager.getInputDevice(id).getSources()) { + mGesturePadWidth = inputDevice.getMotionRange(MotionEvent.AXIS_X).getMax(); + mGesturePadHeight = inputDevice.getMotionRange(MotionEvent.AXIS_Y).getMax(); + break; + } + } + ViewConfiguration vc = ViewConfiguration.get(context); + mTouchSlop = vc.getScaledTouchSlop(); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + mScreenWidth = display.getWidth(); + mScreenHeight = display.getHeight(); + mGestureDetector = new GestureDetector(context, this); + mGestureDetector.setOnDoubleTapListener(this); + mOrientationListener = new OrientationEventListener(context) { + @Override + public void onOrientationChanged(int orientation) { + if (orientation == -1) { + return; + } + mOrientation = (orientation + 45) / 90 * 90; + } + }; + } + + /** + * Called to enqueue the input event for filtering. + * The event must be recycled after the input filter processed it. + * This method is guaranteed to be non-reentrant. + * + * @see InputFilter#filterInputEvent(InputEvent, int) + * @param event The input event to enqueue. + */ + // called by the input dispatcher thread + public void filterInputEvent(InputEvent event, int policyFlags) + throws RemoteException { + if (DEBUG) Slog.d(TAG, event.toString()); + + try { + if (event.getSource() != InputDevice.SOURCE_GESTURE_SENSOR + || !(event instanceof MotionEvent)) { + try { + mHost.sendInputEvent(event, policyFlags); + } catch (RemoteException e) { + /* ignore */ + } + return; + } + + MotionEvent motionEvent = (MotionEvent) event; + mGestureDetector.onTouchEvent(motionEvent); + } finally { + event.recycle(); + } + } + + // called by the input dispatcher thread + public void install(IInputFilterHost host) throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "Gesture input filter installed."); + } + mHost = host; + mOrientationListener.enable(); + } + + // called by the input dispatcher thread + public void uninstall() throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "Gesture input filter uninstalled."); + } + mHost = null; + mOrientationListener.disable(); + mContext = null; + } + + // should never be called + public IBinder asBinder() { + throw new UnsupportedOperationException(); + } + + // called by a Binder thread + public void dump(PrintWriter pw, String prefix) { + + } + + private boolean generateSwipe(MotionEvent e1, MotionEvent e2) { + switch (mOrientation) { + case 90: + Slog.d(TAG, "Adjusting motion for 90 degrees"); + e1.setLocation(e1.getY(), e1.getX()); + e2.setLocation(e2.getY(), e2.getX()); + break; + case 180: + Slog.d(TAG, "Adjusting motion for 180 degrees"); + e1.setLocation(mGesturePadWidth - e1.getX(), + mGesturePadHeight - e1.getY()); + e2.setLocation(mGesturePadWidth - e2.getX(), + mGesturePadHeight - e2.getY()); + break; + case 270: + Slog.d(TAG, "Adjusting motion for 270 degrees"); + e1.setLocation(mGesturePadHeight - e1.getY(), + e1.getX()); + e2.setLocation(mGesturePadHeight - e2.getY(), + e2.getX()); + break; + } + + float deltaX = Math.abs(e1.getX() - e2.getX()); + float deltaY = Math.abs(e1.getY() - e2.getY()); + + if (deltaX < mTouchSlop && deltaY < mTouchSlop) { + return false; + } + + if (deltaX > deltaY) { + e2.setLocation(e2.getX(), e1.getY()); + } else if (deltaY > deltaX) { + e2.setLocation(e1.getX(), e2.getY()); + } + + float scaleX = mScreenWidth / mGesturePadWidth; + float scaleY = mScreenHeight / mGesturePadHeight; + + float magnitudeX = deltaX * scaleX; + float magnitudeY = deltaY * scaleY; + + float origX = mScreenWidth / 2; + float origY = mScreenHeight / 2; + float endX = 0.0f; + float endY = 0.0f; + + if (e2.getY() > e1.getY()) { + if (DEBUG) Slog.d(TAG, "Detected down motion"); + // Ensure selection does not occur + endX = origX + mTouchSlop + 5; + endY = origY + magnitudeY; + } else if (e2.getY() < e1.getY()) { + if (DEBUG) Slog.d(TAG, "Detected up motion"); + endX = origX + mTouchSlop + 5; + endY = origY - magnitudeY; + } else if (e2.getX() > e1.getX()) { + if (DEBUG) Slog.d(TAG, "Detected left motion"); + endX = origX + magnitudeX; + endY = origY + mTouchSlop + 5; + } else if (e2.getX() < e1.getX()) { + if (DEBUG) Slog.d(TAG, "Detected right motion"); + endX = origX - magnitudeX; + endY = origY + mTouchSlop + 5; + } else { + return false; + } + + sendSwipe(origX, origY, endX, endY); + return true; + } + + private void sendSwipe(float x1, float y1, float x2, float y2) { + final long duration = 100; + long now = SystemClock.uptimeMillis(); + final long startTime = now; + final long endTime = startTime + duration; + sendMotionEvent(MotionEvent.ACTION_DOWN, now, x1, y1, 1.0f); + + while (now < endTime) { + long elapsedTime = now - startTime; + float alpha = (float) elapsedTime / duration; + sendMotionEvent(MotionEvent.ACTION_MOVE, now, + lerp(x1, x2, alpha), lerp(y1, y2, alpha), 1.0f); + now = SystemClock.uptimeMillis(); + } + sendMotionEvent(MotionEvent.ACTION_UP, now, x2, y2, 1.0f); + } + + private void sendMotionEvent(int action, long when, float x, float y, + float pressure) { + final float DEFAULT_SIZE = 1.0f; + final int DEFAULT_META_STATE = 0; + final float DEFAULT_PRECISION_X = 1.0f; + final float DEFAULT_PRECISION_Y = 1.0f; + final int DEFAULT_DEVICE_ID = 0; + final int DEFAULT_EDGE_FLAGS = 0; + + MotionEvent e = MotionEvent.obtain(when, when, action, x, y, pressure, + DEFAULT_SIZE, DEFAULT_META_STATE, DEFAULT_PRECISION_X, + DEFAULT_PRECISION_Y, DEFAULT_DEVICE_ID, DEFAULT_EDGE_FLAGS); + e.setSource(InputDevice.SOURCE_TOUCHSCREEN); + sendInputEvent(e); + } + + private void sendInputEvent(InputEvent event) { + mInputManager.injectInputEvent(event, + InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); + } + + private static final float lerp(float a, float b, float alpha) { + return (b - a) * alpha + a; + } + + @Override + public boolean onDown(MotionEvent e) { + return false; + } + + @Override + public void onShowPress(MotionEvent e) { + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + return false; + } + + @Override + public void onLongPress(MotionEvent e) { + if (mLongPressPendingIntent != null) { + try { + mLongPressPendingIntent.send(); + } catch (CanceledException e1) { + e1.printStackTrace(); + } + } + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + return generateSwipe(e1, e2); + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + return false; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + if (mDoubleClickPendingIntent != null) { + try { + mDoubleClickPendingIntent.send(); + return true; + } catch (CanceledException e1) { + e1.printStackTrace(); + } + } + + return false; + } + + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + return false; + } + + public void setOnLongPressPendingIntent(PendingIntent pendingIntent) { + mLongPressPendingIntent = pendingIntent; + } + + public void setOnDoubleClickPendingIntent(PendingIntent pendingIntent) { + mDoubleClickPendingIntent = pendingIntent; + } +} diff --git a/services/core/java/com/android/server/gesture/GestureService.java b/services/core/java/com/android/server/gesture/GestureService.java new file mode 100644 index 0000000..1a01e41 --- /dev/null +++ b/services/core/java/com/android/server/gesture/GestureService.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (Jens Doll) + * + * 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.gesture; + +import android.Manifest; +import android.app.PendingIntent; +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.service.gesture.IGestureService; +import android.util.Slog; + +import com.android.server.input.InputManagerService; + +/** + * A system service to track gesture sensor gestures. This service is + * responsible for creating input events from motion events generated by + * gesture sensor input hardware: + * <li>Installing an input filter to listen for gesture sensor events</li> + * <li>Generating input events to be injected into the input stream</li> + */ +public class GestureService extends IGestureService.Stub { + public static final String TAG = "GestureService"; + public static final boolean DEBUG = false; + + private Context mContext; + private InputManagerService mInputManager; + private GestureInputFilter mInputFilter; + + public GestureService(Context context, InputManagerService inputManager) { + mContext = context; + mInputManager = inputManager; + } + + // called by system server + public void systemReady() { + if (DEBUG) Slog.d(TAG, "Starting Gesture Sensor service"); + mInputFilter = new GestureInputFilter(mContext); + mInputManager.registerSecondaryInputFilter(mInputFilter); + } + + public void setOnLongPressPendingIntent(PendingIntent pendingIntent) { + mInputFilter.setOnLongPressPendingIntent(pendingIntent); + } + + public void setOnDoubleClickPendingIntent(PendingIntent pendingIntent) { + mInputFilter.setOnDoubleClickPendingIntent(pendingIntent); + } +} diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 17b4f9c..1919671 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -150,8 +150,9 @@ public class InputManagerService extends IInputManager.Stub // State for the currently installed input filter. final Object mInputFilterLock = new Object(); - IInputFilter mInputFilter; // guarded by mInputFilterLock - InputFilterHost mInputFilterHost; // guarded by mInputFilterLock + ChainedInputFilterHost mInputFilterHost; // guarded by mInputFilterLock + ArrayList<ChainedInputFilterHost> mInputFilterChain = + new ArrayList<ChainedInputFilterHost>(); // guarded by mInputFilterLock private static native long nativeInit(InputManagerService service, Context context, MessageQueue messageQueue); @@ -186,6 +187,8 @@ public class InputManagerService extends IInputManager.Stub InputChannel fromChannel, InputChannel toChannel); private static native void nativeSetPointerSpeed(long ptr, int speed); private static native void nativeSetShowTouches(long ptr, boolean enabled); + private static native void nativeSetStylusIconEnabled(long ptr, boolean enabled); + private static native void nativeSetVolumeKeysRotation(long ptr, int mode); private static native void nativeSetInteractive(long ptr, boolean interactive); private static native void nativeReloadCalibration(long ptr); private static native void nativeVibrate(long ptr, int deviceId, long[] pattern, @@ -288,17 +291,22 @@ public class InputManagerService extends IInputManager.Stub registerPointerSpeedSettingObserver(); registerShowTouchesSettingObserver(); + registerStylusIconEnabledSettingObserver(); + registerVolumeKeysRotationSettingObserver(); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { updatePointerSpeedFromSettings(); updateShowTouchesFromSettings(); + updateVolumeKeysRotationFromSettings(); } }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler); updatePointerSpeedFromSettings(); updateShowTouchesFromSettings(); + updateStylusIconEnabledFromSettings(); + updateVolumeKeysRotationFromSettings(); } // TODO(BT) Pass in paramter for bluetooth system @@ -495,34 +503,71 @@ public class InputManagerService extends IInputManager.Stub */ public void setInputFilter(IInputFilter filter) { synchronized (mInputFilterLock) { - final IInputFilter oldFilter = mInputFilter; - if (oldFilter == filter) { - return; // nothing to do - } - - if (oldFilter != null) { - mInputFilter = null; + if (mInputFilterHost != null) { mInputFilterHost.disconnectLocked(); + mInputFilterChain.remove(mInputFilterHost); mInputFilterHost = null; - try { - oldFilter.uninstall(); - } catch (RemoteException re) { - /* ignore */ - } } if (filter != null) { - mInputFilter = filter; - mInputFilterHost = new InputFilterHost(); - try { - filter.install(mInputFilterHost); - } catch (RemoteException re) { - /* ignore */ + ChainedInputFilterHost head = mInputFilterChain.isEmpty() ? null : + mInputFilterChain.get(0); + mInputFilterHost = new ChainedInputFilterHost(filter, head); + mInputFilterHost.connectLocked(); + mInputFilterChain.add(0, mInputFilterHost); + } + + nativeSetInputFilterEnabled(mPtr, !mInputFilterChain.isEmpty()); + } + } + + /** + * Registers a secondary input filter. These filters are always behind the "original" + * input filter. This ensures that all input events will be filtered by the + * {@code AccessibilityManagerService} first. + * <p> + * <b>Note:</b> Even though this implementation using AIDL interfaces, it is designed to only + * provide direct access. Therefore, any filter registering should reside in the + * system server DVM only! + * + * @param filter The input filter to register. + */ + public void registerSecondaryInputFilter(IInputFilter filter) { + synchronized (mInputFilterLock) { + ChainedInputFilterHost host = new ChainedInputFilterHost(filter, null); + if (!mInputFilterChain.isEmpty()) { + mInputFilterChain.get(mInputFilterChain.size() - 1).mNext = host; + } + host.connectLocked(); + mInputFilterChain.add(host); + + nativeSetInputFilterEnabled(mPtr, !mInputFilterChain.isEmpty()); + } + } + + public void unregisterSecondaryInputFilter(IInputFilter filter) { + synchronized (mInputFilterLock) { + int index = findInputFilterIndexLocked(filter); + if (index >= 0) { + ChainedInputFilterHost host = mInputFilterChain.get(index); + host.disconnectLocked(); + if (index >= 1) { + mInputFilterChain.get(index - 1).mNext = host.mNext; } + mInputFilterChain.remove(index); } - nativeSetInputFilterEnabled(mPtr, filter != null); + nativeSetInputFilterEnabled(mPtr, !mInputFilterChain.isEmpty()); + } + } + + private int findInputFilterIndexLocked(IInputFilter filter) { + for (int i = 0; i < mInputFilterChain.size(); i++) { + if (mInputFilterChain.get(i).mInputFilter == filter) { + return i; + } } + return -1; } @Override // Binder call @@ -1280,6 +1325,58 @@ public class InputManagerService extends IInputManager.Stub return result; } + public void updateStylusIconEnabledFromSettings() { + int enabled = getStylusIconEnabled(0); + nativeSetStylusIconEnabled(mPtr, enabled != 0); + } + + public void registerStylusIconEnabledSettingObserver() { + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.STYLUS_ICON_ENABLED), false, + new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + updateStylusIconEnabledFromSettings(); + } + }); + } + + private int getStylusIconEnabled(int defaultValue) { + int result = defaultValue; + try { + result = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.STYLUS_ICON_ENABLED); + } catch (SettingNotFoundException snfe) { + } + return result; + } + + public void updateVolumeKeysRotationFromSettings() { + int mode = getVolumeKeysRotationSetting(0); + nativeSetVolumeKeysRotation(mPtr, mode); + } + + public void registerVolumeKeysRotationSettingObserver() { + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.SWAP_VOLUME_KEYS_ON_ROTATION), false, + new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + updateVolumeKeysRotationFromSettings(); + } + }); + } + + private int getVolumeKeysRotationSetting(int defaultValue) { + int result = defaultValue; + try { + result = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.SWAP_VOLUME_KEYS_ON_ROTATION, UserHandle.USER_CURRENT); + } catch (SettingNotFoundException snfe) { + } + return result; + } + // Binder call @Override public void vibrate(int deviceId, long[] pattern, int repeat, IBinder token) { @@ -1435,15 +1532,22 @@ public class InputManagerService extends IInputManager.Stub // Native callback. final boolean filterInputEvent(InputEvent event, int policyFlags) { + ChainedInputFilterHost head = null; synchronized (mInputFilterLock) { - if (mInputFilter != null) { - try { - mInputFilter.filterInputEvent(event, policyFlags); - } catch (RemoteException e) { - /* ignore */ - } - return false; + if (!mInputFilterChain.isEmpty()) { + head = mInputFilterChain.get(0); + } + } + // call filter input event outside of the lock. + // this is safe, because we know that mInputFilter never changes. + // we may loose a event, but this does not differ from the original implementation. + if (head != null) { + try { + head.mInputFilter.filterInputEvent(event, policyFlags); + } catch (RemoteException e) { + /* ignore */ } + return false; } event.recycle(); return true; @@ -1694,6 +1798,66 @@ public class InputManagerService extends IInputManager.Stub } } + /** + * Hosting interface for input filters to call back into the input manager. + */ + private final class ChainedInputFilterHost extends IInputFilterHost.Stub { + private final IInputFilter mInputFilter; + private ChainedInputFilterHost mNext; + private boolean mDisconnected; + + private ChainedInputFilterHost(IInputFilter filter, ChainedInputFilterHost next) { + mInputFilter = filter; + mNext = next; + mDisconnected = false; + } + + public void connectLocked() { + try { + mInputFilter.install(this); + } catch (RemoteException re) { + /* ignore */ + } + } + + public void disconnectLocked() { + try { + mInputFilter.uninstall(); + } catch (RemoteException re) { + /* ignore */ + } + // DO NOT set mInputFilter to null here! mInputFilter is used outside of the lock! + mDisconnected = true; + } + + @Override + public void sendInputEvent(InputEvent event, int policyFlags) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + + synchronized (mInputFilterLock) { + if (!mDisconnected) { + if (mNext == null) { + nativeInjectInputEvent(mPtr, event, Display.DEFAULT_DISPLAY, 0, 0, + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0, + policyFlags | WindowManagerPolicy.FLAG_FILTERED); + } else { + try { + // We need to pass a copy into filterInputEvent as it assumes + // the callee takes responsibility and recycles it - in case + // multiple filters are chained, calling into the second filter + // will cause event to be recycled twice + mNext.mInputFilter.filterInputEvent(event.copy(), policyFlags); + } catch (RemoteException e) { + /* ignore */ + } + } + } + } + } + } + private static final class KeyboardLayoutDescriptor { public String packageName; public String receiverName; diff --git a/services/core/java/com/android/server/lights/Light.java b/services/core/java/com/android/server/lights/Light.java index b496b4c..3eb570c 100644 --- a/services/core/java/com/android/server/lights/Light.java +++ b/services/core/java/com/android/server/lights/Light.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2013 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. @@ -35,6 +36,7 @@ public abstract class Light { public abstract void setBrightness(int brightness, int brightnessMode); public abstract void setColor(int color); public abstract void setFlashing(int color, int mode, int onMS, int offMS); + public abstract void setModes(int brightnessLevel, boolean multipleLeds); public abstract void pulse(); public abstract void pulse(int color, int onMS); public abstract void turnOff(); diff --git a/services/core/java/com/android/server/lights/LightsManager.java b/services/core/java/com/android/server/lights/LightsManager.java index 2f20509..e1e5aa3 100644 --- a/services/core/java/com/android/server/lights/LightsManager.java +++ b/services/core/java/com/android/server/lights/LightsManager.java @@ -25,7 +25,9 @@ public abstract class LightsManager { public static final int LIGHT_ID_ATTENTION = 5; public static final int LIGHT_ID_BLUETOOTH = 6; public static final int LIGHT_ID_WIFI = 7; - public static final int LIGHT_ID_COUNT = 8; + public static final int LIGHT_ID_CAPS = 8; + public static final int LIGHT_ID_FUNC = 9; + public static final int LIGHT_ID_COUNT = 10; public abstract Light getLight(int id); } diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java index ed884ef..16dde26 100644 --- a/services/core/java/com/android/server/lights/LightsService.java +++ b/services/core/java/com/android/server/lights/LightsService.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2008 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. @@ -34,6 +35,9 @@ public class LightsService extends SystemService { private LightImpl(int id) { mId = id; + mBrightnessLevel = 0xFF; + mModesUpdate = false; + mMultipleLeds = false; } @Override @@ -65,6 +69,20 @@ public class LightsService extends SystemService { } @Override + public void setModes(int brightnessLevel, boolean multipleLeds) { + synchronized (this) { + if (mBrightnessLevel != brightnessLevel) { + mBrightnessLevel = brightnessLevel; + mModesUpdate = true; + } + if (mMultipleLeds != multipleLeds) { + mMultipleLeds = multipleLeds; + mModesUpdate = true; + } + } + } + + @Override public void pulse() { pulse(0x00ffffff, 7); } @@ -94,17 +112,20 @@ public class LightsService extends SystemService { } private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) { - if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) { + if (mModesUpdate || color != mColor || mode != mMode || onMS != mOnMS || + offMS != mOffMS) { if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#" + Integer.toHexString(color)); mColor = color; mMode = mode; mOnMS = onMS; mOffMS = offMS; + mModesUpdate = false; Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x" + Integer.toHexString(color) + ")"); try { - setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode); + setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode, + mBrightnessLevel, mMultipleLeds ? 1 : 0); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } @@ -116,7 +137,10 @@ public class LightsService extends SystemService { private int mMode; private int mOnMS; private int mOffMS; + private int mBrightnessLevel; private boolean mFlashing; + private boolean mModesUpdate; + private boolean mMultipleLeds; } public LightsService(Context context) { @@ -163,7 +187,8 @@ public class LightsService extends SystemService { private static native void finalize_native(long ptr); static native void setLight_native(long ptr, int light, int color, int mode, - int onMS, int offMS, int brightnessMode); + int onMS, int offMS, int brightnessMode, int brightnessLevel, + int mMultipleLeds); private long mNativePointer; } diff --git a/services/core/java/com/android/server/location/GeoFencerBase.java b/services/core/java/com/android/server/location/GeoFencerBase.java new file mode 100644 index 0000000..eec07ab --- /dev/null +++ b/services/core/java/com/android/server/location/GeoFencerBase.java @@ -0,0 +1,147 @@ +/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * + * Copyright (C) 2007 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.location; + +import android.os.Binder; +import android.os.Parcelable; +import android.util.Log; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.location.GeoFenceParams; +import android.location.ILocationListener; +import java.io.PrintWriter; +import java.util.Map; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Collection; +import java.util.ArrayList; + +/** + * This class defines a base class for GeoFencers + * + * @hide + */ +public abstract class GeoFencerBase { + private static final String TAG = "GeoFencerBase"; + private HashMap<PendingIntent,GeoFenceParams> mGeoFences; + + public GeoFencerBase() { + mGeoFences = new HashMap<PendingIntent,GeoFenceParams>(); + } + + public void add(double latitude, double longitude, + float radius, long expiration, PendingIntent intent, + String packageName) { + add(new GeoFenceParams(latitude, longitude, radius, + expiration, intent, packageName)); + } + + public void add(GeoFenceParams geoFence) { + synchronized(mGeoFences) { + mGeoFences.put(geoFence.mIntent, geoFence); + } + if (!start(geoFence)) { + synchronized(mGeoFences) { + mGeoFences.remove(geoFence.mIntent); + } + } + } + + public void remove(PendingIntent intent) { + remove(intent, false); + } + + public void remove(PendingIntent intent, boolean localOnly) { + GeoFenceParams geoFence = null; + + synchronized(mGeoFences) { + geoFence = mGeoFences.remove(intent); + } + + if (geoFence != null) { + if (!localOnly && !stop(intent)) { + synchronized(mGeoFences) { + mGeoFences.put(geoFence.mIntent, geoFence); + } + } + } + } + + public int getNumbOfGeoFences() { + return mGeoFences.size(); + } + + public Collection<GeoFenceParams> getAllGeoFences() { + return mGeoFences.values(); + } + + public GeoFenceParams getGeoFence(PendingIntent intent) { + return mGeoFences.get(intent); + } + + public boolean hasCaller(int uid) { + for (GeoFenceParams alert : mGeoFences.values()) { + if (alert.mUid == uid) { + return true; + } + } + return false; + } + + public void removeCaller(int uid) { + ArrayList<PendingIntent> removedFences = null; + for (GeoFenceParams alert : mGeoFences.values()) { + if (alert.mUid == uid) { + if (removedFences == null) { + removedFences = new ArrayList<PendingIntent>(); + } + removedFences.add(alert.mIntent); + } + } + if (removedFences != null) { + for (int i = removedFences.size()-1; i>=0; i--) { + mGeoFences.remove(removedFences.get(i)); + } + } + } + + public void transferService(GeoFencerBase geofencer) { + for (GeoFenceParams alert : geofencer.mGeoFences.values()) { + geofencer.stop(alert.mIntent); + add(alert); + } + } + + public void dump(PrintWriter pw, String prefix) { + if (mGeoFences.size() > 0) { + pw.println(prefix + " GeoFences:"); + prefix += " "; + for (Map.Entry<PendingIntent, GeoFenceParams> i + : mGeoFences.entrySet()) { + pw.println(prefix + i.getKey() + ":"); + i.getValue().dump(pw, prefix); + } + } + } + + abstract protected boolean start(GeoFenceParams geoFence); + abstract protected boolean stop(PendingIntent intent); +} diff --git a/services/core/java/com/android/server/location/GeoFencerProxy.java b/services/core/java/com/android/server/location/GeoFencerProxy.java new file mode 100644 index 0000000..8ffbe8c --- /dev/null +++ b/services/core/java/com/android/server/location/GeoFencerProxy.java @@ -0,0 +1,149 @@ +/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * + * Copyright (C) 2007 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.location; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; +import android.app.PendingIntent; +import android.location.IGeoFencer; +import android.location.IGeoFenceListener; +import android.location.GeoFenceParams; + +/** + * A class for proxying IGeoFenceProvider implementations. + * + * {@hide} + */ +public class GeoFencerProxy extends GeoFencerBase { + + private static final String TAG = "GeoFencerProxy"; + private static final boolean LOGV_ENABLED = true; + + private final Context mContext; + private final Intent mIntent; + private IGeoFencer mGeoFencer; + + private final ServiceConnection mServiceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + synchronized (this) { + mGeoFencer = IGeoFencer.Stub.asInterface(service); + notifyAll(); + } + Log.v(TAG, "onServiceConnected: mGeoFencer - "+mGeoFencer); + } + public void onServiceDisconnected(ComponentName className) { + synchronized (this) { + mGeoFencer = null; + } + Log.v(TAG, "onServiceDisconnected"); + } + }; + + private final IGeoFenceListener.Stub mListener = new IGeoFenceListener.Stub() { + @Override + public void geoFenceExpired(PendingIntent intent) throws RemoteException { + logv("geoFenceExpired - "+intent); + remove(intent, true); + } + }; + + private static GeoFencerProxy mGeoFencerProxy; + public static GeoFencerProxy getGeoFencerProxy(Context context, String serviceName) { + if (mGeoFencerProxy == null) { + mGeoFencerProxy = new GeoFencerProxy(context, serviceName); + } + return mGeoFencerProxy; + } + + private GeoFencerProxy(Context context, String serviceName) { + mContext = context; + mIntent = new Intent(IGeoFencer.class.getName()); + mIntent.setPackage(serviceName); + mContext.bindService(mIntent, mServiceConnection, + Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND + | Context.BIND_ALLOW_OOM_MANAGEMENT); + } + + public void removeCaller(int uid) { + super.removeCaller(uid); + if(mGeoFencer != null) { + try { + mGeoFencer.clearGeoFenceUser(uid); + } catch (RemoteException re) { + } + } + else + Log.e(TAG, "removeCaller - mGeoFencer is null"); + } + + private boolean ensureGeoFencer() { + if (mGeoFencer == null) { + try { + synchronized(mServiceConnection) { + logv("waiting..."); + mServiceConnection.wait(60000); + logv("woke up!!!"); + } + } catch (InterruptedException ie) { + Log.w(TAG, "Interrupted while waiting for GeoFencer"); + return false; + } + + if (mGeoFencer == null) { + Log.w(TAG, "Timed out. No GeoFencer connection"); + return false; + } + } + + return true; + } + + protected boolean start(GeoFenceParams geofence) { + if (ensureGeoFencer()) { + try { + return mGeoFencer.setGeoFence(mListener, geofence); + } catch (RemoteException re) { + } + } + return false; + } + + protected boolean stop(PendingIntent intent) { + if (ensureGeoFencer()) { + try { + mGeoFencer.clearGeoFence(mListener, intent); + return true; + } catch (RemoteException re) { + } + } + return false; + } + + private void logv(String s) { + if (LOGV_ENABLED) Log.v(TAG, s); + } +} diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java index ba5f516..13db771 100644 --- a/services/core/java/com/android/server/location/GpsLocationProvider.java +++ b/services/core/java/com/android/server/location/GpsLocationProvider.java @@ -1929,7 +1929,7 @@ public class GpsLocationProvider implements LocationProviderInterface { type = AGPS_REF_LOCATION_TYPE_GSM_CELLID; } native_agps_set_ref_location_cellid(type, mcc, mnc, - gsm_cell.getLac(), gsm_cell.getCid()); + gsm_cell.getLac(), gsm_cell.getCid(), gsm_cell.getPsc()); } else { Log.e(TAG,"Error getting cell location info."); } @@ -2287,7 +2287,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // AGPS ril suport private native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc, - int lac, int cid); + int lac, int cid, int psc); private native void native_agps_set_id(int type, String setid); private native void native_update_network_state(boolean connected, int type, diff --git a/services/core/java/com/android/server/net/NetPluginDelegate.java b/services/core/java/com/android/server/net/NetPluginDelegate.java index 192905a..6716a6b 100644 --- a/services/core/java/com/android/server/net/NetPluginDelegate.java +++ b/services/core/java/com/android/server/net/NetPluginDelegate.java @@ -45,9 +45,13 @@ class NetPluginDelegate { private static Class tetherExtensionClass = null; private static Object tetherExtensionObj = null; + private static boolean extensionFailed; + static void getTetherStats(NetworkStats uidStats, NetworkStats devStats, NetworkStats xtStats) { - loadTetherExtJar(); + if (!loadTetherExtJar()) { + return; + } try { tetherExtensionClass.getMethod("getTetherStats", NetworkStats.class, NetworkStats.class, NetworkStats.class).invoke(tetherExtensionObj, uidStats, @@ -59,7 +63,9 @@ class NetPluginDelegate { } static void setQuota(String iface, long quota) { - loadTetherExtJar(); + if (!loadTetherExtJar()) { + return; + } try { tetherExtensionClass.getMethod("setQuota", String.class, long.class).invoke( tetherExtensionObj, iface, quota); @@ -70,24 +76,26 @@ class NetPluginDelegate { - private static void loadTetherExtJar() { + private static boolean loadTetherExtJar() { final String realProvider = "com.qualcomm.qti.tetherstatsextension.TetherStatsReporting"; final String realProviderPath = "/system/framework/ConnectivityExt.jar"; - if (tetherExtensionClass == null && tetherExtensionObj == null) { - if (LOGV) Slog.v(TAG, "loading ConnectivityExt jar"); - try { + if (!extensionFailed && tetherExtensionClass == null && tetherExtensionObj == null) { + if (LOGV) Slog.v(TAG, "loading ConnectivityExt jar"); + try { - PathClassLoader classLoader = new PathClassLoader(realProviderPath, - ClassLoader.getSystemClassLoader()); + PathClassLoader classLoader = new PathClassLoader(realProviderPath, + ClassLoader.getSystemClassLoader()); - tetherExtensionClass = classLoader.loadClass(realProvider); - tetherExtensionObj = tetherExtensionClass.newInstance(); - if (LOGV) - Slog.v(TAG, "ConnectivityExt jar loaded"); - } catch (Exception e) { - e.printStackTrace(); - Log.w(TAG, "unable to ConnectivityExt jar"); - } + tetherExtensionClass = classLoader.loadClass(realProvider); + tetherExtensionObj = tetherExtensionClass.newInstance(); + if (LOGV) + Slog.v(TAG, "ConnectivityExt jar loaded"); + extensionFailed = false; + } catch (Exception e) { + Log.w(TAG, "Connectivity extension is not available"); + extensionFailed = true; + } } + return !extensionFailed; } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 522ec48..f041f0d 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -804,7 +804,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } else { notifyUnderLimitLocked(policy.template); - if (policy.isOverWarning(totalBytes) && policy.lastWarningSnooze < start) { + if (policy.isOverWarning(totalBytes) && policy.lastWarningSnooze < start + && policy.limitBytes != LIMIT_DISABLED) { enqueueNotification(policy, TYPE_WARNING, totalBytes); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e6fcdf1..f549f7c 100644..100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 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. @@ -23,6 +24,7 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.START_TAG; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.AppGlobals; @@ -30,6 +32,7 @@ import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.ITransientNotification; +import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.NotificationManager.Policy; @@ -41,7 +44,9 @@ import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; @@ -58,6 +63,9 @@ import android.media.AudioManager; import android.media.AudioManagerInternal; import android.media.AudioSystem; import android.media.IRingtonePlayer; +import android.media.session.MediaController; +import android.media.session.MediaSessionManager; +import android.media.session.PlaybackState; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -92,7 +100,9 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Log; +import android.util.LruCache; import android.util.Slog; +import android.util.SparseIntArray; import android.util.Xml; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -101,6 +111,9 @@ import android.widget.Toast; import com.android.internal.R; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.cm.SpamFilter; +import com.android.internal.util.cm.SpamFilter.SpamContract.NotificationTable; +import com.android.internal.util.cm.SpamFilter.SpamContract.PackageTable; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -134,10 +147,15 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashSet; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; /** {@hide} */ public class NotificationManagerService extends SystemService { @@ -170,6 +188,8 @@ public class NotificationManagerService extends SystemService { static final int JUNK_SCORE = -1000; static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER; + private static final String IS_FILTERED_QUERY = NotificationTable.NORMALIZED_TEXT + "=? AND " + + PackageTable.PACKAGE_NAME + "=?"; // Notifications with scores below this will not interrupt the user, either via LED or // sound or vibration @@ -198,6 +218,9 @@ public class NotificationManagerService extends SystemService { /** notification_enqueue status value for an ignored notification. */ private static final int EVENTLOG_ENQUEUE_STATUS_IGNORED = 2; + /** notification light maximum brightness value to use. */ + private static final int LIGHT_BRIGHTNESS_MAXIMUM = 255; + private IActivityManager mAm; AudioManager mAudioManager; AudioManagerInternal mAudioManagerInternal; @@ -217,10 +240,35 @@ public class NotificationManagerService extends SystemService { private int mDefaultNotificationLedOff; private long[] mDefaultVibrationPattern; + private boolean mAdjustableNotificationLedBrightness; + private int mNotificationLedBrightnessLevel = LIGHT_BRIGHTNESS_MAXIMUM; + + private boolean mMultipleNotificationLeds; + private boolean mMultipleLedsEnabledSetting = false; + + private boolean mScreenOnEnabled = false; + private boolean mScreenOnDefault = false; + private long[] mFallbackVibrationPattern; private boolean mUseAttentionLight; boolean mSystemReady; + private final SparseIntArray mSpamCache; + private ExecutorService mSpamExecutor = Executors.newSingleThreadExecutor(); + + private static final Uri FILTER_MSG_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SpamFilter.AUTHORITY) + .appendPath("messages") + .build(); + + private static final Uri UPDATE_MSG_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SpamFilter.AUTHORITY) + .appendPath(SpamFilter.MESSAGE_PATH) + .appendEncodedPath("inc_count") + .build(); + private boolean mDisableNotificationEffects; private int mCallState; private String mSoundNotificationKey; @@ -235,6 +283,11 @@ public class NotificationManagerService extends SystemService { private boolean mScreenOn = true; private boolean mInCall = false; private boolean mNotificationPulseEnabled; + private HashMap<String, NotificationLedValues> mNotificationPulseCustomLedValues; + private Map<String, String> mPackageNameMappings; + + // for checking lockscreen status + private KeyguardManager mKeyguardManager; // used as a mutex for access to all active notifications & listeners final ArrayList<NotificationRecord> mNotificationList = @@ -275,6 +328,8 @@ public class NotificationManagerService extends SystemService { private NotificationListeners mListeners; private ConditionProviders mConditionProviders; private NotificationUsageStats mUsageStats; + private boolean mDisableDuckingWhileMedia; + private boolean mActiveMedia; private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); @@ -487,6 +542,12 @@ public class NotificationManagerService extends SystemService { } } + class NotificationLedValues { + public int color; + public int onMS; + public int offMS; + } + private final NotificationDelegate mNotificationDelegate = new NotificationDelegate() { @Override @@ -610,9 +671,12 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } - // light - mLights.clear(); - updateLightsLocked(); + // lights + // clear only if lockscreen is not active + if (mKeyguardManager != null && !mKeyguardManager.isKeyguardLocked()) { + mLights.clear(); + updateLightsLocked(); + } } } @@ -774,12 +838,16 @@ public class NotificationManagerService extends SystemService { } } else if (action.equals(Intent.ACTION_USER_PRESENT)) { // turn off LED when user passes through lock screen - mNotificationLight.turnOff(); - mStatusBar.notificationLightOff(); + // if lights with screen on is disabled. + if (!mScreenOnEnabled) { + mNotificationLight.turnOff(); + mStatusBar.notificationLightOff(); + } } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); // reload per-user settings mSettingsObserver.update(null); + mSpamFilterObserver.update(null); mUserProfiles.updateCache(context); // Refresh managed services mConditionProviders.onUserSwitched(user); @@ -794,9 +862,103 @@ public class NotificationManagerService extends SystemService { } }; - private final class SettingsObserver extends ContentObserver { + class SpamFilterObserver extends ContentObserver { + + private Future mTask; + + public SpamFilterObserver(Handler handler) { + super(handler); + } + + private void addToCache(Cursor c) { + int notifId = c.getInt(c.getColumnIndex( + NotificationTable.ID)); + String pkgName = c.getString(c.getColumnIndex( + PackageTable.PACKAGE_NAME)); + String normalizedText = c.getString(c.getColumnIndex( + NotificationTable.NORMALIZED_TEXT)); + int hash = getSpamCacheHash(normalizedText, pkgName); + synchronized (mSpamCache) { + mSpamCache.put(hash, notifId); + } + } + + private Runnable mFetchAllFilters = new Runnable() { + @Override + public void run() { + Cursor c = getContext().getContentResolver().query(FILTER_MSG_URI, + null, null, null, null); + if (c != null) { + synchronized (mSpamCache) { + mSpamCache.clear(); + while (c.moveToNext()) { + addToCache(c); + if (Thread.interrupted()) { + break; + } + c.close(); + } + } + } + } + }; + + @Override + public void onChange(boolean selfChange, Uri uri) { + update(uri); + } + + void update(final Uri uri) { + if (mTask != null && !mTask.isDone()) { + mTask.cancel(true); + } + if (uri == null) { + mTask = mSpamExecutor.submit(mFetchAllFilters); + } else { + Runnable r = new Runnable() { + @Override + public void run() { + String id = uri.getLastPathSegment(); + Cursor c = getContext().getContentResolver().query( + uri, null, null, null, null); + + if (c != null) { + int index; + synchronized (mSpamCache) { + index = mSpamCache.indexOfValue(Integer.parseInt(id)); + } + if (!c.moveToFirst()) { + synchronized (mSpamCache) { + // Filter was deleted + if (index >= 0) { + mSpamCache.removeAt(index); + } + } + } else if (index < 0) { + // Filter was added/updated + addToCache(c); + } + c.close(); + } + } + }; + mTask = mSpamExecutor.submit(r); + } + } + + public void observe() { + ContentResolver resolver = getContext().getContentResolver(); + resolver.registerContentObserver(SpamFilter.NOTIFICATION_URI, + true, this, UserHandle.USER_ALL); + update(null); + } + } + + class SettingsObserver extends ContentObserver { private final Uri NOTIFICATION_LIGHT_PULSE_URI = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE); + private final Uri ENABLED_NOTIFICATION_LISTENERS_URI + = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); SettingsObserver(Handler handler) { super(handler); @@ -804,8 +966,41 @@ public class NotificationManagerService extends SystemService { void observe() { ContentResolver resolver = getContext().getContentResolver(); - resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, + resolver.registerContentObserver( + NOTIFICATION_LIGHT_PULSE_URI, false, this, UserHandle.USER_ALL); + resolver.registerContentObserver( + ENABLED_NOTIFICATION_LISTENERS_URI, false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR), false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON), + false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF), + false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE), + false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES), + false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_SCREEN_ON), + false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.ZEN_DISABLE_DUCKING_DURING_MEDIA_PLAYBACK), false, + this, UserHandle.USER_ALL); + if (mAdjustableNotificationLedBrightness) { + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL), + false, this, UserHandle.USER_ALL); + } + if (mMultipleNotificationLeds) { + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE), + false, this, UserHandle.USER_ALL); + } update(null); } @@ -815,18 +1010,77 @@ public class NotificationManagerService extends SystemService { public void update(Uri uri) { ContentResolver resolver = getContext().getContentResolver(); - if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { - boolean pulseEnabled = Settings.System.getInt(resolver, - Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0; - if (mNotificationPulseEnabled != pulseEnabled) { - mNotificationPulseEnabled = pulseEnabled; - updateNotificationPulse(); - } + + // LED enabled + mNotificationPulseEnabled = Settings.System.getIntForUser(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) != 0; + + // LED default color + mDefaultNotificationColor = Settings.System.getIntForUser(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, + mDefaultNotificationColor, UserHandle.USER_CURRENT); + + // LED default on MS + mDefaultNotificationLedOn = Settings.System.getIntForUser(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, + mDefaultNotificationLedOn, UserHandle.USER_CURRENT); + + // LED default off MS + mDefaultNotificationLedOff = Settings.System.getIntForUser(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, + mDefaultNotificationLedOff, UserHandle.USER_CURRENT); + + // LED custom notification colors + mNotificationPulseCustomLedValues.clear(); + if (Settings.System.getIntForUser(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE, 0, + UserHandle.USER_CURRENT) != 0) { + parseNotificationPulseCustomValuesString(Settings.System.getStringForUser(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES, + UserHandle.USER_CURRENT)); + } + + // Notification LED brightness + if (mAdjustableNotificationLedBrightness) { + mNotificationLedBrightnessLevel = Settings.System.getIntForUser(resolver, + Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL, + LIGHT_BRIGHTNESS_MAXIMUM, UserHandle.USER_CURRENT); + } + + // Multiple LEDs enabled + if (mMultipleNotificationLeds) { + mMultipleLedsEnabledSetting = (Settings.System.getIntForUser(resolver, + Settings.System.NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE, + mMultipleNotificationLeds ? 1 : 0, UserHandle.USER_CURRENT) != 0); } + + // Notification lights with screen on + mScreenOnEnabled = (Settings.System.getIntForUser(resolver, + Settings.System.NOTIFICATION_LIGHT_SCREEN_ON, + mScreenOnDefault ? 1 : 0, UserHandle.USER_CURRENT) != 0); + + updateNotificationPulse(); + + mDisableDuckingWhileMedia = Settings.Global.getInt(resolver, + Settings.Global.ZEN_DISABLE_DUCKING_DURING_MEDIA_PLAYBACK, 0) == 1; + updateDisableDucking(); + } + } + + private void updateDisableDucking() { + if (!mSystemReady) { + return; + } + final MediaSessionManager mediaSessionManager = (MediaSessionManager) getContext() + .getSystemService(Context.MEDIA_SESSION_SERVICE); + mediaSessionManager.removeOnActiveSessionsChangedListener(mSessionListener); + if (mDisableDuckingWhileMedia) { + mediaSessionManager.addOnActiveSessionsChangedListener(mSessionListener, null); } } private SettingsObserver mSettingsObserver; + private SpamFilterObserver mSpamFilterObserver; private ZenModeHelper mZenModeHelper; private final Runnable mBuzzBeepBlinked = new Runnable() { @@ -851,6 +1105,7 @@ public class NotificationManagerService extends SystemService { public NotificationManagerService(Context context) { super(context); + mSpamCache = new SparseIntArray(); } @Override @@ -861,6 +1116,8 @@ public class NotificationManagerService extends SystemService { mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); + mKeyguardManager = + (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE); mHandler = new WorkerHandler(); mRankingThread.start(); @@ -916,6 +1173,16 @@ public class NotificationManagerService extends SystemService { mDefaultNotificationLedOff = resources.getInteger( R.integer.config_defaultNotificationLedOff); + mNotificationPulseCustomLedValues = new HashMap<String, NotificationLedValues>(); + + mPackageNameMappings = new HashMap<String, String>(); + final String[] defaultMapping = resources.getStringArray( + com.android.internal.R.array.notification_light_package_mapping); + for (String mapping : defaultMapping) { + String[] map = mapping.split("\\|"); + mPackageNameMappings.put(map[0], map[1]); + } + mDefaultVibrationPattern = getLongArray(resources, R.array.config_defaultNotificationVibePattern, VIBRATE_PATTERN_MAXLEN, @@ -926,6 +1193,11 @@ public class NotificationManagerService extends SystemService { VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); + mAdjustableNotificationLedBrightness = resources.getBoolean( + com.android.internal.R.bool.config_adjustableNotificationLedBrightness); + mMultipleNotificationLeds = resources.getBoolean( + com.android.internal.R.bool.config_multipleNotificationLeds); + mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight); // Don't start allowing notifications until the setup wizard has run once. @@ -969,6 +1241,10 @@ public class NotificationManagerService extends SystemService { null); mSettingsObserver = new SettingsObserver(mHandler); + mSettingsObserver.observe(); + + mSpamFilterObserver = new SpamFilterObserver(mHandler); + mSpamFilterObserver.observe(); mArchive = new Archive(resources.getInteger( R.integer.config_notificationServiceArchiveSize)); @@ -1011,10 +1287,13 @@ public class NotificationManagerService extends SystemService { mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); mAudioManagerInternal = getLocalService(AudioManagerInternal.class); mZenModeHelper.onSystemReady(); + + updateDisableDucking(); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { // This observer will force an update when observe is called, causing us to // bind to listener services. mSettingsObserver.observe(); + mSpamFilterObserver.observe(); mListeners.onBootPhaseAppsCanStart(); mConditionProviders.onBootPhaseAppsCanStart(); } @@ -2167,6 +2446,11 @@ public class NotificationManagerService extends SystemService { return; } + if (isNotificationSpam(notification, pkg)) { + mArchive.record(r.sbn); + return; + } + int index = indexOfNotificationLocked(n.getKey()); if (index < 0) { mNotificationList.add(r); @@ -2310,6 +2594,21 @@ public class NotificationManagerService extends SystemService { return false; } + private MediaSessionManager.OnActiveSessionsChangedListener mSessionListener = + new MediaSessionManager.OnActiveSessionsChangedListener() { + @Override + public void onActiveSessionsChanged(@Nullable List<MediaController> controllers) { + for (MediaController activeSession : controllers) { + PlaybackState playbackState = activeSession.getPlaybackState(); + if (playbackState != null && playbackState.getState() == PlaybackState.STATE_PLAYING) { + mActiveMedia = true; + return; + } + } + mActiveMedia = false; + } + }; + private void buzzBeepBlinkLocked(NotificationRecord record) { boolean buzz = false; boolean beep = false; @@ -2379,7 +2678,7 @@ public class NotificationManagerService extends SystemService { hasValidSound = (soundUri != null); } - if (hasValidSound) { + if (hasValidSound && (!mDisableDuckingWhileMedia || !mActiveMedia)) { boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0; AudioAttributes audioAttributes = audioAttributesForNotification(notification); @@ -2457,7 +2756,8 @@ public class NotificationManagerService extends SystemService { // light // release the light boolean wasShowLights = mLights.remove(record.getKey()); - if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold) { + final boolean aboveThresholdWithLight = aboveThreshold || isLedNotificationForcedOn(record); + if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThresholdWithLight) { mLights.add(record.getKey()); updateLightsLocked(); if (mUseAttentionLight) { @@ -2743,6 +3043,33 @@ public class NotificationManagerService extends SystemService { return (x < low) ? low : ((x > high) ? high : x); } + private int getSpamCacheHash(CharSequence message, String packageName) { + return (message + packageName).hashCode(); + } + + private boolean isNotificationSpam(Notification notification, String basePkg) { + CharSequence normalizedContent = SpamFilter + .getNormalizedNotificationContent(notification); + int notificationHash = getSpamCacheHash(normalizedContent, basePkg); + int notificationId; + synchronized (mSpamCache) { + notificationId = mSpamCache.get(notificationHash, -1); + } + if (notificationId != -1) { + final String notifIdString = String.valueOf(notificationId); + mSpamExecutor.submit(new Runnable() { + @Override + public void run() { + Uri updateUri = Uri.withAppendedPath(UPDATE_MSG_URI, + notifIdString); + getContext().getContentResolver().update(updateUri, + new ContentValues(), null, null); + } + }); + } + return notificationId != -1; + } + void sendAccessibilityEvent(Notification notification, CharSequence packageName) { AccessibilityManager manager = AccessibilityManager.getInstance(getContext()); if (!manager.isEnabled()) { @@ -3042,6 +3369,16 @@ public class NotificationManagerService extends SystemService { } } + private boolean isLedNotificationForcedOn(NotificationRecord r) { + if (r != null) { + final Notification n = r.sbn.getNotification(); + if (n.extras != null) { + return n.extras.getBoolean(Notification.EXTRA_FORCE_SHOW_LIGHTS, false); + } + } + return false; + } + // lock on mNotificationList void updateLightsLocked() { @@ -3057,29 +3394,101 @@ public class NotificationManagerService extends SystemService { } // Don't flash while we are in a call or screen is on - if (ledNotification == null || mInCall || mScreenOn) { + // (unless Notification has EXTRA_FORCE_SHOW_LGHTS) + final boolean enableLed; + if (ledNotification == null) { + enableLed = false; + } else if (isLedNotificationForcedOn(ledNotification)) { + enableLed = true; + } else if (!mScreenOnEnabled && (mInCall || mScreenOn)) { + enableLed = false; + } else { + enableLed = true; + } + + if (!enableLed) { mNotificationLight.turnOff(); mStatusBar.notificationLightOff(); } else { final Notification ledno = ledNotification.sbn.getNotification(); - int ledARGB = ledno.ledARGB; - int ledOnMS = ledno.ledOnMS; - int ledOffMS = ledno.ledOffMS; - if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0) { + final NotificationLedValues ledValues = getLedValuesForNotification(ledNotification); + int ledARGB; + int ledOnMS; + int ledOffMS; + + if (ledValues != null) { + ledARGB = ledValues.color != 0 ? ledValues.color : mDefaultNotificationColor; + ledOnMS = ledValues.onMS >= 0 ? ledValues.onMS : mDefaultNotificationLedOn; + ledOffMS = ledValues.offMS >= 0 ? ledValues.offMS : mDefaultNotificationLedOff; + } else if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0) { ledARGB = mDefaultNotificationColor; ledOnMS = mDefaultNotificationLedOn; ledOffMS = mDefaultNotificationLedOff; + } else { + ledARGB = ledno.ledARGB; + ledOnMS = ledno.ledOnMS; + ledOffMS = ledno.ledOffMS; } + + // update the LEDs modes variables + mNotificationLight.setModes(mNotificationLedBrightnessLevel, + mMultipleLedsEnabledSetting); + if (mNotificationPulseEnabled) { // pulse repeatedly mNotificationLight.setFlashing(ledARGB, Light.LIGHT_FLASH_TIMED, ledOnMS, ledOffMS); } + // let SystemUI make an independent decision mStatusBar.notificationLightPulse(ledARGB, ledOnMS, ledOffMS); } } + private void parseNotificationPulseCustomValuesString(String customLedValuesString) { + if (TextUtils.isEmpty(customLedValuesString)) { + return; + } + + for (String packageValuesString : customLedValuesString.split("\\|")) { + String[] packageValues = packageValuesString.split("="); + if (packageValues.length != 2) { + Log.e(TAG, "Error parsing custom led values for unknown package"); + continue; + } + String packageName = packageValues[0]; + String[] values = packageValues[1].split(";"); + if (values.length != 3) { + Log.e(TAG, "Error parsing custom led values '" + + packageValues[1] + "' for " + packageName); + continue; + } + NotificationLedValues ledValues = new NotificationLedValues(); + try { + ledValues.color = Integer.parseInt(values[0]); + ledValues.onMS = Integer.parseInt(values[1]); + ledValues.offMS = Integer.parseInt(values[2]); + } catch (NumberFormatException e) { + Log.e(TAG, "Error parsing custom led values '" + + packageValues[1] + "' for " + packageName); + continue; + } + mNotificationPulseCustomLedValues.put(packageName, ledValues); + } + } + + private NotificationLedValues getLedValuesForNotification(NotificationRecord ledNotification) { + final String packageName = ledNotification.sbn.getPackageName(); + return mNotificationPulseCustomLedValues.get(mapPackage(packageName)); + } + + private String mapPackage(String pkg) { + if (!mPackageNameMappings.containsKey(pkg)) { + return pkg; + } + return mPackageNameMappings.get(pkg); + } + // lock on mNotificationList int indexOfNotificationLocked(String pkg, String tag, int id, int userId) { diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java index 52d0928..cab4691 100644 --- a/services/core/java/com/android/server/pm/BasePermission.java +++ b/services/core/java/com/android/server/pm/BasePermission.java @@ -53,6 +53,8 @@ final class BasePermission { */ private boolean perUser; + boolean allowViaWhitelist; + BasePermission(String _name, String _sourcePackage, int _type) { name = _name; sourcePackage = _sourcePackage; diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index b59b4b2..dba38a3 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -97,14 +97,51 @@ public final class Installer extends SystemService { debuggable, outputPath); } - public int idmap(String targetApkPath, String overlayApkPath, int uid) { + public int idmap(String targetApkPath, String overlayApkPath, String cachePath, + int uid, int targetHash, int overlayHash) { StringBuilder builder = new StringBuilder("idmap"); builder.append(' '); builder.append(targetApkPath); builder.append(' '); builder.append(overlayApkPath); builder.append(' '); + builder.append(cachePath); + builder.append(' '); builder.append(uid); + builder.append(' '); + builder.append(targetHash); + builder.append(' '); + builder.append(overlayHash); + return mInstaller.execute(builder.toString()); + } + + public int aapt(String themeApkPath, String internalPath, String resTablePath, int uid, + int pkgId, int minSdkVersion, String commonResourcesPath) { + + StringBuilder builder = new StringBuilder(); + if (TextUtils.isEmpty(commonResourcesPath)) { + builder.append("aapt"); + } else { + builder.append("aapt_with_common"); + } + builder.append(' '); + builder.append(themeApkPath); + builder.append(' '); + builder.append(internalPath); + builder.append(' '); + builder.append(resTablePath); + builder.append(' '); + builder.append(uid); + builder.append(' '); + builder.append(pkgId); + builder.append(' '); + builder.append(minSdkVersion); + + if (!TextUtils.isEmpty(commonResourcesPath)) { + builder.append(' '); + builder.append(commonResourcesPath); + } + return mInstaller.execute(builder.toString()); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 4a473fd..65f5bce 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -381,7 +381,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (stageDir != null && deltaBytes > 0) { mPm.freeStorage(params.volumeUuid, deltaBytes); } - Libcore.os.posix_fallocate(targetFd, 0, lengthBytes); + try { + Libcore.os.posix_fallocate(targetFd, 0, lengthBytes); + } catch (ErrnoException e) { + if (e.errno == OsConstants.ENOTSUP) { + Libcore.os.ftruncate(targetFd, lengthBytes); + } else { + throw e.rethrowAsIOException(); + } + } } if (offsetBytes > 0) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f49285f..8fcade2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +17,7 @@ package com.android.server.pm; +import static android.Manifest.permission.ACCESS_THEME_MANAGER; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_MEDIA_STORAGE; @@ -42,6 +44,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; +import static android.content.pm.PackageManager.INSTALL_FAILED_REGION_LOCKED_PREBUNDLE; import static android.content.pm.PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE; import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY; @@ -64,6 +67,7 @@ import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING; import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.content.pm.PackageManager.INSTALL_FAILED_UNINSTALLED_PREBUNDLE; import static android.content.pm.PackageParser.isApkFile; import static android.os.Process.PACKAGE_INFO_GID; import static android.os.Process.SYSTEM_UID; @@ -82,12 +86,22 @@ import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet; import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_FAILURE; import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS; import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; +import static com.android.internal.util.ArrayUtils.removeInt; + +import android.content.res.Configuration; import android.Manifest; + +import cyanogenmod.app.suggest.AppSuggestManager; + import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.AppGlobals; +import android.app.ComposedIconInfo; +import android.app.AppOpsManager; import android.app.IActivityManager; +import android.app.IconPackHelper; +import android.app.PackageInstallObserver; import android.app.admin.IDevicePolicyManager; import android.app.backup.IBackupManager; import android.app.usage.UsageStats; @@ -129,6 +143,9 @@ import android.content.pm.PackageParser.ActivityIntentInfo; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageStats; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageParser.Activity; +import android.content.pm.PackageParser.Package; import android.content.pm.PackageUserState; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; @@ -138,10 +155,15 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.content.pm.UserInfo; +import android.content.pm.ManifestDigest; +import android.content.pm.ThemeUtils; import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VerifierInfo; import android.content.res.Resources; +import android.content.res.AssetManager; +import android.content.res.ThemeConfig; +import android.content.res.ThemeManager; import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.Debug; @@ -172,6 +194,8 @@ import android.os.storage.StorageEventListener; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; +import android.provider.Settings.Global; +import android.provider.Settings.Secure; import android.security.KeyStore; import android.security.SystemKeyStore; import android.system.ErrnoException; @@ -188,6 +212,7 @@ import android.util.ExceptionUtils; import android.util.Log; import android.util.LogPrinter; import android.util.MathUtils; +import android.util.Pair; import android.util.PrintStreamPrinter; import android.util.Slog; import android.util.SparseArray; @@ -196,6 +221,7 @@ import android.util.SparseIntArray; import android.util.Xml; import android.view.Display; +import cyanogenmod.providers.CMSettings; import dalvik.system.DexFile; import dalvik.system.VMRuntime; @@ -237,16 +263,25 @@ import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.BufferedWriter; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; +import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.CertificateEncodingException; @@ -268,6 +303,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; /** * Keep track of all those .apks everywhere. @@ -298,6 +335,7 @@ public class PackageManagerService extends IPackageManager.Stub { private static final boolean DEBUG_VERIFY = false; private static final boolean DEBUG_DEXOPT = false; private static final boolean DEBUG_ABI_SELECTION = false; + private static final boolean DEBUG_PREBUNDLED_SCAN = false; static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false; @@ -414,6 +452,21 @@ public class PackageManagerService extends IPackageManager.Stub { final ServiceThread mHandlerThread; + //Where overlays are be found in a theme APK + private static final String APK_PATH_TO_OVERLAY = "assets/overlays/"; + + //Where the icon pack can be found in a themed apk + private static final String APK_PATH_TO_ICONS = "assets/icons/"; + + private static final String COMMON_OVERLAY = ThemeUtils.COMMON_RES_TARGET; + + private static final long COMMON_RESOURCE_EXPIRATION = 3*60*1000; // 3 minutes + + /** + * The offset in bytes to the beginning of the hashes in an idmap + */ + private static final int IDMAP_HASH_START_OFFSET = 16; + final PackageHandler mHandler; /** @@ -500,10 +553,13 @@ public class PackageManagerService extends IPackageManager.Stub { final Settings mSettings; boolean mRestoredSettings; + private Resources mCustomResources; + // System configuration read by SystemConfig. final int[] mGlobalGids; final SparseArray<ArraySet<String>> mSystemPermissions; final ArrayMap<String, FeatureInfo> mAvailableFeatures; + final ArrayMap<Signature, ArraySet<String>> mSignatureAllowances; // If mac_permissions.xml was found for seinfo labeling. boolean mFoundPolicyFile; @@ -819,6 +875,16 @@ public class PackageManagerService extends IPackageManager.Stub { private IntentFilterVerifier mIntentFilterVerifier; + private IconPackHelper mIconPackHelper; + + private Map<String, Long> mAvailableCommonResources = new ArrayMap<String, Long>(); + + private ThemeConfig mBootThemeConfig; + + 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> @@ -1388,18 +1454,22 @@ public class PackageManagerService extends IPackageManager.Stub { } } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, - packageName, extras, null, null, firstUsers); + packageName, null, extras, null, null, firstUsers); final boolean update = res.removedInfo.removedPackage != null; if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } + String category = null; + if(res.pkg.mIsThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, - packageName, extras, null, null, updateUsers); + packageName, null, extras, null, null, updateUsers); if (update) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - packageName, extras, null, null, updateUsers); + packageName, null, extras, null, null, updateUsers); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, - null, null, packageName, null, updateUsers); + null, null, null, packageName, null, updateUsers); // treat asec-hosted packages like removable media on upgrade if (res.pkg.isForwardLocked() || isExternal(res.pkg)) { @@ -1414,6 +1484,10 @@ public class PackageManagerService extends IPackageManager.Stub { pkgList,uidArray, null); } } + // if this was a theme, send it off to the theme service for processing + if(res.pkg.mIsThemeApk || res.pkg.mIsLegacyIconPackApk) { + processThemeResourcesInThemeService(res.pkg.packageName); + } if (res.removedInfo.args != null) { // Remove the replaced package's older resources safely now deleteOld = true; @@ -1432,6 +1506,17 @@ 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()); @@ -1832,6 +1917,8 @@ public class PackageManagerService extends IPackageManager.Stub { } mDexOptLRUThresholdInMills = dexOptLRUThresholdInMinutes * 60 * 1000; + mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { if ("*".equals(separateProcesses)) { @@ -1858,10 +1945,13 @@ public class PackageManagerService extends IPackageManager.Stub { getDefaultDisplayMetrics(context, mMetrics); + removeLegacyResourceCache(); + SystemConfig systemConfig = SystemConfig.getInstance(); mGlobalGids = systemConfig.getGlobalGids(); mSystemPermissions = systemConfig.getSystemPermissions(); mAvailableFeatures = systemConfig.getAvailableFeatures(); + mSignatureAllowances = systemConfig.getSignatureAllowances(); synchronized (mInstallLock) { // writer @@ -1907,10 +1997,13 @@ public class PackageManagerService extends IPackageManager.Stub { mFoundPolicyFile = SELinuxMMAC.readInstallPolicy(); mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false), - mSdkVersion, mOnlyCore); + mSdkVersion, mOnlyCore, mInstaller); - String customResolverActivity = Resources.getSystem().getString( - R.string.config_customResolverActivity); + String customResolverActivity = SystemProperties.get("ro.custom.resolver.activity"); + if (TextUtils.isEmpty(customResolverActivity)) { + customResolverActivity = Resources.getSystem().getString( + R.string.config_customResolverActivity); + } if (TextUtils.isEmpty(customResolverActivity)) { customResolverActivity = null; } else { @@ -2002,6 +2095,10 @@ public class PackageManagerService extends IPackageManager.Stub { // avoid the resulting log spew. alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar"); + // Gross hack for now: we know this file doesn't contain any + // code, so don't dexopt it to avoid the resulting log spew + alreadyDexOpted.add(frameworkDir.getPath() + "/org.cyanogenmod.platform-res.apk"); + /** * There are a number of commands implemented in Java, which * we currently need to do the dexopt on so that they can be @@ -2056,30 +2153,32 @@ public class PackageManagerService extends IPackageManager.Stub { } } + mBootThemeConfig = ThemeConfig.getSystemTheme(); + // Collect vendor overlay packages. // (Do this before scanning any apps.) // For security and version matching reason, only consider // overlay packages if they reside in VENDOR_OVERLAY_DIR. File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR); scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0); + | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0, null); // Find base frameworks (resource packages without code). scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR | PackageParser.PARSE_IS_PRIVILEGED, - scanFlags | SCAN_NO_DEX, 0); + scanFlags | SCAN_NO_DEX, 0, null); // Collected privileged system packages. final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app"); scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR - | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0); + | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0, null); // Collect ordinary system packages. final File systemAppDir = new File(Environment.getRootDirectory(), "app"); scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0); + | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0, null); // Collect all vendor packages. File vendorAppDir = new File("/vendor/app"); @@ -2089,12 +2188,19 @@ public class PackageManagerService extends IPackageManager.Stub { // failed to look up canonical path, continue with original one } scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0); + | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0, null); // Collect all OEM packages. final File oemAppDir = new File(Environment.getOemDirectory(), "app"); scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0); + | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0, null); + + // Collect all prebundled packages. + createAndSetCustomResources(); + scanDirLI(Environment.getPrebundledDirectory(), + PackageParser.PARSE_IS_PREBUNDLED_DIR, scanFlags, 0, UserHandle.OWNER); + // Clean up + mCustomResources = null; if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands"); mInstaller.moveFiles(); @@ -2169,10 +2275,10 @@ public class PackageManagerService extends IPackageManager.Stub { if (!mOnlyCore) { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START, SystemClock.uptimeMillis()); - scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0); + scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0, null); scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK, - scanFlags | SCAN_REQUIRE_KNOWN, 0); + scanFlags | SCAN_REQUIRE_KNOWN, 0, null); /** * Remove disable package settings for any updated system @@ -2284,6 +2390,12 @@ public class PackageManagerService extends IPackageManager.Stub { updatePermissionsLPw(null, null, updateFlags); ver.sdkVersion = mSdkVersion; + // Remove any stale app permissions (declared permission that now are undeclared + // by the same app, removed from its Manifest in newer versions) + if (!onlyCore) { + mSettings.removeStalePermissions(); + } + // If this is the first boot or an update from pre-M, and it is a normal // boot, then we need to initialize the default preferred apps across // all defined users. @@ -2295,6 +2407,38 @@ public class PackageManagerService extends IPackageManager.Stub { } } + // Disable components marked for disabling at build-time + mDisabledComponentsList = new ArrayList<ComponentName>(); + for (String name : mContext.getResources().getStringArray( + com.android.internal.R.array.config_disabledComponents)) { + ComponentName cn = ComponentName.unflattenFromString(name); + mDisabledComponentsList.add(cn); + Slog.v(TAG, "Disabling " + name); + String className = cn.getClassName(); + PackageSetting pkgSetting = mSettings.mPackages.get(cn.getPackageName()); + if (pkgSetting == null || pkgSetting.pkg == null + || !pkgSetting.pkg.hasComponentClassName(className)) { + Slog.w(TAG, "Unable to disable " + name); + continue; + } + pkgSetting.disableComponentLPw(className, UserHandle.USER_OWNER); + } + + // Enable components marked for forced-enable at build-time + for (String name : mContext.getResources().getStringArray( + com.android.internal.R.array.config_forceEnabledComponents)) { + ComponentName cn = ComponentName.unflattenFromString(name); + Slog.v(TAG, "Enabling " + name); + String className = cn.getClassName(); + PackageSetting pkgSetting = mSettings.mPackages.get(cn.getPackageName()); + if (pkgSetting == null || pkgSetting.pkg == null + || !pkgSetting.pkg.hasComponentClassName(className)) { + Slog.w(TAG, "Unable to enable " + name); + continue; + } + pkgSetting.enableComponentLPw(className, UserHandle.USER_OWNER); + } + // If this is first boot after an OTA, and a normal boot, then // we need to clear code cache directories. if (mIsUpgrade && !onlyCore) { @@ -3326,6 +3470,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false; // We'll take care of setting this one. if (!compareStrings(pi1.packageName, pi2.packageName)) return false; + if (pi1.allowViaWhitelist != pi2.allowViaWhitelist) return false; // These are not currently stored in settings. //if (!compareStrings(pi1.group, pi2.group)) return false; //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false; @@ -3450,6 +3595,16 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private boolean isAllowedSignature(PackageParser.Package pkg, String permissionName) { + for (Signature pkgSig : pkg.mSignatures) { + ArraySet<String> perms = mSignatureAllowances.get(pkgSig); + if (perms != null && perms.contains(permissionName)) { + return true; + } + } + return false; + } + @Override public void grantRuntimePermission(String packageName, String name, final int userId) { if (!sUserManager.exists(userId)) { @@ -3943,7 +4098,7 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.SIGNATURE_NO_MATCH; } - // Since both signature sets are of size 1, we can compare without HashSets. + // Since both signature sets are of size 1, we can compare without ArraySets. if (s1.length == 1) { return s1[0].equals(s2[0]) ? PackageManager.SIGNATURE_MATCH : @@ -4246,6 +4401,17 @@ public class PackageManagerService extends IPackageManager.Stub { if (ri.activityInfo.metaData == null) ri.activityInfo.metaData = new Bundle(); ri.activityInfo.metaData.putBoolean(Intent.METADATA_DOCK_HOME, true); return ri; + } else if (shouldIncludeResolveActivity(intent)) { + if (userId != 0) { + ResolveInfo ri = new ResolveInfo(mResolveInfo); + ri.activityInfo = new ActivityInfo(ri.activityInfo); + ri.activityInfo.applicationInfo = new ApplicationInfo( + ri.activityInfo.applicationInfo); + ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId, + UserHandle.getAppId(ri.activityInfo.applicationInfo.uid)); + return ri; + } + return mResolveInfo; } } return null; @@ -4506,6 +4672,13 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } + private boolean shouldIncludeResolveActivity(Intent intent) { + synchronized(mPackages) { + AppSuggestManager suggest = AppSuggestManager.getInstance(mContext); + return mResolverReplaced && (suggest != null) ? suggest.handles(intent) : false; + } + } + @Override public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) { @@ -5571,11 +5744,17 @@ public class PackageManagerService extends IPackageManager.Stub { private boolean createIdmapForPackagePairLI(PackageParser.Package pkg, PackageParser.Package opkg) { + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, + "Generating idmaps between " + pkg.packageName + ":" + opkg.packageName); if (!opkg.mTrustedOverlay) { Slog.w(TAG, "Skipping target and overlay pair " + pkg.baseCodePath + " and " + opkg.baseCodePath + ": overlay not trusted"); return false; } + + // Some apps like to take on the package name of an existing app so we'll use the + // "real" package name, if it is non-null, when performing the idmap + final String pkgName = pkg.mRealPackage != null ? pkg.mRealPackage : pkg.packageName; ArrayMap<String, PackageParser.Package> overlaySet = mOverlays.get(pkg.packageName); if (overlaySet == null) { Slog.e(TAG, "was about to create idmap for " + pkg.baseCodePath + " and " + @@ -5583,30 +5762,19 @@ public class PackageManagerService extends IPackageManager.Stub { return false; } final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); - // TODO: generate idmap for split APKs - if (mInstaller.idmap(pkg.baseCodePath, opkg.baseCodePath, sharedGid) != 0) { - Slog.e(TAG, "Failed to generate idmap for " + pkg.baseCodePath + " and " - + opkg.baseCodePath); + final String cachePath = + ThemeUtils.getTargetCacheDir(pkgName, opkg.packageName); + if (mInstaller.idmap(pkg.baseCodePath, opkg.baseCodePath, cachePath, sharedGid, + pkg.manifestHashCode, opkg.manifestHashCode) != 0) { + Slog.e(TAG, "Failed to generate idmap for " + pkg.baseCodePath + + " and " + opkg.baseCodePath); return false; } - PackageParser.Package[] overlayArray = - overlaySet.values().toArray(new PackageParser.Package[0]); - Comparator<PackageParser.Package> cmp = new Comparator<PackageParser.Package>() { - public int compare(PackageParser.Package p1, PackageParser.Package p2) { - return p1.mOverlayPriority - p2.mOverlayPriority; - } - }; - Arrays.sort(overlayArray, cmp); - - pkg.applicationInfo.resourceDirs = new String[overlayArray.length]; - int i = 0; - for (PackageParser.Package p : overlayArray) { - pkg.applicationInfo.resourceDirs[i++] = p.baseCodePath; - } return true; } - private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) { + private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime, + UserHandle user) { final File[] files = dir.listFiles(); if (ArrayUtils.isEmpty(files)) { Log.d(TAG, "No files in app dir " + dir); @@ -5618,6 +5786,16 @@ public class PackageManagerService extends IPackageManager.Stub { + " flags=0x" + Integer.toHexString(parseFlags)); } + int prebundledUserId = user == null ? UserHandle.USER_OWNER : user.getIdentifier(); + boolean isPrebundled = (parseFlags & PackageParser.PARSE_IS_PREBUNDLED_DIR) != 0; + if (isPrebundled) { + synchronized (mPackages) { + if (DEBUG_PREBUNDLED_SCAN) Log.d(TAG, "Reading prebundled packages for user " + + prebundledUserId); + mSettings.readPrebundledPackagesLPr(prebundledUserId); + } + } + for (File file : files) { final boolean isPackage = (isApkFile(file) || file.isDirectory()) && !PackageInstallerService.isStageName(file.getName()); @@ -5627,7 +5805,27 @@ public class PackageManagerService extends IPackageManager.Stub { } try { scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, - scanFlags, currentTime, null); + scanFlags, currentTime, user); + if (isPrebundled) { + final PackageParser.Package pkg; + try { + pkg = new PackageParser().parsePackage(file, parseFlags); + } catch (PackageParserException e) { + throw PackageManagerException.from(e); + } + synchronized (mPackages) { + if (DEBUG_PREBUNDLED_SCAN) Log.d(TAG, + "Marking prebundled packages for user " + prebundledUserId); + mSettings.markPrebundledPackageInstalledLPr(prebundledUserId, + pkg.packageName); + // do this for every other user + for (UserInfo userInfo : sUserManager.getUsers(true)) { + if (userInfo.id == prebundledUserId) continue; + mSettings.markPrebundledPackageInstalledLPr(userInfo.id, + pkg.packageName); + } + } + } } catch (PackageManagerException e) { Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage()); @@ -5643,6 +5841,14 @@ public class PackageManagerService extends IPackageManager.Stub { } } } + + if (isPrebundled) { + synchronized (mPackages) { + if (DEBUG_PREBUNDLED_SCAN) Log.d(TAG, "Writing prebundled packages for user " + + prebundledUserId); + mSettings.writePrebundledPackagesLPr(prebundledUserId); + } + } } private static File getSettingsProblemFile() { @@ -5696,6 +5902,12 @@ public class PackageManagerService extends IPackageManager.Stub { // if the package appears to be unchanged. pkg.mSignatures = ps.signatures.mSignatures; pkg.mSigningKeys = signingKs; + // Collect manifest digest + try{ + pp.collectManifestDigest(pkg); + } catch (PackageParserException e) { + throw PackageManagerException.from(e); + } return; } @@ -5715,7 +5927,7 @@ public class PackageManagerService extends IPackageManager.Stub { /* * Scan a package and return the newly parsed package. - * Returns null in case of errors and the error code is stored in mLastScanError + * Returns null in case of errors and the error code is stored in */ private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { @@ -5737,6 +5949,36 @@ public class PackageManagerService extends IPackageManager.Stub { throw PackageManagerException.from(e); } + if ((parseFlags & PackageParser.PARSE_IS_PREBUNDLED_DIR) != 0) { + synchronized (mPackages) { + PackageSetting existingSettings = mSettings.peekPackageLPr(pkg.packageName); + if (mSettings.wasPrebundledPackageInstalledLPr(user.getIdentifier() + , pkg.packageName) && existingSettings == null) { + // The prebundled app was installed at some point in time, but now it is + // gone. Assume that the user uninstalled it intentionally: do not reinstall. + throw new PackageManagerException(INSTALL_FAILED_UNINSTALLED_PREBUNDLE, + "skip reinstall for " + pkg.packageName); + } else if (existingSettings == null && + !mSettings.shouldPrebundledPackageBeInstalled(mContext.getResources(), + pkg.packageName, mCustomResources)) { + // The prebundled app is not needed for the default mobile country code, + // skip installing it + throw new PackageManagerException(INSTALL_FAILED_REGION_LOCKED_PREBUNDLE, + "skip install for " + pkg.packageName); + } else if (existingSettings != null + && existingSettings.versionCode >= pkg.mVersionCode + && !existingSettings.codePathString.contains( + Environment.getPrebundledDirectory().getPath())) { + // This app is installed in a location that is not the prebundled location + // and has a higher (or same) version as the prebundled one. Skip + // installing the prebundled version. + Slog.d(TAG, pkg.packageName + " already installed at " + + existingSettings.codePathString); + return null; // return null so we still mark package as installed + } + } + } + PackageSetting ps = null; PackageSetting updatedPkg; // reader @@ -6197,13 +6439,11 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_DEXOPT) { Log.i(TAG, "Optimizing app " + curr + " of " + total + ": " + pkg.packageName); } - if (!isFirstBoot()) { - try { - ActivityManagerNative.getDefault().showBootMessage( - mContext.getResources().getString(R.string.android_upgrading_apk, - curr, total), true); - } catch (RemoteException e) { - } + try { + ActivityManagerNative.getDefault().showBootMessage( + mContext.getResources().getString(R.string.android_upgrading_apk, + curr, total), true); + } catch (RemoteException e) { } PackageParser.Package p = pkg; synchronized (mInstallLock) { @@ -6601,6 +6841,20 @@ public class PackageManagerService extends IPackageManager.Stub { } } + if (Build.TAGS.equals("test-keys") && + !pkg.applicationInfo.sourceDir.startsWith(Environment.getRootDirectory().getPath()) && + !pkg.applicationInfo.sourceDir.startsWith("/vendor")) { + Object obj = mSettings.getUserIdLPr(1000); + Signature[] s1 = null; + if (obj instanceof SharedUserSetting) { + s1 = ((SharedUserSetting)obj).signatures.mSignatures; + } + if ((compareSignatures(pkg.mSignatures, s1) == PackageManager.SIGNATURE_MATCH)) { + throw new PackageManagerException(INSTALL_FAILED_INVALID_INSTALL_LOCATION, + "Cannot install platform packages to user storage!"); + } + } + // Initialize package source and resource directories File destCodeFile = new File(pkg.applicationInfo.getCodePath()); File destResourceFile = new File(pkg.applicationInfo.getResourcePath()); @@ -6615,6 +6869,16 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.mAdoptPermissions = null; } + // collect manifest digest which includes getting manifest hash code for themes + if (pkg.manifestDigest == null || pkg.manifestHashCode == 0) { + PackageParser pp = new PackageParser(); + try { + pp.collectManifestDigest(pkg); + } catch (PackageParserException e) { + Slog.w(TAG, "Unable to collect manifest digest", e); + } + } + // writer synchronized (mPackages) { if (pkg.mSharedUserId != null) { @@ -6980,7 +7244,7 @@ public class PackageManagerService extends IPackageManager.Stub { final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting); if ((scanFlags & SCAN_NEW_INSTALL) == 0) { - derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */); + derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */, parseFlags); // Some system apps still use directory structure for native libraries // in which case we might end up not detecting abi solely based on apk @@ -6988,7 +7252,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() && pkg.applicationInfo.primaryCpuAbi == null) { setBundledAppAbisAndRoots(pkg, pkgSetting); - setNativeLibraryPaths(pkg); + setNativeLibraryPaths(pkg, parseFlags); } } else { @@ -7004,7 +7268,7 @@ public class PackageManagerService extends IPackageManager.Stub { // ABIs we've determined above. For non-moves, the path will be updated based on the // ABIs we determined during compilation, but the path will depend on the final // package path (after the rename away from the stage path). - setNativeLibraryPaths(pkg); + setNativeLibraryPaths(pkg, parseFlags); } if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path); @@ -7207,6 +7471,13 @@ public class PackageManagerService extends IPackageManager.Stub { // Add the new setting to mSettings mSettings.insertPackageSettingLPw(pkgSetting, pkg); + + // Themes: handle case where app was installed after icon mapping applied + if (mIconPackHelper != null) { + int id = mIconPackHelper.getResourceIdForApp(pkg.packageName); + pkg.applicationInfo.themedIcon = id; + } + // Add the new setting to mPackages mPackages.put(pkg.applicationInfo.packageName, pkg); // Make sure we don't accidentally delete its data. @@ -7345,6 +7616,13 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Activity a = pkg.activities.get(i); a.info.processName = fixProcessName(pkg.applicationInfo.processName, a.info.processName, pkg.applicationInfo.uid); + + // Themes: handle case where app was installed after icon mapping applied + if (mIconPackHelper != null) { + a.info.themedIcon = mIconPackHelper + .getResourceIdForActivityIcon(a.info); + } + mActivities.addActivity(a, "activity"); if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) { if (r == null) { @@ -7430,6 +7708,7 @@ public class PackageManagerService extends IPackageManager.Stub { bp.perm = p; bp.uid = pkg.applicationInfo.uid; bp.sourcePackage = p.info.packageName; + bp.allowViaWhitelist = p.info.allowViaWhitelist; p.info.flags |= PermissionInfo.FLAG_INSTALLED; } else if (!currentOwnerIsSystem) { String msg = "New decl " + p.owner + " of permission " @@ -7443,6 +7722,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (bp == null) { bp = new BasePermission(p.info.name, p.info.packageName, BasePermission.TYPE_NORMAL); + bp.allowViaWhitelist = p.info.allowViaWhitelist; permissionMap.put(p.info.name, bp); } @@ -7456,6 +7736,7 @@ public class PackageManagerService extends IPackageManager.Stub { bp.perm = p; bp.uid = pkg.applicationInfo.uid; bp.sourcePackage = p.info.packageName; + bp.allowViaWhitelist = p.info.allowViaWhitelist; p.info.flags |= PermissionInfo.FLAG_INSTALLED; if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) { if (r == null) { @@ -7531,27 +7812,85 @@ public class PackageManagerService extends IPackageManager.Stub { pkgSetting.setTimeStamp(scanFileTime); - // Create idmap files for pairs of (packages, overlay packages). - // Note: "android", ie framework-res.apk, is handled by native layers. - if (pkg.mOverlayTarget != null) { - // This is an overlay package. - if (pkg.mOverlayTarget != null && !pkg.mOverlayTarget.equals("android")) { - if (!mOverlays.containsKey(pkg.mOverlayTarget)) { - mOverlays.put(pkg.mOverlayTarget, - new ArrayMap<String, PackageParser.Package>()); + final boolean isBootScan = (scanFlags & SCAN_BOOTING) != 0; + // Generate resources & idmaps if pkg is NOT a theme + // We must compile resources here because during the initial boot process we may get + // here before a default theme has had a chance to compile its resources + if (pkg.mOverlayTargets.isEmpty() && mOverlays.containsKey(pkg.packageName)) { + ArrayMap<String, PackageParser.Package> themes = mOverlays.get(pkg.packageName); + for(PackageParser.Package themePkg : themes.values()) { + if (!isBootScan || (mBootThemeConfig != null && + (themePkg.packageName.equals(mBootThemeConfig.getOverlayPkgName()) || + themePkg.packageName.equals( + mBootThemeConfig.getOverlayPkgNameForApp(pkg.packageName))))) { + try { + compileResourcesAndIdmapIfNeeded(pkg, themePkg); + } catch (Exception e) { + // Do not stop a pkg installation just because of one bad theme + // Also we don't break here because we should try to compile other + // themes + Log.e(TAG, "Unable to compile " + themePkg.packageName + + " for target " + pkg.packageName, e); + } } - ArrayMap<String, PackageParser.Package> map = mOverlays.get(pkg.mOverlayTarget); - map.put(pkg.packageName, pkg); - PackageParser.Package orig = mPackages.get(pkg.mOverlayTarget); - if (orig != null && !createIdmapForPackagePairLI(orig, pkg)) { - throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, - "scanPackageLI failed to createIdmap"); + } + } + + // If this is a theme we should re-compile common resources if they exist so + // remove this package from mAvailableCommonResources. + if (!isBootScan && pkg.mOverlayTargets.size() > 0) { + mAvailableCommonResources.remove(pkg.packageName); + } + + // Generate Idmaps and res tables if pkg is a theme + for(String target : pkg.mOverlayTargets) { + Exception failedException = null; + int failReason = 0; + + insertIntoOverlayMap(target, pkg); + if (isBootScan && mBootThemeConfig != null && + (pkg.packageName.equals(mBootThemeConfig.getOverlayPkgName()) || + pkg.packageName.equals( + mBootThemeConfig.getOverlayPkgNameForApp(target)))) { + try { + compileResourcesAndIdmapIfNeeded(mPackages.get(target), pkg); + } catch (IdmapException e) { + failedException = e; + failReason = PackageManager.INSTALL_FAILED_THEME_IDMAP_ERROR; + } catch (AaptException e) { + failedException = e; + failReason = PackageManager.INSTALL_FAILED_THEME_AAPT_ERROR; + } catch (Exception e) { + failedException = e; + failReason = PackageManager.INSTALL_FAILED_THEME_UNKNOWN_ERROR; + } + + if (failedException != null) { + Log.w(TAG, "Unable to process theme " + pkgName, failedException); + uninstallThemeForAllApps(pkg); + deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false); + throw new PackageManagerException(failReason, + "Unable to process theme " + pkgName, failedException); + } + } + } + + //Icon Packs need aapt too + if (isBootScan && (mBootThemeConfig != null && + pkg.packageName.equals(mBootThemeConfig.getIconPackPkgName()))) { + if (isIconCompileNeeded(pkg)) { + try { + ThemeUtils.createCacheDirIfNotExists(); + ThemeUtils.createIconDirIfNotExists(pkg.packageName); + compileIconPack(pkg); + } catch (Exception e) { + uninstallThemeForAllApps(pkg); + deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false); + throw new PackageManagerException( + PackageManager.INSTALL_FAILED_THEME_AAPT_ERROR, + "Unable to process theme " + pkgName); } } - } else if (mOverlays.containsKey(pkg.packageName) && - !pkg.packageName.equals("android")) { - // This is a regular package, with one or more known overlay packages. - createIdmapsForPackageLI(pkg); } } @@ -7566,7 +7905,8 @@ public class PackageManagerService extends IPackageManager.Stub { * If {@code extractLibs} is true, native libraries are extracted from the app if required. */ public void derivePackageAbi(PackageParser.Package pkg, File scanFile, - String cpuAbiOverride, boolean extractLibs) + String cpuAbiOverride, boolean extractLibs, + int parseFlags) throws PackageManagerException { // TODO: We can probably be smarter about this stuff. For installed apps, // we can calculate this information at install time once and for all. For @@ -7575,7 +7915,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Give ourselves some initial paths; we'll come back for another // pass once we've determined ABI below. - setNativeLibraryPaths(pkg); + setNativeLibraryPaths(pkg, parseFlags); // We would never need to extract libs for forward-locked and external packages, // since the container service will do it for us. We shouldn't attempt to @@ -7695,7 +8035,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Now that we've calculated the ABIs and determined if it's an internal app, // we will go ahead and populate the nativeLibraryPath. - setNativeLibraryPaths(pkg); + setNativeLibraryPaths(pkg, parseFlags); } /** @@ -7791,6 +8131,305 @@ public class PackageManagerService extends IPackageManager.Stub { } } + + private boolean isIconCompileNeeded(Package pkg) { + if (!pkg.hasIconPack) return false; + // Read in the stored hash value and compare to the pkgs computed hash value + FileInputStream in = null; + DataInputStream dataInput = null; + try { + String hashFile = ThemeUtils.getIconHashFile(pkg.packageName); + in = new FileInputStream(hashFile); + dataInput = new DataInputStream(in); + int storedHashCode = dataInput.readInt(); + int actualHashCode = pkg.manifestHashCode; + return storedHashCode != actualHashCode; + } catch(IOException e) { + // all is good enough for government work here, + // we'll just return true and the icons will be processed + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(dataInput); + } + + return true; + } + + private void compileResourcesAndIdmapIfNeeded(PackageParser.Package targetPkg, + PackageParser.Package themePkg) + throws IdmapException, AaptException, IOException, Exception + { + if (!shouldCreateIdmap(targetPkg, themePkg)) { + return; + } + + // Always use the manifest's pkgName when compiling resources + // the member value of "packageName" is dependent on whether this was a clean install + // or an upgrade w/ If the app is an upgrade then the original package name is used. + // because libandroidfw uses the manifests's pkgName during idmap creation we must + // be consistent here and use the same name, otherwise idmap will look in the wrong place + // for the resource table. + String pkgName = targetPkg.mRealPackage != null ? + targetPkg.mRealPackage : targetPkg.packageName; + compileResourcesIfNeeded(pkgName, themePkg); + generateIdmap(targetPkg.packageName, themePkg); + } + + private void compileResourcesIfNeeded(String target, PackageParser.Package pkg) + throws AaptException, IOException, Exception + { + ThemeUtils.createCacheDirIfNotExists(); + + if (hasCommonResources(pkg) + && shouldCompileCommonResources(pkg)) { + ThemeUtils.createResourcesDirIfNotExists(COMMON_OVERLAY, pkg.packageName); + compileResources(COMMON_OVERLAY, pkg); + mAvailableCommonResources.put(pkg.packageName, System.currentTimeMillis()); + } + + ThemeUtils.createResourcesDirIfNotExists(target, pkg.packageName); + compileResources(target, pkg); + } + + private void compileResources(String target, PackageParser.Package pkg) throws Exception { + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Compile resource table for " + pkg.packageName); + //TODO: cleanup this hack. Modify aapt? Aapt uses the manifests package name + //when creating the resource table. We care about the resource table's name because + //it is used when removing the table by cookie. + try { + createTempManifest(COMMON_OVERLAY.equals(target) + ? ThemeUtils.getCommonPackageName(pkg.packageName) : pkg.packageName); + compileResourcesWithAapt(target, pkg); + } finally { + cleanupTempManifest(); + } + } + + private void compileIconPack(Package pkg) throws Exception { + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Compile resource table for " + pkg.packageName); + OutputStream out = null; + DataOutputStream dataOut = null; + try { + createTempManifest(pkg.packageName); + int code = pkg.manifestHashCode; + String hashFile = ThemeUtils.getIconHashFile(pkg.packageName); + out = new FileOutputStream(hashFile); + dataOut = new DataOutputStream(out); + dataOut.writeInt(code); + compileIconsWithAapt(pkg); + } finally { + IoUtils.closeQuietly(out); + IoUtils.closeQuietly(dataOut); + cleanupTempManifest(); + } + } + + private void insertIntoOverlayMap(String target, PackageParser.Package opkg) { + if (!mOverlays.containsKey(target)) { + mOverlays.put(target, + new ArrayMap<String, PackageParser.Package>()); + } + ArrayMap<String, PackageParser.Package> map = mOverlays.get(target); + map.put(opkg.packageName, opkg); + } + + private void generateIdmap(String target, PackageParser.Package opkg) throws IdmapException { + PackageParser.Package targetPkg = mPackages.get(target); + if (targetPkg != null && !createIdmapForPackagePairLI(targetPkg, opkg)) { + throw new IdmapException("idmap failed for targetPkg: " + targetPkg + + " and opkg: " + opkg); + } + } + + public class AaptException extends Exception { + public AaptException(String message) { + super(message); + } + } + + public class IdmapException extends Exception { + public IdmapException(String message) { + super(message); + } + } + + private boolean hasCommonResources(PackageParser.Package pkg) throws Exception { + boolean ret = false; + // check if assets/overlays/common exists in this theme + AssetManager assets = new AssetManager(); + assets.addAssetPath(pkg.baseCodePath); + String[] common = assets.list("overlays/common"); + if (common != null && common.length > 0) ret = true; + + return ret; + } + + private void compileResourcesWithAapt(String target, PackageParser.Package pkg) + throws Exception { + String internalPath = APK_PATH_TO_OVERLAY + target + File.separator; + String resPath = ThemeUtils.getTargetCacheDir(target, pkg); + final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); + int pkgId; + if ("android".equals(target)) { + pkgId = Resources.THEME_FRAMEWORK_PKG_ID; + } else if (COMMON_OVERLAY.equals(target)) { + pkgId = Resources.THEME_COMMON_PKG_ID; + } else { + pkgId = Resources.THEME_APP_PKG_ID; + } + + boolean hasCommonResources = (hasCommonResources(pkg) && !COMMON_OVERLAY.equals(target)); + if (mInstaller.aapt(pkg.baseCodePath, internalPath, resPath, sharedGid, pkgId, + pkg.applicationInfo.targetSdkVersion, + hasCommonResources ? ThemeUtils.getTargetCacheDir(COMMON_OVERLAY, pkg) + + File.separator + "resources.apk" : "") != 0) { + throw new AaptException("Failed to run aapt"); + } + } + + private void compileIconsWithAapt(Package pkg) throws Exception { + String resPath = ThemeUtils.getIconPackDir(pkg.packageName); + final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); + + if (mInstaller.aapt(pkg.baseCodePath, APK_PATH_TO_ICONS, resPath, sharedGid, + Resources.THEME_ICON_PKG_ID, + pkg.applicationInfo.targetSdkVersion, + "") != 0) { + throw new AaptException("Failed to run aapt"); + } + } + + private void uninstallThemeForAllApps(PackageParser.Package opkg) { + for(String target : opkg.mOverlayTargets) { + ArrayMap<String, PackageParser.Package> map = mOverlays.get(target); + if (map != null) { + map.remove(opkg.packageName); + + if (map.isEmpty()) { + mOverlays.remove(target); + } + } + } + + // Now simply delete the root overlay cache directory and all its contents + recursiveDelete(new File(ThemeUtils.getOverlayResourceCacheDir(opkg.packageName))); + } + + private void uninstallThemeForApp(PackageParser.Package appPkg) { + ArrayMap<String, PackageParser.Package> map = mOverlays.get(appPkg.packageName); + if (map == null) return; + + for(PackageParser.Package opkg : map.values()) { + String cachePath = ThemeUtils.getTargetCacheDir(appPkg.packageName, opkg.packageName); + recursiveDelete(new File(cachePath)); + } + } + + private void recursiveDelete(File f) { + if (f.isDirectory()) { + for (File c : f.listFiles()) + recursiveDelete(c); + } + f.delete(); + } + + private void createTempManifest(String pkgName) throws Exception { + StringBuilder manifest = new StringBuilder(); + manifest.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); + manifest.append("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\""); + manifest.append(" package=\"" + pkgName+ "\">"); + manifest.append(" </manifest>"); + + BufferedWriter bw = null; + try { + bw = new BufferedWriter(new FileWriter("/data/app/AndroidManifest.xml")); + bw.write(manifest.toString()); + bw.flush(); + bw.close(); + File resFile = new File("/data/app/AndroidManifest.xml"); + FileUtils.setPermissions(resFile, + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH, -1, -1); + } finally { + IoUtils.closeQuietly(bw); + } + } + + private void cleanupTempManifest() { + File resFile = new File("/data/app/AndroidManifest.xml"); + resFile.delete(); + } + + /** + * Compares the 32 bit hash of the target and overlay to those stored + * in the idmap and returns true if either hash differs + * @param targetPkg + * @param overlayPkg + * @return + */ + private boolean shouldCreateIdmap(PackageParser.Package targetPkg, + PackageParser.Package overlayPkg) { + if (targetPkg == null || targetPkg.baseCodePath == null || overlayPkg == null) return false; + + int targetHash = targetPkg.manifestHashCode; + int overlayHash = overlayPkg.manifestHashCode; + + File idmap = + new File(ThemeUtils.getIdmapPath(targetPkg.packageName, overlayPkg.packageName)); + if (!idmap.exists()) { + return true; + } + + int[] hashes; + try { + hashes = getIdmapHashes(idmap); + } catch (IOException e) { + return true; + } + + if (targetHash == 0 || overlayHash == 0 || + targetHash != hashes[0] || overlayHash != hashes[1]) { + // if the overlay changed we'll want to recreate the common resources if it has any + if (overlayHash != hashes[1] + && mAvailableCommonResources.containsKey(overlayPkg.packageName)) { + mAvailableCommonResources.remove(overlayPkg.packageName); + } + return true; + } + return false; + } + + private boolean shouldCompileCommonResources(PackageParser.Package pkg) { + if (!mAvailableCommonResources.containsKey(pkg.packageName)) return true; + + long lastUpdated = mAvailableCommonResources.get(pkg.packageName); + long currentTime = System.currentTimeMillis(); + if (currentTime - lastUpdated > COMMON_RESOURCE_EXPIRATION) { + return true; + } + return false; + } + + /** + * Get the file modified times for the overlay and target from the idmap + * @param idmap + * @return + * @throws IOException + */ + private int[] getIdmapHashes(File idmap) throws IOException { + int[] times = new int[2]; + ByteBuffer bb = ByteBuffer.allocate(8); + bb.order(ByteOrder.LITTLE_ENDIAN); + FileInputStream fis = new FileInputStream(idmap); + fis.skip(IDMAP_HASH_START_OFFSET); + fis.read(bb.array()); + fis.close(); + final IntBuffer ib = bb.asIntBuffer(); + times[0] = ib.get(0); + times[1] = ib.get(1); + + return times; + } + private void setUpCustomResolverActivity(PackageParser.Package pkg) { synchronized (mPackages) { mResolverReplaced = true; @@ -7851,7 +8490,7 @@ public class PackageManagerService extends IPackageManager.Stub { * Derive and set the location of native libraries for the given package, * which varies depending on where and how the package was installed. */ - private void setNativeLibraryPaths(PackageParser.Package pkg) { + private void setNativeLibraryPaths(PackageParser.Package pkg, int parseFlags) { final ApplicationInfo info = pkg.applicationInfo; final String codePath = pkg.codePath; final File codeFile = new File(codePath); @@ -7897,10 +8536,17 @@ public class PackageManagerService extends IPackageManager.Stub { info.nativeLibraryRootRequiresIsa = false; info.nativeLibraryDir = info.nativeLibraryRootDir; } else { - // Cluster install - info.nativeLibraryRootDir = new File(codeFile, LIB_DIR_NAME).getAbsolutePath(); + if ((parseFlags & PackageParser.PARSE_IS_PREBUNDLED_DIR) != 0) { + // mAppLib32InstallDir is the directory /data/app-lib which is used to store native + // libs for apps from the system paritition. It isn't really specific to 32bit in + // any way except for the variable name, the system will use the primary/secondary + // ABI computed below. + info.nativeLibraryRootDir = + new File(mAppLib32InstallDir, pkg.packageName).getAbsolutePath(); + } else { + info.nativeLibraryRootDir = new File(codeFile, LIB_DIR_NAME).getAbsolutePath(); + } info.nativeLibraryRootRequiresIsa = true; - info.nativeLibraryDir = new File(info.nativeLibraryRootDir, getPrimaryInstructionSet(info)).getAbsolutePath(); @@ -8617,7 +9263,6 @@ public class PackageManagerService extends IPackageManager.Stub { & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) { if (isSystemApp(pkg)) { // For updated system applications, a system permission - // is granted only if it had been defined by the original application. if (pkg.isUpdatedSystemApp()) { final PackageSetting sysPs = mSettings .getDisabledSystemPkgLPr(pkg.packageName); @@ -8685,6 +9330,9 @@ public class PackageManagerService extends IPackageManager.Stub { allowed = origPermissions.hasInstallPermission(perm); } } + if (!allowed && bp.allowViaWhitelist) { + allowed = isAllowedSignature(pkg, perm); + } return allowed; } @@ -8701,8 +9349,24 @@ public class PackageManagerService extends IPackageManager.Stub { int userId) { if (!sUserManager.exists(userId)) return null; mFlags = flags; - return super.queryIntent(intent, resolvedType, + List<ResolveInfo> list = super.queryIntent(intent, resolvedType, (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId); + // Remove protected Application components + int callingUid = Binder.getCallingUid(); + String[] pkgs = getPackagesForUid(callingUid); + List<String> packages = (pkgs != null) ? Arrays.asList(pkgs) : Collections.EMPTY_LIST; + if (callingUid != Process.SYSTEM_UID && + (getFlagsForUid(callingUid) & ApplicationInfo.FLAG_SYSTEM) == 0) { + Iterator<ResolveInfo> itr = list.iterator(); + while (itr.hasNext()) { + ActivityInfo activityInfo = itr.next().activityInfo; + if (activityInfo.applicationInfo.protect && (packages == null + || !packages.contains(activityInfo.packageName))) { + itr.remove(); + } + } + } + return list; } public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, @@ -9382,8 +10046,8 @@ public class PackageManagerService extends IPackageManager.Stub { }; final void sendPackageBroadcast(final String action, final String pkg, - final Bundle extras, final String targetPkg, final IIntentReceiver finishedReceiver, - final int[] userIds) { + final String intentCategory, final Bundle extras, final String targetPkg, + final IIntentReceiver finishedReceiver, final int[] userIds) { mHandler.post(new Runnable() { @Override public void run() { @@ -9420,6 +10084,9 @@ public class PackageManagerService extends IPackageManager.Stub { + intent.toShortString(false, true, false, false) + " " + intent.getExtras(), here); } + if (intentCategory != null) { + intent.addCategory(intentCategory); + } am.broadcastIntent(null, intent, null, finishedReceiver, 0, null, null, null, android.app.AppOpsManager.OP_NONE, null, finishedReceiver != null, false, id); @@ -9586,7 +10253,7 @@ public class PackageManagerService extends IPackageManager.Stub { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId)); - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, null, packageName, extras, null, null, new int[] {userId}); try { IActivityManager am = ActivityManagerNative.getDefault(); @@ -10498,6 +11165,9 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { PackageParser.Package pkg = mPackages.get(packageName); + if (pkgLite.isTheme) { + return PackageHelper.RECOMMEND_INSTALL_INTERNAL; + } if (pkg != null) { if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { // Check for downgrading. @@ -10637,7 +11307,7 @@ public class PackageManagerService extends IPackageManager.Stub { loc = installLocationPolicy(pkgLite); if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) { ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; - } else if (!onSd && !onInt) { + } else if ((!onSd && !onInt) || pkgLite.isTheme) { // Override install location with flags if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { // Set the flag to install on external media. @@ -10649,6 +11319,9 @@ public class PackageManagerService extends IPackageManager.Stub { installFlags |= PackageManager.INSTALL_INTERNAL; installFlags &= ~PackageManager.INSTALL_EXTERNAL; } + if (pkgLite.isTheme) { + installFlags &= ~PackageManager.INSTALL_FORWARD_LOCK; + } } } } @@ -12367,7 +13040,7 @@ public class PackageManagerService extends IPackageManager.Stub { try { derivePackageAbi(pkg, new File(pkg.codePath), args.abiOverride, - true /* extract libs */); + true /* extract libs */, parseFlags); } catch (PackageManagerException pme) { Slog.e(TAG, "Error deriving application ABI", pme); res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI"); @@ -12761,11 +13434,16 @@ public class PackageManagerService extends IPackageManager.Stub { ? info.removedAppId : info.uid); extras.putBoolean(Intent.EXTRA_REPLACING, true); - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, + String category = null; + if (info.isThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } + + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, category, extras, null, null, null); - sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, + sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, category, extras, null, null, null); - sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, + sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, null, packageName, null, null); } } @@ -12790,6 +13468,7 @@ public class PackageManagerService extends IPackageManager.Stub { boolean isRemovedPackageSystemUpdate = false; // Clean up resources deleted packages. InstallArgs args = null; + boolean isThemeApk = false; void sendBroadcast(boolean fullRemove, boolean replacing, boolean removedForAllUsers) { Bundle extras = new Bundle(1); @@ -12800,15 +13479,19 @@ public class PackageManagerService extends IPackageManager.Stub { } extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers); if (removedPackage != null) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, + String category = null; + if (isThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } + sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, category, extras, null, null, removedUsers); if (fullRemove && !replacing) { sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, - extras, null, null, removedUsers); + category, extras, null, null, removedUsers); } } if (removedAppId >= 0) { - sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null, + sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, null, extras, null, null, removedUsers); } } @@ -13115,7 +13798,8 @@ public class PackageManagerService extends IPackageManager.Stub { false, //hidden null, null, null, false, // blockUninstall - ps.readUserState(userId).domainVerificationStatus, 0); + ps.readUserState(userId).domainVerificationStatus, 0, + null, null); if (!isSystemApp(ps)) { if (ps.isAnyInstalled(sUserManager.getUserIds())) { // Other user still have this package installed, so all @@ -13188,6 +13872,15 @@ public class PackageManagerService extends IPackageManager.Stub { outInfo, writeSettings); } + //Cleanup theme related data + if (ps.pkg != null) { + if (ps.pkg.mOverlayTargets.size() > 0) { + uninstallThemeForAllApps(ps.pkg); + } else if (mOverlays.containsKey(ps.pkg.packageName)) { + uninstallThemeForApp(ps.pkg); + } + } + return ret; } @@ -14353,6 +15046,12 @@ public class PackageManagerService extends IPackageManager.Stub { public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags, int userId) { if (!sUserManager.exists(userId)) return; + // Don't allow to enable components marked for disabling at build-time + if (mDisabledComponentsList.contains(componentName)) { + Slog.d(TAG, "Ignoring attempt to set enabled state of disabled component " + + componentName.flattenToString()); + return; + } setEnabledSetting(componentName.getPackageName(), componentName.getClassName(), newState, flags, userId, null); } @@ -14367,6 +15066,7 @@ public class PackageManagerService extends IPackageManager.Stub { throw new IllegalArgumentException("Invalid new component state: " + newState); } + PackageSetting pkgSetting; final int uid = Binder.getCallingUid(); final int permission = mContext.checkCallingOrSelfPermission( @@ -14494,7 +15194,7 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList); extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag); extras.putInt(Intent.EXTRA_UID, packageUid); - sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, null, null, + sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, null, extras, null, null, new int[] {UserHandle.getUserId(packageUid)}); } @@ -15485,7 +16185,7 @@ public class PackageManagerService extends IPackageManager.Stub { } String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; - sendPackageBroadcast(action, null, extras, null, finishedReceiver, null); + sendPackageBroadcast(action, null, null, extras, null, finishedReceiver, null); } } @@ -16220,6 +16920,14 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.createNewUserLILPw(this, mInstaller, userHandle); applyFactoryDefaultBrowserLPw(userHandle); primeDomainVerificationsLPw(userHandle); + // Set flag to monitor and not change apk file paths when + // scanning install directories. + final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING; + createAndSetCustomResources(); + scanDirLI(Environment.getPrebundledDirectory(), + PackageParser.PARSE_IS_PREBUNDLED_DIR, scanFlags, 0, + new UserHandle(userHandle)); + mCustomResources = null; } } @@ -16275,6 +16983,69 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public void setComponentProtectedSetting(ComponentName componentName, boolean newState, + int userId) { + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "set protected"); + + String packageName = componentName.getPackageName(); + String className = componentName.getClassName(); + + PackageSetting pkgSetting; + ArrayList<String> components; + + synchronized (mPackages) { + pkgSetting = mSettings.mPackages.get(packageName); + + if (pkgSetting == null) { + if (className == null) { + throw new IllegalArgumentException( + "Unknown package: " + packageName); + } + throw new IllegalArgumentException( + "Unknown component: " + packageName + + "/" + className); + } + + //Protection levels must be applied at the Component Level! + if (className == null) { + throw new IllegalArgumentException( + "Must specify Component Class name." + ); + } else { + PackageParser.Package pkg = pkgSetting.pkg; + if (pkg == null || !pkg.hasComponentClassName(className)) { + if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) { + throw new IllegalArgumentException("Component class " + className + + " does not exist in " + packageName); + } else { + Slog.w(TAG, "Failed setComponentProtectedSetting: component class " + + className + " does not exist in " + packageName); + } + } + + pkgSetting.protectComponentLPw(className, newState, userId); + mSettings.writePackageRestrictionsLPr(userId); + + components = mPendingBroadcasts.get(userId, packageName); + final boolean newPackage = components == null; + if (newPackage) { + components = new ArrayList<String>(); + } + if (!components.contains(className)) { + components.add(className); + } + } + } + + long callingId = Binder.clearCallingIdentity(); + try { + int packageUid = UserHandle.getUid(userId, pkgSetting.appId); + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + + @Override public boolean isStorageLow() { final long token = Binder.clearCallingIdentity(); try { @@ -16448,6 +17219,39 @@ public class PackageManagerService extends IPackageManager.Stub { } } + @Override + public void updateIconMapping(String pkgName) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CHANGE_CONFIGURATION, + "could not update icon mapping because caller " + + "does not have change config permission"); + + if (pkgName == null) { + clearIconMapping(); + return; + } + mIconPackHelper = new IconPackHelper(mContext); + try { + mIconPackHelper.loadIconPack(pkgName); + } catch(NameNotFoundException e) { + Log.e(TAG, "Unable to find icon pack: " + pkgName); + clearIconMapping(); + return; + } + + for (Activity activity : mActivities.mActivities.values()) { + activity.info.themedIcon = + mIconPackHelper.getResourceIdForActivityIcon(activity.info); + } + + synchronized (mPackages) { + for (Package pkg : mPackages.values()) { + pkg.applicationInfo.themedIcon = + mIconPackHelper.getResourceIdForApp(pkg.packageName); + } + } + } + private static class MoveCallbacks extends Handler { private static final int MSG_CREATED = 1; private static final int MSG_STATUS_CHANGED = 2; @@ -16674,4 +17478,109 @@ public class PackageManagerService extends IPackageManager.Stub { "Cannot call " + tag + " from UID " + callingUid); } } + + private void clearIconMapping() { + mIconPackHelper = null; + for (Activity activity : mActivities.mActivities.values()) { + activity.info.themedIcon = 0; + } + + for (Package pkg : mPackages.values()) { + pkg.applicationInfo.themedIcon = 0; + } + } + + @Override + public ComposedIconInfo getComposedIconInfo() { + return mIconPackHelper != null ? mIconPackHelper.getComposedIconInfo() : null; + } + + @Override + public int processThemeResources(String themePkgName) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_THEME_MANAGER, null); + PackageParser.Package pkg = mPackages.get(themePkgName); + if (pkg == null) { + Log.w(TAG, "Unable to get pkg for processing " + themePkgName); + return 0; + } + + // Process icons + if (isIconCompileNeeded(pkg)) { + try { + ThemeUtils.createCacheDirIfNotExists(); + ThemeUtils.createIconDirIfNotExists(pkg.packageName); + compileIconPack(pkg); + } catch (Exception e) { + uninstallThemeForAllApps(pkg); + deletePackageX(themePkgName, getCallingUid(), PackageManager.DELETE_ALL_USERS); + return PackageManager.INSTALL_FAILED_THEME_AAPT_ERROR; + } + } + + int errorCode = 0; + // Generate Idmaps and res tables if pkg is a theme + for(String target : pkg.mOverlayTargets) { + Exception failedException = null; + try { + compileResourcesAndIdmapIfNeeded(mPackages.get(target), pkg); + } catch (IdmapException e) { + failedException = e; + errorCode = PackageManager.INSTALL_FAILED_THEME_IDMAP_ERROR; + } catch (AaptException e) { + failedException = e; + errorCode = PackageManager.INSTALL_FAILED_THEME_AAPT_ERROR; + } catch (Exception e) { + failedException = e; + errorCode = PackageManager.INSTALL_FAILED_THEME_UNKNOWN_ERROR; + } + + if (failedException != null) { + Log.e(TAG, "Unable to process theme, uninstalling " + pkg.packageName, + failedException); + uninstallThemeForAllApps(pkg); + deletePackageX(themePkgName, getCallingUid(), PackageManager.DELETE_ALL_USERS); + return errorCode; + } + } + + return 0; + } + + private void processThemeResourcesInThemeService(String pkgName) { + ThemeManager tm = + (ThemeManager) mContext.getSystemService(Context.THEME_SERVICE); + if (tm != null) { + tm.processThemeResources(pkgName); + } + } + + private void createAndSetCustomResources() { + Configuration tempConfiguration = new Configuration(); + String mcc = SystemProperties.get("ro.prebundled.mcc"); + if (!TextUtils.isEmpty(mcc)) { + tempConfiguration.mcc = Integer.parseInt(mcc); + mCustomResources = new Resources(new AssetManager(), new DisplayMetrics(), + tempConfiguration); + } + } + + /** + * The new resource cache structure does not flatten the paths for idmaps, so this method + * checks for files that end with @idmap and assumes this indicates the older format and + * removes all files and directories from the resource cache so that it can be rebuilt + * using the new format. + */ + private static void removeLegacyResourceCache() { + File cacheDir = new File(ThemeUtils.RESOURCE_CACHE_DIR); + if (cacheDir.exists()) { + for (File f : cacheDir.listFiles()) { + if (f.getName().endsWith(ThemeUtils.IDMAP_SUFFIX)) { + Log.i(TAG, "Removing old resource cache"); + FileUtils.deleteContents(new File(ThemeUtils.RESOURCE_CACHE_DIR)); + return; + } + } + } + } } diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index bbdfe31..e02e4da 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import static android.content.pm.PackageManager.COMPONENT_VISIBLE_STATUS; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; @@ -343,7 +344,8 @@ abstract class PackageSettingBase extends SettingBase { boolean notLaunched, boolean hidden, String lastDisableAppCaller, ArraySet<String> enabledComponents, ArraySet<String> disabledComponents, boolean blockUninstall, int domainVerifState, - int linkGeneration) { + int linkGeneration, + ArraySet<String> protectedComponents, ArraySet<String> visibleComponents) { PackageUserState state = modifyUserState(userId); state.enabled = enabled; state.installed = installed; @@ -356,6 +358,8 @@ abstract class PackageSettingBase extends SettingBase { state.blockUninstall = blockUninstall; state.domainVerificationStatus = domainVerifState; state.appLinkGeneration = linkGeneration; + state.protectedComponents = protectedComponents; + state.visibleComponents = visibleComponents; } ArraySet<String> getEnabledComponents(int userId) { @@ -395,6 +399,17 @@ abstract class PackageSettingBase extends SettingBase { return state; } + PackageUserState modifyUserStateComponents(int userId) { + PackageUserState state = modifyUserState(userId); + if (state.protectedComponents == null) { + state.protectedComponents = new ArraySet<String>(1); + } + if (state.visibleComponents == null) { + state.visibleComponents = new ArraySet<String>(1); + } + return state; + } + void addDisabledComponent(String componentClassName, int userId) { modifyUserStateComponents(userId, true, false).disabledComponents.add(componentClassName); } @@ -440,6 +455,27 @@ abstract class PackageSettingBase extends SettingBase { } } + boolean protectComponentLPw(String componentClassName, boolean protect, int userId) { + PackageUserState state = modifyUserStateComponents(userId); + boolean changed = false; + if (protect == COMPONENT_VISIBLE_STATUS) { + changed = state.protectedComponents != null + ? state.protectedComponents.remove(componentClassName) : false; + changed |= state.visibleComponents.add(componentClassName); + } else { + changed = state.visibleComponents != null + ? state.visibleComponents.remove(componentClassName) : false; + changed |= state.protectedComponents.add(componentClassName); + } + + return changed; + } + + ArraySet<String> getProtectedComponents(int userId) { + PackageUserState state = modifyUserStateComponents(userId); + return state.protectedComponents; + } + void removeUser(int userId) { userState.delete(userId); } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index a762014..f514e71 100644..100755 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import static android.content.pm.PackageManager.INSTALL_FAILED_UNINSTALLED_PREBUNDLE; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; @@ -33,6 +34,8 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.content.res.Resources; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -56,6 +59,8 @@ import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; +import com.android.internal.R; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.JournaledFile; @@ -67,6 +72,7 @@ import com.android.server.pm.PermissionsState.PermissionState; import java.io.FileNotFoundException; import java.util.Collection; +import java.util.HashSet; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -84,6 +90,7 @@ import android.content.pm.Signature; import android.content.pm.UserInfo; import android.content.pm.PackageUserState; import android.content.pm.VerifierDeviceIdentity; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -93,9 +100,13 @@ import android.util.SparseIntArray; import android.util.Xml; import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; @@ -182,6 +193,9 @@ final class Settings { private static final String TAG_DEFAULT_BROWSER = "default-browser"; private static final String TAG_VERSION = "version"; + private static final String TAG_PROTECTED_COMPONENTS = "protected-components"; + private static final String TAG_VISIBLE_COMPONENTS = "visible-components"; + private static final String ATTR_NAME = "name"; private static final String ATTR_USER = "user"; private static final String ATTR_CODE = "code"; @@ -214,6 +228,7 @@ final class Settings { private final File mPackageListFilename; private final File mStoppedPackagesFilename; private final File mBackupStoppedPackagesFilename; + private final File mPrebundledPackagesFilename; final ArrayMap<String, PackageSetting> mPackages = new ArrayMap<String, PackageSetting>(); @@ -281,6 +296,10 @@ final class Settings { final SparseArray<PersistentPreferredIntentResolver> mPersistentPreferredActivities = new SparseArray<PersistentPreferredIntentResolver>(); + // The persistent prebundled packages for a user + final SparseArray<HashSet<String>> mPrebundledPackages = + new SparseArray<HashSet<String>>(); + // For every user, it is used to find to which other users the intent can be forwarded. final SparseArray<CrossProfileIntentResolver> mCrossProfileIntentResolvers = new SparseArray<CrossProfileIntentResolver>(); @@ -359,6 +378,7 @@ final class Settings { // Deprecated: Needed for migration mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml"); mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); + mPrebundledPackagesFilename = new File(mSystemDir, "prebundled-packages.list"); } PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage, @@ -652,7 +672,10 @@ final class Settings { false, // hidden null, null, null, false, // blockUninstall - INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0); + INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0, + null, + null + ); writePackageRestrictionsLPr(user.id); } } @@ -1021,6 +1044,15 @@ final class Settings { return pir; } + HashSet<String> editPrebundledPackagesLPw(int userId) { + HashSet<String> hashSet = mPrebundledPackages.get(userId); + if (hashSet == null) { + hashSet = new HashSet<String>(); + mPrebundledPackages.put(userId, hashSet); + } + return hashSet; + } + PersistentPreferredIntentResolver editPersistentPreferredActivitiesLPw(int userId) { PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId); if (ppir == null) { @@ -1190,6 +1222,10 @@ final class Settings { "package-restrictions-backup.xml"); } + private File getUserPrebundledStateFile(int userId) { + return new File(Environment.getUserSystemDirectory(userId), "prebundled-packages.list"); + } + void writeAllUsersPackageRestrictionsLPr() { List<UserInfo> users = getAllUsers(); if (users == null) return; @@ -1414,7 +1450,10 @@ final class Settings { false, // hidden null, null, null, false, // blockUninstall - INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0); + INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0, + null, + null + ); } return; } @@ -1499,6 +1538,8 @@ final class Settings { ArraySet<String> enabledComponents = null; ArraySet<String> disabledComponents = null; + ArraySet<String> protectedComponents = null; + ArraySet<String> visibleComponents = null; int packageDepth = parser.getDepth(); while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -1513,12 +1554,17 @@ final class Settings { enabledComponents = readComponentsLPr(parser); } else if (tagName.equals(TAG_DISABLED_COMPONENTS)) { disabledComponents = readComponentsLPr(parser); + } else if (tagName.equals(TAG_PROTECTED_COMPONENTS)) { + protectedComponents = readComponentsLPr(parser); + } else if (tagName.equals(TAG_VISIBLE_COMPONENTS)) { + visibleComponents = readComponentsLPr(parser); } } ps.setUserState(userId, enabled, installed, stopped, notLaunched, hidden, enabledCaller, enabledComponents, disabledComponents, blockUninstall, - verifState, linkGeneration); + verifState, linkGeneration, + protectedComponents, visibleComponents); } else if (tagName.equals("preferred-activities")) { readPreferredActivitiesLPw(parser, userId); } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) { @@ -1758,7 +1804,11 @@ final class Settings { && ustate.disabledComponents.size() > 0) || ustate.blockUninstall || (ustate.domainVerificationStatus != - PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED)) { + PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) + || (ustate.protectedComponents != null + && ustate.protectedComponents.size() > 0) + || (ustate.visibleComponents != null + && ustate.visibleComponents.size() > 0)) { serializer.startTag(null, TAG_PACKAGE); serializer.attribute(null, ATTR_NAME, pkg.name); if (DEBUG_MU) Log.i(TAG, " pkg=" + pkg.name + ", state=" + ustate.enabled); @@ -1815,7 +1865,26 @@ final class Settings { } serializer.endTag(null, TAG_DISABLED_COMPONENTS); } - + if (ustate.protectedComponents != null + && ustate.protectedComponents.size() > 0) { + serializer.startTag(null, TAG_PROTECTED_COMPONENTS); + for (final String name : ustate.protectedComponents) { + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, name); + serializer.endTag(null, TAG_ITEM); + } + serializer.endTag(null, TAG_PROTECTED_COMPONENTS); + } + if (ustate.visibleComponents != null + && ustate.visibleComponents.size() > 0) { + serializer.startTag(null, TAG_VISIBLE_COMPONENTS); + for (final String name : ustate.visibleComponents) { + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, name); + serializer.endTag(null, TAG_ITEM); + } + serializer.endTag(null, TAG_VISIBLE_COMPONENTS); + } serializer.endTag(null, TAG_PACKAGE); } } @@ -2301,6 +2370,165 @@ final class Settings { } } + // Migrate from previous prebundled packages file to new one + void migratePrebundedPackagesIfNeededLPr(List<UserInfo> users, Installer installer) { + if (mPrebundledPackagesFilename.exists()) { + // Read old file + editPrebundledPackagesLPw(UserHandle.USER_OWNER); + readPrebundledPackagesOldLPw(); + mPrebundledPackagesFilename.delete(); + // Migrate to new file based on user + writePrebundledPackagesLPr(UserHandle.USER_OWNER); + } else { + if (users == null) { + readPrebundledPackagesLPr(UserHandle.USER_OWNER); + } else { + for (UserInfo user : users) { + editPrebundledPackagesLPw(user.id); + readPrebundledPackagesLPr(user.id); + // mark all existing users as having packages installed from OWNER + try { + markAllAsInstalledForUser(user.id, installer); + } catch (PackageManagerException e) { + Log.d(TAG, e.toString()); + } + } + } + } + } + + void writePrebundledPackagesLPr(int userId) { + editPrebundledPackagesLPw(userId); + PrintWriter writer = null; + try { + writer = new PrintWriter( + new BufferedWriter(new FileWriter(getUserPrebundledStateFile(userId), false))); + + for (String packageName : mPrebundledPackages.get(userId)) { + writer.println(packageName); + } + } catch (IOException e) { + Slog.e(PackageManagerService.TAG, "Unable to write prebundled package list", e); + } finally { + if (writer != null) { + writer.close(); + } + } + } + + // This is done for an intermediate migration step on upgrade + void readPrebundledPackagesOldLPw() { + if (!mPrebundledPackagesFilename.exists()) { + return; + } + + readPrebundledPackagesForUserFromFileLPr(UserHandle.USER_OWNER, + mPrebundledPackagesFilename); + } + + void readPrebundledPackagesLPr(int userId) { + if (!getUserPrebundledStateFile(userId).exists()) { + return; + } + readPrebundledPackagesForUserFromFileLPr(userId, getUserPrebundledStateFile(userId)); + } + + private void readPrebundledPackagesForUserFromFileLPr(int userId, File file) { + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(file)); + String packageName = reader.readLine(); + while (packageName != null) { + if (!TextUtils.isEmpty(packageName)) { + mPrebundledPackages.get(userId).add(packageName); + } + packageName = reader.readLine(); + } + } catch (IOException e) { + Slog.e(PackageManagerService.TAG, "Unable to read prebundled package list", e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) {} + } + } + } + + private void markAllAsInstalledForUser(int userHandle, Installer installer) + throws PackageManagerException { + if (mPrebundledPackages.get(userHandle) == null) { + throw new PackageManagerException(INSTALL_FAILED_UNINSTALLED_PREBUNDLE, + "Failure migrating prebundled packages to existing user " + userHandle); + } + + // grab all the packages from the user account, move them over + for (String s : mPrebundledPackages.get(UserHandle.USER_OWNER)) { + mPrebundledPackages.get(userHandle).add(s); + } + + for (PackageSetting ps : mPackages.values()) { + if (ps.pkg == null || ps.pkg.applicationInfo == null) { + continue; + } + // Mark the app as installed + boolean setInstalled = + wasPrebundledPackageInstalledLPr(UserHandle.USER_OWNER, ps.name); + ps.setInstalled(setInstalled, userHandle); + // Tell the installer to create the user data for the application + installer.createUserData(ps.name, + UserHandle.getUid(userHandle, ps.appId), userHandle, + ps.pkg.applicationInfo.seinfo); + } + // Write the package restrications + writePackageRestrictionsLPr(userHandle); + } + + void markPrebundledPackageInstalledLPr(int userId, String packageName) { + editPrebundledPackagesLPw(userId); + mPrebundledPackages.get(userId).add(packageName); + } + + boolean wasPrebundledPackageInstalledLPr(int userId, String packageName) { + if (mPrebundledPackages.get(userId) == null) { + return false; + } + return mPrebundledPackages.get(userId).contains(packageName); + } + + boolean shouldPrebundledPackageBeInstalled(Resources res, String packageName, + Resources configuredResources) { + // Default fallback on lack of bad package + if (TextUtils.isEmpty(packageName)) { + return false; + } + + // Configured resources can be null if the device + // is not region locked. In such cases, fall back to + // the default resources object + Resources resources = configuredResources; + if (configuredResources == null) { + resources = res; + } + + // If the package is compatible with the current region, install it + // Note : If a package needs to be installed only if the device is + // not provisioned, overlay values/config_region_locked_packages + // TODO change config_region_locked_packages to something that is + // not confusing inside a non region resource bucket + String[] prebundledArray + = resources.getStringArray(R.array.config_region_locked_packages); + if (ArrayUtils.contains(prebundledArray, packageName)) { + return true; + } + + // If the package is not compatible with the current region, check if its locked + // to any other region before installing it. + prebundledArray = resources + .getStringArray(R.array.config_restrict_to_region_locked_devices); + return !ArrayUtils.contains(prebundledArray, packageName); + } + void writeDisabledSysPackageLPr(XmlSerializer serializer, final PackageSetting pkg) throws java.io.IOException { serializer.startTag(null, "updated-package"); @@ -2459,6 +2687,9 @@ final class Settings { } } } + if (bp.allowViaWhitelist) { + serializer.attribute(null, "allowViaWhitelist", Integer.toString(1)); + } serializer.endTag(null, TAG_ITEM); } } @@ -2484,7 +2715,7 @@ final class Settings { } boolean readLPw(PackageManagerService service, List<UserInfo> users, int sdkVersion, - boolean onlyCore) { + boolean onlyCore, Installer installer) { FileInputStream str = null; if (mBackupSettingsFilename.exists()) { try { @@ -2738,6 +2969,8 @@ final class Settings { } } + migratePrebundedPackagesIfNeededLPr(users, installer); + /* * Make sure all the updated system packages have their shared users * associated with them. @@ -2959,6 +3192,11 @@ final class Settings { for (int i=0; i<ri.size(); i++) { ActivityInfo ai = ri.get(i).activityInfo; set[i] = new ComponentName(ai.packageName, ai.name); + // We have already discovered the best third party match, + // so we only need to finish filling set with all results. + if (haveNonSys != null) { + continue; + } if ((ai.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) { if (ri.get(i).match >= thirdPartyMatch) { // Keep track of the best match we find of all third @@ -2967,7 +3205,6 @@ final class Settings { if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result " + ai.packageName + "/" + ai.name + ": non-system!"); haveNonSys = set[i]; - break; } } else if (cn.getPackageName().equals(ai.packageName) && cn.getClassName().equals(ai.name)) { @@ -3112,6 +3349,8 @@ final class Settings { bp.protectionLevel = readInt(parser, null, "protection", PermissionInfo.PROTECTION_NORMAL); bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel); + bp.allowViaWhitelist = readInt(parser, null, + "allowViaWhitelist", 0) == 1; if (dynamic) { PermissionInfo pi = new PermissionInfo(); pi.packageName = sourcePackage.intern(); @@ -3119,6 +3358,7 @@ final class Settings { pi.icon = readInt(parser, null, "icon", 0); pi.nonLocalizedLabel = parser.getAttributeValue(null, "label"); pi.protectionLevel = bp.protectionLevel; + pi.allowViaWhitelist = bp.allowViaWhitelist; bp.pendingInfo = pi; } out.put(bp.name, bp); @@ -3655,7 +3895,9 @@ final class Settings { continue; } // Only system apps are initially installed. - ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle); + boolean setInstalled = ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0) || + wasPrebundledPackageInstalledLPr(UserHandle.USER_OWNER, ps.name); + ps.setInstalled(setInstalled, userHandle); // Need to create a data directory for all apps under this user. installer.createUserData(ps.volumeUuid, ps.name, UserHandle.getUid(userHandle, ps.appId), userHandle, @@ -3664,6 +3906,7 @@ final class Settings { applyDefaultPreferredAppsLPw(service, userHandle); writePackageRestrictionsLPr(userHandle); writePackageListLPr(userHandle); + writePrebundledPackagesLPr(userHandle); } void removeUserLPw(int userId) { @@ -3672,10 +3915,13 @@ final class Settings { entry.getValue().removeUser(userId); } mPreferredActivities.remove(userId); + mPrebundledPackages.remove(userId); File file = getUserPackagesStateFile(userId); file.delete(); file = getUserPackagesStateBackupFile(userId); file.delete(); + file = getUserPrebundledStateFile(userId); + file.delete(); removeCrossProfileIntentFiltersLPw(userId); mRuntimePermissionsPersistence.onUserRemoved(userId); @@ -3850,7 +4096,7 @@ final class Settings { if (pkgSetting.getNotLaunched(userId)) { if (pkgSetting.installerPackageName != null) { yucky.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, - pkgSetting.name, null, + pkgSetting.name, null, null, pkgSetting.installerPackageName, null, new int[] {userId}); } pkgSetting.setNotLaunched(false, userId); @@ -3860,6 +4106,57 @@ final class Settings { return false; } + void removeStalePermissions() { + /* + * Remove any permission that is not currently declared by any package + */ + List<BasePermission> permissionsToRemove = new ArrayList<>(); + for (BasePermission basePerm : mPermissions.values()) { + // Ignore permissions declared by the system + if (basePerm.sourcePackage.equals("android") || + basePerm.sourcePackage.equals("cyanogenmod.platform")) { + continue; + } + // Ignore permissions other than NORMAL (ignore DYNAMIC and BUILTIN), like the + // ones based on permission-trees + if (basePerm.type != BasePermission.TYPE_NORMAL) { + continue; + } + + if (!mPackages.containsKey(basePerm.sourcePackage)) { + // Package doesn't exist + permissionsToRemove.add(basePerm); + continue; + } + PackageSetting pkgSettings = mPackages.get(basePerm.sourcePackage); + if (pkgSettings.pkg == null || pkgSettings.pkg.permissions == null) { + // Package doesn't declare permissions + permissionsToRemove.add(basePerm); + continue; + } + boolean found = false; + for (PackageParser.Permission perm : pkgSettings.pkg.permissions) { + if (perm.info.name != null && basePerm.name.equals(perm.info.name)) { + // The original package still declares the permission + found = true; + break; + } + } + if (!found) { + // The original package doesn't currently declare the permission + permissionsToRemove.add(basePerm); + } + } + // And now remove all stale permissions + for (BasePermission basePerm : permissionsToRemove) { + String msg = "Removed stale permission: " + basePerm.name + " originally " + + "assigned to " + basePerm.sourcePackage + "\n"; + mReadMessages.append(msg); + PackageManagerService.reportSettingsProblem(Log.WARN, msg); + mPermissions.remove(basePerm.name); + } + } + List<UserInfo> getAllUsers() { long id = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java index 8e73495..9caa4c1 100644 --- a/services/core/java/com/android/server/policy/GlobalActions.java +++ b/services/core/java/com/android/server/policy/GlobalActions.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2010-2015 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,20 +29,27 @@ import android.app.ActivityManagerNative; import android.app.AlertDialog; import android.app.Dialog; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; +import android.content.ContentResolver; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ThemeUtils; import android.content.pm.UserInfo; +import android.content.ServiceConnection; import android.database.ContentObserver; import android.graphics.drawable.Drawable; +import android.Manifest; import android.media.AudioManager; import android.net.ConnectivityManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.IPowerManager; import android.os.Message; +import android.os.Messenger; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -79,6 +87,9 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.List; +import java.util.UUID; + +import static com.android.internal.util.cm.PowerMenuConstants.*; /** * Helper to show the global actions dialog. Each item is an {@link Action} that @@ -91,21 +102,9 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac private static final boolean SHOW_SILENT_TOGGLE = true; - /* Valid settings for global actions keys. - * see config.xml config_globalActionList */ - private static final String GLOBAL_ACTION_KEY_POWER = "power"; - private static final String GLOBAL_ACTION_KEY_REBOOT = "reboot"; - private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; - private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; - private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; - private static final String GLOBAL_ACTION_KEY_USERS = "users"; - private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; - private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; - private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; - private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; - private final Context mContext; private final WindowManagerFuncs mWindowManagerFuncs; + private Context mUiContext; private final AudioManager mAudioManager; private final IDreamManager mDreamManager; @@ -125,6 +124,9 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac private boolean mHasVibrator; private final boolean mShowSilentToggle; + // Power menu customizations + String mActions; + /** * @param context everything needs a context :( */ @@ -139,6 +141,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.UPDATE_POWER_MENU); filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); context.registerReceiver(mBroadcastReceiver, filter); @@ -158,6 +161,8 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); + + updatePowerMenuActions(); } /** @@ -167,12 +172,14 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { mKeyguardShowing = keyguardShowing; mDeviceProvisioned = isDeviceProvisioned; - if (mDialog != null) { + if (mDialog != null && mUiContext == null) { mDialog.dismiss(); mDialog = null; + mDialog = createDialog(); // Show delayed, so that the dismiss of the previous dialog completes mHandler.sendEmptyMessage(MESSAGE_SHOW); } else { + mDialog = createDialog(); handleShow(); } } @@ -191,7 +198,6 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac private void handleShow() { awakenIfNecessary(); - mDialog = createDialog(); prepareDialog(); // If we only have 1 item and it's a simple press action, just do this action. @@ -208,6 +214,14 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } } + private Context getUiContext() { + if (mUiContext == null) { + mUiContext = ThemeUtils.createUiContext(mContext); + mUiContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar); + } + return mUiContext != null ? mUiContext : mContext; + } + /** * Create the global actions dialog. * @return A new dialog. @@ -263,20 +277,31 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac onAirplaneModeChanged(); mItems = new ArrayList<Action>(); - String[] defaultActions = mContext.getResources().getStringArray( - com.android.internal.R.array.config_globalActionsList); + + String[] actionsArray; + if (mActions == null) { + actionsArray = mContext.getResources().getStringArray( + com.android.internal.R.array.config_globalActionsList); + } else { + actionsArray = mActions.split("\\|"); + } + + // Always add the power off option + mItems.add(new PowerAction()); ArraySet<String> addedKeys = new ArraySet<String>(); - for (int i = 0; i < defaultActions.length; i++) { - String actionKey = defaultActions[i]; + for (int i = 0; i < actionsArray.length; i++) { + String actionKey = actionsArray[i]; if (addedKeys.contains(actionKey)) { // If we already have added this, don't add it again. continue; } if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { - mItems.add(new PowerAction()); + continue; } else if (GLOBAL_ACTION_KEY_REBOOT.equals(actionKey)) { mItems.add(new RebootAction()); + } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) { + mItems.add(getScreenshotAction()); } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { mItems.add(mAirplaneModeOn); } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { @@ -289,7 +314,9 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac mItems.add(mSilentModeAction); } } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) { - if (SystemProperties.getBoolean("fw.power_user_switcher", false)) { + List<UserInfo> users = ((UserManager) mContext.getSystemService( + Context.USER_SERVICE)).getUsers(); + if (users.size() > 1) { addUsersToMenu(mItems); } } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { @@ -309,12 +336,12 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac mAdapter = new MyAdapter(); - AlertParams params = new AlertParams(mContext); + AlertParams params = new AlertParams(getUiContext()); params.mAdapter = mAdapter; params.mOnClickListener = this; params.mForceInverseBackground = true; - GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params); + GlobalActionsDialog dialog = new GlobalActionsDialog(getUiContext(), params); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. dialog.getListView().setItemsCanFocus(true); @@ -400,6 +427,24 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } } + private Action getScreenshotAction() { + return new SinglePressAction(com.android.internal.R.drawable.ic_lock_screenshot, + R.string.global_action_screenshot) { + + public void onPress() { + takeScreenshot(); + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return true; + } + }; + } + private Action getBugReportAction() { return new SinglePressAction(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title) { @@ -456,7 +501,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } private Action getSettingsAction() { - return new SinglePressAction(com.android.internal.R.drawable.ic_settings, + return new SinglePressAction(com.android.internal.R.drawable.ic_lock_settings, R.string.global_action_settings) { @Override @@ -573,7 +618,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath) : null; SinglePressAction switchToUser = new SinglePressAction( - com.android.internal.R.drawable.ic_menu_cc, icon, + com.android.internal.R.drawable.ic_lock_user, icon, (user.name != null ? user.name : "Primary") + (isCurrentUser ? " \u2714" : "")) { public void onPress() { @@ -598,6 +643,90 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } } + /** + * functions needed for taking screenhots. + * This leverages the built in ICS screenshot functionality + */ + final Object mScreenshotLock = new Object(); + ServiceConnection mScreenshotConnection = null; + + final Runnable mScreenshotTimeout = new Runnable() { + @Override public void run() { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != null) { + mContext.unbindService(mScreenshotConnection); + mScreenshotConnection = null; + } + } + } + }; + + private void takeScreenshot() { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != null) { + return; + } + ComponentName cn = new ComponentName("com.android.systemui", + "com.android.systemui.screenshot.TakeScreenshotService"); + Intent intent = new Intent(); + intent.setComponent(cn); + ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != this) { + return; + } + Messenger messenger = new Messenger(service); + Message msg = Message.obtain(null, 1); + final ServiceConnection myConn = this; + Handler h = new Handler(mHandler.getLooper()) { + @Override + public void handleMessage(Message msg) { + synchronized (mScreenshotLock) { + if (mScreenshotConnection == myConn) { + mContext.unbindService(mScreenshotConnection); + mScreenshotConnection = null; + mHandler.removeCallbacks(mScreenshotTimeout); + } + } + } + }; + msg.replyTo = new Messenger(h); + msg.arg1 = msg.arg2 = 0; + + /* remove for the time being + if (mStatusBar != null && mStatusBar.isVisibleLw()) + msg.arg1 = 1; + if (mNavigationBar != null && mNavigationBar.isVisibleLw()) + msg.arg2 = 1; + */ + + /* wait for the dialog box to close */ + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + // Do nothing + } + + /* take the screenshot */ + try { + messenger.send(msg); + } catch (RemoteException e) { + // Do nothing + } + } + } + @Override + public void onServiceDisconnected(ComponentName name) {} + }; + if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) { + mScreenshotConnection = conn; + mHandler.postDelayed(mScreenshotTimeout, 10000); + } + } + } + private void prepareDialog() { refreshSilentMode(); mAirplaneModeOn.updateState(mAirplaneState); @@ -704,7 +833,8 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac public View getView(int position, View convertView, ViewGroup parent) { Action action = getItem(position); - return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); + final Context context = getUiContext(); + return action.create(context, convertView, parent, LayoutInflater.from(context)); } } @@ -1073,10 +1203,24 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac mIsWaitingForEcmExit = false; changeAirplaneModeSystemSetting(true); } + } else if (Intent.UPDATE_POWER_MENU.equals(action)) { + updatePowerMenuActions(); } } }; + protected void updatePowerMenuActions() { + ContentResolver resolver = mContext.getContentResolver(); + mActions = Settings.Secure.getStringForUser(resolver, + Settings.Secure.POWER_MENU_ACTIONS, UserHandle.USER_CURRENT); + } + + private BroadcastReceiver mThemeChangeReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }; + PhoneStateListener mPhoneStateListener = new PhoneStateListener() { @Override public void onServiceStateChanged(ServiceState serviceState) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 899c176..ca586e8 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -31,6 +31,7 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; @@ -47,6 +48,7 @@ import android.graphics.Rect; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPlaybackClient; import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback; +import android.hardware.input.InputManager; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioSystem; @@ -80,6 +82,11 @@ import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.speech.RecognizerIntent; import android.telecom.TelecomManager; +import android.service.gesture.EdgeGestureManager; +import com.android.internal.os.DeviceKeyHandler; + +import com.android.internal.util.cm.ActionUtils; +import dalvik.system.DexClassLoader; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -113,9 +120,15 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; +import android.widget.Toast; + import com.android.internal.R; +import com.android.internal.policy.IKeyguardService; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ScreenShapeHelper; +import com.android.internal.util.gesture.EdgeGesturePosition; +import com.android.internal.util.gesture.EdgeServiceConstants; +import com.android.internal.view.RotationPolicy; import com.android.internal.widget.PointerLocationView; import com.android.server.LocalServices; import com.android.server.policy.keyguard.KeyguardServiceDelegate; @@ -128,6 +141,7 @@ import java.io.PrintWriter; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.lang.reflect.Constructor; import static android.view.WindowManager.LayoutParams.*; import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; @@ -200,6 +214,30 @@ public class PhoneWindowManager implements WindowManagerPolicy { static public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; static public final String SYSTEM_DIALOG_REASON_ASSIST = "assist"; + // Available custom actions to perform on a key press. + // Must match values for KEY_HOME_LONG_PRESS_ACTION in: + // core/java/android/provider/Settings.java + private static final int KEY_ACTION_NOTHING = 0; + private static final int KEY_ACTION_MENU = 1; + private static final int KEY_ACTION_APP_SWITCH = 2; + private static final int KEY_ACTION_SEARCH = 3; + private static final int KEY_ACTION_VOICE_SEARCH = 4; + private static final int KEY_ACTION_IN_APP_SEARCH = 5; + private static final int KEY_ACTION_LAUNCH_CAMERA = 6; + private static final int KEY_ACTION_SLEEP = 7; + private static final int KEY_ACTION_LAST_APP = 8; + + // Masks for checking presence of hardware keys. + // Must match values in core/res/res/values/config.xml + private static final int KEY_MASK_HOME = 0x01; + private static final int KEY_MASK_BACK = 0x02; + private static final int KEY_MASK_MENU = 0x04; + private static final int KEY_MASK_ASSIST = 0x08; + private static final int KEY_MASK_APP_SWITCH = 0x10; + private static final int KEY_MASK_CAMERA = 0x20; + private static final int KEY_MASK_VOLUME = 0x40; + + /** * These are the system UI flags that, when changing, can cause the layout * of the screen to change. @@ -266,6 +304,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */ static final int WAITING_FOR_DRAWN_TIMEOUT = 1000; + private DeviceKeyHandler mDeviceKeyHandler; + /** * Lock protecting internal state. Must not call out into window * manager with lock held. (This lock will be acquired in places @@ -324,10 +364,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mCanHideNavigationBar = false; boolean mNavigationBarCanMove = false; // can the navigation bar ever move to the side? boolean mNavigationBarOnBottom = true; // is the navigation bar on the bottom *right now*? + boolean mNavigationBarLeftInLandscape = false; // Navigation bar left handed? int[] mNavigationBarHeightForRotation = new int[4]; int[] mNavigationBarWidthForRotation = new int[4]; - boolean mBootMessageNeedsHiding; KeyguardServiceDelegate mKeyguardDelegate; final Runnable mWindowManagerDrawCallback = new Runnable() { @Override @@ -341,6 +381,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void onDrawn() { if (DEBUG_WAKEUP) Slog.d(TAG, "mKeyguardDelegate.ShowListener.onDrawn."); mHandler.sendEmptyMessage(MSG_KEYGUARD_DRAWN_COMPLETE); + hideBootMessages(); } }; @@ -378,6 +419,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mUiMode; int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED; int mLidOpenRotation; + boolean mHasRemovableLid; int mCarDockRotation; int mDeskDockRotation; int mUndockedHdmiRotation; @@ -395,6 +437,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE; int mUserRotation = Surface.ROTATION_0; + int mUserRotationAngles = -1; boolean mAccelerometerDefault; boolean mSupportAutoRotation; @@ -420,6 +463,28 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mHasSoftInput = false; boolean mTranslucentDecorEnabled = true; boolean mUseTvRouting; + int mBackKillTimeout; + + int mDeviceHardwareKeys; + + // Button wake control flags + boolean mHomeWakeScreen; + boolean mBackWakeScreen; + boolean mMenuWakeScreen; + boolean mAssistWakeScreen; + boolean mAppSwitchWakeScreen; + boolean mCameraWakeScreen; + boolean mVolumeWakeScreen; + + // Camera button control flags and actions + boolean mCameraSleepOnRelease; + boolean mIsFocusPressed; + boolean mCameraLaunch; + + // During wakeup by volume keys, we still need to capture subsequent events + // until the key is released. This is required since the beep sound is produced + // post keypressed. + boolean mVolumeWakeTriggered; int mPointerLocationMode = 0; // guarded by mLock @@ -427,6 +492,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowState mFocusedWindow; IApplicationToken mFocusedApp; + // Behavior of volbtn music controls + boolean mVolBtnMusicControls; + boolean mIsLongPress; + PointerLocationView mPointerLocationView; // The current size of the screen; really; extends into the overscan area of @@ -511,6 +580,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mForcingShowNavBar; int mForcingShowNavBarLayer; + boolean mDevForceNavbar = false; + // States of keyguard dismiss. private static final int DISMISS_KEYGUARD_NONE = 0; // Keyguard not being dismissed. private static final int DISMISS_KEYGUARD_START = 1; // Keyguard needs to be dismissed. @@ -545,6 +616,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mHomePressed; boolean mHomeConsumed; boolean mHomeDoubleTapPending; + boolean mMenuPressed; + boolean mAppSwitchLongPressed; Intent mHomeIntent; Intent mCarDockIntent; Intent mDeskDockIntent; @@ -553,6 +626,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mAssistKeyLongPressed; boolean mPendingMetaAction; + // Tracks user-customisable behavior for certain key events + private int mLongPressOnHomeBehavior = -1; + private int mPressOnMenuBehavior = -1; + private int mLongPressOnMenuBehavior = -1; + private int mPressOnAssistBehavior = -1; + private int mLongPressOnAssistBehavior = -1; + private int mPressOnAppSwitchBehavior = -1; + private int mLongPressOnAppSwitchBehavior = -1; + // support for activating the lock screen while the screen is on boolean mAllowLockscreenWhenOn; int mLockScreenTimeout; @@ -579,9 +661,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mOverscanRight = 0; int mOverscanBottom = 0; - // What we do when the user long presses on home - private int mLongPressOnHomeBehavior; - // What we do when the user double-taps on home private int mDoubleTapOnHomeBehavior; @@ -648,9 +727,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_POWER_DELAYED_PRESS = 13; private static final int MSG_POWER_LONG_PRESS = 14; private static final int MSG_UPDATE_DREAMING_SLEEP_TOKEN = 15; + private static final int MSG_DISPATCH_VOLKEY_WITH_WAKE_LOCK = 16; + private static final int MSG_CAMERA_LONG_PRESS = 17; + boolean mWifiDisplayConnected = false; int mWifiDisplayCustomRotation = -1; + private boolean mHasPermanentMenuKey; + private class PolicyHandler extends Handler { @Override public void handleMessage(Message msg) { @@ -701,6 +785,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_UPDATE_DREAMING_SLEEP_TOKEN: updateDreamingSleepToken(msg.arg1 != 0); break; + case MSG_DISPATCH_VOLKEY_WITH_WAKE_LOCK: { + KeyEvent event = (KeyEvent) msg.obj; + mIsLongPress = true; + dispatchMediaKeyWithWakeLockToAudioService(event); + dispatchMediaKeyWithWakeLockToAudioService( + KeyEvent.changeAction(event, KeyEvent.ACTION_UP)); + break; + } + case MSG_CAMERA_LONG_PRESS: { + KeyEvent event = (KeyEvent) msg.obj; + mIsLongPress = true; + break; + } } } } @@ -741,6 +838,30 @@ public class PhoneWindowManager implements WindowManagerPolicy { resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.POINTER_LOCATION), false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.KEY_HOME_LONG_PRESS_ACTION), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.KEY_HOME_DOUBLE_TAP_ACTION), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.KEY_MENU_ACTION), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.KEY_MENU_LONG_PRESS_ACTION), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.KEY_ASSIST_ACTION), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.KEY_ASSIST_LONG_PRESS_ACTION), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.KEY_APP_SWITCH_ACTION), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION), false, this, + UserHandle.USER_ALL); resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.DEFAULT_INPUT_METHOD), false, this, UserHandle.USER_ALL); @@ -750,6 +871,54 @@ public class PhoneWindowManager implements WindowManagerPolicy { resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.POLICY_CONTROL), false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.DEV_FORCE_SHOW_NAVBAR), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.ACCELEROMETER_ROTATION_ANGLES), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.DEV_FORCE_SHOW_NAVBAR), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.VOLBTN_MUSIC_CONTROLS), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.USE_EDGE_SERVICE_FOR_GESTURES), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.BACK_WAKE_SCREEN), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.MENU_WAKE_SCREEN), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.ASSIST_WAKE_SCREEN), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.APP_SWITCH_WAKE_SCREEN), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.CAMERA_WAKE_SCREEN), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.CAMERA_SLEEP_ON_RELEASE), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.CAMERA_LAUNCH), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.VOLUME_WAKE_SCREEN), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.HOME_WAKE_SCREEN), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.VOLUME_WAKE_SCREEN), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NAVBAR_LEFT_IN_LANDSCAPE), false, this, + UserHandle.USER_ALL); updateSettings(); } @@ -802,6 +971,67 @@ public class PhoneWindowManager implements WindowManagerPolicy { private SystemGesturesPointerEventListener mSystemGestures; + private EdgeGestureManager.EdgeGestureActivationListener mEdgeGestureActivationListener + = new EdgeGestureManager.EdgeGestureActivationListener() { + + @Override + public void onEdgeGestureActivation(int touchX, int touchY, + EdgeGesturePosition position, int flags) { + WindowState target = null; + + if (position == EdgeGesturePosition.TOP) { + target = mStatusBar; + } else if (position == EdgeGesturePosition.BOTTOM && mNavigationBarOnBottom) { + target = mNavigationBar; + } else if (position == EdgeGesturePosition.LEFT + && !mNavigationBarOnBottom && mNavigationBarLeftInLandscape) { + target = mNavigationBar; + } else if (position == EdgeGesturePosition.RIGHT && !mNavigationBarOnBottom) { + target = mNavigationBar; + } + + if (target != null) { + requestTransientBars(target); + dropEventsUntilLift(); + mEdgeListenerActivated = true; + } else { + restoreListenerState(); + } + } + }; + private EdgeGestureManager mEdgeGestureManager = null; + private int mLastEdgePositions = 0; + private boolean mEdgeListenerActivated = false; + private boolean mUsingEdgeGestureServiceForGestures = false; + + private void updateEdgeGestureListenerState() { + int flags = 0; + if (mUsingEdgeGestureServiceForGestures) { + flags = EdgeServiceConstants.LONG_LIVING | EdgeServiceConstants.UNRESTRICTED; + if (mStatusBar != null && !mStatusBar.isVisibleLw()) { + flags |= EdgeGesturePosition.TOP.FLAG; + } + if (mNavigationBar != null && !mNavigationBar.isVisibleLw() && !isStatusBarKeyguard()) { + if (mNavigationBarOnBottom) { + flags |= EdgeGesturePosition.BOTTOM.FLAG; + } else if (mNavigationBarLeftInLandscape) { + flags |= EdgeGesturePosition.LEFT.FLAG; + } else { + flags |= EdgeGesturePosition.RIGHT.FLAG; + } + } + } + if (mEdgeListenerActivated) { + mEdgeGestureActivationListener.restoreListenerState(); + mEdgeListenerActivated = false; + } + if (flags != mLastEdgePositions) { + mEdgeGestureManager.updateEdgeGestureActivationListener(mEdgeGestureActivationListener, + flags); + mLastEdgePositions = flags; + } + } + IStatusBarService getStatusBarService() { synchronized (mServiceAquireLock) { if (mStatusBarService == null) { @@ -910,7 +1140,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // taken over the whole screen. boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive, SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags)); - if (panic) { + if (panic && !PolicyControl.isImmersiveFiltersActive()) { mHandler.post(mHiddenNavPanic); } @@ -1208,6 +1438,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHandler.sendEmptyMessage(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS); } + Runnable mBackLongPress = new Runnable() { + public void run() { + if (ActionUtils.killForegroundApp(mContext, mCurrentUserId)) { + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + Toast.makeText(mContext, R.string.app_killed_message, Toast.LENGTH_SHORT).show(); + } + } + }; + void showGlobalActionsInternal() { sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); if (mGlobalActions == null) { @@ -1287,26 +1526,57 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void handleLongPressOnHome(int deviceId) { - if (mLongPressOnHomeBehavior != LONG_PRESS_HOME_NOTHING) { - mHomeConsumed = true; - performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + private void triggerVirtualKeypress(final int keyCode) { + InputManager im = InputManager.getInstance(); + long now = SystemClock.uptimeMillis(); + final KeyEvent downEvent = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, + keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, + KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_KEYBOARD); + final KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP); - if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI) { - toggleRecentApps(); - } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_ASSIST) { - launchAssistAction(null, deviceId); - } - } + im.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + im.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } - private void handleDoubleTapOnHome() { - if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) { - mHomeConsumed = true; - toggleRecentApps(); - } + private void launchCameraAction() { + sendCloseSystemWindows(); + Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); + startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); } + private void performKeyAction(int behavior, KeyEvent event) { + switch (behavior) { + case KEY_ACTION_NOTHING: + break; + case KEY_ACTION_MENU: + triggerVirtualKeypress(KeyEvent.KEYCODE_MENU); + break; + case KEY_ACTION_APP_SWITCH: + toggleRecentApps(); + break; + case KEY_ACTION_SEARCH: + launchAssistAction(null, event.getDeviceId()); + break; + case KEY_ACTION_VOICE_SEARCH: + launchAssistLongPressAction(); + break; + case KEY_ACTION_IN_APP_SEARCH: + triggerVirtualKeypress(KeyEvent.KEYCODE_SEARCH); + break; + case KEY_ACTION_LAUNCH_CAMERA: + launchCameraAction(); + break; + case KEY_ACTION_SLEEP: + mPowerManager.goToSleep(SystemClock.uptimeMillis()); + break; + case KEY_ACTION_LAST_APP: + ActionUtils.switchToLastApp(mContext, mCurrentUserId); + break; + default: + break; + } + } + private final Runnable mHomeDoubleTapTimeoutRunnable = new Runnable() { @Override public void run() { @@ -1456,7 +1726,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION; - readConfigurationDependentBehaviors(); + mDeviceHardwareKeys = mContext.getResources().getInteger( + com.android.internal.R.integer.config_deviceHardwareKeys); + mHasRemovableLid = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_hasRemovableLid); + mBackKillTimeout = mContext.getResources().getInteger( + com.android.internal.R.integer.config_backKillTimeout); + + updateKeyAssignments(); mAccessibilityManager = (AccessibilityManager) context.getSystemService( Context.ACCESSIBILITY_SERVICE); @@ -1502,7 +1779,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override public void onSwipeFromRight() { - if (mNavigationBar != null && !mNavigationBarOnBottom) { + if (mNavigationBar != null && !mNavigationBarOnBottom && + !mNavigationBarLeftInLandscape) { + requestTransientBars(mNavigationBar); + } + } + @Override + public void onSwipeFromLeft() { + if (mNavigationBar != null && !mNavigationBarOnBottom && + mNavigationBarLeftInLandscape) { requestTransientBars(mNavigationBar); } } @@ -1561,27 +1846,125 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerInternal.registerAppTransitionListener( mStatusBarController.getAppTransitionListener()); + + String deviceKeyHandlerLib = mContext.getResources().getString( + com.android.internal.R.string.config_deviceKeyHandlerLib); + + String deviceKeyHandlerClass = mContext.getResources().getString( + com.android.internal.R.string.config_deviceKeyHandlerClass); + + if (!deviceKeyHandlerLib.isEmpty() && !deviceKeyHandlerClass.isEmpty()) { + DexClassLoader loader = new DexClassLoader(deviceKeyHandlerLib, + new ContextWrapper(mContext).getCacheDir().getAbsolutePath(), + null, + ClassLoader.getSystemClassLoader()); + try { + Class<?> klass = loader.loadClass(deviceKeyHandlerClass); + Constructor<?> constructor = klass.getConstructor(Context.class); + mDeviceKeyHandler = (DeviceKeyHandler) constructor.newInstance( + mContext); + if(DEBUG) Slog.d(TAG, "Device key handler loaded"); + } catch (Exception e) { + Slog.w(TAG, "Could not instantiate device key handler " + + deviceKeyHandlerClass + " from class " + + deviceKeyHandlerLib, e); + } + } + } - /** - * Read values from config.xml that may be overridden depending on - * the configuration of the device. - * eg. Disable long press on home goes to recents on sw600dp. - */ - private void readConfigurationDependentBehaviors() { + private void updateKeyAssignments() { + int activeHardwareKeys = mDeviceHardwareKeys; + + if (mDevForceNavbar) { + activeHardwareKeys = 0; + } + final boolean hasMenu = (activeHardwareKeys & KEY_MASK_MENU) != 0; + final boolean hasHome = (activeHardwareKeys & KEY_MASK_HOME) != 0; + final boolean hasAssist = (activeHardwareKeys & KEY_MASK_ASSIST) != 0; + final boolean hasAppSwitch = (activeHardwareKeys & KEY_MASK_APP_SWITCH) != 0; + + final ContentResolver resolver = mContext.getContentResolver(); + + // Initialize all assignments to sane defaults. + mPressOnMenuBehavior = KEY_ACTION_MENU; + + mLongPressOnMenuBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_longPressOnMenuBehavior); + + if (mLongPressOnMenuBehavior == KEY_ACTION_NOTHING && + (hasMenu && !hasAssist)) { + mLongPressOnMenuBehavior = KEY_ACTION_SEARCH; + } + mPressOnAssistBehavior = KEY_ACTION_SEARCH; + mLongPressOnAssistBehavior = KEY_ACTION_VOICE_SEARCH; + mPressOnAppSwitchBehavior = KEY_ACTION_APP_SWITCH; + mLongPressOnAppSwitchBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_longPressOnAppSwitchBehavior); + mLongPressOnHomeBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_longPressOnHomeBehavior); - if (mLongPressOnHomeBehavior < LONG_PRESS_HOME_NOTHING || - mLongPressOnHomeBehavior > LONG_PRESS_HOME_ASSIST) { - mLongPressOnHomeBehavior = LONG_PRESS_HOME_NOTHING; + if (mLongPressOnHomeBehavior < KEY_ACTION_NOTHING || + mLongPressOnHomeBehavior > KEY_ACTION_SLEEP) { + mLongPressOnHomeBehavior = KEY_ACTION_NOTHING; } mDoubleTapOnHomeBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_doubleTapOnHomeBehavior); - if (mDoubleTapOnHomeBehavior < DOUBLE_TAP_HOME_NOTHING || - mDoubleTapOnHomeBehavior > DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) { - mDoubleTapOnHomeBehavior = LONG_PRESS_HOME_NOTHING; + if (mDoubleTapOnHomeBehavior < KEY_ACTION_NOTHING || + mDoubleTapOnHomeBehavior > KEY_ACTION_SLEEP) { + mDoubleTapOnHomeBehavior = KEY_ACTION_NOTHING; + } + + boolean hasPermanentMenu = false; + + // Check for custom assignments and whether KEY_ACTION_MENU is assigned. + if (hasHome) { + mLongPressOnHomeBehavior = Settings.System.getIntForUser(resolver, + Settings.System.KEY_HOME_LONG_PRESS_ACTION, + mLongPressOnHomeBehavior, UserHandle.USER_CURRENT); + mDoubleTapOnHomeBehavior = Settings.System.getIntForUser(resolver, + Settings.System.KEY_HOME_DOUBLE_TAP_ACTION, + mDoubleTapOnHomeBehavior, UserHandle.USER_CURRENT); + + hasPermanentMenu = mLongPressOnHomeBehavior == KEY_ACTION_MENU + || mDoubleTapOnHomeBehavior == KEY_ACTION_MENU; + } + if (hasMenu) { + mPressOnMenuBehavior = Settings.System.getIntForUser(resolver, + Settings.System.KEY_MENU_ACTION, + mPressOnMenuBehavior, UserHandle.USER_CURRENT); + mLongPressOnMenuBehavior = Settings.System.getIntForUser(resolver, + Settings.System.KEY_MENU_LONG_PRESS_ACTION, + mLongPressOnMenuBehavior, UserHandle.USER_CURRENT); + + hasPermanentMenu |= mPressOnMenuBehavior == KEY_ACTION_MENU + || mLongPressOnMenuBehavior == KEY_ACTION_MENU; + } + if (hasAssist) { + mPressOnAssistBehavior = Settings.System.getIntForUser(resolver, + Settings.System.KEY_ASSIST_ACTION, + mPressOnAssistBehavior, UserHandle.USER_CURRENT); + mLongPressOnAssistBehavior = Settings.System.getIntForUser(resolver, + Settings.System.KEY_ASSIST_LONG_PRESS_ACTION, + mLongPressOnAssistBehavior, UserHandle.USER_CURRENT); + + hasPermanentMenu |= mPressOnAssistBehavior == KEY_ACTION_MENU + || mLongPressOnAssistBehavior == KEY_ACTION_MENU; + } + if (hasAppSwitch) { + mPressOnAppSwitchBehavior = Settings.System.getIntForUser(resolver, + Settings.System.KEY_APP_SWITCH_ACTION, + mPressOnAppSwitchBehavior, UserHandle.USER_CURRENT); + mLongPressOnAppSwitchBehavior = Settings.System.getIntForUser(resolver, + Settings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION, + mLongPressOnAppSwitchBehavior, UserHandle.USER_CURRENT); + + hasPermanentMenu |= mPressOnAppSwitchBehavior == KEY_ACTION_MENU + || mLongPressOnAppSwitchBehavior == KEY_ACTION_MENU; } + + mHasPermanentMenuKey = hasPermanentMenu; } @Override @@ -1690,7 +2073,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { * navigation bar and touch exploration is not enabled */ private boolean canHideNavigationBar() { - return mHasNavigationBar + return hasNavigationBar() && !mAccessibilityManager.isTouchExplorationEnabled(); } @@ -1712,6 +2095,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void updateSettings() { ContentResolver resolver = mContext.getContentResolver(); boolean updateRotation = false; + int mDeviceHardwareWakeKeys = mContext.getResources().getInteger( + com.android.internal.R.integer.config_deviceHardwareWakeKeys); synchronized (mLock) { mEndcallBehavior = Settings.System.getIntForUser(resolver, Settings.System.END_BUTTON_BEHAVIOR, @@ -1721,6 +2106,33 @@ public class PhoneWindowManager implements WindowManagerPolicy { Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT, UserHandle.USER_CURRENT); + mHomeWakeScreen = (Settings.System.getIntForUser(resolver, + Settings.System.HOME_WAKE_SCREEN, 1, UserHandle.USER_CURRENT) == 1) && + ((mDeviceHardwareWakeKeys & KEY_MASK_HOME) != 0); + mBackWakeScreen = (Settings.System.getIntForUser(resolver, + Settings.System.BACK_WAKE_SCREEN, 0, UserHandle.USER_CURRENT) == 1) && + ((mDeviceHardwareWakeKeys & KEY_MASK_BACK) != 0); + mMenuWakeScreen = (Settings.System.getIntForUser(resolver, + Settings.System.MENU_WAKE_SCREEN, 0, UserHandle.USER_CURRENT) == 1) && + ((mDeviceHardwareWakeKeys & KEY_MASK_MENU) != 0); + mAssistWakeScreen = (Settings.System.getIntForUser(resolver, + Settings.System.ASSIST_WAKE_SCREEN, 0, UserHandle.USER_CURRENT) == 1) && + ((mDeviceHardwareWakeKeys & KEY_MASK_ASSIST) != 0); + mAppSwitchWakeScreen = (Settings.System.getIntForUser(resolver, + Settings.System.APP_SWITCH_WAKE_SCREEN, 0, UserHandle.USER_CURRENT) == 1) && + ((mDeviceHardwareWakeKeys & KEY_MASK_APP_SWITCH) != 0); + mCameraWakeScreen = (Settings.System.getIntForUser(resolver, + Settings.System.CAMERA_WAKE_SCREEN, 0, UserHandle.USER_CURRENT) == 1) && + ((mDeviceHardwareWakeKeys & KEY_MASK_CAMERA) != 0); + mCameraSleepOnRelease = (Settings.System.getIntForUser(resolver, + Settings.System.CAMERA_SLEEP_ON_RELEASE, 0, UserHandle.USER_CURRENT) == 1); + mCameraLaunch = (Settings.System.getIntForUser(resolver, + Settings.System.CAMERA_LAUNCH, 0, UserHandle.USER_CURRENT) == 1); + mVolumeWakeScreen = (Settings.System.getIntForUser(resolver, + Settings.System.VOLUME_WAKE_SCREEN, 0, UserHandle.USER_CURRENT) == 1) && + ((mDeviceHardwareWakeKeys & KEY_MASK_VOLUME) != 0); + mVolBtnMusicControls = (Settings.System.getIntForUser(resolver, + Settings.System.VOLBTN_MUSIC_CONTROLS, 1, UserHandle.USER_CURRENT) == 1); // Configure wake gesture. boolean wakeGestureEnabledSetting = Settings.Secure.getIntForUser(resolver, @@ -1731,6 +2143,30 @@ public class PhoneWindowManager implements WindowManagerPolicy { updateWakeGestureListenerLp(); } + final boolean useEdgeService = Settings.System.getIntForUser(resolver, + Settings.System.USE_EDGE_SERVICE_FOR_GESTURES, 1, UserHandle.USER_CURRENT) == 1; + if (useEdgeService ^ mUsingEdgeGestureServiceForGestures && mSystemReady) { + if (!mUsingEdgeGestureServiceForGestures && useEdgeService) { + mUsingEdgeGestureServiceForGestures = true; + mWindowManagerFuncs.unregisterPointerEventListener(mSystemGestures); + } else if (mUsingEdgeGestureServiceForGestures && !useEdgeService) { + mUsingEdgeGestureServiceForGestures = false; + mWindowManagerFuncs.registerPointerEventListener(mSystemGestures); + } + updateEdgeGestureListenerState(); + } + + boolean devForceNavbar = Settings.Secure.getIntForUser(resolver, + Settings.Secure.DEV_FORCE_SHOW_NAVBAR, 0, UserHandle.USER_CURRENT) == 1; + if (devForceNavbar != mDevForceNavbar) { + mDevForceNavbar = devForceNavbar; + } + + mNavigationBarLeftInLandscape = Settings.System.getIntForUser(resolver, + Settings.System.NAVBAR_LEFT_IN_LANDSCAPE, 0, UserHandle.USER_CURRENT) == 1; + + updateKeyAssignments(); + // Configure rotation lock. int userRotation = Settings.System.getIntForUser(resolver, Settings.System.USER_ROTATION, Surface.ROTATION_0, @@ -1749,6 +2185,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { updateOrientationListenerLp(); } + mUserRotationAngles = Settings.System.getInt(resolver, + Settings.System.ACCELEROMETER_ROTATION_ANGLES, -1); + if (mSystemReady) { int pointerLocation = Settings.System.getIntForUser(resolver, Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT); @@ -2034,13 +2473,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private boolean isBuiltInKeyboardVisible() { + return mHaveBuiltInKeyboard && !isHidden(mLidKeyboardAccessibility); + } + /** {@inheritDoc} */ @Override public void adjustConfigurationLw(Configuration config, int keyboardPresence, int navigationPresence) { mHaveBuiltInKeyboard = (keyboardPresence & PRESENCE_INTERNAL) != 0; - readConfigurationDependentBehaviors(); readLidState(); applyLidSwitchState(); @@ -2182,7 +2624,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation) { - if (mHasNavigationBar) { + if (hasNavigationBar()) { // For a basic navigation bar, when we are in landscape mode we place // the navigation bar to the side. if (mNavigationBarCanMove && fullWidth > fullHeight) { @@ -2194,7 +2636,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation) { - if (mHasNavigationBar) { + if (hasNavigationBar()) { // For a basic navigation bar, when we are in portrait mode we place // the navigation bar to the bottom. if (!mNavigationBarCanMove || fullWidth < fullHeight) { @@ -2492,7 +2934,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (win.getAttrs().windowAnimations != 0) { return 0; } - // This can be on either the bottom or the right. + // This can be on either the bottom, left, or the right. if (mNavigationBarOnBottom) { if (transit == TRANSIT_EXIT || transit == TRANSIT_HIDE) { @@ -2504,10 +2946,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else { if (transit == TRANSIT_EXIT || transit == TRANSIT_HIDE) { - return R.anim.dock_right_exit; + return mNavigationBarLeftInLandscape + ? R.anim.dock_left_exit : R.anim.dock_right_exit; } else if (transit == TRANSIT_ENTER || transit == TRANSIT_SHOW) { - return R.anim.dock_right_enter; + return mNavigationBarLeftInLandscape + ? R.anim.dock_left_enter : R.anim.dock_right_enter; } } } @@ -2595,9 +3039,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override - public Animation createForceHideWallpaperExitAnimation(boolean goingToNotificationShade) { + public Animation createForceHideWallpaperExitAnimation(boolean goingToNotificationShade, + boolean keyguardShowingMedia) { if (goingToNotificationShade) { return null; + } else if (keyguardShowingMedia) { + return AnimationUtils.loadAnimation(mContext, R.anim.lock_screen_wallpaper_exit_noop); } else { return AnimationUtils.loadAnimation(mContext, R.anim.lock_screen_wallpaper_exit); } @@ -2645,12 +3092,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) { final boolean keyguardOn = keyguardOn(); - final int keyCode = event.getKeyCode(); final int repeatCount = event.getRepeatCount(); final int metaState = event.getMetaState(); final int flags = event.getFlags(); final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final boolean canceled = event.isCanceled(); + final boolean longPress = (flags & KeyEvent.FLAG_LONG_PRESS) != 0; + final boolean virtualKey = event.getDeviceId() == KeyCharacterMap.VIRTUAL_KEYBOARD; + final int keyCode = event.getKeyCode(); if (DEBUG_INPUT) { Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount=" @@ -2685,6 +3134,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingMetaAction = false; } + if (keyCode == KeyEvent.KEYCODE_BACK && !down) { + mHandler.removeCallbacks(mBackLongPress); + } + // First we always handle the home key here, so applications // can never break it, although if keyguard is on, we do let // it handle it, because that gives us the correct 5 second @@ -2694,7 +3147,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { // If we have released the home key, and didn't do anything else // while it was pressed, then it is time to go home! if (!down) { - cancelPreloadRecentApps(); + if (mDoubleTapOnHomeBehavior != KEY_ACTION_APP_SWITCH) { + cancelPreloadRecentApps(); + } mHomePressed = false; if (mHomeConsumed) { @@ -2717,7 +3172,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // Delay handling home if a double-tap is possible. - if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_NOTHING) { + if (mDoubleTapOnHomeBehavior != KEY_ACTION_NOTHING) { mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case mHomeDoubleTapPending = true; mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable, @@ -2755,44 +3210,83 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mHomeDoubleTapPending) { mHomeDoubleTapPending = false; mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); - handleDoubleTapOnHome(); - } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI - || mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) { + performKeyAction(mDoubleTapOnHomeBehavior, event); + mHomeConsumed = mDoubleTapOnHomeBehavior != KEY_ACTION_SLEEP; + } else if (mLongPressOnHomeBehavior == KEY_ACTION_APP_SWITCH + || mDoubleTapOnHomeBehavior == KEY_ACTION_APP_SWITCH) { preloadRecentApps(); } - } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { - if (!keyguardOn) { - handleLongPressOnHome(event.getDeviceId()); + } else if (longPress) { + if (!keyguardOn && !mHomeConsumed && + mLongPressOnHomeBehavior != KEY_ACTION_NOTHING) { + if (mLongPressOnHomeBehavior != KEY_ACTION_APP_SWITCH) { + cancelPreloadRecentApps(); + } + mHomePressed = true; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + performKeyAction(mLongPressOnHomeBehavior, event); + mHomeConsumed = true; } } return -1; } else if (keyCode == KeyEvent.KEYCODE_MENU) { // Hijack modified menu keys for debugging features final int chordBug = KeyEvent.META_SHIFT_ON; + if (virtualKey || keyguardOn) { + // Let the app handle the key + return 0; + } - if (down && repeatCount == 0) { - if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) { - Intent intent = new Intent(Intent.ACTION_BUG_REPORT); - mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, - null, null, null, 0, null, null); - return -1; - } else if (SHOW_PROCESSES_ON_ALT_MENU && - (metaState & KeyEvent.META_ALT_ON) == KeyEvent.META_ALT_ON) { - Intent service = new Intent(); - service.setClassName(mContext, "com.android.server.LoadAverageService"); - ContentResolver res = mContext.getContentResolver(); - boolean shown = Settings.Global.getInt( - res, Settings.Global.SHOW_PROCESSES, 0) != 0; - if (!shown) { - mContext.startService(service); - } else { - mContext.stopService(service); + if (down) { + if (mPressOnMenuBehavior == KEY_ACTION_APP_SWITCH + || mLongPressOnMenuBehavior == KEY_ACTION_APP_SWITCH) { + preloadRecentApps(); + } + if (repeatCount == 0) { + mMenuPressed = true; + if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) { + Intent intent = new Intent(Intent.ACTION_BUG_REPORT); + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, + null, null, null, 0, null, null); + return -1; + } else if (SHOW_PROCESSES_ON_ALT_MENU && + (metaState & KeyEvent.META_ALT_ON) == KeyEvent.META_ALT_ON) { + Intent service = new Intent(); + service.setClassName(mContext, "com.android.server.LoadAverageService"); + ContentResolver res = mContext.getContentResolver(); + boolean shown = Settings.Global.getInt( + res, Settings.Global.SHOW_PROCESSES, 0) != 0; + if (!shown) { + mContext.startService(service); + } else { + mContext.stopService(service); + } + Settings.Global.putInt( + res, Settings.Global.SHOW_PROCESSES, shown ? 0 : 1); + return -1; + } + } else if (longPress) { + if (!keyguardOn && mLongPressOnMenuBehavior != KEY_ACTION_NOTHING) { + if (mLongPressOnMenuBehavior != KEY_ACTION_APP_SWITCH) { + cancelPreloadRecentApps(); + } + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + performKeyAction(mLongPressOnMenuBehavior, event); + mMenuPressed = false; + return -1; } - Settings.Global.putInt( - res, Settings.Global.SHOW_PROCESSES, shown ? 0 : 1); - return -1; } } + if (!down && mMenuPressed) { + if (mPressOnMenuBehavior != KEY_ACTION_APP_SWITCH) { + cancelPreloadRecentApps(); + } + mMenuPressed = false; + if (!canceled) { + performKeyAction(mPressOnMenuBehavior, event); + } + } + return -1; } else if (keyCode == KeyEvent.KEYCODE_SEARCH) { if (down) { if (repeatCount == 0) { @@ -2809,10 +3303,34 @@ public class PhoneWindowManager implements WindowManagerPolicy { return 0; } else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) { if (!keyguardOn) { - if (down && repeatCount == 0) { - preloadRecentApps(); - } else if (!down) { - toggleRecentApps(); + if (down) { + if (mPressOnAppSwitchBehavior == KEY_ACTION_APP_SWITCH + || mLongPressOnAppSwitchBehavior == KEY_ACTION_APP_SWITCH) { + preloadRecentApps(); + } + if (repeatCount == 0) { + mAppSwitchLongPressed = false; + } else if (longPress) { + if (mLongPressOnAppSwitchBehavior != KEY_ACTION_NOTHING) { + if (mLongPressOnAppSwitchBehavior != KEY_ACTION_APP_SWITCH) { + cancelPreloadRecentApps(); + } + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + performKeyAction(mLongPressOnAppSwitchBehavior, event); + mAppSwitchLongPressed = true; + } + } + } else { + if (mAppSwitchLongPressed) { + mAppSwitchLongPressed = false; + } else { + if (mPressOnAppSwitchBehavior != KEY_ACTION_APP_SWITCH) { + cancelPreloadRecentApps(); + } + if (!canceled) { + performKeyAction(mPressOnAppSwitchBehavior, event); + } + } } } return -1; @@ -2829,20 +3347,31 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } else if (keyCode == KeyEvent.KEYCODE_ASSIST) { if (down) { + if (mPressOnAssistBehavior == KEY_ACTION_APP_SWITCH + || mLongPressOnAssistBehavior == KEY_ACTION_APP_SWITCH) { + preloadRecentApps(); + } if (repeatCount == 0) { mAssistKeyLongPressed = false; - } else if (repeatCount == 1) { - mAssistKeyLongPressed = true; - if (!keyguardOn) { - launchAssistLongPressAction(); + } else if (longPress) { + if (!keyguardOn && mLongPressOnAssistBehavior != KEY_ACTION_NOTHING) { + if (mLongPressOnAssistBehavior != KEY_ACTION_APP_SWITCH) { + cancelPreloadRecentApps(); + } + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + performKeyAction(mLongPressOnAssistBehavior, event); + mAssistKeyLongPressed = true; } } } else { if (mAssistKeyLongPressed) { mAssistKeyLongPressed = false; } else { - if (!keyguardOn) { - launchAssistAction(null, event.getDeviceId()); + if (mPressOnAssistBehavior != KEY_ACTION_APP_SWITCH) { + cancelPreloadRecentApps(); + } + if (!canceled) { + performKeyAction(mPressOnAssistBehavior, event); } } } @@ -2915,6 +3444,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, event.getDeviceId()); } return -1; + } else if (keyCode == KeyEvent.KEYCODE_BACK) { + if (Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.KILL_APP_LONGPRESS_BACK, 0) == 1) { + if (down && repeatCount == 0) { + mHandler.postDelayed(mBackLongPress, mBackKillTimeout); + } + } } // Shortcuts are invoked through Search+key, so intercept those here @@ -3023,6 +3559,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { return -1; } + // Specific device key handling + if (mDeviceKeyHandler != null) { + try { + // The device only should consume known keys. + if (mDeviceKeyHandler.handleKeyEvent(event)) { + return -1; + } + } catch (Exception e) { + Slog.w(TAG, "Could not dispatch event to device key handler", e); + } + } + // Reserve all the META modifier combos for system behavior if ((metaState & KeyEvent.META_META_ON) != 0) { return -1; @@ -3054,6 +3602,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0; + // Specific device key handling + if (mDeviceKeyHandler != null) { + try { + // The device only should consume known keys. + if (mDeviceKeyHandler.handleKeyEvent(event)) { + return null; + } + } catch (Exception e) { + Slog.w(TAG, "Could not dispatch event to device key handler", e); + } + } + // Check for fallback actions specified by the key character map. final FallbackAction fallbackAction; if (initialDown) { @@ -3308,8 +3868,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); hideRecentApps(false, true); - } else { - // Otherwise, just launch Home + } else if (mScreenOnFully) { + // check if screen is fully on before going home + // to avoid hardware home button wake going home sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); startDockOrHome(true /*fromHomeKey*/, awakenFromDreams); } @@ -3397,6 +3958,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility); mRecentsVisible = (visibility & View.RECENT_APPS_VISIBLE) > 0; + updateEdgeGestureListenerState(); + // Reset any bits in mForceClearingStatusBarVisibility that // are now clear. mResettingSystemUiFlags &= visibility; @@ -3620,6 +4183,34 @@ public class PhoneWindowManager implements WindowManagerPolicy { // we can tell the app that it is covered by it. mSystemBottom = mTmpNavigationFrame.top; } + } else if (mNavigationBarLeftInLandscape) { + // Landscape screen; nav bar goes to the left. + int right = overscanLeft + mNavigationBarWidthForRotation[displayRotation]; + mTmpNavigationFrame.set(0, 0, right, displayHeight); + mStableLeft = mStableFullscreenLeft = mTmpNavigationFrame.right; + mStableFullscreenLeft = mTmpNavigationFrame.right; + if (transientNavBarShowing) { + mNavigationBarController.setBarShowingLw(true); + } else if (navVisible) { + mNavigationBarController.setBarShowingLw(true); + mDockLeft = mTmpNavigationFrame.right; + mRestrictedScreenLeft = mDockLeft; + mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft; + mRestrictedOverscanScreenLeft = mRestrictedScreenLeft; + mRestrictedOverscanScreenWidth = mDockRight + - mRestrictedOverscanScreenLeft; + } else { + // We currently want to hide the navigation UI. + mNavigationBarController.setBarShowingLw(false); + } + + if (navVisible && !navTranslucent && !mNavigationBar.isAnimatingLw() + && !mNavigationBarController.wasRecentlyTranslucent()) { + // If the nav bar is currently requested to be visible, + // and not in the process of animating on or off, then + // we can tell the app that it is covered by it. + mSystemLeft = mTmpNavigationFrame.right; + } } else { // Landscape screen; nav bar goes to the right. int left = displayWidth - overscanRight @@ -4666,6 +5257,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // update since mAllowLockscreenWhenOn might have changed updateLockScreenTimeout(); + updateEdgeGestureListenerState(); return changes; } @@ -4808,6 +5400,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { setHdmiPlugged(!mHdmiPlugged); } + /** + * @return Whether music is being played right now "locally" (e.g. on the device's speakers + * or wired headphones) or "remotely" (e.g. on a device using the Cast protocol and + * controlled by this device, or through remote submix). + */ + private boolean isMusicActive() { + final AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE); + if (am == null) { + Log.w(TAG, "isMusicActive: couldn't get AudioManager reference"); + return false; + } + return am.isMusicActive(); + } + final Object mScreenshotLock = new Object(); ServiceConnection mScreenshotConnection = null; @@ -4889,6 +5495,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final boolean canceled = event.isCanceled(); final int keyCode = event.getKeyCode(); + final int scanCode = event.getScanCode(); final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0; @@ -4920,8 +5527,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { // If we're currently dozing with the screen on and the keyguard showing, pass the key // to the application but preserve its wake key status to make sure we still move // from dozing to fully interactive if we would normally go from off to fully - // interactive. + // interactive, unless the user has explicitly disabled this wake key. result = ACTION_PASS_TO_USER; + isWakeKey = isWakeKey && isWakeKeyEnabled(keyCode); } else { // When the screen is off and the key is not injected, determine whether // to wake the device but don't pass the key to the application. @@ -4954,6 +5562,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { // On TVs volume keys never go to the foreground app result &= ~ACTION_PASS_TO_USER; } + // Eat all down & up keys when using volume wake. + // This disables volume control, music control, and "beep" on key up. + if (isWakeKey && mVolumeWakeScreen) { + mVolumeWakeTriggered = true; + break; + } else if (mVolumeWakeTriggered && !down) { + result &= ~ACTION_PASS_TO_USER; + mVolumeWakeTriggered = false; + break; + } + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { if (down) { if (interactive && !mScreenshotChordVolumeDownKeyTriggered @@ -5013,23 +5632,101 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; } } + } + + // Disable music and volume control when used as wake key + if ((result & ACTION_PASS_TO_USER) == 0 && !mVolumeWakeScreen) { + boolean mayChangeVolume = false; + + if (isMusicActive()) { + if (mVolBtnMusicControls && (keyCode != KeyEvent.KEYCODE_VOLUME_MUTE)) { + // Detect long key presses. + if (down) { + mIsLongPress = false; + // TODO: Long press of MUTE could be mapped to KEYCODE_MEDIA_PLAY_PAUSE + int newKeyCode = event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP ? + KeyEvent.KEYCODE_MEDIA_NEXT : KeyEvent.KEYCODE_MEDIA_PREVIOUS; + scheduleLongPressKeyEvent(event, newKeyCode); + // Consume key down events of all presses. + break; + } else { + mHandler.removeMessages(MSG_DISPATCH_VOLKEY_WITH_WAKE_LOCK); + // Consume key up events of long presses only. + if (mIsLongPress) { + break; + } + // Change volume only on key up events of short presses. + mayChangeVolume = true; + } + } else { + // Long key press detection not applicable, change volume only + // on key down events + mayChangeVolume = down; + } + } - if ((result & ACTION_PASS_TO_USER) == 0) { + if (mayChangeVolume) { if (mUseTvRouting) { dispatchDirectAudioEvent(event); } else { // If we aren't passing to the user and no one else - // handled it send it to the session manager to - // figure out. + // handled it send it to the session manager to figure + // out. + + // Rewrite the event to use key-down as sendVolumeKeyEvent will + // only change the volume on key down. + KeyEvent newEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode); MediaSessionLegacyHelper.getHelper(mContext) - .sendVolumeKeyEvent(event, true); + .sendVolumeKeyEvent(newEvent, true); } - break; } + break; } break; } + case KeyEvent.KEYCODE_HOME: + if (down && !interactive && mHomeWakeScreen) { + isWakeKey = true; + } + break; + + case KeyEvent.KEYCODE_FOCUS: + if (down && !interactive && mCameraSleepOnRelease) { + mIsFocusPressed = true; + } else if ((event.getAction() == KeyEvent.ACTION_UP) + && mScreenOnFully && mIsFocusPressed) { + // Check if screen is fully on before letting the device go to sleep + mPowerManager.goToSleep(SystemClock.uptimeMillis()); + mIsFocusPressed = false; + } + break; + + case KeyEvent.KEYCODE_CAMERA: + if (down && mIsFocusPressed) { + mIsFocusPressed = false; + } + if (down) { + mIsLongPress = false; + scheduleLongPressKeyEvent(event, KeyEvent.KEYCODE_CAMERA); + // Consume key down events of all presses. + break; + } else { + mHandler.removeMessages(MSG_CAMERA_LONG_PRESS); + // Consume key up events of long presses only. + if (mIsLongPress && mCameraLaunch) { + Intent intent; + if (keyguardActive) { + intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE); + } else { + intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); + } + isWakeKey = true; + startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); + } + } + break; + case KeyEvent.KEYCODE_ENDCALL: { result &= ~ACTION_PASS_TO_USER; if (down) { @@ -5068,6 +5765,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } case KeyEvent.KEYCODE_POWER: { + if ((mTopFullscreenOpaqueWindowState.getAttrs().privateFlags + & WindowManager.LayoutParams.PRIVATE_FLAG_PREVENT_POWER_KEY) != 0 + && mScreenOnFully) { + return result; + } result &= ~ACTION_PASS_TO_USER; isWakeKey = false; // wake-up will be handled separately if (down) { @@ -5173,6 +5875,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { return result; } + private void scheduleLongPressKeyEvent(KeyEvent origEvent, int keyCode) { + KeyEvent event = new KeyEvent(origEvent.getDownTime(), origEvent.getEventTime(), + origEvent.getAction(), keyCode, 0); + Message msg; + if (keyCode == KeyEvent.KEYCODE_CAMERA) { + msg = mHandler.obtainMessage(MSG_CAMERA_LONG_PRESS, event); + } else { + msg = mHandler.obtainMessage(MSG_DISPATCH_VOLKEY_WITH_WAKE_LOCK, event); + } + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, ViewConfiguration.getLongPressTimeout()); + } + /** * Returns true if the key can have global actions attached to it. * We reserve all power management keys for the system since they require @@ -5190,6 +5905,32 @@ public class PhoneWindowManager implements WindowManagerPolicy { } /** + * Check if the given keyCode represents a key that is considered a wake key + * and is currently enabled by the user in Settings or for another reason. + */ + private boolean isWakeKeyEnabled(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_MUTE: + // Volume keys are still wake keys if the device is docked. + return mVolumeWakeScreen || mDockMode != Intent.EXTRA_DOCK_STATE_UNDOCKED; + case KeyEvent.KEYCODE_BACK: + return mBackWakeScreen; + case KeyEvent.KEYCODE_MENU: + return mMenuWakeScreen; + case KeyEvent.KEYCODE_ASSIST: + return mAssistWakeScreen; + case KeyEvent.KEYCODE_APP_SWITCH: + return mAppSwitchWakeScreen; + case KeyEvent.KEYCODE_CAMERA: + case KeyEvent.KEYCODE_FOCUS: + return mCameraWakeScreen; + } + return true; + } + + /** * When the screen is off we ignore some keys that might otherwise typically * be considered wake keys. We filter them out here. * @@ -5202,9 +5943,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: - return mDockMode != Intent.EXTRA_DOCK_STATE_UNDOCKED; + return mVolumeWakeScreen || mDockMode != Intent.EXTRA_DOCK_STATE_UNDOCKED; - // ignore media and camera keys + // ignore media keys case KeyEvent.KEYCODE_MUTE: case KeyEvent.KEYCODE_HEADSETHOOK: case KeyEvent.KEYCODE_MEDIA_PLAY: @@ -5217,8 +5958,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_MEDIA_RECORD: case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: - case KeyEvent.KEYCODE_CAMERA: return false; + + case KeyEvent.KEYCODE_BACK: + return mBackWakeScreen; + case KeyEvent.KEYCODE_MENU: + return mMenuWakeScreen; + case KeyEvent.KEYCODE_ASSIST: + return mAssistWakeScreen; + case KeyEvent.KEYCODE_APP_SWITCH: + return mAppSwitchWakeScreen; + case KeyEvent.KEYCODE_CAMERA: + case KeyEvent.KEYCODE_FOCUS: + return mCameraWakeScreen; } return true; } @@ -5263,7 +6015,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { IDreamManager dreamManager = getDreamManager(); try { - if (dreamManager != null && dreamManager.isDreaming()) { + if (dreamManager != null && dreamManager.isDreaming() && !dreamManager.isDozing()) { return true; } } catch (RemoteException e) { @@ -5425,6 +6177,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // current user. mSettingsObserver.onChange(false); + if (mGlobalActions != null) { + mGlobalActions.updatePowerMenuActions(); + } + // force a re-application of focused window sysui visibility. // the window may never have been shown for this user // e.g. the keyguard when going through the new-user setup flow @@ -5684,10 +6440,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!mKeyguardDrawnOnce && mAwake) { mKeyguardDrawnOnce = true; enableScreen = true; - if (mBootMessageNeedsHiding) { - mBootMessageNeedsHiding = false; - hideBootMessages(); - } } else { enableScreen = false; } @@ -5707,9 +6459,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { private void handleHideBootMessage() { synchronized (mLock) { - if (!mKeyguardDrawnOnce) { - mBootMessageNeedsHiding = true; - return; // keyguard hasn't drawn the first time yet, not done booting + if (!mKeyguardDrawComplete) { + return; // keyguard hasn't completed drawing, not done booting. } } @@ -5842,8 +6593,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } final int preferredRotation; - if (mLidState == LID_OPEN && mLidOpenRotation >= 0) { - // Ignore sensor when lid switch is open and rotation is forced. + if ((mLidState == LID_OPEN && mLidOpenRotation >= 0) + && !(mHasRemovableLid + && mDockMode == Intent.EXTRA_DOCK_STATE_UNDOCKED)) { + // Ignore sensor when lid switch is open and rotation is forced + // and a removable lid was not undocked. preferredRotation = mLidOpenRotation; } else if (mDockMode == Intent.EXTRA_DOCK_STATE_CAR && (mCarDockEnablesAccelerometer || mCarDockRotation >= 0)) { @@ -5905,10 +6659,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { mAllowAllRotations = mContext.getResources().getBoolean( com.android.internal.R.bool.config_allowAllRotations) ? 1 : 0; } - if (sensorRotation != Surface.ROTATION_180 - || mAllowAllRotations == 1 - || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR - || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) { + + // use sensor orientation if it's forced, or if the user has allowed it + boolean useSensorRotation = + orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR + || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER + || RotationPolicy.isRotationAllowed(sensorRotation, mUserRotationAngles, + mAllowAllRotations != 0); + if (useSensorRotation) { preferredRotation = sensorRotation; } else { preferredRotation = lastRotation; @@ -6076,6 +6834,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDelegate = new KeyguardServiceDelegate(mContext); mKeyguardDelegate.onSystemReady(); + mEdgeGestureManager = EdgeGestureManager.getInstance(); + mEdgeGestureManager.setEdgeGestureActivationListener(mEdgeGestureActivationListener); + readCameraLensCoverState(); updateUiMode(); boolean bindKeyguardNow; @@ -6309,14 +7070,37 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void applyLidSwitchState() { - if (mLidState == LID_CLOSED && mLidControlsSleep) { - mPowerManager.goToSleep(SystemClock.uptimeMillis(), - PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH, - PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); - } + mPowerManager.setKeyboardVisibility(isBuiltInKeyboardVisible()); + if (mLidControlsSleep) { + IDreamManager dreamManager = getDreamManager(); + if (dreamManager != null) { + try { + dreamManager.setLidState(mLidState); + } catch (RemoteException e) { + } + } - synchronized (mLock) { - updateWakeGestureListenerLp(); + if (mLidState == LID_CLOSED) { + if (mFocusedWindow != null && (mFocusedWindow.getAttrs().flags + & WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) != 0) { + // if an application requests that the screen be turned on + // and there's a closed device cover, don't turn the screen off! + return; + } + + TelecomManager telephonyService = getTelecommService(); + if (!(telephonyService == null + || telephonyService.isRinging())) { + mPowerManager.goToSleep(SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH, + PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); + } + + } + + synchronized (mLock) { + updateWakeGestureListenerLp(); + } } } @@ -6391,7 +7175,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { ActivityInfo ai = null; ResolveInfo info = mContext.getPackageManager().resolveActivityAsUser( intent, - PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA, + PackageManager.MATCH_DEFAULT_ONLY + | PackageManager.GET_META_DATA + | PackageManager.GET_RESOLVED_FILTER, mCurrentUserId); if (info != null) { ai = info.activityInfo; @@ -6798,6 +7584,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { // overridden by qemu.hw.mainkeys in the emulator. @Override public boolean hasNavigationBar() { + return mHasNavigationBar || mDevForceNavbar; + } + + @Override + public boolean hasPermanentMenuKey() { + return !hasNavigationBar() && mHasPermanentMenuKey; + } + + public boolean needsNavigationBar() { return mHasNavigationBar; } diff --git a/services/core/java/com/android/server/policy/PolicyControl.java b/services/core/java/com/android/server/policy/PolicyControl.java index dbafc42..0f6fc58 100644 --- a/services/core/java/com/android/server/policy/PolicyControl.java +++ b/services/core/java/com/android/server/policy/PolicyControl.java @@ -125,6 +125,10 @@ public class PolicyControl { } } + public static boolean isImmersiveFiltersActive() { + return sImmersiveStatusFilter != null || sImmersiveNavigationFilter != null; + } + public static void dump(String prefix, PrintWriter pw) { dump("sImmersiveStatusFilter", sImmersiveStatusFilter, prefix, pw); dump("sImmersiveNavigationFilter", sImmersiveNavigationFilter, prefix, pw); diff --git a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java index 627b328..31fc653 100644 --- a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java +++ b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java @@ -36,6 +36,7 @@ public class SystemGesturesPointerEventListener implements PointerEventListener private static final int SWIPE_FROM_TOP = 1; private static final int SWIPE_FROM_BOTTOM = 2; private static final int SWIPE_FROM_RIGHT = 3; + private static final int SWIPE_FROM_LEFT = 4; private final int mSwipeStartThreshold; private final int mSwipeDistanceThreshold; @@ -100,6 +101,9 @@ public class SystemGesturesPointerEventListener implements PointerEventListener } else if (swipe == SWIPE_FROM_RIGHT) { if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight"); mCallbacks.onSwipeFromRight(); + } else if (swipe == SWIPE_FROM_LEFT) { + if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft"); + mCallbacks.onSwipeFromLeft(); } } break; @@ -182,6 +186,11 @@ public class SystemGesturesPointerEventListener implements PointerEventListener && elapsed < SWIPE_TIMEOUT_MS) { return SWIPE_FROM_BOTTOM; } + if (fromX <= mSwipeStartThreshold + && x > fromX + mSwipeDistanceThreshold + && elapsed < SWIPE_TIMEOUT_MS) { + return SWIPE_FROM_LEFT; + } if (fromX >= screenWidth - mSwipeStartThreshold && x < fromX - mSwipeDistanceThreshold && elapsed < SWIPE_TIMEOUT_MS) { @@ -196,6 +205,7 @@ public class SystemGesturesPointerEventListener implements PointerEventListener void onSwipeFromRight(); void onDown(); void onUpOrCancel(); + void onSwipeFromLeft(); void onDebug(); } } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 7d64096..3682950 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -18,10 +18,12 @@ package com.android.server.power; import android.app.ActivityManager; import android.util.SparseIntArray; + import com.android.internal.app.IAppOpsService; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BackgroundThread; import com.android.server.EventLogTags; +import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.am.BatteryStatsService; @@ -29,6 +31,8 @@ import com.android.server.lights.Light; import com.android.server.lights.LightsManager; import com.android.server.Watchdog; +import cyanogenmod.power.PerformanceManagerInternal; + import android.Manifest; import android.app.AppOpsManager; import android.content.BroadcastReceiver; @@ -39,6 +43,9 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.SystemSensorManager; import android.hardware.display.DisplayManagerInternal; @@ -63,6 +70,7 @@ import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; import android.service.dreams.DreamManagerInternal; +import android.telephony.TelephonyManager; import android.util.EventLog; import android.util.Slog; import android.util.TimeUtils; @@ -99,6 +107,8 @@ public final class PowerManagerService extends SystemService // Message: Sent when the screen brightness boost expires. private static final int MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT = 3; + private static final int MSG_WAKE_UP = 5; + // Dirty bit: mWakeLocks changed private static final int DIRTY_WAKE_LOCKS = 1 << 0; // Dirty bit: mWakefulness changed @@ -156,9 +166,18 @@ public final class PowerManagerService extends SystemService // Power features defined in hardware/libhardware/include/hardware/power.h. private static final int POWER_FEATURE_DOUBLE_TAP_TO_WAKE = 1; + private static final int DEFAULT_BUTTON_ON_DURATION = 5 * 1000; + // Default setting for double tap to wake. private static final int DEFAULT_DOUBLE_TAP_TO_WAKE = 0; + private static final int BUTTON_ON_DURATION = 5 * 1000; + + private static final float PROXIMITY_NEAR_THRESHOLD = 5.0f; + + // Max time (microseconds) to allow a CPU boost for + private static final int MAX_CPU_BOOST_TIME = 5000000; + private final Context mContext; private final ServiceThread mHandlerThread; private final PowerManagerHandler mHandler; @@ -174,6 +193,16 @@ public final class PowerManagerService extends SystemService private SettingsObserver mSettingsObserver; private DreamManagerInternal mDreamManager; private Light mAttentionLight; + private Light mButtonsLight; + private Light mKeyboardLight; + private Light mCapsLight; + private Light mFnLight; + + private int mButtonTimeout; + private int mButtonBrightness; + private int mButtonBrightnessSettingDefault; + private int mKeyboardBrightness; + private int mKeyboardBrightnessSettingDefault; private final Object mLock = new Object(); @@ -351,6 +380,9 @@ public final class PowerManagerService extends SystemService // Whether device supports double tap to wake. private boolean mSupportsDoubleTapWakeConfig; + // Default value for proximity prevent accidental wakeups + private boolean mProximityWakeEnabledByDefaultConfig; + // The screen off timeout setting value in milliseconds. private int mScreenOffTimeoutSetting; @@ -362,9 +394,12 @@ public final class PowerManagerService extends SystemService private int mMaximumScreenOffTimeoutFromDeviceAdmin = Integer.MAX_VALUE; // The stay on while plugged in setting. - // A bitfield of battery conditions under which to make the screen stay on. + // 0: Not enabled; 1: debugging over usb; >2: charging. private int mStayOnWhilePluggedInSetting; + // True if the device should wake up when plugged or unplugged + private int mWakeUpWhenPluggedOrUnpluggedSetting; + // True if the device should stay on. private boolean mStayOn; @@ -393,6 +428,11 @@ public final class PowerManagerService extends SystemService // Use -1 to disable. private int mScreenBrightnessOverrideFromWindowManager = -1; + // The button brightness setting override from the window manager + // to allow the current foreground activity to override the button brightness. + // Use -1 to disable. + private int mButtonBrightnessOverrideFromWindowManager = -1; + // The user activity timeout override from the window manager // to allow the current foreground activity to override the user activity timeout. // Use -1 to disable. @@ -464,6 +504,19 @@ public final class PowerManagerService extends SystemService private static native void nativeSetAutoSuspend(boolean enable); private static native void nativeSendPowerHint(int hintId, int data); private static native void nativeSetFeature(int featureId, int data); + private static native int nativeGetFeature(int featureId); + + private boolean mKeyboardVisible = false; + + private SensorManager mSensorManager; + private Sensor mProximitySensor; + private boolean mProximityWakeEnabled; + private int mProximityTimeOut; + private boolean mProximityWakeSupported; + android.os.PowerManager.WakeLock mProximityWakeLock; + SensorEventListener mProximityListener; + + private PerformanceManagerInternal mPerf; public PowerManagerService(Context context) { super(context); @@ -509,6 +562,7 @@ public final class PowerManagerService extends SystemService userActivityNoUpdateLocked( now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); updatePowerStateLocked(); + mPerf = LocalServices.getService(PerformanceManagerInternal.class); } } } @@ -526,6 +580,8 @@ public final class PowerManagerService extends SystemService mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting(); mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting(); mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting(); + mButtonBrightnessSettingDefault = pm.getDefaultButtonBrightness(); + mKeyboardBrightnessSettingDefault = pm.getDefaultKeyboardBrightness(); SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper()); @@ -543,11 +599,19 @@ public final class PowerManagerService extends SystemService mLightsManager = getLocalService(LightsManager.class); mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); + mButtonsLight = mLightsManager.getLight(LightsManager.LIGHT_ID_BUTTONS); + mKeyboardLight = mLightsManager.getLight(LightsManager.LIGHT_ID_KEYBOARD); + mCapsLight = mLightsManager.getLight(LightsManager.LIGHT_ID_CAPS); + mFnLight = mLightsManager.getLight(LightsManager.LIGHT_ID_FUNC); // Initialize display power management. mDisplayManagerInternal.initPowerManagement( mDisplayPowerCallbacks, mHandler, sensorManager); + // Initialize proximity sensor + mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); + mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + // Register for broadcasts from other components of the system. IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); @@ -608,6 +672,22 @@ public final class PowerManagerService extends SystemService resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.DOUBLE_TAP_TO_WAKE), false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.BUTTON_BRIGHTNESS), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.KEYBOARD_BRIGHTNESS), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.BUTTON_BACKLIGHT_TIMEOUT), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.PROXIMITY_ON_WAKE), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.WAKE_WHEN_PLUGGED_OR_UNPLUGGED), + false, mSettingsObserver, UserHandle.USER_ALL); + // Go. readConfigurationLocked(); updateSettingsLocked(); @@ -655,6 +735,17 @@ public final class PowerManagerService extends SystemService com.android.internal.R.fraction.config_maximumScreenDimRatio, 1, 1); mSupportsDoubleTapWakeConfig = resources.getBoolean( com.android.internal.R.bool.config_supportDoubleTapWake); + mProximityTimeOut = resources.getInteger( + com.android.internal.R.integer.config_proximityCheckTimeout); + mProximityWakeSupported = resources.getBoolean( + com.android.internal.R.bool.config_proximityCheckOnWake); + mProximityWakeEnabledByDefaultConfig = resources.getBoolean( + com.android.internal.R.bool.config_proximityCheckOnWakeEnabledByDefault); + if (mProximityWakeSupported) { + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mProximityWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "ProximityWakeLock"); + } } private void updateSettingsLocked() { @@ -679,9 +770,14 @@ public final class PowerManagerService extends SystemService Settings.Secure.SLEEP_TIMEOUT, DEFAULT_SLEEP_TIMEOUT, UserHandle.USER_CURRENT); mStayOnWhilePluggedInSetting = Settings.Global.getInt(resolver, - Settings.Global.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC); + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); mTheaterModeEnabled = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) == 1; + mWakeUpWhenPluggedOrUnpluggedSetting = Settings.Global.getInt(resolver, + Settings.Global.WAKE_WHEN_PLUGGED_OR_UNPLUGGED, + (mWakeUpWhenPluggedOrUnpluggedConfig ? 1 : 0)); + mProximityWakeEnabled = Settings.System.getInt(resolver, + Settings.System.PROXIMITY_ON_WAKE, mProximityWakeEnabledByDefaultConfig ? 1 : 0) == 1; if (mSupportsDoubleTapWakeConfig) { boolean doubleTapWakeEnabled = Settings.Secure.getIntForUser(resolver, @@ -725,6 +821,17 @@ public final class PowerManagerService extends SystemService updateLowPowerModeLocked(); } + mButtonTimeout = Settings.Secure.getIntForUser(resolver, + Settings.Secure.BUTTON_BACKLIGHT_TIMEOUT, + DEFAULT_BUTTON_ON_DURATION, UserHandle.USER_CURRENT); + + mButtonBrightness = Settings.Secure.getIntForUser(resolver, + Settings.Secure.BUTTON_BRIGHTNESS, mButtonBrightnessSettingDefault, + UserHandle.USER_CURRENT); + mKeyboardBrightness = Settings.Secure.getIntForUser(resolver, + Settings.Secure.KEYBOARD_BRIGHTNESS, mKeyboardBrightnessSettingDefault, + UserHandle.USER_CURRENT); + mDirty |= DIRTY_SETTINGS; } @@ -736,6 +843,7 @@ public final class PowerManagerService extends SystemService // Turn setting off if powered Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0); + // update performance profile mLowPowerModeSetting = false; } final boolean autoLowPowerModeEnabled = !mIsPowered && mAutoLowPowerModeConfigured @@ -1389,7 +1497,7 @@ public final class PowerManagerService extends SystemService private boolean shouldWakeUpWhenPluggedOrUnpluggedLocked( boolean wasPowered, int oldPlugType, boolean dockedOnWirelessCharger) { // Don't wake when powered unless configured to do so. - if (!mWakeUpWhenPluggedOrUnpluggedConfig) { + if (mWakeUpWhenPluggedOrUnpluggedSetting == 0) { return false; } @@ -1431,7 +1539,16 @@ public final class PowerManagerService extends SystemService final boolean wasStayOn = mStayOn; if (mStayOnWhilePluggedInSetting != 0 && !isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) { - mStayOn = mBatteryManagerInternal.isPowered(mStayOnWhilePluggedInSetting); + switch (mStayOnWhilePluggedInSetting) { + case 1: // Debugging only over usb + mStayOn = ((mPlugType & BatteryManager.BATTERY_PLUGGED_USB) != 0) + && Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ADB_ENABLED, 0) != 0;; + break; + default: // charging + mStayOn = mIsPowered; + break; + } } else { mStayOn = false; } @@ -1544,10 +1661,38 @@ public final class PowerManagerService extends SystemService + screenOffTimeout - screenDimDuration; if (now < nextTimeout) { mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT; + if (mWakefulness == WAKEFULNESS_AWAKE) { + int buttonBrightness, keyboardBrightness; + if (mButtonBrightnessOverrideFromWindowManager >= 0) { + buttonBrightness = mButtonBrightnessOverrideFromWindowManager; + keyboardBrightness = mButtonBrightnessOverrideFromWindowManager; + } else { + buttonBrightness = mButtonBrightness; + keyboardBrightness = mKeyboardBrightness; + } + + mKeyboardLight.setBrightness(mKeyboardVisible ? + keyboardBrightness : 0); + if (mButtonTimeout != 0 + && now > mLastUserActivityTime + mButtonTimeout) { + mButtonsLight.setBrightness(0); + } else { + if (!mProximityPositive) { + mButtonsLight.setBrightness(buttonBrightness); + if (buttonBrightness != 0 && mButtonTimeout != 0) { + nextTimeout = now + mButtonTimeout; + } + } + } + } } else { nextTimeout = mLastUserActivityTime + screenOffTimeout; if (now < nextTimeout) { mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM; + if (mWakefulness == WAKEFULNESS_AWAKE) { + mButtonsLight.setBrightness(0); + mKeyboardLight.setBrightness(0); + } } } } @@ -1811,7 +1956,7 @@ public final class PowerManagerService extends SystemService } // Dream has ended or will be stopped. Update the power state. - if (isItBedTimeYetLocked()) { + if (isItBedTimeYetLocked() && !mDreamsActivatedOnSleepByDefaultConfig) { goToSleepNoUpdateLocked(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID); updatePowerStateLocked(); @@ -2831,6 +2976,10 @@ public final class PowerManagerService extends SystemService case MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT: handleScreenBrightnessBoostTimeout(); break; + case MSG_WAKE_UP: + cleanupProximity(); + ((Runnable) msg.obj).run(); + break; } } } @@ -3010,6 +3159,22 @@ public final class PowerManagerService extends SystemService } } + private void cleanupProximity() { + synchronized (mProximityWakeLock) { + cleanupProximityLocked(); + } + } + + private void cleanupProximityLocked() { + if (mProximityWakeLock.isHeld()) { + mProximityWakeLock.release(); + } + if (mProximityListener != null) { + mSensorManager.unregisterListener(mProximityListener); + mProximityListener = null; + } + } + private final class BinderService extends IPowerManager.Stub { @Override // Binder call public void acquireWakeLockWithUid(IBinder lock, int flags, String tag, @@ -3165,7 +3330,46 @@ public final class PowerManagerService extends SystemService } @Override // Binder call - public void wakeUp(long eventTime, String reason, String opPackageName) { + public void setKeyboardVisibility(boolean visible) { + synchronized (mLock) { + if (DEBUG_SPEW) { + Slog.d(TAG, "setKeyboardVisibility: " + visible); + } + if (mKeyboardVisible != visible) { + mKeyboardVisible = visible; + if (!visible) { + // If hiding keyboard, turn off leds + setKeyboardLight(false, 1); + setKeyboardLight(false, 2); + } + synchronized (mLock) { + mDirty |= DIRTY_USER_ACTIVITY; + updatePowerStateLocked(); + } + } + } + } + + @Override // Binder call + public void setKeyboardLight(boolean on, int key) { + if (key == 1) { + if (on) + mCapsLight.setColor(0x00ffffff); + else + mCapsLight.turnOff(); + } else if (key == 2) { + if (on) + mFnLight.setColor(0x00ffffff); + else + mFnLight.turnOff(); + } + } + + /** + * @hide + */ + private void wakeUp(final long eventTime, final String reason, final String opPackageName, + boolean checkProximity) { if (eventTime > SystemClock.uptimeMillis()) { throw new IllegalArgumentException("event time must not be in the future"); } @@ -3174,14 +3378,86 @@ public final class PowerManagerService extends SystemService android.Manifest.permission.DEVICE_POWER, null); final int uid = Binder.getCallingUid(); - final long ident = Binder.clearCallingIdentity(); - try { - wakeUpInternal(eventTime, reason, uid, opPackageName, uid); - } finally { - Binder.restoreCallingIdentity(ident); + Runnable r = new Runnable() { + @Override + public void run() { + final long ident = Binder.clearCallingIdentity(); + try { + wakeUpInternal(eventTime, reason, uid, opPackageName, uid); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + }; + if (checkProximity) { + runWithProximityCheck(r); + } else { + r.run(); + } + } + + private void runWithProximityCheck(Runnable r) { + if (mHandler.hasMessages(MSG_WAKE_UP)) { + // There is already a message queued; + return; + } + + TelephonyManager tm = (TelephonyManager)mContext.getSystemService( + Context.TELEPHONY_SERVICE); + boolean hasIncomingCall = tm.getCallState() == TelephonyManager.CALL_STATE_RINGING; + + if (mProximityWakeSupported && mProximityWakeEnabled && mProximitySensor != null + && !hasIncomingCall) { + Message msg = mHandler.obtainMessage(MSG_WAKE_UP); + msg.obj = r; + mHandler.sendMessageDelayed(msg, mProximityTimeOut); + runPostProximityCheck(r); + } else { + r.run(); } } + private void runPostProximityCheck(final Runnable r) { + if (mSensorManager == null) { + r.run(); + return; + } + synchronized (mProximityWakeLock) { + mProximityWakeLock.acquire(); + mProximityListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + cleanupProximityLocked(); + if (!mHandler.hasMessages(MSG_WAKE_UP)) { + Slog.w(TAG, "The proximity sensor took too long, wake event already triggered!"); + return; + } + mHandler.removeMessages(MSG_WAKE_UP); + float distance = event.values[0]; + if (distance >= PROXIMITY_NEAR_THRESHOLD || + distance >= mProximitySensor.getMaximumRange()) { + r.run(); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) {} + }; + mSensorManager.registerListener(mProximityListener, + mProximitySensor, SensorManager.SENSOR_DELAY_FASTEST); + } + } + + @Override // Binder call + public void wakeUpWithProximityCheck(long eventTime, String reason, String opPackageName) { + wakeUp(eventTime, reason, opPackageName, true); + } + + @Override // Binder call + public void wakeUp(long eventTime, String reason, String opPackageName) { + wakeUp(eventTime, reason, opPackageName, false); + } + @Override // Binder call public void goToSleep(long eventTime, int reason, int flags) { if (eventTime > SystemClock.uptimeMillis()) { @@ -3444,6 +3720,11 @@ public final class PowerManagerService extends SystemService } @Override // Binder call + public void cpuBoost(int duration) { + mPerf.cpuBoost(duration); + } + + @Override // Binder call protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -3499,6 +3780,16 @@ public final class PowerManagerService extends SystemService } } + private void setButtonBrightnessOverrideFromWindowManagerInternal(int brightness) { + synchronized (mLock) { + if (mButtonBrightnessOverrideFromWindowManager != brightness) { + mButtonBrightnessOverrideFromWindowManager = brightness; + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + } + private final class LocalService extends PowerManagerInternal { @Override public void setScreenBrightnessOverrideFromWindowManager(int screenBrightness) { @@ -3511,8 +3802,15 @@ public final class PowerManagerService extends SystemService @Override public void setButtonBrightnessOverrideFromWindowManager(int screenBrightness) { - // Do nothing. - // Button lights are not currently supported in the new implementation. + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setButtonBrightnessOverrideFromWindowManagerInternal(screenBrightness); + } finally { + Binder.restoreCallingIdentity(ident); + } + } @Override @@ -3583,6 +3881,26 @@ public final class PowerManagerService extends SystemService public void uidGone(int uid) { uidGoneInternal(uid); } + + @Override + public void powerHint(int hintId, int data) { + powerHintInternal(hintId, data); + } + + @Override + public boolean setPowerSaveMode(boolean mode) { + return setLowPowerModeInternal(mode); + } + + @Override + public int getFeature(int featureId) { + return nativeGetFeature(featureId); + } + + @Override + public void setFeature(int featureId, int data) { + nativeSetFeature(featureId, data); + } } private boolean updateBlockedWakelock(WakeLock wakeLock, boolean update) { diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index d5ad30c..5185747 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2013 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. @@ -21,9 +22,11 @@ import android.app.ActivityManagerNative; import android.app.AlertDialog; import android.app.Dialog; import android.app.IActivityManager; +import android.app.KeyguardManager; import android.app.ProgressDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.IBluetoothManager; +import android.content.pm.ThemeUtils; import android.media.AudioAttributes; import android.nfc.NfcAdapter; import android.nfc.INfcAdapter; @@ -32,7 +35,11 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; import android.os.Handler; +import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; @@ -44,13 +51,16 @@ import android.os.Vibrator; import android.os.SystemVibrator; import android.os.storage.IMountService; import android.os.storage.IMountShutdownObserver; +import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; +import android.widget.ListView; import com.android.internal.telephony.ITelephony; import com.android.server.pm.PackageManagerService; - +import com.android.server.power.PowerManagerService; import android.util.Log; +import android.view.IWindowManager; import android.view.WindowManager; import java.lang.reflect.Method; import dalvik.system.PathClassLoader; @@ -58,7 +68,11 @@ import dalvik.system.PathClassLoader; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.PrintWriter; +import java.io.OutputStreamWriter; public final class ShutdownThread extends Thread { // constants @@ -76,6 +90,8 @@ public final class ShutdownThread extends Thread { private static final int RADIO_STOP_PERCENT = 18; private static final int MOUNT_SERVICE_STOP_PERCENT = 20; + private static final String SOFT_REBOOT = "soft_reboot"; + // length of vibration before shutting down private static final int SHUTDOWN_VIBRATE_MS = 500; @@ -113,10 +129,20 @@ public final class ShutdownThread extends Thread { private PowerManager.WakeLock mCpuWakeLock; private PowerManager.WakeLock mScreenWakeLock; private Handler mHandler; + private static MediaPlayer mMediaPlayer; + private static final String OEM_BOOTANIMATION_FILE = "/oem/media/shutdownanimation.zip"; + private static final String SYSTEM_BOOTANIMATION_FILE = "/system/media/shutdownanimation.zip"; + private static final String SYSTEM_ENCRYPTED_BOOTANIMATION_FILE = "/system/media/shutdownanimation-encrypted.zip"; + + private static final String SHUTDOWN_MUSIC_FILE = "/system/media/shutdown.wav"; + private static final String OEM_SHUTDOWN_MUSIC_FILE = "/oem/media/shutdown.wav"; + + private boolean isShutdownMusicPlaying = false; private static AlertDialog sConfirmDialog; private ProgressDialog mProgressDialog; + private static AudioManager mAudioManager; private ShutdownThread() { } @@ -129,9 +155,20 @@ public final class ShutdownThread extends Thread { * @param confirm true if user confirmation is needed before shutting down. */ public static void shutdown(final Context context, boolean confirm) { + final Context uiContext = getUiContext(context); mReboot = false; mRebootSafeMode = false; - shutdownInner(context, confirm); + shutdownInner(uiContext, confirm); + } + + private static boolean isAdvancedRebootPossible(final Context context) { + KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + boolean keyguardLocked = km.inKeyguardRestrictedInputMode() && km.isKeyguardSecure(); + boolean advancedRebootEnabled = Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.ADVANCED_REBOOT, 0) == 1; + boolean isPrimaryUser = UserHandle.getCallingUserId() == UserHandle.USER_OWNER; + + return advancedRebootEnabled && !keyguardLocked && isPrimaryUser; } static void shutdownInner(final Context context, boolean confirm) { @@ -145,10 +182,19 @@ public final class ShutdownThread extends Thread { } boolean showRebootOption = false; - String[] defaultActions = context.getResources().getStringArray( - com.android.internal.R.array.config_globalActionsList); - for (int i = 0; i < defaultActions.length; i++) { - if (defaultActions[i].equals("reboot")) { + + String[] actionsArray; + String actions = Settings.Secure.getStringForUser(context.getContentResolver(), + Settings.Secure.POWER_MENU_ACTIONS, UserHandle.USER_CURRENT); + if (actions == null) { + actionsArray = context.getResources().getStringArray( + com.android.internal.R.array.config_globalActionsList); + } else { + actionsArray = actions.split("\\|"); + } + + for (int i = 0; i < actionsArray.length; i++) { + if (actionsArray[i].equals("reboot")) { showRebootOption = true; break; } @@ -168,23 +214,57 @@ public final class ShutdownThread extends Thread { if (confirm) { final CloseDialogReceiver closer = new CloseDialogReceiver(context); + final boolean advancedReboot = isAdvancedRebootPossible(context); + final Context uiContext = getUiContext(context); + if (sConfirmDialog != null) { sConfirmDialog.dismiss(); + sConfirmDialog = null; } - sConfirmDialog = new AlertDialog.Builder(context) + AlertDialog.Builder confirmDialogBuilder = new AlertDialog.Builder(uiContext) .setTitle(mRebootSafeMode ? com.android.internal.R.string.reboot_safemode_title : showRebootOption ? com.android.internal.R.string.reboot_title - : com.android.internal.R.string.power_off) - .setMessage(resourceId) - .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { + : com.android.internal.R.string.power_off); + + if (!advancedReboot || mRebootSafeMode) { + confirmDialogBuilder.setMessage(resourceId); + } else { + confirmDialogBuilder + .setSingleChoiceItems(com.android.internal.R.array.shutdown_reboot_options, + 0, null); + } + + confirmDialogBuilder.setPositiveButton(com.android.internal.R.string.yes, + new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { + if (!mRebootSafeMode && advancedReboot) { + boolean softReboot = false; + ListView reasonsList = ((AlertDialog)dialog).getListView(); + int selected = reasonsList.getCheckedItemPosition(); + if (selected != ListView.INVALID_POSITION) { + String actions[] = context.getResources().getStringArray( + com.android.internal.R.array.shutdown_reboot_actions); + if (selected >= 0 && selected < actions.length) { + mRebootReason = actions[selected]; + if (actions[selected].equals(SOFT_REBOOT)) { + doSoftReboot(); + return; + } + } + } + + mReboot = true; + } beginShutdownSequence(context); - } - }) - .setNegativeButton(com.android.internal.R.string.no, null) - .create(); + } + }); + + confirmDialogBuilder.setNegativeButton(com.android.internal.R.string.no, null); + sConfirmDialog = confirmDialogBuilder.create(); + closer.dialog = sConfirmDialog; sConfirmDialog.setOnDismissListener(closer); sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); @@ -194,6 +274,18 @@ public final class ShutdownThread extends Thread { } } + private static void doSoftReboot() { + try { + final IActivityManager am = + ActivityManagerNative.asInterface(ServiceManager.checkService("activity")); + if (am != null) { + am.restart(); + } + } catch (RemoteException e) { + Log.e(TAG, "failure trying to perform soft reboot", e); + } + } + private static class CloseDialogReceiver extends BroadcastReceiver implements DialogInterface.OnDismissListener { private Context mContext; @@ -225,13 +317,35 @@ public final class ShutdownThread extends Thread { * @param confirm true if user confirmation is needed before shutting down. */ public static void reboot(final Context context, String reason, boolean confirm) { + final Context uiContext = getUiContext(context); mReboot = true; mRebootSafeMode = false; mRebootUpdate = false; mRebootReason = reason; - shutdownInner(context, confirm); + shutdownInner(uiContext, confirm); + } + + private static String getShutdownMusicFilePath() { + final String[] fileName = {OEM_SHUTDOWN_MUSIC_FILE, SHUTDOWN_MUSIC_FILE}; + File checkFile = null; + for(String music : fileName) { + checkFile = new File(music); + if (checkFile.exists()) { + return music; + } + } + return null; } + private static void lockDevice() { + IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager + .getService(Context.WINDOW_SERVICE)); + try { + wm.updateRotation(false, false); + } catch (RemoteException e) { + Log.w(TAG, "boot animation can not lock device!"); + } + } /** * Request a reboot into safe mode. Must be called from a Looper thread in which its UI * is shown. @@ -295,14 +409,32 @@ public final class ShutdownThread extends Thread { pd.setIndeterminate(true); } } else { + if (mReboot) { + pd.setTitle(context.getText(com.android.internal.R.string.reboot_title)); + pd.setMessage(context.getText(com.android.internal.R.string.reboot_progress)); + } else { + pd.setTitle(context.getText(com.android.internal.R.string.power_off)); + pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); + } + pd.setTitle(context.getText(com.android.internal.R.string.power_off)); pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); pd.setIndeterminate(true); } - pd.setCancelable(false); - pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - pd.show(); + //acquire audio focus to make the other apps to stop playing muisc + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + mAudioManager.requestAudioFocus(null, + AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); + + if (!checkAnimationFileExist()) { + // throw up an indeterminate system dialog to indicate radio is + // shutting down. + pd.setCancelable(false); + pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + + pd.show(); + } sInstance.mProgressDialog = pd; sInstance.mContext = context; @@ -433,6 +565,40 @@ public final class ShutdownThread extends Thread { sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null); } + String shutDownFile = null; + + //showShutdownAnimation() is called from here to sync + //music and animation properly + if(checkAnimationFileExist()) { + lockDevice(); + showShutdownAnimation(); + + if (!isSilentMode() + && (shutDownFile = getShutdownMusicFilePath()) != null) { + isShutdownMusicPlaying = true; + shutdownMusicHandler.obtainMessage(0, shutDownFile).sendToTarget(); + } + } + + Log.i(TAG, "wait for shutdown music"); + final long endTimeForMusic = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME; + synchronized (mActionDoneSync) { + while (isShutdownMusicPlaying) { + long delay = endTimeForMusic - SystemClock.elapsedRealtime(); + if (delay <= 0) { + Log.w(TAG, "play shutdown music timeout!"); + break; + } + try { + mActionDoneSync.wait(delay); + } catch (InterruptedException e) { + } + } + if (!isShutdownMusicPlaying) { + Log.i(TAG, "play shutdown music complete."); + } + } + // Shutdown radios. shutdownRadios(MAX_RADIO_WAIT_TIME); if (mRebootUpdate) { @@ -523,11 +689,10 @@ public final class ShutdownThread extends Thread { ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); final IBluetoothManager bluetooth = IBluetoothManager.Stub.asInterface(ServiceManager.checkService( - BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE)); + BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE)); try { - nfcOff = nfc == null || - nfc.getState() == NfcAdapter.STATE_OFF; + nfcOff = nfc == null || nfc.getState() == NfcAdapter.STATE_OFF; if (!nfcOff) { Log.w(TAG, "Turning off NFC..."); nfc.disable(false); // Don't persist new state @@ -760,4 +925,65 @@ public final class ShutdownThread extends Thread { Log.e(TAG, "Unknown exception while trying to invoke rebootOrShutdown"); } } + + private static boolean checkAnimationFileExist() { + if (new File(OEM_BOOTANIMATION_FILE).exists() + || new File(SYSTEM_BOOTANIMATION_FILE).exists() + || new File(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE).exists()) + return true; + else + return false; + } + + private static boolean isSilentMode() { + return mAudioManager.isSilentMode(); + } + + private static void showShutdownAnimation() { + /* + * When boot completed, "service.bootanim.exit" property is set to 1. + * Bootanimation checks this property to stop showing the boot animation. + * Since we use the same code for shutdown animation, we + * need to reset this property to 0. If this is not set to 0 then shutdown + * will stop and exit after displaying the first frame of the animation + */ + SystemProperties.set("service.bootanim.exit", "0"); + + SystemProperties.set("ctl.start", "bootanim"); + } + + private Handler shutdownMusicHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + String path = (String) msg.obj; + mMediaPlayer = new MediaPlayer(); + try + { + mMediaPlayer.reset(); + mMediaPlayer.setDataSource(path); + mMediaPlayer.prepare(); + mMediaPlayer.start(); + mMediaPlayer.setOnCompletionListener(new OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + synchronized (mActionDoneSync) { + isShutdownMusicPlaying = false; + mActionDoneSync.notifyAll(); + } + } + }); + } catch (IOException e) { + Log.d(TAG, "play shutdown music error:" + e); + } + } + }; + + private static Context getUiContext(Context context) { + Context uiContext = null; + if (context != null) { + uiContext = ThemeUtils.createUiContext(context); + uiContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar); + } + return uiContext != null ? uiContext : context; + } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 7784884..ee926a4 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -106,6 +106,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { static final int MAX_WALLPAPER_COMPONENT_LOG_LENGTH = 128; static final String WALLPAPER = "wallpaper"; static final String WALLPAPER_INFO = "wallpaper_info.xml"; + static final String KEYGUARD_WALLPAPER = "keyguard_wallpaper"; + static final String KEYGUARD_WALLPAPER_INFO = "keyguard_wallpaper_info.xml"; /** * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks @@ -116,17 +118,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { private class WallpaperObserver extends FileObserver { final WallpaperData mWallpaper; + final KeyguardWallpaperData mKeyguardWallpaper; final File mWallpaperDir; final File mWallpaperFile; final File mWallpaperInfoFile; + final File mKeyguardWallpaperFile; - public WallpaperObserver(WallpaperData wallpaper) { + public WallpaperObserver(WallpaperData wallpaper, KeyguardWallpaperData keyguardWallpaper) { super(getWallpaperDir(wallpaper.userId).getAbsolutePath(), CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF); mWallpaperDir = getWallpaperDir(wallpaper.userId); mWallpaper = wallpaper; mWallpaperFile = new File(mWallpaperDir, WALLPAPER); mWallpaperInfoFile = new File(mWallpaperDir, WALLPAPER_INFO); + mKeyguardWallpaper = keyguardWallpaper; + mKeyguardWallpaperFile = new File(mWallpaperDir, KEYGUARD_WALLPAPER); } @Override @@ -145,6 +151,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { Binder.restoreCallingIdentity(origId); } if (mWallpaperFile.equals(changedFile)) { + // changing the wallpaper means we'll need to back up the new one + long origId = Binder.clearCallingIdentity(); + BackupManager bm = new BackupManager(mContext); + bm.dataChanged(); + Binder.restoreCallingIdentity(origId); + notifyCallbacksLocked(mWallpaper); final boolean written = (event == CLOSE_WRITE || event == MOVED_TO); if (mWallpaper.wallpaperComponent == null @@ -157,6 +169,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { false, mWallpaper, null); saveSettingsLocked(mWallpaper); } + } else if (mKeyguardWallpaperFile.equals(changedFile)) { + notifyCallbacksLocked(mKeyguardWallpaper); + if (event == CLOSE_WRITE + || mKeyguardWallpaper.imageWallpaperPending) { + mKeyguardWallpaper.imageWallpaperPending = false; + saveSettingsLocked(mKeyguardWallpaper); + } } } } @@ -176,6 +195,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { final ComponentName mImageWallpaper; SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>(); + SparseArray<KeyguardWallpaperData> mKeyguardWallpaperMap + = new SparseArray<KeyguardWallpaperData>(); int mCurrentUserId; @@ -227,6 +248,37 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + static class KeyguardWallpaperData { + + int userId; + + File wallpaperFile; + + /** + * Client is currently writing a new image wallpaper. + */ + boolean imageWallpaperPending; + + /** + * Resource name if using a picture from the wallpaper gallery + */ + String name = ""; + + /** + * List of callbacks registered they should each be notified when the wallpaper is changed. + */ + private RemoteCallbackList<IWallpaperManagerCallback> callbacks + = new RemoteCallbackList<IWallpaperManagerCallback>(); + + int width = -1; + int height = -1; + + KeyguardWallpaperData(int userId) { + this.userId = userId; + wallpaperFile = new File(getWallpaperDir(userId), KEYGUARD_WALLPAPER); + } + } + class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection { final WallpaperInfo mInfo; @@ -486,6 +538,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { mMonitor.register(context, null, UserHandle.ALL, true); getWallpaperDir(UserHandle.USER_OWNER).mkdirs(); loadSettingsLocked(UserHandle.USER_OWNER); + loadKeyguardSettingsLocked(UserHandle.USER_OWNER); } private static File getWallpaperDir(int userId) { @@ -504,8 +557,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { public void systemRunning() { if (DEBUG) Slog.v(TAG, "systemReady"); WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER); + KeyguardWallpaperData keyguardWallpaper = mKeyguardWallpaperMap.get(UserHandle.USER_OWNER); switchWallpaper(wallpaper, null); - wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper); + wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper, keyguardWallpaper); wallpaper.wallpaperObserver.startWatching(); IntentFilter userFilter = new IntentFilter(); @@ -572,6 +626,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { wallpaper.wallpaperObserver = null; } mWallpaperMap.remove(userId); + mKeyguardWallpaperMap.remove(userId); } } } @@ -584,6 +639,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { wallpaperFile.delete(); File wallpaperInfoFile = new File(getWallpaperDir(userId), WALLPAPER_INFO); wallpaperInfoFile.delete(); + File keyguardWallpaperFile = new File(getWallpaperDir(userId), KEYGUARD_WALLPAPER); + keyguardWallpaperFile.delete(); + File keyguardWallpaperInfoFile = new File(getWallpaperDir(userId), + KEYGUARD_WALLPAPER_INFO); + keyguardWallpaperInfoFile.delete(); } } @@ -591,9 +651,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { synchronized (mLock) { mCurrentUserId = userId; WallpaperData wallpaper = getWallpaperSafeLocked(userId); + KeyguardWallpaperData keyguardWallpaper = getKeyguardWallpaperSafeLocked(userId); // Not started watching yet, in case wallpaper data was loaded for other reasons. if (wallpaper.wallpaperObserver == null) { - wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper); + wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper, keyguardWallpaper); wallpaper.wallpaperObserver.startWatching(); } switchWallpaper(wallpaper, reply); @@ -669,6 +730,40 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + /** + * @hide + */ + public void clearKeyguardWallpaper() { + if (DEBUG) Slog.v(TAG, "clearWallpaper"); + synchronized (mLock) { + clearKeyguardWallpaperLocked(UserHandle.getCallingUserId(), null); + } + } + + void clearKeyguardWallpaperLocked(int userId, IRemoteCallback reply) { + KeyguardWallpaperData wallpaper = mKeyguardWallpaperMap.get(userId); + final long ident = Binder.clearCallingIdentity(); + wallpaper.imageWallpaperPending = false; + wallpaper.height = -1; + wallpaper.width = -1; + wallpaper.name = ""; + + File f = new File(getWallpaperDir(userId), KEYGUARD_WALLPAPER); + if (f.exists()) { + f.delete(); + } + if (userId != mCurrentUserId) + return; + Binder.restoreCallingIdentity(ident); + + if (reply != null) { + try { + reply.sendResult(null); + } catch (RemoteException e1) { + } + } + } + public boolean hasNamedWallpaper(String name) { synchronized (mLock) { List<UserInfo> users; @@ -837,6 +932,31 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + /** @hide */ + public ParcelFileDescriptor getKeyguardWallpaper(IWallpaperManagerCallback cb, + Bundle outParams) { + synchronized (mLock) { + int wallpaperUserId = mCurrentUserId; + KeyguardWallpaperData wallpaper = mKeyguardWallpaperMap.get(wallpaperUserId); + try { + if (outParams != null) { + outParams.putInt("width", wallpaper.width); + outParams.putInt("height", wallpaper.height); + } + wallpaper.callbacks.register(cb); + File f = new File(getWallpaperDir(wallpaperUserId), KEYGUARD_WALLPAPER); + if (!f.exists()) { + return null; + } + return ParcelFileDescriptor.open(f, MODE_READ_ONLY); + } catch (FileNotFoundException e) { + /* Shouldn't happen as we check to see if the file exists */ + Slog.w(TAG, "Error getting wallpaper", e); + } + return null; + } + } + public WallpaperInfo getWallpaperInfo() { int userId = UserHandle.getCallingUserId(); synchronized (mLock) { @@ -848,6 +968,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + /** @hide */ + public boolean isKeyguardWallpaperSet() { + int userId = UserHandle.getCallingUserId(); + synchronized (mLock) { + KeyguardWallpaperData data = mKeyguardWallpaperMap.get(userId); + return data.wallpaperFile.exists(); + } + } + public ParcelFileDescriptor setWallpaper(String name, String callingPackage) { checkPermission(android.Manifest.permission.SET_WALLPAPER); if (!isWallpaperSupported(callingPackage)) { @@ -901,6 +1030,58 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + public ParcelFileDescriptor setKeyguardWallpaper(String name, String callingPackage) { + checkPermission(android.Manifest.permission.SET_KEYGUARD_WALLPAPER); + if (!isWallpaperSupported(callingPackage)) { + return null; + } + synchronized (mLock) { + if (DEBUG) Slog.v(TAG, "setKeyguardWallpaper"); + int userId = UserHandle.getCallingUserId(); + KeyguardWallpaperData wallpaper = mKeyguardWallpaperMap.get(userId); + if (wallpaper == null) { + throw new IllegalStateException("Keyguard wallpaper not yet initialized for user " + + userId); + } + final long ident = Binder.clearCallingIdentity(); + try { + ParcelFileDescriptor pfd = updateKeyguardWallpaperBitmapLocked(name, wallpaper); + if (pfd != null) { + wallpaper.imageWallpaperPending = true; + } + return pfd; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public ParcelFileDescriptor updateKeyguardWallpaperBitmapLocked(String name, + KeyguardWallpaperData wallpaper) { + if (name == null) name = ""; + try { + File dir = getWallpaperDir(wallpaper.userId); + if (!dir.exists()) { + dir.mkdir(); + FileUtils.setPermissions( + dir.getPath(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, + -1, -1); + } + File file = new File(dir, KEYGUARD_WALLPAPER); + ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, + MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE); + if (!SELinux.restorecon(file)) { + return null; + } + wallpaper.name = name; + return fd; + } catch (FileNotFoundException e) { + Slog.w(TAG, "Error setting wallpaper", e); + } + return null; + } + // ToDo: Remove this version of the function public void setWallpaperComponent(ComponentName name) { checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT); @@ -1125,6 +1306,22 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId)); } + private void notifyCallbacksLocked(KeyguardWallpaperData wallpaper) { + final int n = wallpaper.callbacks.beginBroadcast(); + for (int i = 0; i < n; i++) { + try { + wallpaper.callbacks.getBroadcastItem(i).onKeyguardWallpaperChanged(); + } catch (RemoteException e) { + + // The RemoteCallbackList will take care of removing + // the dead object for us. + } + } + wallpaper.callbacks.finishBroadcast(); + final Intent intent = new Intent(Intent.ACTION_KEYGUARD_WALLPAPER_CHANGED); + mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId)); + } + private void checkPermission(String permission) { if (PackageManager.PERMISSION_GRANTED!= mContext.checkCallingOrSelfPermission(permission)) { throw new SecurityException("Access denied to process: " + Binder.getCallingPid() @@ -1142,7 +1339,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } private static JournaledFile makeJournaledFile(int userId) { - final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath(); + return makeJournaledFile(WALLPAPER_INFO, userId); + } + + private static JournaledFile makeJournaledFile(String name, int userId) { + final String base = new File(getWallpaperDir(userId), name).getAbsolutePath(); return new JournaledFile(new File(base), new File(base + ".tmp")); } @@ -1195,6 +1396,36 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + private void saveSettingsLocked(KeyguardWallpaperData wallpaper) { + JournaledFile journal = makeJournaledFile(KEYGUARD_WALLPAPER_INFO, wallpaper.userId); + FileOutputStream stream = null; + try { + stream = new FileOutputStream(journal.chooseForWrite(), false); + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(stream, "utf-8"); + out.startDocument(null, true); + + out.startTag(null, "kwp"); + out.attribute(null, "width", Integer.toString(wallpaper.width)); + out.attribute(null, "height", Integer.toString(wallpaper.height)); + out.attribute(null, "name", wallpaper.name); + out.endTag(null, "kwp"); + + out.endDocument(); + stream.close(); + journal.commit(); + } catch (IOException e) { + try { + if (stream != null) { + stream.close(); + } + } catch (IOException ex) { + // Ignore + } + journal.rollback(); + } + } + private void migrateFromOld() { File oldWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY); File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY); @@ -1232,6 +1463,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { return wallpaper; } + private KeyguardWallpaperData getKeyguardWallpaperSafeLocked(int userId) { + KeyguardWallpaperData wallpaper = mKeyguardWallpaperMap.get(userId); + if (wallpaper == null) { + loadKeyguardSettingsLocked(userId); + wallpaper = mKeyguardWallpaperMap.get(userId); + } + return wallpaper; + } + + private void loadSettingsLocked(int userId) { if (DEBUG) Slog.v(TAG, "loadSettingsLocked"); @@ -1326,6 +1567,80 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + private void loadKeyguardSettingsLocked(int userId) { + if (DEBUG) Slog.v(TAG, "loadKeyguardSettingsLocked"); + + JournaledFile journal = makeJournaledFile(KEYGUARD_WALLPAPER_INFO, userId); + FileInputStream stream = null; + File file = journal.chooseForRead(); + KeyguardWallpaperData keyguardWallpaper = mKeyguardWallpaperMap.get(userId); + if (keyguardWallpaper == null) { + keyguardWallpaper = new KeyguardWallpaperData(userId); + mKeyguardWallpaperMap.put(userId, keyguardWallpaper); + } + boolean success = false; + try { + stream = new FileInputStream(file); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, null); + + int type; + do { + type = parser.next(); + if (type == XmlPullParser.START_TAG) { + String tag = parser.getName(); + if ("kwp".equals(tag)) { + keyguardWallpaper.width = Integer.parseInt(parser.getAttributeValue(null, + "width")); + keyguardWallpaper.height = Integer.parseInt(parser + .getAttributeValue(null, "height")); + keyguardWallpaper.name = parser.getAttributeValue(null, "name"); + if (DEBUG) { + Slog.v(TAG, "mWidth:" + keyguardWallpaper.width); + Slog.v(TAG, "mHeight:" + keyguardWallpaper.height); + Slog.v(TAG, "mName:" + keyguardWallpaper.name); + } + } + } + } while (type != XmlPullParser.END_DOCUMENT); + success = true; + } catch (FileNotFoundException e) { + Slog.w(TAG, "no current wallpaper -- first boot?"); + } catch (NullPointerException e) { + Slog.w(TAG, "failed parsing " + file + " " + e); + } catch (NumberFormatException e) { + Slog.w(TAG, "failed parsing " + file + " " + e); + } catch (XmlPullParserException e) { + Slog.w(TAG, "failed parsing " + file + " " + e); + } catch (IOException e) { + Slog.w(TAG, "failed parsing " + file + " " + e); + } catch (IndexOutOfBoundsException e) { + Slog.w(TAG, "failed parsing " + file + " " + e); + } + try { + if (stream != null) { + stream.close(); + } + } catch (IOException e) { + // Ignore + } + + if (!success) { + keyguardWallpaper.width = -1; + keyguardWallpaper.height = -1; + keyguardWallpaper.name = ""; + } + + // We always want to have some reasonable width hint. + int baseSize = getMaximumSizeDimension(); + if (keyguardWallpaper.width < baseSize) { + keyguardWallpaper.width = baseSize; + } + if (keyguardWallpaper.height < baseSize) { + keyguardWallpaper.height = baseSize; + } + } + private int getMaximumSizeDimension() { WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); Display d = wm.getDefaultDisplay(); diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index a8ba0f9..f0793b8 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -429,6 +429,34 @@ final class Session extends IWindowSession.Stub } } + /** + * @hide + */ + public int getLastWallpaperX() { + synchronized(mService.mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + return mService.getLastWallpaperX(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + /** + * @hide + */ + public int getLastWallpaperY() { + synchronized(mService.mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + return mService.getLastWallpaperY(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, int z, Bundle extras, boolean sync) { synchronized(mService.mWindowMap) { diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java index e226e3d..3ae783e 100644 --- a/services/core/java/com/android/server/wm/Watermark.java +++ b/services/core/java/com/android/server/wm/Watermark.java @@ -23,6 +23,7 @@ import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.Paint.FontMetricsInt; +import android.os.SystemProperties; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; @@ -76,6 +77,14 @@ class Watermark { else c2 -= '0'; builder.append((char)(255-((c1*16)+c2))); } + + int appendDisplayVersion = (WindowManagerService.getPropertyInt(tokens, 10, + TypedValue.COMPLEX_UNIT_PX, 0, dm)); + if (appendDisplayVersion != 0) { + builder.append(" - "); + builder.append(SystemProperties.get("ro.cm.display.version")); + } + mText = builder.toString(); if (false) { Log.i(WindowManagerService.TAG, "Final text: " + mText); @@ -149,27 +158,32 @@ class Watermark { if (c != null) { c.drawColor(0, PorterDuff.Mode.CLEAR); - int deltaX = mDeltaX; - int deltaY = mDeltaY; - - // deltaX shouldn't be close to a round fraction of our - // x step, or else things will line up too much. - int div = (dw+mTextWidth)/deltaX; - int rem = (dw+mTextWidth) - (div*deltaX); - int qdelta = deltaX/4; - if (rem < qdelta || rem > (deltaX-qdelta)) { - deltaX += deltaX/3; - } + if (mDeltaX != 0 || mDeltaY != 0) { + int deltaX = mDeltaX; + int deltaY = mDeltaY; + + // deltaX shouldn't be close to a round fraction of our + // x step, or else things will line up too much. + int div = (dw+mTextWidth)/deltaX; + int rem = (dw+mTextWidth) - (div*deltaX); + int qdelta = deltaX/4; + if (rem < qdelta || rem > (deltaX-qdelta)) { + deltaX += deltaX/3; + } - int y = -mTextHeight; - int x = -mTextWidth; - while (y < (dh+mTextHeight)) { - c.drawText(mText, x, y, mTextPaint); - x += deltaX; - if (x >= dw) { - x -= (dw+mTextWidth); - y += deltaY; + int y = -mTextHeight; + int x = -mTextWidth; + while (y < (dh+mTextHeight)) { + c.drawText(mText, x, y, mTextPaint); + x += deltaX; + if (x >= dw) { + x -= (dw+mTextWidth); + y += deltaY; + } } + } else { + c.drawText(mText, dw - mTextWidth, + dh - mTextHeight*4, mTextPaint); } mSurface.unlockCanvasAndPost(c); } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 85a9624..c5cd729 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -92,6 +92,7 @@ public class WindowAnimator { boolean mKeyguardGoingAway; boolean mKeyguardGoingAwayToNotificationShade; boolean mKeyguardGoingAwayDisableWindowAnimations; + boolean mKeyguardGoingAwayShowingMedia; /** Use one animation for all entering activities after keyguard is dismissed. */ Animation mPostKeyguardExitAnimation; @@ -486,7 +487,7 @@ public class WindowAnimator { && !mKeyguardGoingAwayDisableWindowAnimations) { if (DEBUG_KEYGUARD) Slog.d(TAG, "updateWindowsLocked: wallpaper animating away"); Animation a = mPolicy.createForceHideWallpaperExitAnimation( - mKeyguardGoingAwayToNotificationShade); + mKeyguardGoingAwayToNotificationShade, mKeyguardGoingAwayShowingMedia); if (a != null) { wallpaper.mWinAnimator.setAnimation(a); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8a0a7c8..867292a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -298,6 +298,7 @@ public class WindowManagerService extends IWindowManager.Stub private static final String SYSTEM_SECURE = "ro.secure"; private static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; + private static final String PERSIST_SYS_LCD_DENSITY = "persist.sys.lcd_density"; private static final String DENSITY_OVERRIDE = "ro.config.density_override"; private static final String SIZE_OVERRIDE = "ro.config.size_override"; @@ -2145,6 +2146,36 @@ public class WindowManagerService extends IWindowManager.Stub } } + public int getLastWallpaperX() { + int curTokenIndex = mWallpaperTokens.size(); + while (curTokenIndex > 0) { + curTokenIndex--; + WindowToken token = mWallpaperTokens.get(curTokenIndex); + int curWallpaperIndex = token.windows.size(); + while (curWallpaperIndex > 0) { + curWallpaperIndex--; + WindowState wallpaperWin = token.windows.get(curWallpaperIndex); + return wallpaperWin.mXOffset; + } + } + return -1; + } + + public int getLastWallpaperY() { + int curTokenIndex = mWallpaperTokens.size(); + while (curTokenIndex > 0) { + curTokenIndex--; + WindowToken token = mWallpaperTokens.get(curTokenIndex); + int curWallpaperIndex = token.windows.size(); + while (curWallpaperIndex > 0) { + curWallpaperIndex--; + WindowState wallpaperWin = token.windows.get(curWallpaperIndex); + return wallpaperWin.mYOffset; + } + } + return -1; + } + boolean updateWallpaperOffsetLocked(WindowState wallpaperWin, int dw, int dh, boolean sync) { boolean changed = false; @@ -5466,7 +5497,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void keyguardGoingAway(boolean disableWindowAnimations, - boolean keyguardGoingToNotificationShade) { + boolean keyguardGoingToNotificationShade, boolean keyguardShowingMedia) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires DISABLE_KEYGUARD permission"); @@ -5477,6 +5508,7 @@ public class WindowManagerService extends IWindowManager.Stub mAnimator.mKeyguardGoingAway = true; mAnimator.mKeyguardGoingAwayToNotificationShade = keyguardGoingToNotificationShade; mAnimator.mKeyguardGoingAwayDisableWindowAnimations = disableWindowAnimations; + mAnimator.mKeyguardGoingAwayShowingMedia = keyguardShowingMedia; requestTraversalLocked(); } } @@ -5777,7 +5809,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean wallpaperEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_enableWallpaperService) && !mOnlyCore; - boolean haveKeyguard = true; + boolean haveKeyguard = false; // TODO(multidisplay): Expand to all displays? final WindowList windows = getDefaultWindowListLocked(); final int N = windows.size(); @@ -8558,6 +8590,9 @@ public class WindowManagerService extends IWindowManager.Stub @Override public int getInitialDisplayDensity(int displayId) { + if (displayId == Display.DEFAULT_DISPLAY) { + return DisplayMetrics.DENSITY_DEVICE_DEFAULT; + } synchronized (mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { @@ -8571,6 +8606,9 @@ public class WindowManagerService extends IWindowManager.Stub @Override public int getBaseDisplayDensity(int displayId) { + if (displayId == Display.DEFAULT_DISPLAY) { + return DisplayMetrics.DENSITY_PREFERRED; + } synchronized (mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { @@ -8598,6 +8636,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent != null) { + SystemProperties.set(PERSIST_SYS_LCD_DENSITY, Integer.toString(density)); setForcedDisplayDensityLocked(displayContent, density); Settings.Global.putString(mContext.getContentResolver(), Settings.Global.DISPLAY_DENSITY_FORCED, Integer.toString(density)); @@ -8606,6 +8645,10 @@ public class WindowManagerService extends IWindowManager.Stub } finally { Binder.restoreCallingIdentity(ident); } + try { + ActivityManagerNative.getDefault().restart(); + } catch (RemoteException e) { + } } // displayContent must not be null @@ -8634,6 +8677,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent != null) { + SystemProperties.set(PERSIST_SYS_LCD_DENSITY, null); setForcedDisplayDensityLocked(displayContent, displayContent.mInitialDisplayDensity); Settings.Global.putString(mContext.getContentResolver(), @@ -8643,6 +8687,10 @@ public class WindowManagerService extends IWindowManager.Stub } finally { Binder.restoreCallingIdentity(ident); } + try { + ActivityManagerNative.getDefault().restart(); + } catch (RemoteException e) { + } } // displayContent must not be null @@ -11097,6 +11145,16 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public boolean hasPermanentMenuKey() { + return mPolicy.hasPermanentMenuKey(); + } + + @Override + public boolean needsNavigationBar() { + return mPolicy.needsNavigationBar(); + } + + @Override public void lockNow(Bundle options) { mPolicy.lockNow(options); } |