Implement issue #16330060: Inform ActivityManager about WebView...

...state changes.

Add a new API to tell the activity manager about a new dependency
one process has on another package.  Start using it already for
when apps is Context.createPackageContext() to load code from another
app.

Also do some work on getting the monitoring of proc/uid states
in shape so it can be used by unundled code, along with an
AppImportanceMonitor class for doing so.

Some small fixes and additions to VoiceInteractionService.

Improve handling of unaccounted/overcounted battery use so that
they aren't shown to the user unless they are significant.

Change-Id: I22dd79a73f4e70103d3f8964494aebc8a31f971c
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c8cab6f..d2540f1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1819,7 +1819,29 @@
          * actively running code.
          */
         public static final int IMPORTANCE_EMPTY = 500;
-        
+
+        /**
+         * Constant for {@link #importance}: this process does not exist.
+         */
+        public static final int IMPORTANCE_GONE = 1000;
+
+        /** @hide */
+        public static int procStateToImportance(int procState) {
+            if (procState >= ActivityManager.PROCESS_STATE_HOME) {
+                return ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
+            } else if (procState >= ActivityManager.PROCESS_STATE_SERVICE) {
+                return ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
+            } else if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+                return ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE;
+            } else if (procState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
+                return ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE;
+            } else if (procState >= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                return ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+            } else {
+                return ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+            }
+        }
+
         /**
          * The relative importance level that the system places on this
          * process.  May be one of {@link #IMPORTANCE_FOREGROUND},
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index e2b5a84..fb70098 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1465,7 +1465,15 @@
             reply.writeNoException();
             return true;
         }
-        
+
+        case ADD_PACKAGE_DEPENDENCY_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            String packageName = data.readString();
+            addPackageDependency(packageName);
+            reply.writeNoException();
+            return true;
+        }
+
         case KILL_APPLICATION_WITH_APPID_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             String pkg = data.readString();
@@ -1475,7 +1483,7 @@
             reply.writeNoException();
             return true;
         }
-        
+
         case CLOSE_SYSTEM_DIALOGS_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             String reason = data.readString();
@@ -4050,7 +4058,18 @@
         reply.recycle();
         data.recycle();
     }
-    
+
+    public void addPackageDependency(String packageName) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(packageName);
+        mRemote.transact(ADD_PACKAGE_DEPENDENCY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     public void killApplicationWithAppId(String pkg, int appid, String reason)
             throws RemoteException {
         Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7c8c83a..15f3a75 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1684,6 +1684,7 @@
                 && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
                         ? !UserHandle.isSameApp(ai.uid, mBoundApplication.appInfo.uid)
                         : true);
+        boolean registerPackage = includeCode && (flags&Context.CONTEXT_REGISTER_PACKAGE) != 0;
         if ((flags&(Context.CONTEXT_INCLUDE_CODE
                 |Context.CONTEXT_IGNORE_SECURITY))
                 == Context.CONTEXT_INCLUDE_CODE) {
@@ -1698,12 +1699,13 @@
                 throw new SecurityException(msg);
             }
         }
-        return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode);
+        return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode,
+                registerPackage);
     }
 
     public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
             CompatibilityInfo compatInfo) {
-        return getPackageInfo(ai, compatInfo, null, false, true);
+        return getPackageInfo(ai, compatInfo, null, false, true, false);
     }
 
     public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) {
@@ -1719,7 +1721,8 @@
     }
 
     private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
-            ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
+            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
+            boolean registerPackage) {
         synchronized (mResourcesManager) {
             WeakReference<LoadedApk> ref;
             if (includeCode) {
@@ -1738,7 +1741,7 @@
                 packageInfo =
                     new LoadedApk(this, aInfo, compatInfo, baseLoader,
                             securityViolation, includeCode &&
-                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
+                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
                 if (includeCode) {
                     mPackages.put(aInfo.packageName,
                             new WeakReference<LoadedApk>(packageInfo));
@@ -4394,7 +4397,7 @@
             instrApp.dataDir = ii.dataDir;
             instrApp.nativeLibraryDir = ii.nativeLibraryDir;
             LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
-                    appContext.getClassLoader(), false, true);
+                    appContext.getClassLoader(), false, true, false);
             ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
 
             try {
diff --git a/core/java/android/app/AppImportanceMonitor.java b/core/java/android/app/AppImportanceMonitor.java
new file mode 100644
index 0000000..c760e1e
--- /dev/null
+++ b/core/java/android/app/AppImportanceMonitor.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2014 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 android.app;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import java.util.List;
+
+/**
+ * Helper for monitoring the current importance of applications.
+ * @hide
+ */
+public class AppImportanceMonitor {
+    final Context mContext;
+
+    final SparseArray<AppEntry> mApps = new SparseArray<>();
+
+    static class AppEntry {
+        final int uid;
+        final SparseArray<Integer> procs = new SparseArray<>(1);
+        int importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+
+        AppEntry(int _uid) {
+            uid = _uid;
+        }
+    }
+
+    final IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
+        @Override
+        public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
+        }
+
+        @Override
+        public void onProcessStateChanged(int pid, int uid, int procState) {
+            synchronized (mApps) {
+                updateImportanceLocked(pid, uid,
+                        ActivityManager.RunningAppProcessInfo.procStateToImportance(procState),
+                        true);
+            }
+        }
+
+        @Override
+        public void onProcessDied(int pid, int uid) {
+            synchronized (mApps) {
+                updateImportanceLocked(pid, uid,
+                        ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, true);
+            }
+        }
+    };
+
+    static final int MSG_UPDATE = 1;
+
+    final Handler mHandler;
+
+    public AppImportanceMonitor(Context context, Looper looper) {
+        mContext = context;
+        mHandler = new Handler(looper) {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_UPDATE:
+                        onImportanceChanged(msg.arg1, msg.arg2&0xffff, msg.arg2>>16);
+                        break;
+                    default:
+                        super.handleMessage(msg);
+                }
+            }
+        };
+        ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
+        try {
+            ActivityManagerNative.getDefault().registerProcessObserver(mProcessObserver);
+        } catch (RemoteException e) {
+        }
+        List<ActivityManager.RunningAppProcessInfo> apps = am.getRunningAppProcesses();
+        if (apps != null) {
+            for (int i=0; i<apps.size(); i++) {
+                ActivityManager.RunningAppProcessInfo app = apps.get(i);
+                updateImportanceLocked(app.uid, app.pid, app.importance, false);
+            }
+        }
+    }
+
+    public int getImportance(int uid) {
+        AppEntry ent = mApps.get(uid);
+        if (ent == null) {
+            return ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+        }
+        return ent.importance;
+    }
+
+    /**
+     * Report when an app's importance changed. Called on looper given to constructor.
+     */
+    public void onImportanceChanged(int uid, int importance, int oldImportance) {
+    }
+
+    void updateImportanceLocked(int uid, int pid, int importance, boolean repChange) {
+        AppEntry ent = mApps.get(uid);
+        if (ent == null) {
+            ent = new AppEntry(uid);
+            mApps.put(uid, ent);
+        }
+        if (importance >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE) {
+            ent.procs.remove(pid);
+        } else {
+            ent.procs.put(pid, importance);
+        }
+        updateImportanceLocked(ent, repChange);
+    }
+
+    void updateImportanceLocked(AppEntry ent, boolean repChange) {
+        int appImp = ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+        for (int i=0; i<ent.procs.size(); i++) {
+            int procImp = ent.procs.valueAt(i);
+            if (procImp < appImp) {
+                appImp = procImp;
+            }
+        }
+        if (appImp != ent.importance) {
+            int impCode = appImp | (ent.importance<<16);
+            ent.importance = appImp;
+            if (appImp >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE) {
+                mApps.remove(ent.uid);
+            }
+            if (repChange) {
+                mHandler.obtainMessage(MSG_UPDATE, ent.uid, impCode).sendToTarget();
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d7c384a..cbfde14 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2097,7 +2097,7 @@
         }
 
         LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
-                flags, user.getIdentifier());
+                flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
         if (pi != null) {
             ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
                     user, restricted, mDisplay, mOverrideConfiguration);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index c6921a2..ac29161 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -294,7 +294,9 @@
     
     public void stopAppSwitches() throws RemoteException;
     public void resumeAppSwitches() throws RemoteException;
-    
+
+    public void addPackageDependency(String packageName) throws RemoteException;
+
     public void killApplicationWithAppId(String pkg, int appid, String reason)
             throws RemoteException;
     
@@ -640,7 +642,7 @@
     int UNBIND_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+91;
     int GET_UID_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+92;
     int HANDLE_INCOMING_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+93;
-    int ___AVAILABLE_2___ = IBinder.FIRST_CALL_TRANSACTION+94;
+    int ADD_PACKAGE_DEPENDENCY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+94;
     int KILL_APPLICATION_WITH_APPID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+95;
     int CLOSE_SYSTEM_DIALOGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+96;
     int GET_PROCESS_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+97;
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 38614a0..24c2835 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -93,6 +93,7 @@
     private final ClassLoader mBaseClassLoader;
     private final boolean mSecurityViolation;
     private final boolean mIncludeCode;
+    private final boolean mRegisterPackage;
     private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
     Resources mResources;
     private ClassLoader mClassLoader;
@@ -121,7 +122,7 @@
      */
     public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
             CompatibilityInfo compatInfo, ClassLoader baseLoader,
-            boolean securityViolation, boolean includeCode) {
+            boolean securityViolation, boolean includeCode, boolean registerPackage) {
         final int myUid = Process.myUid();
         aInfo = adjustNativeLibraryPaths(aInfo);
 
@@ -144,6 +145,7 @@
         mBaseClassLoader = baseLoader;
         mSecurityViolation = securityViolation;
         mIncludeCode = includeCode;
+        mRegisterPackage = registerPackage;
         mDisplayAdjustments.setCompatibilityInfo(compatInfo);
     }
 
@@ -189,6 +191,7 @@
         mBaseClassLoader = null;
         mSecurityViolation = false;
         mIncludeCode = true;
+        mRegisterPackage = false;
         mClassLoader = ClassLoader.getSystemClassLoader();
         mResources = Resources.getSystem();
     }
@@ -272,6 +275,13 @@
                 final ArrayList<String> zipPaths = new ArrayList<>();
                 final ArrayList<String> libPaths = new ArrayList<>();
 
+                if (mRegisterPackage) {
+                    try {
+                        ActivityManagerNative.getDefault().addPackageDependency(mPackageName);
+                    } catch (RemoteException e) {
+                    }
+                }
+
                 zipPaths.add(mAppDir);
                 if (mSplitAppDirs != null) {
                     Collections.addAll(zipPaths, mSplitAppDirs);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7d15233..1dd018f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2475,11 +2475,11 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
-     * android.net.ethernet.EthernetManager} for handling management of
+     * android.net.EthernetManager} for handling management of
      * Ethernet access.
      *
      * @see #getSystemService
-     * @see android.net.ethernet.EthernetManager
+     * @see android.net.EthernetManager
      *
      * @hide
      */
@@ -3251,6 +3251,12 @@
     public static final int CONTEXT_RESTRICTED = 0x00000004;
 
     /**
+     * @hide Used to indicate we should tell the activity manager about the process
+     * loading this code.
+     */
+    public static final int CONTEXT_REGISTER_PACKAGE = 0x40000000;
+
+    /**
      * Return a new Context object for the given application name.  This
      * Context is the same as what the named application gets when it is
      * launched, containing the same resources and class loader.  Each call to
diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl
index e9e2f4c..c9915a2 100644
--- a/core/java/android/service/voice/IVoiceInteractionService.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionService.aidl
@@ -20,4 +20,5 @@
  * @hide
  */
 oneway interface IVoiceInteractionService {
+    void ready();
 }
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index a9b1959..1f5d327 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -18,15 +18,19 @@
 
 import android.annotation.SdkConstant;
 import android.app.Service;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
 import android.hardware.soundtrigger.SoundTriggerHelper;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
+import android.provider.Settings;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractionManagerService;
 
@@ -64,14 +68,58 @@
     public static final String SERVICE_META_DATA = "android.voice_interaction";
 
     IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
+        @Override public void ready() {
+            mHandler.sendEmptyMessage(MSG_READY);
+        }
     };
 
+    MyHandler mHandler;
+
     IVoiceInteractionManagerService mSystemService;
 
     private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
     private SoundTriggerHelper mSoundTriggerHelper;
 
+    static final int MSG_READY = 1;
+
+    class MyHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_READY:
+                    onReady();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Check whether the given service component is the currently active
+     * VoiceInteractionService.
+     */
+    public static boolean isActiveService(Context context, ComponentName service) {
+        String cur = Settings.Secure.getString(context.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE);
+        if (cur == null || cur.isEmpty()) {
+            return false;
+        }
+        ComponentName curComp = ComponentName.unflattenFromString(cur);
+        if (curComp == null) {
+            return false;
+        }
+        return curComp.equals(cur);
+    }
+
+    /**
+     * Initiate the execution of a new {@link android.service.voice.VoiceInteractionSession}.
+     * @param args Arbitrary arguments that will be propagated to the session.
+     */
     public void startSession(Bundle args) {
+        if (mSystemService == null) {
+            throw new IllegalStateException("Not available until onReady() is called");
+        }
         try {
             mSystemService.startSession(mInterface, args);
         } catch (RemoteException e) {
@@ -81,10 +129,7 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
-                ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
-        mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager());
-        mSoundTriggerHelper = new SoundTriggerHelper();
+        mHandler = new MyHandler();
     }
 
     @Override
@@ -96,6 +141,19 @@
     }
 
     /**
+     * Called during service initialization to tell you when the system is ready
+     * to receive interaction from it.  You should generally do initialization here
+     * rather than in {@link #onCreate()}.  Methods such as {@link #startSession}
+     * and {@link #getAlwaysOnHotwordDetector} will not be operational until this point.
+     */
+    public void onReady() {
+        mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
+                ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+        mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager());
+        mSoundTriggerHelper = new SoundTriggerHelper();
+    }
+
+    /**
      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
      * @param locale The locale for which the enrollment needs to be performed.
      *        This is a Java locale, for example "en_US".
@@ -104,6 +162,11 @@
      */
     public final AlwaysOnHotwordDetector getAlwaysOnHotwordDetector(
             String keyphrase, String locale, AlwaysOnHotwordDetector.Callback callback) {
+        if (mSystemService == null) {
+            throw new IllegalStateException("Not available until onReady() is called");
+        }
+        // TODO: Cache instances and return the same one instead of creating a new interactor
+        // for the same keyphrase/locale combination.
         return new AlwaysOnHotwordDetector(keyphrase, locale, callback,
                 mKeyphraseEnrollmentInfo, mSoundTriggerHelper, mInterface, mSystemService);
     }
diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
index a909ead..d27e2cd 100644
--- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -40,6 +40,7 @@
 
     private ServiceInfo mServiceInfo;
     private String mSessionService;
+    private String mRecognitionService;
     private String mSettingsActivity;
 
     public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp)
@@ -82,6 +83,8 @@
                     com.android.internal.R.styleable.VoiceInteractionService);
             mSessionService = array.getString(
                     com.android.internal.R.styleable.VoiceInteractionService_sessionService);
+            mRecognitionService = array.getString(
+                    com.android.internal.R.styleable.VoiceInteractionService_recognitionService);
             mSettingsActivity = array.getString(
                     com.android.internal.R.styleable.VoiceInteractionService_settingsActivity);
             array.recycle();
@@ -119,6 +122,10 @@
         return mSessionService;
     }
 
+    public String getRecognitionService() {
+        return mRecognitionService;
+    }
+
     public String getSettingsActivity() {
         return mSettingsActivity;
     }
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index 247c8fe..cfeca08 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -80,6 +80,15 @@
 
     @Override
     public int compareTo(BatterySipper other) {
+        // Over-counted always goes to the bottom.
+        if (drainType != other.drainType) {
+            if (drainType == DrainType.OVERCOUNTED) {
+                // This is "larger"
+                return 1;
+            } else if (other.drainType == DrainType.OVERCOUNTED) {
+                return -1;
+            }
+        }
         // Return the flipped value because we want the items in descending order
         return Double.compare(other.value, value);
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 023ba03..6c9b4b8 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -48,7 +48,6 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
-import java.util.Arrays;
 
 /**
  * A helper class for retrieving the power usage information for all applications and services.
@@ -95,6 +94,7 @@
 
     private long mStatsPeriod = 0;
     private double mMaxPower = 1;
+    private double mMaxRealPower = 1;
     private double mComputedPower;
     private double mTotalPower;
     private double mWifiPower;
@@ -208,6 +208,7 @@
         getStats();
 
         mMaxPower = 0;
+        mMaxRealPower = 0;
         mComputedPower = 0;
         mTotalPower = 0;
         mWifiPower = 0;
@@ -542,6 +543,7 @@
                 } else {
                     mUsageList.add(app);
                     if (power > mMaxPower) mMaxPower = power;
+                    if (power > mMaxRealPower) mMaxRealPower = power;
                     mComputedPower += power;
                 }
                 if (u.getUid() == 0) {
@@ -567,6 +569,7 @@
                 osApp.value += power;
                 osApp.values[0] += power;
                 if (osApp.value > mMaxPower) mMaxPower = osApp.value;
+                if (osApp.value > mMaxRealPower) mMaxRealPower = osApp.value;
                 mComputedPower += power;
             }
         }
@@ -806,6 +809,7 @@
 
     private BatterySipper addEntry(DrainType drainType, long time, double power) {
         mComputedPower += power;
+        if (power > mMaxRealPower) mMaxRealPower = power;
         return addEntryNoTotal(drainType, time, power);
     }
 
@@ -831,6 +835,8 @@
 
     public double getMaxPower() { return mMaxPower; }
 
+    public double getMaxRealPower() { return mMaxRealPower; }
+
     public double getTotalPower() { return mTotalPower; }
 
     public double getComputedPower() { return mComputedPower; }
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5b55a24..ab19ad4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6772,7 +6772,10 @@
          its {@link android.service.voice.VoiceInteractionService#SERVICE_META_DATA} meta-data entry.
          Described here are the attributes that can be included in that tag. -->
     <declare-styleable name="VoiceInteractionService">
+        <!-- The service that hosts active voice interaction sessions. -->
         <attr name="sessionService" format="string" />
+        <!-- The service that provides voice recognition. -->
+        <attr name="recognitionService" format="string" />
         <attr name="settingsActivity" />
     </declare-styleable>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 1b6ea95..e4484ad 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2250,6 +2250,7 @@
   <public type="attr" name="dateSelectorYearListItemTextAppearance" />
   <public type="attr" name="dateSelectorYearListSelectedCircleColor" />
   <public type="attr" name="calendarTextColor" />
+  <public type="attr" name="recognitionService" />
 
   <!-- For the TimePicker -->
   <public type="attr" name="timePickerStyle" />