summaryrefslogtreecommitdiffstats
path: root/core/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/com')
-rw-r--r--core/java/com/android/internal/app/DumpHeapActivity.java37
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl1
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl26
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl22
-rw-r--r--core/java/com/android/internal/app/LocalePicker.java3
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java31
-rw-r--r--core/java/com/android/internal/backup/IBackupTransport.aidl1
-rw-r--r--core/java/com/android/internal/content/PackageHelper.java125
-rw-r--r--core/java/com/android/internal/logging/EventLogTags.logtags7
-rw-r--r--core/java/com/android/internal/logging/MetricsConstants.java153
-rw-r--r--core/java/com/android/internal/logging/MetricsLogger.java67
-rw-r--r--core/java/com/android/internal/midi/EventScheduler.java244
-rw-r--r--core/java/com/android/internal/midi/MidiConstants.java88
-rw-r--r--core/java/com/android/internal/midi/MidiDispatcher.java86
-rw-r--r--core/java/com/android/internal/midi/MidiEventScheduler.java146
-rw-r--r--core/java/com/android/internal/midi/MidiFramer.java92
-rw-r--r--core/java/com/android/internal/os/BatterySipper.java84
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHelper.java723
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java1222
-rw-r--r--core/java/com/android/internal/os/BluetoothPowerCalculator.java53
-rw-r--r--core/java/com/android/internal/os/CpuPowerCalculator.java113
-rw-r--r--core/java/com/android/internal/os/InstallerConnection.java6
-rw-r--r--core/java/com/android/internal/os/KernelWakelockReader.java192
-rw-r--r--core/java/com/android/internal/os/KernelWakelockStats.java37
-rw-r--r--core/java/com/android/internal/os/MobileRadioPowerCalculator.java147
-rw-r--r--core/java/com/android/internal/os/PowerCalculator.java56
-rw-r--r--core/java/com/android/internal/os/PowerProfile.java59
-rw-r--r--core/java/com/android/internal/os/SensorPowerCalculator.java63
-rw-r--r--core/java/com/android/internal/os/WakelockPowerCalculator.java81
-rw-r--r--core/java/com/android/internal/os/WifiPowerCalculator.java84
-rw-r--r--core/java/com/android/internal/os/WifiPowerEstimator.java95
-rw-r--r--core/java/com/android/internal/os/Zygote.java9
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java6
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java6
-rw-r--r--core/java/com/android/internal/os/storage/ExternalStorageFormatter.java237
-rw-r--r--core/java/com/android/internal/util/ImageUtils.java39
-rw-r--r--core/java/com/android/internal/util/IndentingPrintWriter.java5
-rw-r--r--core/java/com/android/internal/view/FloatingActionMode.java141
-rw-r--r--core/java/com/android/internal/widget/FloatingToolbar.java596
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java34
-rw-r--r--core/java/com/android/internal/widget/ViewPager.java121
-rw-r--r--core/java/com/android/server/backup/PreferredActivityBackupHelper.java175
-rw-r--r--core/java/com/android/server/backup/SystemBackupAgent.java30
43 files changed, 4077 insertions, 1466 deletions
diff --git a/core/java/com/android/internal/app/DumpHeapActivity.java b/core/java/com/android/internal/app/DumpHeapActivity.java
index 7e70b0c..0ce501e 100644
--- a/core/java/com/android/internal/app/DumpHeapActivity.java
+++ b/core/java/com/android/internal/app/DumpHeapActivity.java
@@ -17,13 +17,16 @@
package com.android.internal.app;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.DebugUtils;
+import android.util.Slog;
/**
* This activity is displayed when the system has collected a heap dump from
@@ -34,6 +37,8 @@ public class DumpHeapActivity extends Activity {
public static final String KEY_PROCESS = "process";
/** The size limit the process reached */
public static final String KEY_SIZE = "size";
+ /** Optional name of package to directly launch */
+ public static final String KEY_DIRECT_LAUNCH = "direct_launch";
// Broadcast action to determine when to delete the current dump heap data.
public static final String ACTION_DELETE_DUMPHEAP = "com.android.server.am.DELETE_DUMPHEAP";
@@ -54,6 +59,28 @@ public class DumpHeapActivity extends Activity {
mProcess = getIntent().getStringExtra(KEY_PROCESS);
mSize = getIntent().getLongExtra(KEY_SIZE, 0);
+
+ String directLaunch = getIntent().getStringExtra(KEY_DIRECT_LAUNCH);
+ if (directLaunch != null) {
+ Intent intent = new Intent(ActivityManager.ACTION_REPORT_HEAP_LIMIT);
+ intent.setPackage(directLaunch);
+ ClipData clip = ClipData.newUri(getContentResolver(), "Heap Dump", JAVA_URI);
+ intent.setClipData(clip);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setType(clip.getDescription().getMimeType(0));
+ intent.putExtra(Intent.EXTRA_STREAM, JAVA_URI);
+ try {
+ startActivity(intent);
+ scheduleDelete();
+ mHandled = true;
+ finish();
+ return;
+ } catch (ActivityNotFoundException e) {
+ Slog.i("DumpHeapActivity", "Unable to direct launch to " + directLaunch
+ + ": " + e.getMessage());
+ }
+ }
+
AlertDialog.Builder b = new AlertDialog.Builder(this,
android.R.style.Theme_Material_Light_Dialog_Alert);
b.setTitle(com.android.internal.R.string.dump_heap_title);
@@ -71,9 +98,7 @@ public class DumpHeapActivity extends Activity {
@Override
public void onClick(DialogInterface dialog, int which) {
mHandled = true;
- Intent broadcast = new Intent(ACTION_DELETE_DUMPHEAP);
- broadcast.putExtra(EXTRA_DELAY_DELETE, true);
- sendBroadcast(broadcast);
+ scheduleDelete();
Intent intent = new Intent(Intent.ACTION_SEND);
ClipData clip = ClipData.newUri(getContentResolver(), "Heap Dump", JAVA_URI);
intent.setClipData(clip);
@@ -88,6 +113,12 @@ public class DumpHeapActivity extends Activity {
mDialog = b.show();
}
+ void scheduleDelete() {
+ Intent broadcast = new Intent(ACTION_DELETE_DUMPHEAP);
+ broadcast.putExtra(EXTRA_DELAY_DELETE, true);
+ sendBroadcast(broadcast);
+ }
+
@Override
protected void onStop() {
super.onStop();
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index bea4ece..1746bed 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -109,6 +109,7 @@ interface IBatteryStats {
void noteWifiBatchedScanStoppedFromSource(in WorkSource ws);
void noteWifiMulticastEnabledFromSource(in WorkSource ws);
void noteWifiMulticastDisabledFromSource(in WorkSource ws);
+ void noteWifiRadioPowerState(int powerState, long timestampNs);
void noteNetworkInterfaceType(String iface, int type);
void noteNetworkStatsEnabled();
void noteDeviceIdleMode(boolean enabled, boolean fromActive, boolean fromMotion);
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 6450d52..d149c5b 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -16,9 +16,11 @@
package com.android.internal.app;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
@@ -80,4 +82,28 @@ interface IVoiceInteractionManagerService {
*/
int stopRecognition(in IVoiceInteractionService service, int keyphraseId,
in IRecognitionStatusCallback callback);
+
+ /**
+ * @return the component name for the currently active voice interaction service
+ */
+ ComponentName getActiveServiceComponentName();
+
+ /**
+ * Shows the session for the currently active service. Used to start a new session from system
+ * affordances.
+ *
+ * @param showCallback callback to be notified when the session was shown
+ */
+ void showSessionForActiveService(IVoiceInteractionSessionShowCallback showCallback);
+
+ /**
+ * Indicates whether there is a voice session running (but not necessarily showing).
+ */
+ boolean isSessionRunning();
+
+ /**
+ * Indicates whether the currently active voice interaction service is capable of handling the
+ * assist gesture.
+ */
+ boolean activeServiceSupportsAssistGesture();
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl
new file mode 100644
index 0000000..15fa89b
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 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.internal.app;
+
+oneway interface IVoiceInteractionSessionShowCallback {
+ void onFailed();
+ void onShown();
+}
diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index 1f0bb76..4efefa9 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -246,9 +246,8 @@ public class LocalePicker extends ListFragment {
IActivityManager am = ActivityManagerNative.getDefault();
Configuration config = am.getConfiguration();
- // Will set userSetLocale to indicate this isn't some passing default - the user
- // wants this remembered
config.setLocale(locale);
+ config.userSetLocale = true;
am.updateConfiguration(config);
// Trigger the dirty bit for the Settings Provider.
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 3ceea9d..6b35f3f 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -604,9 +604,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
if ((mAlwaysUseOption || mAdapter.hasFilteredItem()) && mAdapter.mOrigResolveList != null) {
// Build a reasonable intent filter, based on what matched.
IntentFilter filter = new IntentFilter();
+ String action = intent.getAction();
- if (intent.getAction() != null) {
- filter.addAction(intent.getAction());
+ if (action != null) {
+ filter.addAction(action);
}
Set<String> categories = intent.getCategories();
if (categories != null) {
@@ -688,8 +689,30 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
if (r.match > bestMatch) bestMatch = r.match;
}
if (alwaysCheck) {
- getPackageManager().addPreferredActivity(filter, bestMatch, set,
- intent.getComponent());
+ PackageManager pm = getPackageManager();
+
+ // Set the preferred Activity
+ pm.addPreferredActivity(filter, bestMatch, set, intent.getComponent());
+
+ // Update Domain Verification status
+ int userId = getUserId();
+ ComponentName cn = intent.getComponent();
+ String packageName = cn.getPackageName();
+ String dataScheme = (data != null) ? data.getScheme() : null;
+
+ boolean isHttpOrHttps = (dataScheme != null) &&
+ (dataScheme.equals(IntentFilter.SCHEME_HTTP) ||
+ dataScheme.equals(IntentFilter.SCHEME_HTTPS));
+
+ boolean isViewAction = (action != null) && action.equals(Intent.ACTION_VIEW);
+ boolean hasCategoryBrowsable = (categories != null) &&
+ categories.contains(Intent.CATEGORY_BROWSABLE);
+
+ if (isHttpOrHttps && isViewAction && hasCategoryBrowsable) {
+ pm.updateIntentVerificationStatus(packageName,
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS,
+ userId);
+ }
} else {
try {
AppGlobals.getPackageManager().setLastChosenActivity(intent,
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 6158a7b..083d6c7 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -238,6 +238,7 @@ interface IBackupTransport {
long requestFullBackupTime();
int performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket);
+ int checkFullBackupSize(long size);
int sendBackupData(int numBytes);
void cancelFullBackup();
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 7bdb4be..255f1fd 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -25,15 +25,16 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageParser.PackageLite;
import android.os.Environment;
-import android.os.Environment.UserEnvironment;
import android.os.FileUtils;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.UserHandle;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
import android.os.storage.StorageResultCode;
+import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
+import android.util.ArraySet;
import android.util.Log;
import libcore.io.IoUtils;
@@ -335,6 +336,94 @@ public class PackageHelper {
/**
* Given a requested {@link PackageInfo#installLocation} and calculated
+ * install size, pick the actual volume to install the app. Only considers
+ * internal and private volumes, and prefers to keep an existing package on
+ * its current volume.
+ *
+ * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
+ * for internal storage.
+ */
+ public static String resolveInstallVolume(Context context, String packageName,
+ int installLocation, long sizeBytes) throws IOException {
+ // TODO: handle existing apps installed in ASEC; currently assumes
+ // they'll end up back on internal storage
+ ApplicationInfo existingInfo = null;
+ try {
+ existingInfo = context.getPackageManager().getApplicationInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ } catch (NameNotFoundException ignored) {
+ }
+
+ final StorageManager storageManager = context.getSystemService(StorageManager.class);
+ final boolean fitsOnInternal = fitsOnInternal(context, sizeBytes);
+
+ final ArraySet<String> allCandidates = new ArraySet<>();
+ VolumeInfo bestCandidate = null;
+ long bestCandidateAvailBytes = Long.MIN_VALUE;
+ for (VolumeInfo vol : storageManager.getVolumes()) {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.state == VolumeInfo.STATE_MOUNTED) {
+ final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path));
+ if (availBytes >= sizeBytes) {
+ allCandidates.add(vol.fsUuid);
+ }
+ if (availBytes >= bestCandidateAvailBytes) {
+ bestCandidate = vol;
+ bestCandidateAvailBytes = availBytes;
+ }
+ }
+ }
+
+ // System apps always forced to internal storage
+ if (existingInfo != null && existingInfo.isSystemApp()) {
+ installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ }
+
+ // If app expresses strong desire for internal space, honor it
+ if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+ if (fitsOnInternal) {
+ return null;
+ } else {
+ throw new IOException("Requested internal only, but not enough space");
+ }
+ }
+
+ // If app already exists somewhere, prefer to stay on that volume
+ if (existingInfo != null) {
+ if (existingInfo.volumeUuid == null && fitsOnInternal) {
+ return null;
+ }
+ if (allCandidates.contains(existingInfo.volumeUuid)) {
+ return existingInfo.volumeUuid;
+ }
+ }
+
+ // We're left with either preferring external or auto, so just pick
+ // volume with most space
+ if (bestCandidate != null) {
+ return bestCandidate.fsUuid;
+ } else if (fitsOnInternal) {
+ return null;
+ } else {
+ throw new IOException("No special requests, but no room anywhere");
+ }
+ }
+
+ public static boolean fitsOnInternal(Context context, long sizeBytes) {
+ final StorageManager storage = context.getSystemService(StorageManager.class);
+ final File target = Environment.getDataDirectory();
+ return (sizeBytes <= storage.getStorageBytesUntilLow(target));
+ }
+
+ public static boolean fitsOnExternal(Context context, long sizeBytes) {
+ final StorageManager storage = context.getSystemService(StorageManager.class);
+ final StorageVolume primary = storage.getPrimaryVolume();
+ return (sizeBytes > 0) && !primary.isEmulated()
+ && Environment.MEDIA_MOUNTED.equals(primary.getState())
+ && sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
+ }
+
+ /**
+ * Given a requested {@link PackageInfo#installLocation} and calculated
* install size, pick the actual location to install the app.
*/
public static int resolveInstallLocation(Context context, String packageName,
@@ -363,6 +452,7 @@ public class PackageHelper {
} else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
// When app is already installed, prefer same medium
if (existingInfo != null) {
+ // TODO: distinguish if this is external ASEC
if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
prefer = RECOMMEND_INSTALL_EXTERNAL;
} else {
@@ -377,30 +467,21 @@ public class PackageHelper {
checkBoth = false;
}
- final boolean emulated = Environment.isExternalStorageEmulated();
- final StorageManager storage = StorageManager.from(context);
-
boolean fitsOnInternal = false;
if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
- final File target = Environment.getDataDirectory();
- fitsOnInternal = (sizeBytes <= storage.getStorageBytesUntilLow(target));
+ fitsOnInternal = fitsOnInternal(context, sizeBytes);
}
boolean fitsOnExternal = false;
- if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)) {
- final File target = new UserEnvironment(UserHandle.USER_OWNER)
- .getExternalStorageDirectory();
- // External is only an option when size is known
- if (sizeBytes > 0) {
- fitsOnExternal = (sizeBytes <= storage.getStorageBytesUntilLow(target));
- }
+ if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
+ fitsOnExternal = fitsOnExternal(context, sizeBytes);
}
if (prefer == RECOMMEND_INSTALL_INTERNAL) {
if (fitsOnInternal) {
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
}
- } else if (!emulated && prefer == RECOMMEND_INSTALL_EXTERNAL) {
+ } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
if (fitsOnExternal) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
@@ -409,22 +490,12 @@ public class PackageHelper {
if (checkBoth) {
if (fitsOnInternal) {
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
- } else if (!emulated && fitsOnExternal) {
+ } else if (fitsOnExternal) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
}
- /*
- * If they requested to be on the external media by default, return that
- * the media was unavailable. Otherwise, indicate there was insufficient
- * storage space available.
- */
- if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)
- && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
- return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
- } else {
- return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
- }
+ return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
diff --git a/core/java/com/android/internal/logging/EventLogTags.logtags b/core/java/com/android/internal/logging/EventLogTags.logtags
new file mode 100644
index 0000000..870d20d
--- /dev/null
+++ b/core/java/com/android/internal/logging/EventLogTags.logtags
@@ -0,0 +1,7 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.internal.logging;
+
+# interaction logs
+524287 sysui_view_visibility (category|1|5),(visible|1|6)
+524288 sysui_action (category|1|5)
diff --git a/core/java/com/android/internal/logging/MetricsConstants.java b/core/java/com/android/internal/logging/MetricsConstants.java
new file mode 100644
index 0000000..e5cba84
--- /dev/null
+++ b/core/java/com/android/internal/logging/MetricsConstants.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2015 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.internal.logging;
+
+/**
+ * Constants for mestrics logs.
+ *
+ * @hide
+ */
+public interface MetricsConstants {
+ // These constants must match those in the analytic pipeline.
+ public static final int ACCESSIBILITY = 2;
+ public static final int ACCESSIBILITY_CAPTION_PROPERTIES = 3;
+ public static final int ACCESSIBILITY_SERVICE = 4;
+ public static final int ACCESSIBILITY_TOGGLE_DALTONIZER = 5;
+ public static final int ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE = 6;
+ public static final int ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION = 7;
+ public static final int ACCOUNT = 8;
+ public static final int ACCOUNTS_ACCOUNT_SYNC = 9;
+ public static final int ACCOUNTS_CHOOSE_ACCOUNT_ACTIVITY = 10;
+ public static final int ACCOUNTS_MANAGE_ACCOUNTS = 11;
+ public static final int APN = 12;
+ public static final int APN_EDITOR = 13;
+ public static final int APPLICATION = 16;
+ public static final int APPLICATIONS_APP_LAUNCH = 17;
+ public static final int APPLICATIONS_APP_PERMISSION = 18;
+ public static final int APPLICATIONS_APP_STORAGE = 19;
+ public static final int APPLICATIONS_INSTALLED_APP_DETAILS = 20;
+ public static final int APPLICATIONS_PROCESS_STATS_DETAIL = 21;
+ public static final int APPLICATIONS_PROCESS_STATS_MEM_DETAIL = 22;
+ public static final int APPLICATIONS_PROCESS_STATS_UI = 23;
+ public static final int APP_OPS_DETAILS = 14;
+ public static final int APP_OPS_SUMMARY = 15;
+ public static final int BLUETOOTH = 24;
+ public static final int BLUETOOTH_DEVICE_PICKER = 25;
+ public static final int BLUETOOTH_DEVICE_PROFILES = 26;
+ public static final int CHOOSE_LOCK_GENERIC = 27;
+ public static final int CHOOSE_LOCK_PASSWORD = 28;
+ public static final int CHOOSE_LOCK_PATTERN = 29;
+ public static final int CONFIRM_LOCK_PASSWORD = 30;
+ public static final int CONFIRM_LOCK_PATTERN = 31;
+ public static final int CRYPT_KEEPER = 32;
+ public static final int CRYPT_KEEPER_CONFIRM = 33;
+ public static final int DASHBOARD_SEARCH_RESULTS = 34;
+ public static final int DASHBOARD_SUMMARY = 35;
+ public static final int DATA_USAGE = 36;
+ public static final int DATA_USAGE_SUMMARY = 37;
+ public static final int DATE_TIME = 38;
+ public static final int DEVELOPMENT = 39;
+ public static final int DEVICEINFO = 40;
+ public static final int DEVICEINFO_IMEI_INFORMATION = 41;
+ public static final int DEVICEINFO_MEMORY = 42;
+ public static final int DEVICEINFO_SIM_STATUS = 43;
+ public static final int DEVICEINFO_STATUS = 44;
+ public static final int DEVICEINFO_USB = 45;
+ public static final int DISPLAY = 46;
+ public static final int DREAM = 47;
+ public static final int ENCRYPTION = 48;
+ public static final int FINGERPRINT = 49;
+ public static final int FINGERPRINT_ENROLL = 50;
+ public static final int FUELGAUGE_BATTERY_HISTORY_DETAIL = 51;
+ public static final int FUELGAUGE_BATTERY_SAVER = 52;
+ public static final int FUELGAUGE_POWER_USAGE_DETAIL = 53;
+ public static final int FUELGAUGE_POWER_USAGE_SUMMARY = 54;
+ public static final int HOME = 55;
+ public static final int ICC_LOCK = 56;
+ public static final int INPUTMETHOD_KEYBOARD = 58;
+ public static final int INPUTMETHOD_LANGUAGE = 57;
+ public static final int INPUTMETHOD_SPELL_CHECKERS = 59;
+ public static final int INPUTMETHOD_SUBTYPE_ENABLER = 60;
+ public static final int INPUTMETHOD_USER_DICTIONARY = 61;
+ public static final int INPUTMETHOD_USER_DICTIONARY_ADD_WORD = 62;
+ public static final int LOCATION = 63;
+ public static final int LOCATION_MODE = 64;
+ public static final int MAIN_SETTINGS = 1;
+ public static final int MANAGE_APPLICATIONS = 65;
+ public static final int MASTER_CLEAR = 66;
+ public static final int MASTER_CLEAR_CONFIRM = 67;
+ public static final int NET_DATA_USAGE_METERED = 68;
+ public static final int NFC_BEAM = 69;
+ public static final int NFC_PAYMENT = 70;
+ public static final int NOTIFICATION = 71;
+ public static final int NOTIFICATION_APP_NOTIFICATION = 72;
+ public static final int NOTIFICATION_OTHER_SOUND = 73;
+ public static final int NOTIFICATION_REDACTION = 74;
+ public static final int NOTIFICATION_STATION = 75;
+ public static final int NOTIFICATION_ZEN_MODE = 76;
+ public static final int OWNER_INFO = 77;
+ public static final int PRINT_JOB_SETTINGS = 78;
+ public static final int PRINT_SERVICE_SETTINGS = 79;
+ public static final int PRINT_SETTINGS = 80;
+ public static final int PRIVACY = 81;
+ public static final int PROXY_SELECTOR = 82;
+ public static final int QS_AIRPLANEMODE = 112;
+ public static final int QS_BLUETOOTH = 113;
+ public static final int QS_CAST = 114;
+ public static final int QS_CELLULAR = 115;
+ public static final int QS_COLORINVERSION = 116;
+ public static final int QS_DATAUSAGEDETAIL = 117;
+ public static final int QS_DND = 118;
+ public static final int QS_FLASHLIGHT = 119;
+ public static final int QS_HOTSPOT = 120;
+ public static final int QS_INTENT = 121;
+ public static final int QS_LOCATION = 122;
+ public static final int QS_PANEL = 111;
+ public static final int QS_ROTATIONLOCK = 123;
+ public static final int QS_USERDETAIL = 125;
+ public static final int QS_USERDETAILITE = 124;
+ public static final int QS_WIFI = 126;
+ public static final int RESET_NETWORK = 83;
+ public static final int RESET_NETWORK_CONFIRM = 84;
+ public static final int RUNNING_SERVICE_DETAILS = 85;
+ public static final int SCREEN_PINNING = 86;
+ public static final int SECURITY = 87;
+ public static final int SIM = 88;
+ public static final int TESTING = 89;
+ public static final int TETHER = 90;
+ public static final int TRUSTED_CREDENTIALS = 92;
+ public static final int TRUST_AGENT = 91;
+ public static final int TTS_ENGINE_SETTINGS = 93;
+ public static final int TTS_TEXT_TO_SPEECH = 94;
+ public static final int TYPE_UNKNOWN = 0;
+ public static final int USAGE_ACCESS = 95;
+ public static final int USER = 96;
+ public static final int USERS_APP_RESTRICTIONS = 97;
+ public static final int USER_DETAILS = 98;
+ public static final int VIEW_UNKNOWN = 0;
+ public static final int VOICE_INPUT = 99;
+ public static final int VPN = 100;
+ public static final int WALLPAPER_TYPE = 101;
+ public static final int WFD_WIFI_DISPLAY = 102;
+ public static final int WIFI = 103;
+ public static final int WIFI_ADVANCED = 104;
+ public static final int WIFI_APITEST = 107;
+ public static final int WIFI_CALLING = 105;
+ public static final int WIFI_INFO = 108;
+ public static final int WIFI_P2P = 109;
+ public static final int WIFI_SAVED_ACCESS_POINTS = 106;
+ public static final int WIRELESS = 110;
+}
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
new file mode 100644
index 0000000..1038543
--- /dev/null
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 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.internal.logging;
+
+
+import android.content.Context;
+import android.os.Build;
+
+/**
+ * Log all the things.
+ *
+ * @hide
+ */
+public class MetricsLogger implements MetricsConstants {
+ // These constants are temporary, they should migrate to MetricsConstants.
+ public static final int APPLICATIONS_ADVANCED = 132;
+ public static final int LOCATION_SCANNING = 133;
+ public static final int MANAGE_APPLICATIONS_ALL = 134;
+ public static final int MANAGE_APPLICATIONS_NOTIFICATIONS = 135;
+
+ public static final int ACTION_WIFI_ADD_NETWORK = 136;
+ public static final int ACTION_WIFI_CONNECT = 137;
+ public static final int ACTION_WIFI_FORCE_SCAN = 138;
+ public static final int ACTION_WIFI_FORGET = 139;
+ public static final int ACTION_WIFI_OFF = 140;
+ public static final int ACTION_WIFI_ON = 141;
+
+ public static final int MANAGE_PERMISSIONS = 142;
+ public static final int NOTIFICATION_ZEN_MODE_PRIORITY = 143;
+ public static final int NOTIFICATION_ZEN_MODE_AUTOMATION = 144;
+
+ public static final int MANAGE_DOMAIN_URLS = 143;
+
+ public static void visible(Context context, int category) throws IllegalArgumentException {
+ if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
+ throw new IllegalArgumentException("Must define metric category");
+ }
+ EventLogTags.writeSysuiViewVisibility(category, 100);
+ }
+
+ public static void hidden(Context context, int category) {
+ if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
+ throw new IllegalArgumentException("Must define metric category");
+ }
+ EventLogTags.writeSysuiViewVisibility(category, 0);
+ }
+
+ public static void action(Context context, int category) {
+ if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
+ throw new IllegalArgumentException("Must define metric category");
+ }
+ EventLogTags.writeSysuiAction(category);
+ }
+}
diff --git a/core/java/com/android/internal/midi/EventScheduler.java b/core/java/com/android/internal/midi/EventScheduler.java
new file mode 100644
index 0000000..7b9a48c
--- /dev/null
+++ b/core/java/com/android/internal/midi/EventScheduler.java
@@ -0,0 +1,244 @@
+/*
+ * 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 com.android.internal.midi;
+
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * Store arbitrary timestamped events using a Long timestamp.
+ * Only one Thread can write into the buffer.
+ * And only one Thread can read from the buffer.
+ */
+public class EventScheduler {
+ private static final long NANOS_PER_MILLI = 1000000;
+
+ private final Object mLock = new Object();
+ private SortedMap<Long, FastEventQueue> mEventBuffer;
+ private FastEventQueue mEventPool = null;
+ private int mMaxPoolSize = 200;
+ private boolean mClosed;
+
+ public EventScheduler() {
+ mEventBuffer = new TreeMap<Long, FastEventQueue>();
+ }
+
+ // If we keep at least one node in the list then it can be atomic
+ // and non-blocking.
+ private class FastEventQueue {
+ // One thread takes from the beginning of the list.
+ volatile SchedulableEvent mFirst;
+ // A second thread returns events to the end of the list.
+ volatile SchedulableEvent mLast;
+ volatile long mEventsAdded;
+ volatile long mEventsRemoved;
+
+ FastEventQueue(SchedulableEvent event) {
+ mFirst = event;
+ mLast = mFirst;
+ mEventsAdded = 1;
+ mEventsRemoved = 0;
+ }
+
+ int size() {
+ return (int)(mEventsAdded - mEventsRemoved);
+ }
+
+ /**
+ * Do not call this unless there is more than one event
+ * in the list.
+ * @return first event in the list
+ */
+ public SchedulableEvent remove() {
+ // Take first event.
+ mEventsRemoved++;
+ SchedulableEvent event = mFirst;
+ mFirst = event.mNext;
+ return event;
+ }
+
+ /**
+ * @param event
+ */
+ public void add(SchedulableEvent event) {
+ event.mNext = null;
+ mLast.mNext = event;
+ mLast = event;
+ mEventsAdded++;
+ }
+ }
+
+ /**
+ * Base class for events that can be stored in the EventScheduler.
+ */
+ public static class SchedulableEvent {
+ private long mTimestamp;
+ private SchedulableEvent mNext = null;
+
+ /**
+ * @param timestamp
+ */
+ public SchedulableEvent(long timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ /**
+ * @return timestamp
+ */
+ public long getTimestamp() {
+ return mTimestamp;
+ }
+
+ /**
+ * The timestamp should not be modified when the event is in the
+ * scheduling buffer.
+ */
+ public void setTimestamp(long timestamp) {
+ mTimestamp = timestamp;
+ }
+ }
+
+ /**
+ * Get an event from the pool.
+ * Always leave at least one event in the pool.
+ * @return event or null
+ */
+ public SchedulableEvent removeEventfromPool() {
+ SchedulableEvent event = null;
+ if (mEventPool != null && (mEventPool.size() > 1)) {
+ event = mEventPool.remove();
+ }
+ return event;
+ }
+
+ /**
+ * Return events to a pool so they can be reused.
+ *
+ * @param event
+ */
+ public void addEventToPool(SchedulableEvent event) {
+ if (mEventPool == null) {
+ mEventPool = new FastEventQueue(event);
+ // If we already have enough items in the pool then just
+ // drop the event. This prevents unbounded memory leaks.
+ } else if (mEventPool.size() < mMaxPoolSize) {
+ mEventPool.add(event);
+ }
+ }
+
+ /**
+ * Add an event to the scheduler. Events with the same time will be
+ * processed in order.
+ *
+ * @param event
+ */
+ public void add(SchedulableEvent event) {
+ synchronized (mLock) {
+ FastEventQueue list = mEventBuffer.get(event.getTimestamp());
+ if (list == null) {
+ long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
+ : mEventBuffer.firstKey();
+ list = new FastEventQueue(event);
+ mEventBuffer.put(event.getTimestamp(), list);
+ // If the event we added is earlier than the previous earliest
+ // event then notify any threads waiting for the next event.
+ if (event.getTimestamp() < lowestTime) {
+ mLock.notify();
+ }
+ } else {
+ list.add(event);
+ }
+ }
+ }
+
+ private SchedulableEvent removeNextEventLocked(long lowestTime) {
+ SchedulableEvent event;
+ FastEventQueue list = mEventBuffer.get(lowestTime);
+ // Remove list from tree if this is the last node.
+ if ((list.size() == 1)) {
+ mEventBuffer.remove(lowestTime);
+ }
+ event = list.remove();
+ return event;
+ }
+
+ /**
+ * Check to see if any scheduled events are ready to be processed.
+ *
+ * @param timestamp
+ * @return next event or null if none ready
+ */
+ public SchedulableEvent getNextEvent(long time) {
+ SchedulableEvent event = null;
+ synchronized (mLock) {
+ if (!mEventBuffer.isEmpty()) {
+ long lowestTime = mEventBuffer.firstKey();
+ // Is it time for this list to be processed?
+ if (lowestTime <= time) {
+ event = removeNextEventLocked(lowestTime);
+ }
+ }
+ }
+ // Log.i(TAG, "getNextEvent: event = " + event);
+ return event;
+ }
+
+ /**
+ * Return the next available event or wait until there is an event ready to
+ * be processed. This method assumes that the timestamps are in nanoseconds
+ * and that the current time is System.nanoTime().
+ *
+ * @return event
+ * @throws InterruptedException
+ */
+ public SchedulableEvent waitNextEvent() throws InterruptedException {
+ SchedulableEvent event = null;
+ synchronized (mLock) {
+ while (!mClosed) {
+ long millisToWait = Integer.MAX_VALUE;
+ if (!mEventBuffer.isEmpty()) {
+ long now = System.nanoTime();
+ long lowestTime = mEventBuffer.firstKey();
+ // Is it time for the earliest list to be processed?
+ if (lowestTime <= now) {
+ event = removeNextEventLocked(lowestTime);
+ break;
+ } else {
+ // Figure out how long to sleep until next event.
+ long nanosToWait = lowestTime - now;
+ // Add 1 millisecond so we don't wake up before it is
+ // ready.
+ millisToWait = 1 + (nanosToWait / NANOS_PER_MILLI);
+ // Clip 64-bit value to 32-bit max.
+ if (millisToWait > Integer.MAX_VALUE) {
+ millisToWait = Integer.MAX_VALUE;
+ }
+ }
+ }
+ mLock.wait((int) millisToWait);
+ }
+ }
+ return event;
+ }
+
+ public void close() {
+ synchronized (mLock) {
+ mClosed = true;
+ mLock.notify();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/midi/MidiConstants.java b/core/java/com/android/internal/midi/MidiConstants.java
new file mode 100644
index 0000000..87552e4
--- /dev/null
+++ b/core/java/com/android/internal/midi/MidiConstants.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 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.internal.midi;
+
+/**
+ * MIDI related constants and static methods.
+ */
+public class MidiConstants {
+ public static final byte STATUS_COMMAND_MASK = (byte) 0xF0;
+ public static final byte STATUS_CHANNEL_MASK = (byte) 0x0F;
+
+ // Channel voice messages.
+ public static final byte STATUS_NOTE_OFF = (byte) 0x80;
+ public static final byte STATUS_NOTE_ON = (byte) 0x90;
+ public static final byte STATUS_POLYPHONIC_AFTERTOUCH = (byte) 0xA0;
+ public static final byte STATUS_CONTROL_CHANGE = (byte) 0xB0;
+ public static final byte STATUS_PROGRAM_CHANGE = (byte) 0xC0;
+ public static final byte STATUS_CHANNEL_PRESSURE = (byte) 0xD0;
+ public static final byte STATUS_PITCH_BEND = (byte) 0xE0;
+
+ // System Common Messages.
+ public static final byte STATUS_SYSTEM_EXCLUSIVE = (byte) 0xF0;
+ public static final byte STATUS_MIDI_TIME_CODE = (byte) 0xF1;
+ public static final byte STATUS_SONG_POSITION = (byte) 0xF2;
+ public static final byte STATUS_SONG_SELECT = (byte) 0xF3;
+ public static final byte STATUS_TUNE_REQUEST = (byte) 0xF6;
+ public static final byte STATUS_END_SYSEX = (byte) 0xF7;
+
+ // System Real-Time Messages
+ public static final byte STATUS_TIMING_CLOCK = (byte) 0xF8;
+ public static final byte STATUS_START = (byte) 0xFA;
+ public static final byte STATUS_CONTINUE = (byte) 0xFB;
+ public static final byte STATUS_STOP = (byte) 0xFC;
+ public static final byte STATUS_ACTIVE_SENSING = (byte) 0xFE;
+ public static final byte STATUS_RESET = (byte) 0xFF;
+
+ /** Number of bytes in a message nc from 8c to Ec */
+ public final static int CHANNEL_BYTE_LENGTHS[] = { 3, 3, 3, 3, 2, 2, 3 };
+
+ /** Number of bytes in a message Fn from F0 to FF */
+ public final static int SYSTEM_BYTE_LENGTHS[] = { 1, 2, 3, 2, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1 };
+
+ /********************************************************************/
+
+ public static int getBytesPerMessage(int command) {
+ if ((command < 0x80) || (command > 0xFF)) {
+ return 0;
+ } else if (command >= 0xF0) {
+ return SYSTEM_BYTE_LENGTHS[command & 0x0F];
+ } else {
+ return CHANNEL_BYTE_LENGTHS[(command >> 4) - 8];
+ }
+ }
+
+ /**
+ * @param msg
+ * @param offset
+ * @param count
+ * @return true if the entire message is ActiveSensing commands
+ */
+ public static boolean isAllActiveSensing(byte[] msg, int offset,
+ int count) {
+ // Count bytes that are not active sensing.
+ int goodBytes = 0;
+ for (int i = 0; i < count; i++) {
+ byte b = msg[offset + i];
+ if (b != MidiConstants.STATUS_ACTIVE_SENSING) {
+ goodBytes++;
+ }
+ }
+ return (goodBytes == 0);
+ }
+}
diff --git a/core/java/com/android/internal/midi/MidiDispatcher.java b/core/java/com/android/internal/midi/MidiDispatcher.java
new file mode 100644
index 0000000..377bc68
--- /dev/null
+++ b/core/java/com/android/internal/midi/MidiDispatcher.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 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.internal.midi;
+
+import android.media.midi.MidiReceiver;
+import android.media.midi.MidiSender;
+
+import java.io.IOException;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Utility class for dispatching MIDI data to a list of {@link android.media.midi.MidiReceiver}s.
+ * This class subclasses {@link android.media.midi.MidiReceiver} and dispatches any data it receives
+ * to its receiver list. Any receivers that throw an exception upon receiving data will
+ * be automatically removed from the receiver list, but no IOException will be returned
+ * from the dispatcher's {@link android.media.midi.MidiReceiver#onReceive} in that case.
+ */
+public final class MidiDispatcher extends MidiReceiver {
+
+ private final CopyOnWriteArrayList<MidiReceiver> mReceivers
+ = new CopyOnWriteArrayList<MidiReceiver>();
+
+ private final MidiSender mSender = new MidiSender() {
+ /**
+ * Called to connect a {@link android.media.midi.MidiReceiver} to the sender
+ *
+ * @param receiver the receiver to connect
+ */
+ public void connect(MidiReceiver receiver) {
+ mReceivers.add(receiver);
+ }
+
+ /**
+ * Called to disconnect a {@link android.media.midi.MidiReceiver} from the sender
+ *
+ * @param receiver the receiver to disconnect
+ */
+ public void disconnect(MidiReceiver receiver) {
+ mReceivers.remove(receiver);
+ }
+ };
+
+ /**
+ * Returns the number of {@link android.media.midi.MidiReceiver}s this dispatcher contains.
+ * @return the number of receivers
+ */
+ public int getReceiverCount() {
+ return mReceivers.size();
+ }
+
+ /**
+ * Returns a {@link android.media.midi.MidiSender} which is used to add and remove
+ * {@link android.media.midi.MidiReceiver}s
+ * to the dispatcher's receiver list.
+ * @return the dispatcher's MidiSender
+ */
+ public MidiSender getSender() {
+ return mSender;
+ }
+
+ @Override
+ public void onReceive(byte[] msg, int offset, int count, long timestamp) throws IOException {
+ for (MidiReceiver receiver : mReceivers) {
+ try {
+ receiver.sendWithTimestamp(msg, offset, count, timestamp);
+ } catch (IOException e) {
+ // if the receiver fails we remove the receiver but do not propagate the exception
+ mReceivers.remove(receiver);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/midi/MidiEventScheduler.java b/core/java/com/android/internal/midi/MidiEventScheduler.java
new file mode 100644
index 0000000..42d70f6
--- /dev/null
+++ b/core/java/com/android/internal/midi/MidiEventScheduler.java
@@ -0,0 +1,146 @@
+/*
+ * 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 com.android.internal.midi;
+
+import android.media.midi.MidiReceiver;
+
+import java.io.IOException;
+
+/**
+ * Add MIDI Events to an EventScheduler
+ */
+public class MidiEventScheduler extends EventScheduler {
+ private static final String TAG = "MidiEventScheduler";
+ // Maintain a pool of scheduled events to reduce memory allocation.
+ // This pool increases performance by about 14%.
+ private final static int POOL_EVENT_SIZE = 16;
+
+ private final MidiReceiver[] mReceivers;
+
+ private class SchedulingReceiver extends MidiReceiver {
+ private final int mPortNumber;
+
+ public SchedulingReceiver(int portNumber) {
+ mPortNumber = portNumber;
+ }
+
+ /**
+ * Store these bytes in the EventScheduler to be delivered at the specified
+ * time.
+ */
+ @Override
+ public void onReceive(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+ MidiEvent event = createScheduledEvent(msg, offset, count, timestamp);
+ if (event != null) {
+ event.portNumber = mPortNumber;
+ add(event);
+ }
+ }
+ }
+
+ public static class MidiEvent extends SchedulableEvent {
+ public int portNumber;
+ public int count = 0;
+ public byte[] data;
+
+ private MidiEvent(int count) {
+ super(0);
+ data = new byte[count];
+ }
+
+ private MidiEvent(byte[] msg, int offset, int count, long timestamp) {
+ super(timestamp);
+ data = new byte[count];
+ System.arraycopy(msg, offset, data, 0, count);
+ this.count = count;
+ }
+
+ @Override
+ public String toString() {
+ String text = "Event: ";
+ for (int i = 0; i < count; i++) {
+ text += data[i] + ", ";
+ }
+ return text;
+ }
+ }
+
+ public MidiEventScheduler() {
+ this(0);
+ }
+
+ public MidiEventScheduler(int portCount) {
+ mReceivers = new MidiReceiver[portCount];
+ for (int i = 0; i < portCount; i++) {
+ mReceivers[i] = new SchedulingReceiver(i);
+ }
+ }
+
+ /**
+ * Create an event that contains the message.
+ */
+ private MidiEvent createScheduledEvent(byte[] msg, int offset, int count,
+ long timestamp) {
+ MidiEvent event;
+ if (count > POOL_EVENT_SIZE) {
+ event = new MidiEvent(msg, offset, count, timestamp);
+ } else {
+ event = (MidiEvent) removeEventfromPool();
+ if (event == null) {
+ event = new MidiEvent(POOL_EVENT_SIZE);
+ }
+ System.arraycopy(msg, offset, event.data, 0, count);
+ event.count = count;
+ event.setTimestamp(timestamp);
+ }
+ return event;
+ }
+
+ /**
+ * Return events to a pool so they can be reused.
+ *
+ * @param event
+ */
+ @Override
+ public void addEventToPool(SchedulableEvent event) {
+ // Make sure the event is suitable for the pool.
+ if (event instanceof MidiEvent) {
+ MidiEvent midiEvent = (MidiEvent) event;
+ if (midiEvent.data.length == POOL_EVENT_SIZE) {
+ super.addEventToPool(event);
+ }
+ }
+ }
+
+ /**
+ * This MidiReceiver will write date to the scheduling buffer.
+ * @return the MidiReceiver
+ */
+ public MidiReceiver getReceiver() {
+ return mReceivers[0];
+ }
+
+ /**
+ * This MidiReceiver will write date to the scheduling buffer.
+ * @return the MidiReceiver
+ */
+ public MidiReceiver getReceiver(int portNumber) {
+ return mReceivers[portNumber];
+ }
+
+}
diff --git a/core/java/com/android/internal/midi/MidiFramer.java b/core/java/com/android/internal/midi/MidiFramer.java
new file mode 100644
index 0000000..53d71bb
--- /dev/null
+++ b/core/java/com/android/internal/midi/MidiFramer.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 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.internal.midi;
+
+import android.media.midi.MidiReceiver;
+
+import java.io.IOException;
+
+/**
+ * Convert stream of bytes to discrete messages.
+ *
+ * Parses the incoming bytes and then posts individual messages to the receiver
+ * specified in the constructor. Short messages of 1-3 bytes will be complete.
+ * System Exclusive messages may be posted in pieces.
+ *
+ * Resolves Running Status and
+ * interleaved System Real-Time messages.
+ */
+public class MidiFramer extends MidiReceiver {
+
+ public String TAG = "MidiFramer";
+ private MidiReceiver mReceiver;
+ private byte[] mBuffer = new byte[3];
+ private int mCount;
+ private int mRunningStatus;
+ private int mNeeded;
+
+ public MidiFramer(MidiReceiver receiver) {
+ mReceiver = receiver;
+ }
+
+ public static String formatMidiData(byte[] data, int offset, int count) {
+ String text = "MIDI+" + offset + " : ";
+ for (int i = 0; i < count; i++) {
+ text += String.format("0x%02X, ", data[offset + i]);
+ }
+ return text;
+ }
+
+ /*
+ * @see android.midi.MidiReceiver#onPost(byte[], int, int, long)
+ */
+ @Override
+ public void onReceive(byte[] data, int offset, int count, long timestamp)
+ throws IOException {
+ // Log.i(TAG, formatMidiData(data, offset, count));
+ for (int i = 0; i < count; i++) {
+ int b = data[offset] & 0xFF;
+ if (b >= 0x80) { // status byte?
+ if (b < 0xF0) { // channel message?
+ mRunningStatus = (byte) b;
+ mCount = 1;
+ mNeeded = MidiConstants.getBytesPerMessage(b) - 1;
+ } else if (b < 0xF8) { // system common?
+ mBuffer[0] = (byte) b;
+ mRunningStatus = 0;
+ mCount = 1;
+ mNeeded = MidiConstants.getBytesPerMessage(b) - 1;
+ } else { // real-time?
+ // Single byte message interleaved with other data.
+ mReceiver.sendWithTimestamp(data, offset, 1, timestamp);
+ }
+ } else { // data byte
+ mBuffer[mCount++] = (byte) b;
+ if (--mNeeded == 0) {
+ if (mRunningStatus != 0) {
+ mBuffer[0] = (byte) mRunningStatus;
+ }
+ mReceiver.sendWithTimestamp(mBuffer, 0, mCount, timestamp);
+ mNeeded = MidiConstants.getBytesPerMessage(mBuffer[0]) - 1;
+ mCount = 1;
+ }
+ }
+ ++offset;
+ }
+ }
+
+}
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index 4cd959f..056b0aa 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -23,17 +23,25 @@ import android.os.BatteryStats.Uid;
public class BatterySipper implements Comparable<BatterySipper> {
public int userId;
public Uid uidObj;
- public double value;
- public double[] values;
+ public double totalPowerMah;
public DrainType drainType;
- // Measured in milliseconds.
- public long usageTime;
- public long cpuTime;
- public long gpsTime;
- public long wifiRunningTime;
- public long cpuFgTime;
- public long wakeLockTime;
+ /**
+ * Generic usage time in milliseconds.
+ */
+ public long usageTimeMs;
+
+ /**
+ * Generic power usage in mAh.
+ */
+ public double usagePowerMah;
+
+ // Subsystem usage times.
+ public long cpuTimeMs;
+ public long gpsTimeMs;
+ public long wifiRunningTimeMs;
+ public long cpuFgTimeMs;
+ public long wakeLockTimeMs;
public long mobileRxPackets;
public long mobileTxPackets;
@@ -52,12 +60,13 @@ public class BatterySipper implements Comparable<BatterySipper> {
public String packageWithHighestDrain;
// Measured in mAh (milli-ampere per hour).
- public double wifiPower;
- public double cpuPower;
- public double wakeLockPower;
- public double mobileRadioPower;
- public double gpsPower;
- public double sensorPower;
+ // These are included when summed.
+ public double wifiPowerMah;
+ public double cpuPowerMah;
+ public double wakeLockPowerMah;
+ public double mobileRadioPowerMah;
+ public double gpsPowerMah;
+ public double sensorPowerMah;
public enum DrainType {
IDLE,
@@ -73,17 +82,12 @@ public class BatterySipper implements Comparable<BatterySipper> {
OVERCOUNTED
}
- public BatterySipper(DrainType drainType, Uid uid, double[] values) {
- this.values = values;
- if (values != null) value = values[0];
+ public BatterySipper(DrainType drainType, Uid uid, double value) {
+ this.totalPowerMah = value;
this.drainType = drainType;
uidObj = uid;
}
- public double[] getValues() {
- return values;
- }
-
public void computeMobilemspp() {
long packets = mobileRxPackets+mobileTxPackets;
mobilemspp = packets > 0 ? (mobileActive / (double)packets) : 0;
@@ -101,7 +105,7 @@ public class BatterySipper implements Comparable<BatterySipper> {
}
}
// Return the flipped value because we want the items in descending order
- return Double.compare(other.value, value);
+ return Double.compare(other.totalPowerMah, totalPowerMah);
}
/**
@@ -123,11 +127,14 @@ public class BatterySipper implements Comparable<BatterySipper> {
* Add stats from other to this BatterySipper.
*/
public void add(BatterySipper other) {
- cpuTime += other.cpuTime;
- gpsTime += other.gpsTime;
- wifiRunningTime += other.wifiRunningTime;
- cpuFgTime += other.cpuFgTime;
- wakeLockTime += other.wakeLockTime;
+ totalPowerMah += other.totalPowerMah;
+ usageTimeMs += other.usageTimeMs;
+ usagePowerMah += other.usagePowerMah;
+ cpuTimeMs += other.cpuTimeMs;
+ gpsTimeMs += other.gpsTimeMs;
+ wifiRunningTimeMs += other.wifiRunningTimeMs;
+ cpuFgTimeMs += other.cpuFgTimeMs;
+ wakeLockTimeMs += other.wakeLockTimeMs;
mobileRxPackets += other.mobileRxPackets;
mobileTxPackets += other.mobileTxPackets;
mobileActive += other.mobileActive;
@@ -138,11 +145,20 @@ public class BatterySipper implements Comparable<BatterySipper> {
mobileTxBytes += other.mobileTxBytes;
wifiRxBytes += other.wifiRxBytes;
wifiTxBytes += other.wifiTxBytes;
- wifiPower += other.wifiPower;
- gpsPower += other.gpsPower;
- cpuPower += other.cpuPower;
- sensorPower += other.sensorPower;
- mobileRadioPower += other.mobileRadioPower;
- wakeLockPower += other.wakeLockPower;
+ wifiPowerMah += other.wifiPowerMah;
+ gpsPowerMah += other.gpsPowerMah;
+ cpuPowerMah += other.cpuPowerMah;
+ sensorPowerMah += other.sensorPowerMah;
+ mobileRadioPowerMah += other.mobileRadioPowerMah;
+ wakeLockPowerMah += other.wakeLockPowerMah;
+ }
+
+ /**
+ * Sum all the powers and store the value into `value`.
+ * @return the sum of all the power in this BatterySipper.
+ */
+ public double sumPower() {
+ return totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + sensorPowerMah
+ + mobileRadioPowerMah + wakeLockPowerMah;
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index d3611bf..024b7c5 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -16,17 +16,12 @@
package com.android.internal.os;
-import static android.os.BatteryStats.NETWORK_MOBILE_RX_DATA;
-import static android.os.BatteryStats.NETWORK_MOBILE_TX_DATA;
-import static android.os.BatteryStats.NETWORK_WIFI_RX_DATA;
-import static android.os.BatteryStats.NETWORK_WIFI_TX_DATA;
-
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
import android.os.BatteryStats;
import android.os.BatteryStats.Uid;
import android.os.Bundle;
@@ -38,7 +33,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.telephony.SignalStrength;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
@@ -54,7 +48,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.Map;
/**
* A helper class for retrieving the power usage information for all applications and services.
@@ -63,8 +56,7 @@ import java.util.Map;
* onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
*/
public final class BatteryStatsHelper {
-
- private static final boolean DEBUG = false;
+ static final boolean DEBUG = false;
private static final String TAG = BatteryStatsHelper.class.getSimpleName();
@@ -81,14 +73,24 @@ public final class BatteryStatsHelper {
private Intent mBatteryBroadcast;
private PowerProfile mPowerProfile;
- private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>();
- private final List<BatterySipper> mWifiSippers = new ArrayList<BatterySipper>();
- private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>();
- private final SparseArray<List<BatterySipper>> mUserSippers
- = new SparseArray<List<BatterySipper>>();
- private final SparseArray<Double> mUserPower = new SparseArray<Double>();
+ /**
+ * List of apps using power.
+ */
+ private final List<BatterySipper> mUsageList = new ArrayList<>();
+
+ /**
+ * List of apps using wifi power.
+ */
+ private final List<BatterySipper> mWifiSippers = new ArrayList<>();
+
+ /**
+ * List of apps using bluetooth power.
+ */
+ private final List<BatterySipper> mBluetoothSippers = new ArrayList<>();
+
+ private final SparseArray<List<BatterySipper>> mUserSippers = new SparseArray<>();
- private final List<BatterySipper> mMobilemsppList = new ArrayList<BatterySipper>();
+ private final List<BatterySipper> mMobilemsppList = new ArrayList<>();
private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
@@ -102,29 +104,50 @@ public final class BatteryStatsHelper {
long mChargeTimeRemaining;
private long mStatsPeriod = 0;
+
+ // The largest entry by power.
private double mMaxPower = 1;
+
+ // The largest real entry by power (not undercounted or overcounted).
private double mMaxRealPower = 1;
+
+ // Total computed power.
private double mComputedPower;
private double mTotalPower;
- private double mWifiPower;
- private double mBluetoothPower;
private double mMinDrainedPower;
private double mMaxDrainedPower;
- // How much the apps together have kept the mobile radio active.
- private long mAppMobileActive;
+ PowerCalculator mCpuPowerCalculator;
+ PowerCalculator mWakelockPowerCalculator;
+ MobileRadioPowerCalculator mMobileRadioPowerCalculator;
+ PowerCalculator mWifiPowerCalculator;
+ PowerCalculator mBluetoothPowerCalculator;
+ PowerCalculator mSensorPowerCalculator;
+
+ public static boolean checkWifiOnly(Context context) {
+ ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ }
- // How much the apps together have left WIFI running.
- private long mAppWifiRunning;
+ public static boolean checkHasWifiPowerReporting(Context context, PowerProfile profile) {
+ WifiManager manager = context.getSystemService(WifiManager.class);
+ if (manager.isEnhancedPowerReportingSupported()) {
+ if (profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 &&
+ profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 &&
+ profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
public BatteryStatsHelper(Context context) {
this(context, true);
}
public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) {
- mContext = context;
- mCollectBatteryBroadcast = collectBatteryBroadcast;
- mWifiOnly = checkWifiOnly(context);
+ this(context, collectBatteryBroadcast, checkWifiOnly(context));
}
public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) {
@@ -133,12 +156,6 @@ public final class BatteryStatsHelper {
mWifiOnly = wifiOnly;
}
- public static boolean checkWifiOnly(Context context) {
- ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
- }
-
public void storeStatsHistoryInFile(String fname) {
synchronized (sFileXfer) {
File path = makeFilePath(mContext, fname);
@@ -260,7 +277,7 @@ public final class BatteryStatsHelper {
* Refreshes the power usage list.
*/
public void refreshStats(int statsType, int asUser) {
- SparseArray<UserHandle> users = new SparseArray<UserHandle>(1);
+ SparseArray<UserHandle> users = new SparseArray<>(1);
users.put(asUser, new UserHandle(asUser));
refreshStats(statsType, users);
}
@@ -270,7 +287,7 @@ public final class BatteryStatsHelper {
*/
public void refreshStats(int statsType, List<UserHandle> asUsers) {
final int n = asUsers.size();
- SparseArray<UserHandle> users = new SparseArray<UserHandle>(n);
+ SparseArray<UserHandle> users = new SparseArray<>(n);
for (int i = 0; i < n; ++i) {
UserHandle userHandle = asUsers.get(i);
users.put(userHandle.getIdentifier(), userHandle);
@@ -295,22 +312,52 @@ public final class BatteryStatsHelper {
mMaxRealPower = 0;
mComputedPower = 0;
mTotalPower = 0;
- mWifiPower = 0;
- mBluetoothPower = 0;
- mAppMobileActive = 0;
- mAppWifiRunning = 0;
mUsageList.clear();
mWifiSippers.clear();
mBluetoothSippers.clear();
mUserSippers.clear();
- mUserPower.clear();
mMobilemsppList.clear();
if (mStats == null) {
return;
}
+ if (mCpuPowerCalculator == null) {
+ mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
+ }
+ mCpuPowerCalculator.reset();
+
+ if (mWakelockPowerCalculator == null) {
+ mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile);
+ }
+ mWakelockPowerCalculator.reset();
+
+ if (mMobileRadioPowerCalculator == null) {
+ mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats);
+ }
+ mMobileRadioPowerCalculator.reset(mStats);
+
+ if (mWifiPowerCalculator == null) {
+ if (checkHasWifiPowerReporting(mContext, mPowerProfile)) {
+ mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile);
+ } else {
+ mWifiPowerCalculator = new WifiPowerEstimator(mPowerProfile);
+ }
+ }
+ mWifiPowerCalculator.reset();
+
+ if (mBluetoothPowerCalculator == null) {
+ mBluetoothPowerCalculator = new BluetoothPowerCalculator();
+ }
+ mBluetoothPowerCalculator.reset();
+
+ if (mSensorPowerCalculator == null) {
+ mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile,
+ (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE));
+ }
+ mSensorPowerCalculator.reset();
+
mStatsType = statsType;
mRawUptime = rawUptimeUs;
mRawRealtime = rawRealtimeUs;
@@ -358,383 +405,113 @@ public final class BatteryStatsHelper {
Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
@Override
public int compare(BatterySipper lhs, BatterySipper rhs) {
- if (lhs.mobilemspp < rhs.mobilemspp) {
- return 1;
- } else if (lhs.mobilemspp > rhs.mobilemspp) {
- return -1;
- }
- return 0;
+ return Double.compare(rhs.mobilemspp, lhs.mobilemspp);
}
});
processMiscUsage();
+ Collections.sort(mUsageList);
+
+ // At this point, we've sorted the list so we are guaranteed the max values are at the top.
+ // We have only added real powers so far.
+ if (!mUsageList.isEmpty()) {
+ mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
+ final int usageListCount = mUsageList.size();
+ for (int i = 0; i < usageListCount; i++) {
+ mComputedPower += mUsageList.get(i).totalPowerMah;
+ }
+ }
+
if (DEBUG) {
Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge="
+ makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower));
}
+
mTotalPower = mComputedPower;
if (mStats.getLowDischargeAmountSinceCharge() > 1) {
if (mMinDrainedPower > mComputedPower) {
double amount = mMinDrainedPower - mComputedPower;
mTotalPower = mMinDrainedPower;
- addEntryNoTotal(BatterySipper.DrainType.UNACCOUNTED, 0, amount);
+ BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount);
+
+ // Insert the BatterySipper in its sorted position.
+ int index = Collections.binarySearch(mUsageList, bs);
+ if (index < 0) {
+ index = -(index + 1);
+ }
+ mUsageList.add(index, bs);
+ mMaxPower = Math.max(mMaxPower, amount);
} else if (mMaxDrainedPower < mComputedPower) {
double amount = mComputedPower - mMaxDrainedPower;
- addEntryNoTotal(BatterySipper.DrainType.OVERCOUNTED, 0, amount);
+
+ // Insert the BatterySipper in its sorted position.
+ BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
+ int index = Collections.binarySearch(mUsageList, bs);
+ if (index < 0) {
+ index = -(index + 1);
+ }
+ mUsageList.add(index, bs);
+ mMaxPower = Math.max(mMaxPower, amount);
}
}
-
- Collections.sort(mUsageList);
}
private void processAppUsage(SparseArray<UserHandle> asUsers) {
final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
- final SensorManager sensorManager =
- (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
- final int which = mStatsType;
- final int speedSteps = mPowerProfile.getNumSpeedSteps();
- final double[] powerCpuNormal = new double[speedSteps];
- final long[] cpuSpeedStepTimes = new long[speedSteps];
- for (int p = 0; p < speedSteps; p++) {
- powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
- }
- final double mobilePowerPerPacket = getMobilePowerPerPacket();
- final double mobilePowerPerMs = getMobilePowerPerMs();
- final double wifiPowerPerPacket = getWifiPowerPerPacket();
- long totalAppWakelockTimeUs = 0;
- BatterySipper osApp = null;
mStatsPeriod = mTypeBatteryRealtime;
- final ArrayList<BatterySipper> appList = new ArrayList<>();
-
- // Max values used to normalize later.
- double maxWifiPower = 0;
- double maxCpuPower = 0;
- double maxWakeLockPower = 0;
- double maxMobileRadioPower = 0;
- double maxGpsPower = 0;
- double maxSensorPower = 0;
-
final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
final int NU = uidStats.size();
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
- final BatterySipper app = new BatterySipper(
- BatterySipper.DrainType.APP, u, new double[]{0});
-
- final Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
- if (processStats.size() > 0) {
- // Process CPU time.
-
- // Keep track of the package with highest drain.
- double highestDrain = 0;
-
- for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
- : processStats.entrySet()) {
- Uid.Proc ps = ent.getValue();
- app.cpuFgTime += ps.getForegroundTime(which);
- final long totalCpuTime = ps.getUserTime(which) + ps.getSystemTime(which);
- app.cpuTime += totalCpuTime;
-
- // Calculate the total CPU time spent at the various speed steps.
- long totalTimeAtSpeeds = 0;
- for (int step = 0; step < speedSteps; step++) {
- cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
- totalTimeAtSpeeds += cpuSpeedStepTimes[step];
- }
- totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1);
-
- // Then compute the ratio of time spent at each speed and figure out
- // the total power consumption.
- double cpuPower = 0;
- for (int step = 0; step < speedSteps; step++) {
- final double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
- final double cpuSpeedStepPower =
- ratio * totalCpuTime * powerCpuNormal[step];
- if (DEBUG && ratio != 0) {
- Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
- + step + " ratio=" + makemAh(ratio) + " power="
- + makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
- }
- cpuPower += cpuSpeedStepPower;
- }
-
- if (DEBUG && cpuPower != 0) {
- Log.d(TAG, String.format("process %s, cpu power=%s",
- ent.getKey(), makemAh(cpuPower / (60 * 60 * 1000))));
- }
- app.cpuPower += cpuPower;
-
- // Each App can have multiple packages and with multiple running processes.
- // Keep track of the package who's process has the highest drain.
- if (app.packageWithHighestDrain == null ||
- app.packageWithHighestDrain.startsWith("*")) {
- highestDrain = cpuPower;
- app.packageWithHighestDrain = ent.getKey();
- } else if (highestDrain < cpuPower && !ent.getKey().startsWith("*")) {
- highestDrain = cpuPower;
- app.packageWithHighestDrain = ent.getKey();
- }
- }
- }
-
- // Ensure that the CPU times make sense.
- if (app.cpuFgTime > app.cpuTime) {
- if (DEBUG && app.cpuFgTime > app.cpuTime + 10000) {
- Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
- }
-
- // Statistics may not have been gathered yet.
- app.cpuTime = app.cpuFgTime;
- }
-
- // Convert the CPU power to mAh
- app.cpuPower /= (60 * 60 * 1000);
- maxCpuPower = Math.max(maxCpuPower, app.cpuPower);
-
- // Process wake lock usage
- final Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
- u.getWakelockStats();
- long wakeLockTimeUs = 0;
- for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
- : wakelockStats.entrySet()) {
- final Uid.Wakelock wakelock = wakelockEntry.getValue();
-
- // Only care about partial wake locks since full wake locks
- // are canceled when the user turns the screen off.
- BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
- if (timer != null) {
- wakeLockTimeUs += timer.getTotalTimeLocked(mRawRealtime, which);
- }
- }
- app.wakeLockTime = wakeLockTimeUs / 1000; // convert to millis
- totalAppWakelockTimeUs += wakeLockTimeUs;
-
- // Add cost of holding a wake lock.
- app.wakeLockPower = (app.wakeLockTime *
- mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60 * 60 * 1000);
- if (DEBUG && app.wakeLockPower != 0) {
- Log.d(TAG, "UID " + u.getUid() + ": wake "
- + app.wakeLockTime + " power=" + makemAh(app.wakeLockPower));
- }
- maxWakeLockPower = Math.max(maxWakeLockPower, app.wakeLockPower);
-
- // Add cost of mobile traffic.
- final long mobileActive = u.getMobileRadioActiveTime(mStatsType);
- app.mobileRxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
- app.mobileTxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
- app.mobileActive = mobileActive / 1000;
- app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType);
- app.mobileRxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
- app.mobileTxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);
-
- if (mobileActive > 0) {
- // We are tracking when the radio is up, so can use the active time to
- // determine power use.
- mAppMobileActive += mobileActive;
- app.mobileRadioPower = (mobilePowerPerMs * mobileActive) / 1000;
- } else {
- // We are not tracking when the radio is up, so must approximate power use
- // based on the number of packets.
- app.mobileRadioPower = (app.mobileRxPackets + app.mobileTxPackets)
- * mobilePowerPerPacket;
- }
- if (DEBUG && app.mobileRadioPower != 0) {
- Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
- + (app.mobileRxPackets + app.mobileTxPackets)
- + " active time " + mobileActive
- + " power=" + makemAh(app.mobileRadioPower));
- }
- maxMobileRadioPower = Math.max(maxMobileRadioPower, app.mobileRadioPower);
-
- // Add cost of wifi traffic
- app.wifiRxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
- app.wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
- app.wifiRxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
- app.wifiTxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);
-
- final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets)
- * wifiPowerPerPacket;
- if (DEBUG && wifiPacketPower != 0) {
- Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
- + (app.wifiRxPackets + app.wifiTxPackets)
- + " power=" + makemAh(wifiPacketPower));
- }
-
- // Add cost of keeping WIFI running.
- app.wifiRunningTime = u.getWifiRunningTime(mRawRealtime, which) / 1000;
- mAppWifiRunning += app.wifiRunningTime;
-
- final double wifiLockPower = (app.wifiRunningTime
- * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60 * 60 * 1000);
- if (DEBUG && wifiLockPower != 0) {
- Log.d(TAG, "UID " + u.getUid() + ": wifi running "
- + app.wifiRunningTime + " power=" + makemAh(wifiLockPower));
- }
-
- // Add cost of WIFI scans
- final long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000;
- final double wifiScanPower = (wifiScanTimeMs
- * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN))
- / (60 * 60 * 1000);
- if (DEBUG && wifiScanPower != 0) {
- Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
- + " power=" + makemAh(wifiScanPower));
- }
-
- // Add cost of WIFI batch scans.
- double wifiBatchScanPower = 0;
- for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
- final long batchScanTimeMs =
- u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000;
- final double batchScanPower = ((batchScanTimeMs
- * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))
- ) / (60 * 60 * 1000);
- if (DEBUG && batchScanPower != 0) {
- Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
- + " time=" + batchScanTimeMs + " power=" + makemAh(batchScanPower));
- }
- wifiBatchScanPower += batchScanPower;
- }
-
- // Add up all the WiFi costs.
- app.wifiPower = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower;
- maxWifiPower = Math.max(maxWifiPower, app.wifiPower);
-
- // Process Sensor usage
- final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
- final int NSE = sensorStats.size();
- for (int ise = 0; ise < NSE; ise++) {
- final Uid.Sensor sensor = sensorStats.valueAt(ise);
- final int sensorHandle = sensorStats.keyAt(ise);
- final BatteryStats.Timer timer = sensor.getSensorTime();
- final long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000;
- double sensorPower = 0;
- switch (sensorHandle) {
- case Uid.Sensor.GPS:
- app.gpsTime = sensorTime;
- app.gpsPower = (app.gpsTime
- * mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON))
- / (60 * 60 * 1000);
- sensorPower = app.gpsPower;
- maxGpsPower = Math.max(maxGpsPower, app.gpsPower);
- break;
- default:
- List<Sensor> sensorList = sensorManager.getSensorList(
- android.hardware.Sensor.TYPE_ALL);
- for (android.hardware.Sensor s : sensorList) {
- if (s.getHandle() == sensorHandle) {
- sensorPower = (sensorTime * s.getPower()) / (60 * 60 * 1000);
- app.sensorPower += sensorPower;
- break;
- }
- }
- }
- if (DEBUG && sensorPower != 0) {
- Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
- + " time=" + sensorTime + " power=" + makemAh(sensorPower));
- }
- }
- maxSensorPower = Math.max(maxSensorPower, app.sensorPower);
-
- final double totalUnnormalizedPower = app.cpuPower + app.wifiPower + app.wakeLockPower
- + app.mobileRadioPower + app.gpsPower + app.sensorPower;
- if (DEBUG && totalUnnormalizedPower != 0) {
- Log.d(TAG, String.format("UID %d: total power=%s",
- u.getUid(), makemAh(totalUnnormalizedPower)));
+ final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
+
+ mCpuPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
+ mWakelockPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
+ mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
+ mWifiPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
+ mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
+ mSensorPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
+
+ final double totalPower = app.sumPower();
+ if (DEBUG && totalPower != 0) {
+ Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(),
+ makemAh(totalPower)));
}
// Add the app to the list if it is consuming power.
- if (totalUnnormalizedPower != 0 || u.getUid() == 0) {
- appList.add(app);
- }
- }
-
- // Fetch real power consumption from hardware.
- double actualTotalWifiPower = 0.0;
- if (mStats.getWifiControllerActivity(BatteryStats.CONTROLLER_ENERGY, mStatsType) != 0) {
- final double kDefaultVoltage = 3.36;
- final long energy = mStats.getWifiControllerActivity(
- BatteryStats.CONTROLLER_ENERGY, mStatsType);
- final double voltage = mPowerProfile.getAveragePowerOrDefault(
- PowerProfile.OPERATING_VOLTAGE_WIFI, kDefaultVoltage);
- actualTotalWifiPower = energy / (voltage * 1000*60*60);
- }
-
- final int appCount = appList.size();
- for (int i = 0; i < appCount; i++) {
- // Normalize power where possible.
- final BatterySipper app = appList.get(i);
- if (actualTotalWifiPower != 0) {
- app.wifiPower = (app.wifiPower / maxWifiPower) * actualTotalWifiPower;
- }
-
- // Assign the final power consumption here.
- final double power = app.wifiPower + app.cpuPower + app.wakeLockPower
- + app.mobileRadioPower + app.gpsPower + app.sensorPower;
- app.values[0] = app.value = power;
-
- //
- // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
- //
-
- final int uid = app.getUid();
- final int userId = UserHandle.getUserId(uid);
- if (uid == Process.WIFI_UID) {
- mWifiSippers.add(app);
- mWifiPower += power;
- } else if (uid == Process.BLUETOOTH_UID) {
- mBluetoothSippers.add(app);
- mBluetoothPower += power;
- } else if (!forAllUsers && asUsers.get(userId) == null
- && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
- // We are told to just report this user's apps as one large entry.
- List<BatterySipper> list = mUserSippers.get(userId);
- if (list == null) {
- list = new ArrayList<>();
- mUserSippers.put(userId, list);
- }
- list.add(app);
-
- Double userPower = mUserPower.get(userId);
- if (userPower == null) {
- userPower = power;
+ if (totalPower != 0 || u.getUid() == 0) {
+ //
+ // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
+ //
+ final int uid = app.getUid();
+ final int userId = UserHandle.getUserId(uid);
+ if (uid == Process.WIFI_UID) {
+ mWifiSippers.add(app);
+ } else if (uid == Process.BLUETOOTH_UID) {
+ mBluetoothSippers.add(app);
+ } else if (!forAllUsers && asUsers.get(userId) == null
+ && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
+ // We are told to just report this user's apps as one large entry.
+ List<BatterySipper> list = mUserSippers.get(userId);
+ if (list == null) {
+ list = new ArrayList<>();
+ mUserSippers.put(userId, list);
+ }
+ list.add(app);
} else {
- userPower += power;
+ mUsageList.add(app);
}
- mUserPower.put(userId, userPower);
- } else {
- mUsageList.add(app);
- if (power > mMaxPower) mMaxPower = power;
- if (power > mMaxRealPower) mMaxRealPower = power;
- mComputedPower += power;
- }
-
- if (uid == 0) {
- osApp = app;
- }
- }
- // The device has probably been awake for longer than the screen on
- // time and application wake lock time would account for. Assign
- // this remainder to the OS, if possible.
- if (osApp != null) {
- long wakeTimeMillis = mBatteryUptime / 1000;
- wakeTimeMillis -= (totalAppWakelockTimeUs / 1000)
- + (mStats.getScreenOnTime(mRawRealtime, which) / 1000);
- if (wakeTimeMillis > 0) {
- double power = (wakeTimeMillis
- * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE))
- / (60*60*1000);
- if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "
- + makemAh(power));
- osApp.wakeLockTime += wakeTimeMillis;
- osApp.value += power;
- osApp.values[0] += power;
- if (osApp.value > mMaxPower) mMaxPower = osApp.value;
- if (osApp.value > mMaxRealPower) mMaxRealPower = osApp.value;
- mComputedPower += power;
+ if (uid == 0) {
+ // The device has probably been awake for longer than the screen on
+ // time and application wake lock time would account for. Assign
+ // this remainder to the OS, if possible.
+ mWakelockPowerCalculator.calculateRemaining(app, mStats, mRawRealtime,
+ mRawUptime, mStatsType);
+ app.sumPower();
+ }
}
}
}
@@ -744,7 +521,7 @@ public final class BatteryStatsHelper {
double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
* phoneOnTimeMs / (60*60*1000);
if (phoneOnPower != 0) {
- BatterySipper bs = addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
+ addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
}
}
@@ -773,54 +550,19 @@ public final class BatteryStatsHelper {
}
private void addRadioUsage() {
- double power = 0;
- final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
- long signalTimeMs = 0;
- long noCoverageTimeMs = 0;
- for (int i = 0; i < BINS; i++) {
- long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, mRawRealtime, mStatsType)
- / 1000;
- double p = (strengthTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i))
- / (60*60*1000);
- if (DEBUG && p != 0) {
- Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
- + makemAh(p));
- }
- power += p;
- signalTimeMs += strengthTimeMs;
- if (i == 0) {
- noCoverageTimeMs = strengthTimeMs;
- }
- }
- long scanningTimeMs = mStats.getPhoneSignalScanningTime(mRawRealtime, mStatsType)
- / 1000;
- double p = (scanningTimeMs * mPowerProfile.getAveragePower(
- PowerProfile.POWER_RADIO_SCANNING))
- / (60*60*1000);
- if (DEBUG && p != 0) {
- Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + makemAh(p));
- }
- power += p;
- long radioActiveTimeUs = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType);
- long remainingActiveTime = (radioActiveTimeUs - mAppMobileActive) / 1000;
- if (remainingActiveTime > 0) {
- power += getMobilePowerPerMs() * remainingActiveTime;
- }
- if (power != 0) {
- BatterySipper bs =
- addEntry(BatterySipper.DrainType.CELL, signalTimeMs, power);
- if (signalTimeMs != 0) {
- bs.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
- }
- bs.mobileActive = remainingActiveTime;
- bs.mobileActiveCount = mStats.getMobileRadioActiveUnknownCount(mStatsType);
+ BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
+ mMobileRadioPowerCalculator.calculateRemaining(radio, mStats, mRawRealtime, mRawUptime,
+ mStatsType);
+ radio.sumPower();
+ if (radio.totalPowerMah > 0) {
+ mUsageList.add(radio);
}
}
private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
for (int i=0; i<from.size(); i++) {
BatterySipper wbs = from.get(i);
- if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime);
+ if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTimeMs);
bs.add(wbs);
}
bs.computeMobilemspp();
@@ -847,41 +589,12 @@ public final class BatteryStatsHelper {
* of WiFi to the WiFi subsystem.
*/
private void addWiFiUsage() {
- final long idleTimeMs = mStats.getWifiControllerActivity(
- BatteryStats.CONTROLLER_IDLE_TIME, mStatsType);
- final long txTimeMs = mStats.getWifiControllerActivity(
- BatteryStats.CONTROLLER_TX_TIME, mStatsType);
- final long rxTimeMs = mStats.getWifiControllerActivity(
- BatteryStats.CONTROLLER_RX_TIME, mStatsType);
- final long energy = mStats.getWifiControllerActivity(
- BatteryStats.CONTROLLER_ENERGY, mStatsType);
- final long totalTimeRunning = idleTimeMs + txTimeMs + rxTimeMs;
-
- double powerDrain = 0;
- if (energy == 0 && totalTimeRunning > 0) {
- // Energy is not reported, which means we may have left over power drain not attributed
- // to any app. Assign this power to the WiFi app.
- // TODO(adamlesinski): This mimics the old behavior. However, mAppWifiRunningTime
- // is the accumulation of the time each app kept the WiFi chip on. Multiple apps
- // can do this at the same time, so these times do not add up to the total time
- // the WiFi chip was on. Consider normalizing the time spent running and calculating
- // power from that? Normalizing the times will assign a weight to each app which
- // should better represent power usage.
- powerDrain = ((totalTimeRunning - mAppWifiRunning)
- * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
- }
-
- if (DEBUG && powerDrain != 0) {
- Log.d(TAG, "Wifi active: time=" + (txTimeMs + rxTimeMs)
- + " power=" + makemAh(powerDrain));
- }
-
- // TODO(adamlesinski): mWifiPower is already added as a BatterySipper...
- // Are we double counting here?
- final double power = mWifiPower + powerDrain;
- if (power > 0) {
- BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, totalTimeRunning, power);
+ BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
+ mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime, mStatsType);
+ bs.sumPower();
+ if (bs.totalPowerMah > 0 || !mWifiSippers.isEmpty()) {
aggregateSippers(bs, mWifiSippers, "WIFI");
+ mUsageList.add(bs);
}
}
@@ -890,30 +603,10 @@ public final class BatteryStatsHelper {
* Bluetooth Category.
*/
private void addBluetoothUsage() {
- final double kDefaultVoltage = 3.36;
- final long idleTimeMs = mStats.getBluetoothControllerActivity(
- BatteryStats.CONTROLLER_IDLE_TIME, mStatsType);
- final long txTimeMs = mStats.getBluetoothControllerActivity(
- BatteryStats.CONTROLLER_TX_TIME, mStatsType);
- final long rxTimeMs = mStats.getBluetoothControllerActivity(
- BatteryStats.CONTROLLER_RX_TIME, mStatsType);
- final long energy = mStats.getBluetoothControllerActivity(
- BatteryStats.CONTROLLER_ENERGY, mStatsType);
- final double voltage = mPowerProfile.getAveragePowerOrDefault(
- PowerProfile.OPERATING_VOLTAGE_BLUETOOTH, kDefaultVoltage);
-
- // energy is measured in mA * V * ms, and we are interested in mAh
- final double powerDrain = energy / (voltage * 60*60*1000);
-
- if (DEBUG && powerDrain != 0) {
- Log.d(TAG, "Bluetooth active: time=" + (txTimeMs + rxTimeMs)
- + " power=" + makemAh(powerDrain));
- }
-
- final long totalTime = idleTimeMs + txTimeMs + rxTimeMs;
- final double power = mBluetoothPower + powerDrain;
- if (power > 0) {
- BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, totalTime, power);
+ BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
+ mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime,
+ mStatsType);
+ if (bs.sumPower() > 0) {
aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
}
}
@@ -928,55 +621,16 @@ public final class BatteryStatsHelper {
}
private void addUserUsage() {
- for (int i=0; i<mUserSippers.size(); i++) {
+ for (int i = 0; i < mUserSippers.size(); i++) {
final int userId = mUserSippers.keyAt(i);
- final List<BatterySipper> sippers = mUserSippers.valueAt(i);
- Double userPower = mUserPower.get(userId);
- double power = (userPower != null) ? userPower : 0.0;
- BatterySipper bs = addEntry(BatterySipper.DrainType.USER, 0, power);
+ BatterySipper bs = new BatterySipper(DrainType.USER, null, 0);
bs.userId = userId;
- aggregateSippers(bs, sippers, "User");
+ aggregateSippers(bs, mUserSippers.valueAt(i), "User");
+ bs.sumPower();
+ mUsageList.add(bs);
}
}
- /**
- * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
- */
- private double getMobilePowerPerPacket() {
- final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
- final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
- / 3600;
-
- final long mobileRx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
- final long mobileTx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
- final long mobileData = mobileRx + mobileTx;
-
- final long radioDataUptimeMs
- = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType) / 1000;
- final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0)
- ? (mobileData / (double)radioDataUptimeMs)
- : (((double)MOBILE_BPS) / 8 / 2048);
-
- return (MOBILE_POWER / mobilePps) / (60*60);
- }
-
- /**
- * Return estimated power (in mAs) of keeping the radio up
- */
- private double getMobilePowerPerMs() {
- return mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) / (60*60*1000);
- }
-
- /**
- * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
- */
- private double getWifiPowerPerPacket() {
- final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
- final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
- / 3600;
- return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60);
- }
-
private void processMiscUsage() {
addUserUsage();
addPhoneUsage();
@@ -992,15 +646,10 @@ public final class BatteryStatsHelper {
}
private BatterySipper addEntry(DrainType drainType, long time, double power) {
- mComputedPower += power;
- if (power > mMaxRealPower) mMaxRealPower = power;
- return addEntryNoTotal(drainType, time, power);
- }
-
- private BatterySipper addEntryNoTotal(DrainType drainType, long time, double power) {
- if (power > mMaxPower) mMaxPower = power;
- BatterySipper bs = new BatterySipper(drainType, null, new double[] {power});
- bs.usageTime = time;
+ BatterySipper bs = new BatterySipper(drainType, null, 0);
+ bs.usagePowerMah = power;
+ bs.usageTimeMs = time;
+ bs.sumPower();
mUsageList.add(bs);
return bs;
}
@@ -1015,7 +664,7 @@ public final class BatteryStatsHelper {
public long getStatsPeriod() { return mStatsPeriod; }
- public int getStatsType() { return mStatsType; };
+ public int getStatsType() { return mStatsType; }
public double getMaxPower() { return mMaxPower; }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 2c34ded..793d0d3 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,18 +16,14 @@
package com.android.internal.os;
-import static android.net.NetworkStats.UID_ALL;
-import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
-
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.bluetooth.BluetoothActivityEnergyInfo;
-import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
-import android.net.wifi.IWifiManager;
import android.net.wifi.WifiActivityEnergyInfo;
import android.net.wifi.WifiManager;
import android.os.BadParcelableException;
@@ -42,8 +38,6 @@ import android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Parcelable;
import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.WorkSource;
@@ -61,17 +55,19 @@ import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
+import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.Xml;
import android.view.Display;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
+import com.android.server.NetworkManagementSocketTagger;
+import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -100,6 +96,7 @@ import java.util.concurrent.locks.ReentrantLock;
public final class BatteryStatsImpl extends BatteryStats {
private static final String TAG = "BatteryStatsImpl";
private static final boolean DEBUG = false;
+ private static final boolean DEBUG_ENERGY = false;
private static final boolean DEBUG_HISTORY = false;
private static final boolean USE_OLD_HISTORY = false; // for debugging.
@@ -109,7 +106,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 122 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 123 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -132,6 +129,9 @@ public final class BatteryStatsImpl extends BatteryStats {
static final int MSG_REPORT_POWER_CHANGE = 2;
static final long DELAY_UPDATE_WAKELOCKS = 5*1000;
+ private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+ private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
public interface BatteryCallback {
public void batteryNeedsCpuUpdate();
public void batteryPowerChanged(boolean onBattery);
@@ -160,7 +160,12 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ public interface ExternalStatsSync {
+ void scheduleSync();
+ }
+
public final MyHandler mHandler;
+ private final ExternalStatsSync mExternalSync;
private BatteryCallback mCallback;
@@ -179,22 +184,20 @@ public final class BatteryStatsImpl extends BatteryStats {
// elapsed time by the number of active timers to arrive at that timer's share of the time.
// In order to do this, we must refresh each timer whenever the number of active timers
// changes.
- final ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<StopwatchTimer>();
- final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<StopwatchTimer>();
- final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<StopwatchTimer>();
- final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers
- = new SparseArray<ArrayList<StopwatchTimer>>();
- final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<StopwatchTimer>();
- final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<StopwatchTimer>();
- final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<StopwatchTimer>();
- final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<StopwatchTimer>();
- final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers =
- new SparseArray<ArrayList<StopwatchTimer>>();
- final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<StopwatchTimer>();
- final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<StopwatchTimer>();
+ final ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>();
+ final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
+ final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
+ final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>();
+ final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>();
+ final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>();
+ final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>();
+ final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>();
+ final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = new SparseArray<>();
+ final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>();
+ final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>();
// Last partial timers we use for distributing CPU usage.
- final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<StopwatchTimer>();
+ final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<>();
// These are the objects that will want to do something when the device
// is unplugged from power.
@@ -224,7 +227,7 @@ public final class BatteryStatsImpl extends BatteryStats {
final HistoryItem mHistoryLastLastWritten = new HistoryItem();
final HistoryItem mHistoryReadTmp = new HistoryItem();
final HistoryItem mHistoryAddTmp = new HistoryItem();
- final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap();
+ final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
String[] mReadHistoryStrings;
int[] mReadHistoryUids;
int mReadHistoryChars;
@@ -330,7 +333,7 @@ public final class BatteryStatsImpl extends BatteryStats {
int mPhoneSignalStrengthBin = -1;
int mPhoneSignalStrengthBinRaw = -1;
- final StopwatchTimer[] mPhoneSignalStrengthsTimer =
+ final StopwatchTimer[] mPhoneSignalStrengthsTimer =
new StopwatchTimer[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
StopwatchTimer mPhoneSignalScanningTimer;
@@ -445,18 +448,19 @@ public final class BatteryStatsImpl extends BatteryStats {
private int mLoadedNumConnectivityChange;
private int mUnpluggedNumConnectivityChange;
+ private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry();
+
+ private PowerProfile mPowerProfile;
+
/*
* Holds a SamplingTimer associated with each kernel wakelock name being tracked.
*/
- private final HashMap<String, SamplingTimer> mKernelWakelockStats =
- new HashMap<String, SamplingTimer>();
+ private final HashMap<String, SamplingTimer> mKernelWakelockStats = new HashMap<>();
public Map<String, ? extends Timer> getKernelWakelockStats() {
return mKernelWakelockStats;
}
- private static int sKernelWakelockUpdateVersion = 0;
-
String mLastWakeupReason = null;
long mLastWakeupUptimeMs = 0;
private final HashMap<String, SamplingTimer> mWakeupReasonStats = new HashMap<>();
@@ -465,55 +469,12 @@ public final class BatteryStatsImpl extends BatteryStats {
return mWakeupReasonStats;
}
- private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
- Process.PROC_TAB_TERM|Process.PROC_OUT_STRING| // 0: name
- Process.PROC_QUOTES,
- Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 1: count
- Process.PROC_TAB_TERM,
- Process.PROC_TAB_TERM,
- Process.PROC_TAB_TERM,
- Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 5: totalTime
- };
-
- private static final int[] WAKEUP_SOURCES_FORMAT = new int[] {
- Process.PROC_TAB_TERM|Process.PROC_OUT_STRING, // 0: name
- Process.PROC_TAB_TERM|Process.PROC_COMBINE|
- Process.PROC_OUT_LONG, // 1: count
- Process.PROC_TAB_TERM|Process.PROC_COMBINE,
- Process.PROC_TAB_TERM|Process.PROC_COMBINE,
- Process.PROC_TAB_TERM|Process.PROC_COMBINE,
- Process.PROC_TAB_TERM|Process.PROC_COMBINE,
- Process.PROC_TAB_TERM|Process.PROC_COMBINE
- |Process.PROC_OUT_LONG, // 6: totalTime
- };
-
- private final String[] mProcWakelocksName = new String[3];
- private final long[] mProcWakelocksData = new long[3];
-
- /*
- * Used as a buffer for reading in data from /proc/wakelocks before it is processed and added
- * to mKernelWakelockStats.
- */
- private final Map<String, KernelWakelockStats> mProcWakelockFileStats = new HashMap<>();
-
- private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory();
- private NetworkStats mCurMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
- private NetworkStats mLastMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
- private NetworkStats mCurWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
- private NetworkStats mLastWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
- private NetworkStats mTmpNetworkStats;
- private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry();
-
- @GuardedBy("this")
- private String[] mMobileIfaces = new String[0];
- @GuardedBy("this")
- private String[] mWifiIfaces = new String[0];
-
public BatteryStatsImpl() {
mFile = null;
mCheckinFile = null;
mDailyFile = null;
mHandler = null;
+ mExternalSync = null;
clearHistoryLocked();
}
@@ -523,7 +484,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
static class TimeBase {
- private final ArrayList<TimeBaseObs> mObservers = new ArrayList<TimeBaseObs>();
+ private final ArrayList<TimeBaseObs> mObservers = new ArrayList<>();
private long mUptime;
private long mRealtime;
@@ -969,6 +930,12 @@ public final class BatteryStatsImpl extends BatteryStats {
long mUnpluggedTime;
/**
+ * The total time this timer has been running until the latest mark has been set.
+ * Subtract this from mTotalTime to get the time spent running since the mark was set.
+ */
+ long mTimeBeforeMark;
+
+ /**
* Constructs from a parcel.
* @param type
* @param timeBase
@@ -986,6 +953,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mLoadedTime = in.readLong();
mLastTime = 0;
mUnpluggedTime = in.readLong();
+ mTimeBeforeMark = in.readLong();
timeBase.add(this);
if (DEBUG) Log.i(TAG, "**** READ TIMER #" + mType + ": mTotalTime=" + mTotalTime);
}
@@ -1005,7 +973,7 @@ public final class BatteryStatsImpl extends BatteryStats {
* so can be completely dropped.
*/
boolean reset(boolean detachIfReset) {
- mTotalTime = mLoadedTime = mLastTime = 0;
+ mTotalTime = mLoadedTime = mLastTime = mTimeBeforeMark = 0;
mCount = mLoadedCount = mLastCount = 0;
if (detachIfReset) {
detach();
@@ -1026,8 +994,10 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)));
out.writeLong(mLoadedTime);
out.writeLong(mUnpluggedTime);
+ out.writeLong(mTimeBeforeMark);
}
+ @Override
public void onTimeStarted(long elapsedRealtime, long timeBaseUptime, long baseRealtime) {
if (DEBUG && mType < 0) {
Log.v(TAG, "unplug #" + mType + ": realtime=" + baseRealtime
@@ -1043,6 +1013,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ @Override
public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
if (DEBUG && mType < 0) {
Log.v(TAG, "plug #" + mType + ": realtime=" + baseRealtime
@@ -1096,6 +1067,13 @@ public final class BatteryStatsImpl extends BatteryStats {
return val;
}
+ @Override
+ public long getTimeSinceMarkLocked(long elapsedRealtimeUs) {
+ long val = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs));
+ return val - mTimeBeforeMark;
+ }
+
+ @Override
public void logState(Printer pw, String prefix) {
pw.println(prefix + "mCount=" + mCount
+ " mLoadedCount=" + mLoadedCount + " mLastCount=" + mLastCount
@@ -1121,6 +1099,9 @@ public final class BatteryStatsImpl extends BatteryStats {
mCount = mLoadedCount = in.readInt();
mLastCount = 0;
mUnpluggedCount = mCount;
+
+ // When reading the summary, we set the mark to be the latest information.
+ mTimeBeforeMark = mTotalTime;
}
}
@@ -1516,21 +1497,6 @@ public final class BatteryStatsImpl extends BatteryStats {
return mNesting > 0;
}
- long checkpointRunningLocked(long elapsedRealtimeMs) {
- if (mNesting > 0) {
- // We are running...
- final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
- if (mTimerPool != null) {
- return refreshTimersLocked(batteryRealtime, mTimerPool, this);
- }
- final long heldTime = batteryRealtime - mUpdateTime;
- mUpdateTime = batteryRealtime;
- mTotalTime += heldTime;
- return heldTime;
- }
- return 0;
- }
-
void stopRunningLocked(long elapsedRealtimeMs) {
// Ignore attempt to stop a timer that isn't running
if (mNesting == 0) {
@@ -1608,6 +1574,7 @@ public final class BatteryStatsImpl extends BatteryStats {
return mCount;
}
+ @Override
boolean reset(boolean detachIfReset) {
boolean canDetach = mNesting <= 0;
super.reset(canDetach && detachIfReset);
@@ -1618,6 +1585,7 @@ public final class BatteryStatsImpl extends BatteryStats {
return canDetach;
}
+ @Override
void detach() {
super.detach();
if (mTimerPool != null) {
@@ -1625,10 +1593,31 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ @Override
void readSummaryFromParcelLocked(Parcel in) {
super.readSummaryFromParcelLocked(in);
mNesting = 0;
}
+
+ /**
+ * Set the mark so that we can query later for the total time the timer has
+ * accumulated since this point. The timer can be running or not.
+ *
+ * @param elapsedRealtimeMs the current elapsed realtime in milliseconds.
+ */
+ public void setMark(long elapsedRealtimeMs) {
+ final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
+ if (mNesting > 0) {
+ // We are running.
+ if (mTimerPool != null) {
+ refreshTimersLocked(batteryRealtime, mTimerPool, this);
+ } else {
+ mTotalTime += batteryRealtime - mUpdateTime;
+ mUpdateTime = batteryRealtime;
+ }
+ }
+ mTimeBeforeMark = mTotalTime;
+ }
}
public abstract class OverflowArrayMap<T> {
@@ -1778,147 +1767,6 @@ public final class BatteryStatsImpl extends BatteryStats {
return timer;
}
- private final Map<String, KernelWakelockStats> readKernelWakelockStats() {
-
- FileInputStream is;
- byte[] buffer = new byte[32*1024];
- int len;
- boolean wakeup_sources;
-
- try {
- try {
- is = new FileInputStream("/d/wakeup_sources");
- wakeup_sources = true;
- } catch (java.io.FileNotFoundException e) {
- try {
- is = new FileInputStream("/proc/wakelocks");
- wakeup_sources = false;
- } catch (java.io.FileNotFoundException e2) {
- return null;
- }
- }
-
- len = is.read(buffer);
- is.close();
- } catch (java.io.IOException e) {
- return null;
- }
-
- if (len > 0) {
- if (len >= buffer.length) {
- Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length);
- }
- int i;
- for (i=0; i<len; i++) {
- if (buffer[i] == '\0') {
- len = i;
- break;
- }
- }
- }
-
- return parseProcWakelocks(buffer, len, wakeup_sources);
- }
-
- private final Map<String, KernelWakelockStats> parseProcWakelocks(
- byte[] wlBuffer, int len, boolean wakeup_sources) {
- String name;
- int count;
- long totalTime;
- int startIndex;
- int endIndex;
- int numUpdatedWlNames = 0;
-
- // Advance past the first line.
- int i;
- for (i = 0; i < len && wlBuffer[i] != '\n' && wlBuffer[i] != '\0'; i++);
- startIndex = endIndex = i + 1;
-
- synchronized(this) {
- Map<String, KernelWakelockStats> m = mProcWakelockFileStats;
-
- sKernelWakelockUpdateVersion++;
- while (endIndex < len) {
- for (endIndex=startIndex;
- endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
- endIndex++);
- endIndex++; // endIndex is an exclusive upper bound.
- // Don't go over the end of the buffer, Process.parseProcLine might
- // write to wlBuffer[endIndex]
- if (endIndex >= (len - 1) ) {
- return m;
- }
-
- String[] nameStringArray = mProcWakelocksName;
- long[] wlData = mProcWakelocksData;
- // Stomp out any bad characters since this is from a circular buffer
- // A corruption is seen sometimes that results in the vm crashing
- // This should prevent crashes and the line will probably fail to parse
- for (int j = startIndex; j < endIndex; j++) {
- if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?';
- }
- boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex,
- wakeup_sources ? WAKEUP_SOURCES_FORMAT :
- PROC_WAKELOCKS_FORMAT,
- nameStringArray, wlData, null);
-
- name = nameStringArray[0];
- count = (int) wlData[1];
-
- if (wakeup_sources) {
- // convert milliseconds to microseconds
- totalTime = wlData[2] * 1000;
- } else {
- // convert nanoseconds to microseconds with rounding.
- totalTime = (wlData[2] + 500) / 1000;
- }
-
- if (parsed && name.length() > 0) {
- if (!m.containsKey(name)) {
- m.put(name, new KernelWakelockStats(count, totalTime,
- sKernelWakelockUpdateVersion));
- numUpdatedWlNames++;
- } else {
- KernelWakelockStats kwlStats = m.get(name);
- if (kwlStats.mVersion == sKernelWakelockUpdateVersion) {
- kwlStats.mCount += count;
- kwlStats.mTotalTime += totalTime;
- } else {
- kwlStats.mCount = count;
- kwlStats.mTotalTime = totalTime;
- kwlStats.mVersion = sKernelWakelockUpdateVersion;
- numUpdatedWlNames++;
- }
- }
- }
- startIndex = endIndex;
- }
-
- if (m.size() != numUpdatedWlNames) {
- // Don't report old data.
- Iterator<KernelWakelockStats> itr = m.values().iterator();
- while (itr.hasNext()) {
- if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
- itr.remove();
- }
- }
- }
- return m;
- }
- }
-
- private class KernelWakelockStats {
- public int mCount;
- public long mTotalTime;
- public int mVersion;
-
- KernelWakelockStats(int count, long totalTime, int version) {
- mCount = count;
- mTotalTime = totalTime;
- mVersion = version;
- }
- }
-
/*
* Get the KernelWakelockTimer associated with name, and create a new one if one
* doesn't already exist.
@@ -3390,7 +3238,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime);
} else {
mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
- updateNetworkActivityLocked(NET_UPDATE_MOBILE, realElapsedRealtimeMs);
+ updateMobileRadioStateLocked(realElapsedRealtimeMs);
mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
}
}
@@ -3728,6 +3576,7 @@ public final class BatteryStatsImpl extends BatteryStats {
addHistoryRecordLocked(elapsedRealtime, uptime);
mWifiOn = true;
mWifiOnTimer.startRunningLocked(elapsedRealtime);
+ scheduleSyncExternalStatsLocked();
}
}
@@ -3741,6 +3590,7 @@ public final class BatteryStatsImpl extends BatteryStats {
addHistoryRecordLocked(elapsedRealtime, uptime);
mWifiOn = false;
mWifiOnTimer.stopRunningLocked(elapsedRealtime);
+ scheduleSyncExternalStatsLocked();
}
}
@@ -3903,6 +3753,7 @@ public final class BatteryStatsImpl extends BatteryStats {
int uid = mapUid(ws.get(i));
getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
}
+ scheduleSyncExternalStatsLocked();
} else {
Log.w(TAG, "noteWifiRunningLocked -- called while WIFI running");
}
@@ -3941,6 +3792,7 @@ public final class BatteryStatsImpl extends BatteryStats {
int uid = mapUid(ws.get(i));
getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
}
+ scheduleSyncExternalStatsLocked();
} else {
Log.w(TAG, "noteWifiStoppedLocked -- called while WIFI not running");
}
@@ -3955,6 +3807,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
mWifiState = wifiState;
mWifiStateTimer[wifiState].startRunningLocked(elapsedRealtime);
+ scheduleSyncExternalStatsLocked();
}
}
@@ -4026,6 +3879,7 @@ public final class BatteryStatsImpl extends BatteryStats {
addHistoryRecordLocked(elapsedRealtime, uptime);
mBluetoothOn = true;
mBluetoothOnTimer.startRunningLocked(elapsedRealtime);
+ scheduleSyncExternalStatsLocked();
}
}
@@ -4039,6 +3893,7 @@ public final class BatteryStatsImpl extends BatteryStats {
addHistoryRecordLocked(elapsedRealtime, uptime);
mBluetoothOn = false;
mBluetoothOnTimer.stopRunningLocked(elapsedRealtime);
+ scheduleSyncExternalStatsLocked();
}
}
@@ -4259,7 +4114,9 @@ public final class BatteryStatsImpl extends BatteryStats {
// During device boot, qtaguid isn't enabled until after the inital
// loading of battery stats. Now that they're enabled, take our initial
// snapshot for future delta calculation.
- updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime());
+ final long elapsedRealtimeMs = SystemClock.elapsedRealtime();
+ updateMobileRadioStateLocked(elapsedRealtimeMs);
+ updateWifiStateLocked(null);
}
@Override public long getScreenOnTime(long elapsedRealtimeUs, int which) {
@@ -4539,6 +4396,18 @@ public final class BatteryStatsImpl extends BatteryStats {
LongSamplingCounter mMobileRadioActiveCount;
/**
+ * The amount of time this uid has kept the WiFi controller in idle, tx, and rx mode.
+ */
+ LongSamplingCounter[] mWifiControllerTime =
+ new LongSamplingCounter[NUM_CONTROLLER_ACTIVITY_TYPES];
+
+ /**
+ * The amount of time this uid has kept the Bluetooth controller in idle, tx, and rx mode.
+ */
+ LongSamplingCounter[] mBluetoothControllerTime =
+ new LongSamplingCounter[NUM_CONTROLLER_ACTIVITY_TYPES];
+
+ /**
* The CPU times we had at the last history details update.
*/
long mLastStepUserTime;
@@ -4574,22 +4443,22 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* The statistics we have collected for this uid's sensor activations.
*/
- final SparseArray<Sensor> mSensorStats = new SparseArray<Sensor>();
+ final SparseArray<Sensor> mSensorStats = new SparseArray<>();
/**
* The statistics we have collected for this uid's processes.
*/
- final ArrayMap<String, Proc> mProcessStats = new ArrayMap<String, Proc>();
+ final ArrayMap<String, Proc> mProcessStats = new ArrayMap<>();
/**
* The statistics we have collected for this uid's processes.
*/
- final ArrayMap<String, Pkg> mPackageStats = new ArrayMap<String, Pkg>();
+ final ArrayMap<String, Pkg> mPackageStats = new ArrayMap<>();
/**
* The transient wake stats we have collected for this uid's pids.
*/
- final SparseArray<Pid> mPids = new SparseArray<Pid>();
+ final SparseArray<Pid> mPids = new SparseArray<>();
public Uid(int uid) {
mUid = uid;
@@ -4750,6 +4619,13 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ public void noteWifiControllerActivityLocked(int type, long timeMs) {
+ if (mWifiControllerTime[type] == null) {
+ mWifiControllerTime[type] = new LongSamplingCounter(mOnBatteryTimeBase);
+ }
+ mWifiControllerTime[type].addCountLocked(timeMs);
+ }
+
public StopwatchTimer createAudioTurnedOnTimerLocked() {
if (mAudioTurnedOnTimer == null) {
mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
@@ -5065,6 +4941,15 @@ public final class BatteryStatsImpl extends BatteryStats {
? (int)mMobileRadioActiveCount.getCountLocked(which) : 0;
}
+ @Override
+ public long getWifiControllerActivity(int type, int which) {
+ if (type >= 0 && type < NUM_CONTROLLER_ACTIVITY_TYPES &&
+ mWifiControllerTime[type] != null) {
+ return mWifiControllerTime[type].getCountLocked(which);
+ }
+ return 0;
+ }
+
void initNetworkActivityLocked() {
mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
@@ -5148,6 +5033,16 @@ public final class BatteryStatsImpl extends BatteryStats {
mMobileRadioActiveCount.reset(false);
}
+ for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ if (mWifiControllerTime[i] != null) {
+ mWifiControllerTime[i].reset(false);
+ }
+
+ if (mBluetoothControllerTime[i] != null) {
+ mBluetoothControllerTime[i].reset(false);
+ }
+ }
+
final ArrayMap<String, Wakelock> wakeStats = mWakelockStats.getMap();
for (int iw=wakeStats.size()-1; iw>=0; iw--) {
Wakelock wl = wakeStats.valueAt(iw);
@@ -5270,6 +5165,16 @@ public final class BatteryStatsImpl extends BatteryStats {
mNetworkPacketActivityCounters[i].detach();
}
}
+
+ for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ if (mWifiControllerTime[i] != null) {
+ mWifiControllerTime[i].detach();
+ }
+
+ if (mBluetoothControllerTime[i] != null) {
+ mBluetoothControllerTime[i].detach();
+ }
+ }
mPids.clear();
}
@@ -5359,6 +5264,7 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
+
if (mAudioTurnedOnTimer != null) {
out.writeInt(1);
mAudioTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs);
@@ -5410,6 +5316,24 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
+
+ for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ if (mWifiControllerTime[i] != null) {
+ out.writeInt(1);
+ mWifiControllerTime[i].writeToParcel(out);
+ } else {
+ out.writeInt(0);
+ }
+ }
+
+ for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ if (mBluetoothControllerTime[i] != null) {
+ out.writeInt(1);
+ mBluetoothControllerTime[i].writeToParcel(out);
+ } else {
+ out.writeInt(0);
+ }
+ }
}
void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
@@ -5559,6 +5483,22 @@ public final class BatteryStatsImpl extends BatteryStats {
mNetworkByteActivityCounters = null;
mNetworkPacketActivityCounters = null;
}
+
+ for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ if (in.readInt() != 0) {
+ mWifiControllerTime[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ } else {
+ mWifiControllerTime[i] = null;
+ }
+ }
+
+ for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ if (in.readInt() != 0) {
+ mBluetoothControllerTime[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ } else {
+ mBluetoothControllerTime[i] = null;
+ }
+ }
}
/**
@@ -5949,7 +5889,7 @@ public final class BatteryStatsImpl extends BatteryStats {
Slog.w(TAG, "File corrupt: too many excessive power entries " + N);
return false;
}
-
+
mExcessivePower = new ArrayList<ExcessivePower>();
for (int i=0; i<N; i++) {
ExcessivePower ew = new ExcessivePower();
@@ -6727,7 +6667,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public BatteryStatsImpl(File systemDir, Handler handler) {
+ public BatteryStatsImpl(File systemDir, Handler handler, ExternalStatsSync externalSync) {
if (systemDir != null) {
mFile = new JournaledFile(new File(systemDir, "batterystats.bin"),
new File(systemDir, "batterystats.bin.tmp"));
@@ -6736,6 +6676,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
+ mExternalSync = externalSync;
mHandler = new MyHandler(handler.getLooper());
mStartCount++;
mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase);
@@ -6808,10 +6749,17 @@ public final class BatteryStatsImpl extends BatteryStats {
mCheckinFile = null;
mDailyFile = null;
mHandler = null;
+ mExternalSync = null;
clearHistoryLocked();
readFromParcel(p);
}
+ public void setPowerProfile(PowerProfile profile) {
+ synchronized (this) {
+ mPowerProfile = profile;
+ }
+ }
+
public void setCallback(BatteryCallback cb) {
mCallback = cb;
}
@@ -7479,21 +7427,381 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeScreenOffUnplugLevel = mDischargeCurrentLevel;
}
}
-
+
public void pullPendingStateUpdatesLocked() {
- updateKernelWakelocksLocked();
- updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime());
- // TODO(adamlesinski): enable when bluedroid stops deadlocking. b/19248786
- // updateBluetoothControllerActivityLocked();
- // TODO(adamlesinski): disabled to avoid deadlock. Need to change how external
- // data is pulled/accessed from BatteryStats. b/19729960
- // updateWifiControllerActivityLocked();
if (mOnBatteryInternal) {
final boolean screenOn = mScreenState == Display.STATE_ON;
updateDischargeScreenLevelsLocked(screenOn, screenOn);
}
}
+ private String[] mMobileIfaces = EmptyArray.STRING;
+ private String[] mWifiIfaces = EmptyArray.STRING;
+
+ private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory();
+
+ private static final int NETWORK_STATS_LAST = 0;
+ private static final int NETWORK_STATS_NEXT = 1;
+ private static final int NETWORK_STATS_DELTA = 2;
+
+ private final NetworkStats[] mMobileNetworkStats = new NetworkStats[] {
+ new NetworkStats(SystemClock.elapsedRealtime(), 50),
+ new NetworkStats(SystemClock.elapsedRealtime(), 50),
+ new NetworkStats(SystemClock.elapsedRealtime(), 50)
+ };
+
+ private final NetworkStats[] mWifiNetworkStats = new NetworkStats[] {
+ new NetworkStats(SystemClock.elapsedRealtime(), 50),
+ new NetworkStats(SystemClock.elapsedRealtime(), 50),
+ new NetworkStats(SystemClock.elapsedRealtime(), 50)
+ };
+
+ /**
+ * Retrieves the delta of network stats for the given network ifaces. Uses networkStatsBuffer
+ * as a buffer of NetworkStats objects to cycle through when computing deltas.
+ */
+ private NetworkStats getNetworkStatsDeltaLocked(String[] ifaces,
+ NetworkStats[] networkStatsBuffer)
+ throws IOException {
+ if (!SystemProperties.getBoolean(NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED,
+ false)) {
+ return null;
+ }
+
+ final NetworkStats stats = mNetworkStatsFactory.readNetworkStatsDetail(NetworkStats.UID_ALL,
+ ifaces, NetworkStats.TAG_NONE, networkStatsBuffer[NETWORK_STATS_NEXT]);
+ networkStatsBuffer[NETWORK_STATS_DELTA] = NetworkStats.subtract(stats,
+ networkStatsBuffer[NETWORK_STATS_LAST], null, null,
+ networkStatsBuffer[NETWORK_STATS_DELTA]);
+ networkStatsBuffer[NETWORK_STATS_NEXT] = networkStatsBuffer[NETWORK_STATS_LAST];
+ networkStatsBuffer[NETWORK_STATS_LAST] = stats;
+ return networkStatsBuffer[NETWORK_STATS_DELTA];
+ }
+
+ /**
+ * Distribute WiFi energy info and network traffic to apps.
+ * @param info The energy information from the WiFi controller.
+ */
+ public void updateWifiStateLocked(@Nullable final WifiActivityEnergyInfo info) {
+ final long elapsedRealtimeMs = SystemClock.elapsedRealtime();
+ NetworkStats delta = null;
+ try {
+ if (!ArrayUtils.isEmpty(mWifiIfaces)) {
+ delta = getNetworkStatsDeltaLocked(mWifiIfaces, mWifiNetworkStats);
+ }
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed to get wifi network stats", e);
+ return;
+ }
+
+ if (!mOnBatteryInternal) {
+ return;
+ }
+
+ SparseLongArray rxPackets = new SparseLongArray();
+ SparseLongArray txPackets = new SparseLongArray();
+ long totalTxPackets = 0;
+ long totalRxPackets = 0;
+ if (delta != null) {
+ final int size = delta.size();
+ for (int i = 0; i < size; i++) {
+ final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
+
+ if (DEBUG_ENERGY) {
+ Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes
+ + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
+ + " txPackets=" + entry.txPackets);
+ }
+
+ if (entry.rxBytes == 0 || entry.txBytes == 0) {
+ continue;
+ }
+
+ final Uid u = getUidStatsLocked(mapUid(entry.uid));
+ u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
+ entry.rxPackets);
+ u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
+ entry.txPackets);
+ rxPackets.put(u.getUid(), entry.rxPackets);
+ txPackets.put(u.getUid(), entry.txPackets);
+
+ // Sum the total number of packets so that the Rx Power and Tx Power can
+ // be evenly distributed amongst the apps.
+ totalRxPackets += entry.rxPackets;
+ totalTxPackets += entry.txPackets;
+
+ mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+ entry.rxBytes);
+ mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+ entry.txBytes);
+ mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+ entry.rxPackets);
+ mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+ entry.txPackets);
+ }
+ }
+
+ if (info != null) {
+ // Measured in mAms
+ final long txTimeMs = info.getControllerTxTimeMillis();
+ final long rxTimeMs = info.getControllerRxTimeMillis();
+ final long idleTimeMs = info.getControllerIdleTimeMillis();
+ final long totalTimeMs = txTimeMs + rxTimeMs + idleTimeMs;
+
+ long leftOverRxTimeMs = rxTimeMs;
+
+ if (DEBUG_ENERGY) {
+ Slog.d(TAG, "------ BEGIN WiFi power blaming ------");
+ Slog.d(TAG, " Tx Time: " + txTimeMs + " ms");
+ Slog.d(TAG, " Rx Time: " + rxTimeMs + " ms");
+ Slog.d(TAG, " Idle Time: " + idleTimeMs + " ms");
+ Slog.d(TAG, " Total Time: " + totalTimeMs + " ms");
+ }
+
+ long totalWifiLockTimeMs = 0;
+ long totalScanTimeMs = 0;
+
+ // On the first pass, collect some totals so that we can normalize power
+ // calculations if we need to.
+ final int uidStatsSize = mUidStats.size();
+ for (int i = 0; i < uidStatsSize; i++) {
+ final Uid uid = mUidStats.valueAt(i);
+
+ // Sum the total scan power for all apps.
+ totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked(
+ elapsedRealtimeMs * 1000) / 1000;
+
+ // Sum the total time holding wifi lock for all apps.
+ totalWifiLockTimeMs += uid.mFullWifiLockTimer.getTimeSinceMarkLocked(
+ elapsedRealtimeMs * 1000) / 1000;
+ }
+
+ if (DEBUG_ENERGY && totalScanTimeMs > rxTimeMs) {
+ Slog.d(TAG, " !Estimated scan time > Actual rx time (" + totalScanTimeMs + " ms > "
+ + rxTimeMs + " ms). Normalizing scan time.");
+ }
+
+ // Actually assign and distribute power usage to apps.
+ for (int i = 0; i < uidStatsSize; i++) {
+ final Uid uid = mUidStats.valueAt(i);
+
+ long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
+ elapsedRealtimeMs * 1000) / 1000;
+ if (scanTimeSinceMarkMs > 0) {
+ // Set the new mark so that next time we get new data since this point.
+ uid.mWifiScanTimer.setMark(elapsedRealtimeMs);
+
+ if (totalScanTimeMs > rxTimeMs) {
+ // Our total scan time is more than the reported Rx time.
+ // This is possible because the cost of a scan is approximate.
+ // Let's normalize the result so that we evenly blame each app
+ // scanning.
+ //
+ // This means that we may have apps that received packets not be blamed
+ // for this, but this is fine as scans are relatively more expensive.
+ scanTimeSinceMarkMs = (rxTimeMs * scanTimeSinceMarkMs) / totalScanTimeMs;
+ }
+
+ if (DEBUG_ENERGY) {
+ Slog.d(TAG, " ScanTime for UID " + uid.getUid() + ": "
+ + scanTimeSinceMarkMs + " ms)");
+ }
+ uid.noteWifiControllerActivityLocked(CONTROLLER_RX_TIME, scanTimeSinceMarkMs);
+ leftOverRxTimeMs -= scanTimeSinceMarkMs;
+ }
+
+ // Distribute evenly the power consumed while Idle to each app holding a WiFi
+ // lock.
+ final long wifiLockTimeSinceMarkMs = uid.mFullWifiLockTimer.getTimeSinceMarkLocked(
+ elapsedRealtimeMs * 1000) / 1000;
+ if (wifiLockTimeSinceMarkMs > 0) {
+ // Set the new mark so that next time we get new data since this point.
+ uid.mFullWifiLockTimer.setMark(elapsedRealtimeMs);
+
+ final long myIdleTimeMs = (wifiLockTimeSinceMarkMs * idleTimeMs)
+ / totalWifiLockTimeMs;
+ if (DEBUG_ENERGY) {
+ Slog.d(TAG, " IdleTime for UID " + uid.getUid() + ": "
+ + myIdleTimeMs + " ms");
+ }
+ uid.noteWifiControllerActivityLocked(CONTROLLER_IDLE_TIME, myIdleTimeMs);
+ }
+ }
+
+ if (DEBUG_ENERGY) {
+ Slog.d(TAG, " New RxPower: " + leftOverRxTimeMs + " ms");
+ }
+
+ // Distribute the Tx power appropriately between all apps that transmitted packets.
+ for (int i = 0; i < txPackets.size(); i++) {
+ final Uid uid = getUidStatsLocked(txPackets.keyAt(i));
+ final long myTxTimeMs = (txPackets.valueAt(i) * txTimeMs) / totalTxPackets;
+ if (DEBUG_ENERGY) {
+ Slog.d(TAG, " TxTime for UID " + uid.getUid() + ": " + myTxTimeMs + " ms");
+ }
+ uid.noteWifiControllerActivityLocked(CONTROLLER_TX_TIME, myTxTimeMs);
+ }
+
+ // Distribute the remaining Rx power appropriately between all apps that received
+ // packets.
+ for (int i = 0; i < rxPackets.size(); i++) {
+ final Uid uid = getUidStatsLocked(rxPackets.keyAt(i));
+ final long myRxTimeMs = (rxPackets.valueAt(i) * leftOverRxTimeMs) / totalRxPackets;
+ if (DEBUG_ENERGY) {
+ Slog.d(TAG, " RxTime for UID " + uid.getUid() + ": " + myRxTimeMs + " ms");
+ }
+ uid.noteWifiControllerActivityLocked(CONTROLLER_RX_TIME, myRxTimeMs);
+ }
+
+ // Any left over power use will be picked up by the WiFi category in BatteryStatsHelper.
+
+ // Update WiFi controller stats.
+ mWifiActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
+ info.getControllerRxTimeMillis());
+ mWifiActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
+ info.getControllerTxTimeMillis());
+ mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
+ info.getControllerIdleTimeMillis());
+
+ final double powerDrainMaMs;
+ if (mPowerProfile.getAveragePower(
+ PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) == 0) {
+ powerDrainMaMs = 0.0;
+ } else {
+ powerDrainMaMs = info.getControllerEnergyUsed()
+ / mPowerProfile.getAveragePower(
+ PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE);
+ }
+ mWifiActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked((long) powerDrainMaMs);
+ }
+ }
+
+ /**
+ * Distribute Cell radio energy info and network traffic to apps.
+ */
+ public void updateMobileRadioStateLocked(final long elapsedRealtimeMs) {
+ NetworkStats delta = null;
+ try {
+ if (!ArrayUtils.isEmpty(mMobileIfaces)) {
+ delta = getNetworkStatsDeltaLocked(mMobileIfaces, mMobileNetworkStats);
+ }
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed to get mobile network stats", e);
+ return;
+ }
+
+ if (delta == null || !mOnBatteryInternal) {
+ return;
+ }
+
+ long radioTime = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
+ elapsedRealtimeMs * 1000);
+ mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs);
+ long totalPackets = delta.getTotalPackets();
+
+ final int size = delta.size();
+ for (int i = 0; i < size; i++) {
+ final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
+
+ if (entry.rxBytes == 0 || entry.txBytes == 0) {
+ continue;
+ }
+
+ if (DEBUG_ENERGY) {
+ Slog.d(TAG, "Mobile uid " + entry.uid + ": delta rx=" + entry.rxBytes
+ + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
+ + " txPackets=" + entry.txPackets);
+ }
+
+ final Uid u = getUidStatsLocked(mapUid(entry.uid));
+ u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes,
+ entry.rxPackets);
+ u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes,
+ entry.txPackets);
+
+ if (radioTime > 0) {
+ // Distribute total radio active time in to this app.
+ long appPackets = entry.rxPackets + entry.txPackets;
+ long appRadioTime = (radioTime*appPackets)/totalPackets;
+ u.noteMobileRadioActiveTimeLocked(appRadioTime);
+ // Remove this app from the totals, so that we don't lose any time
+ // due to rounding.
+ radioTime -= appRadioTime;
+ totalPackets -= appPackets;
+ }
+
+ mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
+ entry.rxBytes);
+ mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
+ entry.txBytes);
+ mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
+ entry.rxPackets);
+ mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
+ entry.txPackets);
+ }
+
+ if (radioTime > 0) {
+ // Whoops, there is some radio time we can't blame on an app!
+ mMobileRadioActiveUnknownTime.addCountLocked(radioTime);
+ mMobileRadioActiveUnknownCount.addCountLocked(1);
+ }
+ }
+
+ /**
+ * Distribute Bluetooth energy info and network traffic to apps.
+ * @param info The energy information from the bluetooth controller.
+ */
+ public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info) {
+ if (info != null && mOnBatteryInternal && false) {
+ mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
+ info.getControllerRxTimeMillis());
+ mBluetoothActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
+ info.getControllerTxTimeMillis());
+ mBluetoothActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
+ info.getControllerIdleTimeMillis());
+ mBluetoothActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked(
+ info.getControllerEnergyUsed());
+ }
+ }
+
+ /**
+ * Read and distribute kernel wake lock use across apps.
+ */
+ public void updateKernelWakelocksLocked() {
+ final KernelWakelockStats wakelockStats = mKernelWakelockReader.readKernelWakelockStats(
+ mTmpWakelockStats);
+ if (wakelockStats == null) {
+ // Not crashing might make board bringup easier.
+ Slog.w(TAG, "Couldn't get kernel wake lock stats");
+ return;
+ }
+
+ for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+ String name = ent.getKey();
+ KernelWakelockStats.Entry kws = ent.getValue();
+
+ SamplingTimer kwlt = mKernelWakelockStats.get(name);
+ if (kwlt == null) {
+ kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase,
+ true /* track reported val */);
+ mKernelWakelockStats.put(name, kwlt);
+ }
+ kwlt.updateCurrentReportedCount(kws.mCount);
+ kwlt.updateCurrentReportedTotalTime(kws.mTotalTime);
+ kwlt.setUpdateVersion(kws.mVersion);
+ }
+
+ if (wakelockStats.size() != mKernelWakelockStats.size()) {
+ // Set timers to stale if they didn't appear in /proc/wakelocks this time.
+ for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
+ SamplingTimer st = ent.getValue();
+ if (st.getUpdateVersion() != wakelockStats.kernelWakelockVersion) {
+ st.setStale();
+ }
+ }
+ }
+ }
+
void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
final int oldStatus, final int level) {
boolean doWrite = false;
@@ -7647,340 +7955,132 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ private void scheduleSyncExternalStatsLocked() {
+ if (mExternalSync != null) {
+ mExternalSync.scheduleSync();
+ }
+ }
+
// This should probably be exposed in the API, though it's not critical
- private static final int BATTERY_PLUGGED_NONE = 0;
+ public static final int BATTERY_PLUGGED_NONE = 0;
- public void setBatteryState(int status, int health, int plugType, int level,
+ public void setBatteryStateLocked(int status, int health, int plugType, int level,
int temp, int volt) {
- synchronized(this) {
- final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
- final long uptime = SystemClock.uptimeMillis();
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- if (!mHaveBatteryLevel) {
- mHaveBatteryLevel = true;
- // We start out assuming that the device is plugged in (not
- // on battery). If our first report is now that we are indeed
- // plugged in, then twiddle our state to correctly reflect that
- // since we won't be going through the full setOnBattery().
- if (onBattery == mOnBattery) {
- if (onBattery) {
- mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
- } else {
- mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
- }
- }
- mHistoryCur.batteryStatus = (byte)status;
- mHistoryCur.batteryLevel = (byte)level;
- mMaxChargeStepLevel = mMinDischargeStepLevel =
- mLastChargeStepLevel = mLastDischargeStepLevel = level;
- } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
- recordDailyStatsIfNeededLocked(level >= 100 && onBattery);
- }
- int oldStatus = mHistoryCur.batteryStatus;
- if (onBattery) {
- mDischargeCurrentLevel = level;
- if (!mRecordingHistory) {
- mRecordingHistory = true;
- startRecordingHistory(elapsedRealtime, uptime, true);
- }
- } else if (level < 96) {
- if (!mRecordingHistory) {
- mRecordingHistory = true;
- startRecordingHistory(elapsedRealtime, uptime, true);
- }
- }
- mCurrentBatteryLevel = level;
- if (mDischargePlugLevel < 0) {
- mDischargePlugLevel = level;
- }
- if (onBattery != mOnBattery) {
- mHistoryCur.batteryLevel = (byte)level;
- mHistoryCur.batteryStatus = (byte)status;
- mHistoryCur.batteryHealth = (byte)health;
- mHistoryCur.batteryPlugType = (byte)plugType;
- mHistoryCur.batteryTemperature = (short)temp;
- mHistoryCur.batteryVoltage = (char)volt;
- setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level);
- } else {
- boolean changed = false;
- if (mHistoryCur.batteryLevel != level) {
- mHistoryCur.batteryLevel = (byte)level;
- changed = true;
- }
- if (mHistoryCur.batteryStatus != status) {
- mHistoryCur.batteryStatus = (byte)status;
- changed = true;
- }
- if (mHistoryCur.batteryHealth != health) {
- mHistoryCur.batteryHealth = (byte)health;
- changed = true;
- }
- if (mHistoryCur.batteryPlugType != plugType) {
- mHistoryCur.batteryPlugType = (byte)plugType;
- changed = true;
- }
- if (temp >= (mHistoryCur.batteryTemperature+10)
- || temp <= (mHistoryCur.batteryTemperature-10)) {
- mHistoryCur.batteryTemperature = (short)temp;
- changed = true;
- }
- if (volt > (mHistoryCur.batteryVoltage+20)
- || volt < (mHistoryCur.batteryVoltage-20)) {
- mHistoryCur.batteryVoltage = (char)volt;
- changed = true;
- }
- if (changed) {
- addHistoryRecordLocked(elapsedRealtime, uptime);
- }
- long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
- | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
- | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
+ final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
+ final long uptime = SystemClock.uptimeMillis();
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ if (!mHaveBatteryLevel) {
+ mHaveBatteryLevel = true;
+ // We start out assuming that the device is plugged in (not
+ // on battery). If our first report is now that we are indeed
+ // plugged in, then twiddle our state to correctly reflect that
+ // since we won't be going through the full setOnBattery().
+ if (onBattery == mOnBattery) {
if (onBattery) {
- if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
- mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
- modeBits, elapsedRealtime);
- mDailyDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
- modeBits, elapsedRealtime);
- mLastDischargeStepLevel = level;
- mMinDischargeStepLevel = level;
- mInitStepMode = mCurStepMode;
- mModStepMode = 0;
- }
+ mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
} else {
- if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
- mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
- modeBits, elapsedRealtime);
- mDailyChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
- modeBits, elapsedRealtime);
- mLastChargeStepLevel = level;
- mMaxChargeStepLevel = level;
- mInitStepMode = mCurStepMode;
- mModStepMode = 0;
- }
+ mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
}
}
- if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
- // We don't record history while we are plugged in and fully charged.
- // The next time we are unplugged, history will be cleared.
- mRecordingHistory = DEBUG;
+ mHistoryCur.batteryStatus = (byte)status;
+ mHistoryCur.batteryLevel = (byte)level;
+ mMaxChargeStepLevel = mMinDischargeStepLevel =
+ mLastChargeStepLevel = mLastDischargeStepLevel = level;
+ } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
+ recordDailyStatsIfNeededLocked(level >= 100 && onBattery);
+ }
+ int oldStatus = mHistoryCur.batteryStatus;
+ if (onBattery) {
+ mDischargeCurrentLevel = level;
+ if (!mRecordingHistory) {
+ mRecordingHistory = true;
+ startRecordingHistory(elapsedRealtime, uptime, true);
+ }
+ } else if (level < 96) {
+ if (!mRecordingHistory) {
+ mRecordingHistory = true;
+ startRecordingHistory(elapsedRealtime, uptime, true);
}
}
- }
-
- public void updateKernelWakelocksLocked() {
- Map<String, KernelWakelockStats> m = readKernelWakelockStats();
-
- if (m == null) {
- // Not crashing might make board bringup easier.
- Slog.w(TAG, "Couldn't get kernel wake lock stats");
- return;
+ mCurrentBatteryLevel = level;
+ if (mDischargePlugLevel < 0) {
+ mDischargePlugLevel = level;
}
+ if (onBattery != mOnBattery) {
+ mHistoryCur.batteryLevel = (byte)level;
+ mHistoryCur.batteryStatus = (byte)status;
+ mHistoryCur.batteryHealth = (byte)health;
+ mHistoryCur.batteryPlugType = (byte)plugType;
+ mHistoryCur.batteryTemperature = (short)temp;
+ mHistoryCur.batteryVoltage = (char)volt;
+ setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level);
+ } else {
+ boolean changed = false;
+ if (mHistoryCur.batteryLevel != level) {
+ mHistoryCur.batteryLevel = (byte)level;
+ changed = true;
- for (Map.Entry<String, KernelWakelockStats> ent : m.entrySet()) {
- String name = ent.getKey();
- KernelWakelockStats kws = ent.getValue();
-
- SamplingTimer kwlt = mKernelWakelockStats.get(name);
- if (kwlt == null) {
- kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase,
- true /* track reported val */);
- mKernelWakelockStats.put(name, kwlt);
+ // TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record
+ // which will pull external stats.
+ scheduleSyncExternalStatsLocked();
}
- kwlt.updateCurrentReportedCount(kws.mCount);
- kwlt.updateCurrentReportedTotalTime(kws.mTotalTime);
- kwlt.setUpdateVersion(sKernelWakelockUpdateVersion);
- }
-
- if (m.size() != mKernelWakelockStats.size()) {
- // Set timers to stale if they didn't appear in /proc/wakelocks this time.
- for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
- SamplingTimer st = ent.getValue();
- if (st.getUpdateVersion() != sKernelWakelockUpdateVersion) {
- st.setStale();
- }
+ if (mHistoryCur.batteryStatus != status) {
+ mHistoryCur.batteryStatus = (byte)status;
+ changed = true;
}
- }
- }
-
- static final int NET_UPDATE_MOBILE = 1<<0;
- static final int NET_UPDATE_WIFI = 1<<1;
- static final int NET_UPDATE_ALL = 0xffff;
-
- private void updateNetworkActivityLocked(int which, long elapsedRealtimeMs) {
- if (!SystemProperties.getBoolean(PROP_QTAGUID_ENABLED, false)) return;
-
- if ((which&NET_UPDATE_MOBILE) != 0 && mMobileIfaces.length > 0) {
- final NetworkStats snapshot;
- final NetworkStats last = mCurMobileSnapshot;
- try {
- snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL,
- mMobileIfaces, NetworkStats.TAG_NONE, mLastMobileSnapshot);
- } catch (IOException e) {
- Log.wtf(TAG, "Failed to read mobile network stats", e);
- return;
+ if (mHistoryCur.batteryHealth != health) {
+ mHistoryCur.batteryHealth = (byte)health;
+ changed = true;
}
-
- mCurMobileSnapshot = snapshot;
- mLastMobileSnapshot = last;
-
- if (mOnBatteryInternal) {
- final NetworkStats delta = NetworkStats.subtract(snapshot, last,
- null, null, mTmpNetworkStats);
- mTmpNetworkStats = delta;
-
- long radioTime = mMobileRadioActivePerAppTimer.checkpointRunningLocked(
- elapsedRealtimeMs);
- long totalPackets = delta.getTotalPackets();
-
- final int size = delta.size();
- for (int i = 0; i < size; i++) {
- final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
-
- if (entry.rxBytes == 0 || entry.txBytes == 0) continue;
-
- final Uid u = getUidStatsLocked(mapUid(entry.uid));
- u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes,
- entry.rxPackets);
- u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes,
- entry.txPackets);
-
- if (radioTime > 0) {
- // Distribute total radio active time in to this app.
- long appPackets = entry.rxPackets + entry.txPackets;
- long appRadioTime = (radioTime*appPackets)/totalPackets;
- u.noteMobileRadioActiveTimeLocked(appRadioTime);
- // Remove this app from the totals, so that we don't lose any time
- // due to rounding.
- radioTime -= appRadioTime;
- totalPackets -= appPackets;
- }
-
- mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
- entry.rxBytes);
- mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
- entry.txBytes);
- mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
- entry.rxPackets);
- mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
- entry.txPackets);
- }
-
- if (radioTime > 0) {
- // Whoops, there is some radio time we can't blame on an app!
- mMobileRadioActiveUnknownTime.addCountLocked(radioTime);
- mMobileRadioActiveUnknownCount.addCountLocked(1);
- }
+ if (mHistoryCur.batteryPlugType != plugType) {
+ mHistoryCur.batteryPlugType = (byte)plugType;
+ changed = true;
}
- }
-
- if ((which&NET_UPDATE_WIFI) != 0 && mWifiIfaces.length > 0) {
- final NetworkStats snapshot;
- final NetworkStats last = mCurWifiSnapshot;
- try {
- snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL,
- mWifiIfaces, NetworkStats.TAG_NONE, mLastWifiSnapshot);
- } catch (IOException e) {
- Log.wtf(TAG, "Failed to read wifi network stats", e);
- return;
+ if (temp >= (mHistoryCur.batteryTemperature+10)
+ || temp <= (mHistoryCur.batteryTemperature-10)) {
+ mHistoryCur.batteryTemperature = (short)temp;
+ changed = true;
}
-
- mCurWifiSnapshot = snapshot;
- mLastWifiSnapshot = last;
-
- if (mOnBatteryInternal) {
- final NetworkStats delta = NetworkStats.subtract(snapshot, last,
- null, null, mTmpNetworkStats);
- mTmpNetworkStats = delta;
-
- final int size = delta.size();
- for (int i = 0; i < size; i++) {
- final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
-
- if (DEBUG) {
- final NetworkStats.Entry cur = snapshot.getValues(i, null);
- Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes
- + " tx=" + entry.txBytes + ", cur rx=" + cur.rxBytes
- + " tx=" + cur.txBytes);
- }
-
- if (entry.rxBytes == 0 || entry.txBytes == 0) continue;
-
- final Uid u = getUidStatsLocked(mapUid(entry.uid));
- u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
- entry.rxPackets);
- u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
- entry.txPackets);
-
- mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
- entry.rxBytes);
- mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
- entry.txBytes);
- mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
- entry.rxPackets);
- mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
- entry.txPackets);
+ if (volt > (mHistoryCur.batteryVoltage+20)
+ || volt < (mHistoryCur.batteryVoltage-20)) {
+ mHistoryCur.batteryVoltage = (char)volt;
+ changed = true;
+ }
+ if (changed) {
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ }
+ long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
+ | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
+ | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
+ if (onBattery) {
+ if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
+ mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
+ modeBits, elapsedRealtime);
+ mDailyDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
+ modeBits, elapsedRealtime);
+ mLastDischargeStepLevel = level;
+ mMinDischargeStepLevel = level;
+ mInitStepMode = mCurStepMode;
+ mModStepMode = 0;
+ }
+ } else {
+ if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
+ mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
+ modeBits, elapsedRealtime);
+ mDailyChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
+ modeBits, elapsedRealtime);
+ mLastChargeStepLevel = level;
+ mMaxChargeStepLevel = level;
+ mInitStepMode = mCurStepMode;
+ mModStepMode = 0;
}
}
}
- }
-
- private void updateBluetoothControllerActivityLocked() {
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter == null) {
- return;
- }
-
- // We read the data even if we are not on battery. Each read clears
- // the previous data, so we must always read to make sure the
- // data is for the current interval.
- BluetoothActivityEnergyInfo info = adapter.getControllerActivityEnergyInfo(
- BluetoothAdapter.ACTIVITY_ENERGY_INFO_REFRESHED);
- if (info == null || !info.isValid() || !mOnBatteryInternal) {
- // Bad info or we are not on battery.
- return;
- }
-
- mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
- info.getControllerRxTimeMillis());
- mBluetoothActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
- info.getControllerTxTimeMillis());
- mBluetoothActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
- info.getControllerIdleTimeMillis());
- mBluetoothActivityCounters[CONTROLLER_ENERGY].addCountLocked(
- info.getControllerEnergyUsed());
- }
-
- private void updateWifiControllerActivityLocked() {
- IWifiManager wifiManager = IWifiManager.Stub.asInterface(
- ServiceManager.getService(Context.WIFI_SERVICE));
- if (wifiManager == null) {
- return;
- }
-
- WifiActivityEnergyInfo info;
- try {
- // We read the data even if we are not on battery. Each read clears
- // the previous data, so we must always read to make sure the
- // data is for the current interval.
- info = wifiManager.reportActivityInfo();
- } catch (RemoteException e) {
- // Nothing to report, WiFi is dead.
- return;
- }
-
- if (info == null || !info.isValid() || !mOnBatteryInternal) {
- // Bad info or we are not on battery.
- return;
+ if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
+ // We don't record history while we are plugged in and fully charged.
+ // The next time we are unplugged, history will be cleared.
+ mRecordingHistory = DEBUG;
}
-
- mWifiActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
- info.getControllerRxTimeMillis());
- mWifiActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
- info.getControllerTxTimeMillis());
- mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
- info.getControllerIdleTimeMillis());
- mWifiActivityCounters[CONTROLLER_ENERGY].addCountLocked(
- info.getControllerEnergyUsed());
}
public long getAwakeTimeBattery() {
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
new file mode 100644
index 0000000..3557209
--- /dev/null
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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.internal.os;
+
+import android.os.BatteryStats;
+import android.util.Log;
+
+public class BluetoothPowerCalculator extends PowerCalculator {
+ private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+ private static final String TAG = "BluetoothPowerCalculator";
+
+ @Override
+ public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ // No per-app distribution yet.
+ }
+
+ @Override
+ public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ final long idleTimeMs = stats.getBluetoothControllerActivity(
+ BatteryStats.CONTROLLER_IDLE_TIME, statsType);
+ final long txTimeMs = stats.getBluetoothControllerActivity(
+ BatteryStats.CONTROLLER_TX_TIME, statsType);
+ final long rxTimeMs = stats.getBluetoothControllerActivity(
+ BatteryStats.CONTROLLER_RX_TIME, statsType);
+ final long powerMaMs = stats.getBluetoothControllerActivity(
+ BatteryStats.CONTROLLER_POWER_DRAIN, statsType);
+ final double powerMah = powerMaMs / (double)(1000*60*60);
+ final long totalTimeMs = idleTimeMs + txTimeMs + rxTimeMs;
+
+ if (DEBUG && powerMah != 0) {
+ Log.d(TAG, "Bluetooth active: time=" + (totalTimeMs)
+ + " power=" + BatteryStatsHelper.makemAh(powerMah));
+ }
+
+ app.usagePowerMah = powerMah;
+ app.usageTimeMs = totalTimeMs;
+ }
+}
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
new file mode 100644
index 0000000..6c3f958
--- /dev/null
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 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.internal.os;
+
+import android.os.BatteryStats;
+import android.util.ArrayMap;
+import android.util.Log;
+
+public class CpuPowerCalculator extends PowerCalculator {
+ private static final String TAG = "CpuPowerCalculator";
+ private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+
+ private final double[] mPowerCpuNormal;
+
+ /**
+ * Reusable array for calculations.
+ */
+ private final long[] mSpeedStepTimes;
+
+ public CpuPowerCalculator(PowerProfile profile) {
+ final int speedSteps = profile.getNumSpeedSteps();
+ mPowerCpuNormal = new double[speedSteps];
+ mSpeedStepTimes = new long[speedSteps];
+ for (int p = 0; p < speedSteps; p++) {
+ mPowerCpuNormal[p] = profile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
+ }
+ }
+
+ @Override
+ public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ final int speedSteps = mSpeedStepTimes.length;
+
+ // Keep track of the package with highest drain.
+ double highestDrain = 0;
+
+ final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
+ final int processStatsCount = processStats.size();
+ for (int i = 0; i < processStatsCount; i++) {
+ final BatteryStats.Uid.Proc ps = processStats.valueAt(i);
+ final String processName = processStats.keyAt(i);
+
+ app.cpuFgTimeMs += ps.getForegroundTime(statsType);
+ final long totalCpuTime = ps.getUserTime(statsType) + ps.getSystemTime(statsType);
+ app.cpuTimeMs += totalCpuTime;
+
+ // Calculate the total CPU time spent at the various speed steps.
+ long totalTimeAtSpeeds = 0;
+ for (int step = 0; step < speedSteps; step++) {
+ mSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, statsType);
+ totalTimeAtSpeeds += mSpeedStepTimes[step];
+ }
+ totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1);
+
+ // Then compute the ratio of time spent at each speed and figure out
+ // the total power consumption.
+ double cpuPower = 0;
+ for (int step = 0; step < speedSteps; step++) {
+ final double ratio = (double) mSpeedStepTimes[step] / totalTimeAtSpeeds;
+ final double cpuSpeedStepPower = ratio * totalCpuTime * mPowerCpuNormal[step];
+ if (DEBUG && ratio != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
+ + step + " ratio=" + BatteryStatsHelper.makemAh(ratio) + " power="
+ + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
+ }
+ cpuPower += cpuSpeedStepPower;
+ }
+
+ if (DEBUG && cpuPower != 0) {
+ Log.d(TAG, String.format("process %s, cpu power=%s",
+ processName, BatteryStatsHelper.makemAh(cpuPower / (60 * 60 * 1000))));
+ }
+ app.cpuPowerMah += cpuPower;
+
+ // Each App can have multiple packages and with multiple running processes.
+ // Keep track of the package who's process has the highest drain.
+ if (app.packageWithHighestDrain == null ||
+ app.packageWithHighestDrain.startsWith("*")) {
+ highestDrain = cpuPower;
+ app.packageWithHighestDrain = processName;
+ } else if (highestDrain < cpuPower && !processName.startsWith("*")) {
+ highestDrain = cpuPower;
+ app.packageWithHighestDrain = processName;
+ }
+ }
+
+ // Ensure that the CPU times make sense.
+ if (app.cpuFgTimeMs > app.cpuTimeMs) {
+ if (DEBUG && app.cpuFgTimeMs > app.cpuTimeMs + 10000) {
+ Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
+ }
+
+ // Statistics may not have been gathered yet.
+ app.cpuTimeMs = app.cpuFgTimeMs;
+ }
+
+ // Convert the CPU power to mAh
+ app.cpuPowerMah /= (60 * 60 * 1000);
+ }
+}
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
index 433a54b..a4cdf19 100644
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ b/core/java/com/android/internal/os/InstallerConnection.java
@@ -91,11 +91,11 @@ public class InstallerConnection {
}
public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) {
- return dexopt(apkPath, uid, isPublic, "*", instructionSet, false, false);
+ return dexopt(apkPath, uid, isPublic, "*", instructionSet, false, false, null);
}
public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet, boolean vmSafeMode, boolean debuggable) {
+ String instructionSet, boolean vmSafeMode, boolean debuggable, String outputPath) {
StringBuilder builder = new StringBuilder("dexopt");
builder.append(' ');
builder.append(apkPath);
@@ -108,6 +108,8 @@ public class InstallerConnection {
builder.append(instructionSet);
builder.append(vmSafeMode ? " 1" : " 0");
builder.append(debuggable ? " 1" : " 0");
+ builder.append(' ');
+ builder.append(outputPath != null ? outputPath : "!");
return execute(builder.toString());
}
diff --git a/core/java/com/android/internal/os/KernelWakelockReader.java b/core/java/com/android/internal/os/KernelWakelockReader.java
new file mode 100644
index 0000000..768d586
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelWakelockReader.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2015 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.internal.os;
+
+import android.os.Process;
+import android.util.Slog;
+
+import java.io.FileInputStream;
+import java.util.Iterator;
+
+/**
+ * Reads and parses wakelock stats from the kernel (/proc/wakelocks).
+ */
+public class KernelWakelockReader {
+ private static final String TAG = "KernelWakelockReader";
+ private static int sKernelWakelockUpdateVersion = 0;
+ private static final String sWakelockFile = "/proc/wakelocks";
+ private static final String sWakeupSourceFile = "/d/wakeup_sources";
+
+ private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
+ Process.PROC_TAB_TERM|Process.PROC_OUT_STRING| // 0: name
+ Process.PROC_QUOTES,
+ Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 1: count
+ Process.PROC_TAB_TERM,
+ Process.PROC_TAB_TERM,
+ Process.PROC_TAB_TERM,
+ Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 5: totalTime
+ };
+
+ private static final int[] WAKEUP_SOURCES_FORMAT = new int[] {
+ Process.PROC_TAB_TERM|Process.PROC_OUT_STRING, // 0: name
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE|
+ Process.PROC_OUT_LONG, // 1: count
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE
+ |Process.PROC_OUT_LONG, // 6: totalTime
+ };
+
+ private final String[] mProcWakelocksName = new String[3];
+ private final long[] mProcWakelocksData = new long[3];
+
+ /**
+ * Reads kernel wakelock stats and updates the staleStats with the new information.
+ * @param staleStats Existing object to update.
+ * @return the updated data.
+ */
+ public final KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) {
+ byte[] buffer = new byte[32*1024];
+ int len;
+ boolean wakeup_sources;
+
+ try {
+ FileInputStream is;
+ try {
+ is = new FileInputStream(sWakeupSourceFile);
+ wakeup_sources = true;
+ } catch (java.io.FileNotFoundException e) {
+ try {
+ is = new FileInputStream(sWakelockFile);
+ wakeup_sources = false;
+ } catch (java.io.FileNotFoundException e2) {
+ return null;
+ }
+ }
+
+ len = is.read(buffer);
+ is.close();
+ } catch (java.io.IOException e) {
+ return null;
+ }
+
+ if (len > 0) {
+ if (len >= buffer.length) {
+ Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length);
+ }
+ int i;
+ for (i=0; i<len; i++) {
+ if (buffer[i] == '\0') {
+ len = i;
+ break;
+ }
+ }
+ }
+ return parseProcWakelocks(buffer, len, wakeup_sources, staleStats);
+ }
+
+ /**
+ * Reads the wakelocks and updates the staleStats with the new information.
+ */
+ private KernelWakelockStats parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources,
+ final KernelWakelockStats staleStats) {
+ String name;
+ int count;
+ long totalTime;
+ int startIndex;
+ int endIndex;
+ int numUpdatedWlNames = 0;
+
+ // Advance past the first line.
+ int i;
+ for (i = 0; i < len && wlBuffer[i] != '\n' && wlBuffer[i] != '\0'; i++);
+ startIndex = endIndex = i + 1;
+
+ synchronized(this) {
+ sKernelWakelockUpdateVersion++;
+ while (endIndex < len) {
+ for (endIndex=startIndex;
+ endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
+ endIndex++);
+ endIndex++; // endIndex is an exclusive upper bound.
+ // Don't go over the end of the buffer, Process.parseProcLine might
+ // write to wlBuffer[endIndex]
+ if (endIndex >= (len - 1) ) {
+ return staleStats;
+ }
+
+ String[] nameStringArray = mProcWakelocksName;
+ long[] wlData = mProcWakelocksData;
+ // Stomp out any bad characters since this is from a circular buffer
+ // A corruption is seen sometimes that results in the vm crashing
+ // This should prevent crashes and the line will probably fail to parse
+ for (int j = startIndex; j < endIndex; j++) {
+ if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?';
+ }
+ boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex,
+ wakeup_sources ? WAKEUP_SOURCES_FORMAT :
+ PROC_WAKELOCKS_FORMAT,
+ nameStringArray, wlData, null);
+
+ name = nameStringArray[0];
+ count = (int) wlData[1];
+
+ if (wakeup_sources) {
+ // convert milliseconds to microseconds
+ totalTime = wlData[2] * 1000;
+ } else {
+ // convert nanoseconds to microseconds with rounding.
+ totalTime = (wlData[2] + 500) / 1000;
+ }
+
+ if (parsed && name.length() > 0) {
+ if (!staleStats.containsKey(name)) {
+ staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime,
+ sKernelWakelockUpdateVersion));
+ numUpdatedWlNames++;
+ } else {
+ KernelWakelockStats.Entry kwlStats = staleStats.get(name);
+ if (kwlStats.mVersion == sKernelWakelockUpdateVersion) {
+ kwlStats.mCount += count;
+ kwlStats.mTotalTime += totalTime;
+ } else {
+ kwlStats.mCount = count;
+ kwlStats.mTotalTime = totalTime;
+ kwlStats.mVersion = sKernelWakelockUpdateVersion;
+ numUpdatedWlNames++;
+ }
+ }
+ }
+ startIndex = endIndex;
+ }
+
+ if (staleStats.size() != numUpdatedWlNames) {
+ // Don't report old data.
+ Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator();
+ while (itr.hasNext()) {
+ if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
+ itr.remove();
+ }
+ }
+ }
+
+ staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion;
+ return staleStats;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/KernelWakelockStats.java b/core/java/com/android/internal/os/KernelWakelockStats.java
new file mode 100644
index 0000000..144ea00
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelWakelockStats.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.internal.os;
+
+import java.util.HashMap;
+
+/**
+ * Kernel wakelock stats object.
+ */
+public class KernelWakelockStats extends HashMap<String, KernelWakelockStats.Entry> {
+ public static class Entry {
+ public int mCount;
+ public long mTotalTime;
+ public int mVersion;
+
+ Entry(int count, long totalTime, int version) {
+ mCount = count;
+ mTotalTime = totalTime;
+ mVersion = version;
+ }
+ }
+
+ int kernelWakelockVersion;
+}
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
new file mode 100644
index 0000000..9711c3b
--- /dev/null
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 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.internal.os;
+
+import android.os.BatteryStats;
+import android.telephony.SignalStrength;
+import android.util.Log;
+
+public class MobileRadioPowerCalculator extends PowerCalculator {
+ private static final String TAG = "MobileRadioPowerController";
+ private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+ private final double mPowerRadioOn;
+ private final double[] mPowerBins = new double[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+ private final double mPowerScan;
+ private BatteryStats mStats;
+ private long mTotalAppMobileActiveMs = 0;
+
+ /**
+ * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
+ */
+ private double getMobilePowerPerPacket(long rawRealtimeUs, int statsType) {
+ final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
+ final double MOBILE_POWER = mPowerRadioOn / 3600;
+
+ final long mobileRx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
+ statsType);
+ final long mobileTx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
+ statsType);
+ final long mobileData = mobileRx + mobileTx;
+
+ final long radioDataUptimeMs =
+ mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
+ final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0)
+ ? (mobileData / (double)radioDataUptimeMs)
+ : (((double)MOBILE_BPS) / 8 / 2048);
+ return (MOBILE_POWER / mobilePps) / (60*60);
+ }
+
+ public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) {
+ mPowerRadioOn = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE);
+ for (int i = 0; i < mPowerBins.length; i++) {
+ mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE, i);
+ }
+ mPowerScan = profile.getAveragePower(PowerProfile.POWER_RADIO_SCANNING);
+ mStats = stats;
+ }
+
+ @Override
+ public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ // Add cost of mobile traffic.
+ app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
+ statsType);
+ app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
+ statsType);
+ app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000;
+ app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);
+ app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
+ statsType);
+ app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
+ statsType);
+
+ if (app.mobileActive > 0) {
+ // We are tracking when the radio is up, so can use the active time to
+ // determine power use.
+ mTotalAppMobileActiveMs += app.mobileActive;
+ app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000*60*60);
+ } else {
+ // We are not tracking when the radio is up, so must approximate power use
+ // based on the number of packets.
+ app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets)
+ * getMobilePowerPerPacket(rawRealtimeUs, statsType);
+ }
+ if (DEBUG && app.mobileRadioPowerMah != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
+ + (app.mobileRxPackets + app.mobileTxPackets)
+ + " active time " + app.mobileActive
+ + " power=" + BatteryStatsHelper.makemAh(app.mobileRadioPowerMah));
+ }
+ }
+
+ @Override
+ public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ double power = 0;
+ long signalTimeMs = 0;
+ long noCoverageTimeMs = 0;
+ for (int i = 0; i < mPowerBins.length; i++) {
+ long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType)
+ / 1000;
+ final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000);
+ if (DEBUG && p != 0) {
+ Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
+ + BatteryStatsHelper.makemAh(p));
+ }
+ power += p;
+ signalTimeMs += strengthTimeMs;
+ if (i == 0) {
+ noCoverageTimeMs = strengthTimeMs;
+ }
+ }
+
+ final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType)
+ / 1000;
+ final double p = (scanningTimeMs * mPowerScan) / (60*60*1000);
+ if (DEBUG && p != 0) {
+ Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
+ + " power=" + BatteryStatsHelper.makemAh(p));
+ }
+ power += p;
+ long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
+ long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs;
+ if (remainingActiveTimeMs > 0) {
+ power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60);
+ }
+
+ if (power != 0) {
+ app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
+ app.mobileActive = remainingActiveTimeMs;
+ app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
+ app.mobileRadioPowerMah = power;
+ }
+ }
+
+ @Override
+ public void reset() {
+ mTotalAppMobileActiveMs = 0;
+ }
+
+ public void reset(BatteryStats stats) {
+ reset();
+ mStats = stats;
+ }
+}
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
new file mode 100644
index 0000000..cd69d68
--- /dev/null
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 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.internal.os;
+
+import android.os.BatteryStats;
+
+/**
+ * Calculates power use of a device subsystem for an app.
+ */
+public abstract class PowerCalculator {
+ /**
+ * Calculate the amount of power an app used for this subsystem.
+ * @param app The BatterySipper that represents the power use of an app.
+ * @param u The recorded stats for the app.
+ * @param rawRealtimeUs The raw system realtime in microseconds.
+ * @param rawUptimeUs The raw system uptime in microseconds.
+ * @param statsType The type of stats. Can be {@link BatteryStats#STATS_CURRENT},
+ * {@link BatteryStats#STATS_SINCE_CHARGED}, or
+ * {@link BatteryStats#STATS_SINCE_UNPLUGGED}.
+ */
+ public abstract void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+ long rawUptimeUs, int statsType);
+
+ /**
+ * Calculate the remaining power that can not be attributed to an app.
+ * @param app The BatterySipper that will represent this remaining power.
+ * @param stats The BatteryStats object from which to retrieve data.
+ * @param rawRealtimeUs The raw system realtime in microseconds.
+ * @param rawUptimeUs The raw system uptime in microseconds.
+ * @param statsType The type of stats. Can be {@link BatteryStats#STATS_CURRENT},
+ * {@link BatteryStats#STATS_SINCE_CHARGED}, or
+ * {@link BatteryStats#STATS_SINCE_UNPLUGGED}.
+ */
+ public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ }
+
+ /**
+ * Reset any state maintained in this calculator.
+ */
+ public void reset() {
+ }
+}
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 944eb5a..7e6706c 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -18,6 +18,7 @@ package com.android.internal.os;
import android.content.Context;
+import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import com.android.internal.util.XmlUtils;
@@ -75,10 +76,21 @@ public class PowerProfile {
*/
public static final String POWER_WIFI_ACTIVE = "wifi.active";
- /**
- * Operating voltage of the WiFi controller.
- */
- public static final String OPERATING_VOLTAGE_WIFI = "wifi.voltage";
+ //
+ // Updated power constants. These are not estimated, they are real world
+ // currents and voltages for the underlying bluetooth and wifi controllers.
+ //
+
+ public static final String POWER_WIFI_CONTROLLER_IDLE = "wifi.controller.idle";
+ public static final String POWER_WIFI_CONTROLLER_RX = "wifi.controller.rx";
+ public static final String POWER_WIFI_CONTROLLER_TX = "wifi.controller.tx";
+ public static final String POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE = "wifi.controller.voltage";
+
+ public static final String POWER_BLUETOOTH_CONTROLLER_IDLE = "bluetooth.controller.idle";
+ public static final String POWER_BLUETOOTH_CONTROLLER_RX = "bluetooth.controller.rx";
+ public static final String POWER_BLUETOOTH_CONTROLLER_TX = "bluetooth.controller.tx";
+ public static final String POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE =
+ "bluetooth.controller.voltage";
/**
* Power consumption when GPS is on.
@@ -100,10 +112,6 @@ public class PowerProfile {
*/
public static final String POWER_BLUETOOTH_AT_CMD = "bluetooth.at";
- /**
- * Operating voltage of the Bluetooth controller.
- */
- public static final String OPERATING_VOLTAGE_BLUETOOTH = "bluetooth.voltage";
/**
* Power consumption when screen is on, not including the backlight power.
@@ -162,7 +170,7 @@ public class PowerProfile {
*/
public static final String POWER_BATTERY_CAPACITY = "battery.capacity";
- static final HashMap<String, Object> sPowerMap = new HashMap<String, Object>();
+ static final HashMap<String, Object> sPowerMap = new HashMap<>();
private static final String TAG_DEVICE = "device";
private static final String TAG_ITEM = "item";
@@ -180,7 +188,8 @@ public class PowerProfile {
private void readPowerValuesFromXml(Context context) {
int id = com.android.internal.R.xml.power_profile;
- XmlResourceParser parser = context.getResources().getXml(id);
+ final Resources resources = context.getResources();
+ XmlResourceParser parser = resources.getXml(id);
boolean parsingArray = false;
ArrayList<Double> array = new ArrayList<Double>();
String arrayName = null;
@@ -231,6 +240,36 @@ public class PowerProfile {
} finally {
parser.close();
}
+
+ // Now collect other config variables.
+ int[] configResIds = new int[] {
+ com.android.internal.R.integer.config_bluetooth_idle_cur_ma,
+ com.android.internal.R.integer.config_bluetooth_rx_cur_ma,
+ com.android.internal.R.integer.config_bluetooth_tx_cur_ma,
+ com.android.internal.R.integer.config_bluetooth_operating_voltage_mv,
+ com.android.internal.R.integer.config_wifi_idle_receive_cur_ma,
+ com.android.internal.R.integer.config_wifi_active_rx_cur_ma,
+ com.android.internal.R.integer.config_wifi_tx_cur_ma,
+ com.android.internal.R.integer.config_wifi_operating_voltage_mv,
+ };
+
+ String[] configResIdKeys = new String[] {
+ POWER_BLUETOOTH_CONTROLLER_IDLE,
+ POWER_BLUETOOTH_CONTROLLER_RX,
+ POWER_BLUETOOTH_CONTROLLER_TX,
+ POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE,
+ POWER_WIFI_CONTROLLER_IDLE,
+ POWER_WIFI_CONTROLLER_RX,
+ POWER_WIFI_CONTROLLER_TX,
+ POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE,
+ };
+
+ for (int i = 0; i < configResIds.length; i++) {
+ int value = resources.getInteger(configResIds[i]);
+ if (value > 0) {
+ sPowerMap.put(configResIdKeys[i], (double) value);
+ }
+ }
}
/**
diff --git a/core/java/com/android/internal/os/SensorPowerCalculator.java b/core/java/com/android/internal/os/SensorPowerCalculator.java
new file mode 100644
index 0000000..c98639b
--- /dev/null
+++ b/core/java/com/android/internal/os/SensorPowerCalculator.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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.internal.os;
+
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.os.BatteryStats;
+import android.util.SparseArray;
+
+import java.util.List;
+
+public class SensorPowerCalculator extends PowerCalculator {
+ private final List<Sensor> mSensors;
+ private final double mGpsPowerOn;
+
+ public SensorPowerCalculator(PowerProfile profile, SensorManager sensorManager) {
+ mSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
+ mGpsPowerOn = profile.getAveragePower(PowerProfile.POWER_GPS_ON);
+ }
+
+ @Override
+ public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ // Process Sensor usage
+ final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
+ final int NSE = sensorStats.size();
+ for (int ise = 0; ise < NSE; ise++) {
+ final BatteryStats.Uid.Sensor sensor = sensorStats.valueAt(ise);
+ final int sensorHandle = sensorStats.keyAt(ise);
+ final BatteryStats.Timer timer = sensor.getSensorTime();
+ final long sensorTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
+ switch (sensorHandle) {
+ case BatteryStats.Uid.Sensor.GPS:
+ app.gpsTimeMs = sensorTime;
+ app.gpsPowerMah = (app.gpsTimeMs * mGpsPowerOn) / (1000*60*60);
+ break;
+ default:
+ final int sensorsCount = mSensors.size();
+ for (int i = 0; i < sensorsCount; i++) {
+ final Sensor s = mSensors.get(i);
+ if (s.getHandle() == sensorHandle) {
+ app.sensorPowerMah += (sensorTime * s.getPower()) / (1000*60*60);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
new file mode 100644
index 0000000..7575010f
--- /dev/null
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 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.internal.os;
+
+import android.os.BatteryStats;
+import android.util.ArrayMap;
+import android.util.Log;
+
+public class WakelockPowerCalculator extends PowerCalculator {
+ private static final String TAG = "WakelockPowerCalculator";
+ private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+ private final double mPowerWakelock;
+ private long mTotalAppWakelockTimeMs = 0;
+
+ public WakelockPowerCalculator(PowerProfile profile) {
+ mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_AWAKE);
+ }
+
+ @Override
+ public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawUptimeUs,
+ long rawRealtimeUs, int statsType) {
+ long wakeLockTimeUs = 0;
+ final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
+ u.getWakelockStats();
+ final int wakelockStatsCount = wakelockStats.size();
+ for (int i = 0; i < wakelockStatsCount; i++) {
+ final BatteryStats.Uid.Wakelock wakelock = wakelockStats.valueAt(i);
+
+ // Only care about partial wake locks since full wake locks
+ // are canceled when the user turns the screen off.
+ BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
+ if (timer != null) {
+ wakeLockTimeUs += timer.getTotalTimeLocked(rawRealtimeUs, statsType);
+ }
+ }
+ app.wakeLockTimeMs = wakeLockTimeUs / 1000; // convert to millis
+ mTotalAppWakelockTimeMs += app.wakeLockTimeMs;
+
+ // Add cost of holding a wake lock.
+ app.wakeLockPowerMah = (app.wakeLockTimeMs * mPowerWakelock) / (1000*60*60);
+ if (DEBUG && app.wakeLockPowerMah != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": wake " + app.wakeLockTimeMs
+ + " power=" + BatteryStatsHelper.makemAh(app.wakeLockPowerMah));
+ }
+ }
+
+ @Override
+ public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000;
+ wakeTimeMillis -= mTotalAppWakelockTimeMs
+ + (stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000);
+ if (wakeTimeMillis > 0) {
+ final double power = (wakeTimeMillis * mPowerWakelock) / (1000*60*60);
+ if (DEBUG) {
+ Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "
+ + BatteryStatsHelper.makemAh(power));
+ }
+ app.wakeLockTimeMs += wakeTimeMillis;
+ app.wakeLockPowerMah += power;
+ }
+ }
+
+ @Override
+ public void reset() {
+ mTotalAppWakelockTimeMs = 0;
+ }
+}
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
new file mode 100644
index 0000000..4e77f6b
--- /dev/null
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 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.internal.os;
+
+import android.os.BatteryStats;
+
+/**
+ * WiFi power calculator for when BatteryStats supports energy reporting
+ * from the WiFi controller.
+ */
+public class WifiPowerCalculator extends PowerCalculator {
+ private final double mIdleCurrentMa;
+ private final double mTxCurrentMa;
+ private final double mRxCurrentMa;
+ private double mTotalAppPowerDrain = 0;
+
+ public WifiPowerCalculator(PowerProfile profile) {
+ mIdleCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE);
+ mTxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX);
+ mRxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX);
+ }
+
+ @Override
+ public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ final long idleTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_IDLE_TIME,
+ statsType);
+ final long txTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_TX_TIME, statsType);
+ final long rxTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_RX_TIME, statsType);
+ app.wifiRunningTimeMs = idleTime + rxTime + txTime;
+ app.wifiPowerMah =
+ ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa))
+ / (1000*60*60);
+ mTotalAppPowerDrain += app.wifiPowerMah;
+
+ app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA,
+ statsType);
+ app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA,
+ statsType);
+ app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA,
+ statsType);
+ app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA,
+ statsType);
+ }
+
+ @Override
+ public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ final long idleTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_IDLE_TIME,
+ statsType);
+ final long rxTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_RX_TIME,
+ statsType);
+ final long txTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_TX_TIME,
+ statsType);
+ app.wifiRunningTimeMs = idleTimeMs + rxTimeMs + txTimeMs;
+
+ double powerDrain = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_POWER_DRAIN,
+ statsType) / (1000*60*60);
+ if (powerDrain == 0) {
+ // Some controllers do not report power drain, so we can calculate it here.
+ powerDrain = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa)
+ + (rxTimeMs * mRxCurrentMa)) / (1000*60*60);
+ }
+ app.wifiPowerMah = Math.max(0, powerDrain - mTotalAppPowerDrain);
+ }
+
+ @Override
+ public void reset() {
+ mTotalAppPowerDrain = 0;
+ }
+}
diff --git a/core/java/com/android/internal/os/WifiPowerEstimator.java b/core/java/com/android/internal/os/WifiPowerEstimator.java
new file mode 100644
index 0000000..0172367
--- /dev/null
+++ b/core/java/com/android/internal/os/WifiPowerEstimator.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2015 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.internal.os;
+
+import android.os.BatteryStats;
+
+/**
+ * Estimates WiFi power usage based on timers in BatteryStats.
+ */
+public class WifiPowerEstimator extends PowerCalculator {
+ private final double mWifiPowerPerPacket;
+ private final double mWifiPowerOn;
+ private final double mWifiPowerScan;
+ private final double mWifiPowerBatchScan;
+ private long mTotalAppWifiRunningTimeMs = 0;
+
+ public WifiPowerEstimator(PowerProfile profile) {
+ mWifiPowerPerPacket = getWifiPowerPerPacket(profile);
+ mWifiPowerOn = profile.getAveragePower(PowerProfile.POWER_WIFI_ON);
+ mWifiPowerScan = profile.getAveragePower(PowerProfile.POWER_WIFI_SCAN);
+ mWifiPowerBatchScan = profile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN);
+ }
+
+ /**
+ * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
+ */
+ private static double getWifiPowerPerPacket(PowerProfile profile) {
+ final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
+ final double WIFI_POWER = profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
+ / 3600;
+ return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60);
+ }
+
+ @Override
+ public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA,
+ statsType);
+ app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA,
+ statsType);
+ app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA,
+ statsType);
+ app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA,
+ statsType);
+
+ final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets)
+ * mWifiPowerPerPacket;
+
+ app.wifiRunningTimeMs = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000;
+ mTotalAppWifiRunningTimeMs += app.wifiRunningTimeMs;
+ final double wifiLockPower = (app.wifiRunningTimeMs * mWifiPowerOn) / (1000*60*60);
+
+ final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType);
+ final double wifiScanPower = (wifiScanTimeMs * mWifiPowerScan) / (1000*60*60);
+
+ double wifiBatchScanPower = 0;
+ for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
+ final long batchScanTimeMs =
+ u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000;
+ final double batchScanPower = (batchScanTimeMs * mWifiPowerBatchScan) / (1000*60*60);
+ wifiBatchScanPower += batchScanPower;
+ }
+
+ app.wifiPowerMah = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower;
+ }
+
+ @Override
+ public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ final long totalRunningTimeMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType)
+ / 1000;
+ final double powerDrain = ((totalRunningTimeMs - mTotalAppWifiRunningTimeMs) * mWifiPowerOn)
+ / (1000*60*60);
+ app.wifiRunningTimeMs = totalRunningTimeMs;
+ app.wifiPowerMah = Math.max(0, powerDrain);
+ }
+
+ @Override
+ public void reset() {
+ mTotalAppWifiRunningTimeMs = 0;
+ }
+}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 8674a21..75b6446 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -41,15 +41,10 @@ public final class Zygote {
/** enable the JIT compiler */
public static final int DEBUG_ENABLE_JIT = 1 << 5;
-
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = 0;
- /** Single-user external storage should be mounted. */
- public static final int MOUNT_EXTERNAL_SINGLEUSER = 1;
- /** Multi-user external storage should be mounted. */
- public static final int MOUNT_EXTERNAL_MULTIUSER = 2;
- /** All multi-user external storage should be mounted. */
- public static final int MOUNT_EXTERNAL_MULTIUSER_ALL = 3;
+ /** Default user-specific external storage should be mounted. */
+ public static final int MOUNT_EXTERNAL_DEFAULT = 1;
private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 4d405b2..9106ccd 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -514,10 +514,8 @@ class ZygoteConnection {
"Duplicate arg specified");
}
niceName = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.equals("--mount-external-multiuser")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
- } else if (arg.equals("--mount-external-multiuser-all")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL;
+ } else if (arg.equals("--mount-external-default")) {
+ mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
} else if (arg.equals("--query-abi-list")) {
abiListQuery = true;
} else if (arg.startsWith("--instruction-set=")) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 49d565d..70f7b72 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -32,6 +32,7 @@ import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructPollfd;
+import android.text.Hyphenator;
import android.util.EventLog;
import android.util.Log;
import android.webkit.WebViewFactory;
@@ -182,6 +183,7 @@ public class ZygoteInit {
preloadResources();
preloadOpenGL();
preloadSharedLibraries();
+ preloadTextResources();
// Ask the WebViewFactory to do any initialization that must run in the zygote process,
// for memory sharing purposes.
WebViewFactory.prepareWebViewInZygote();
@@ -201,6 +203,10 @@ public class ZygoteInit {
}
}
+ private static void preloadTextResources() {
+ Hyphenator.init();
+ }
+
/**
* Performs Zygote process initialization. Loads and initializes
* commonly used classes.
diff --git a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
index a529923..1d0511f 100644
--- a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
+++ b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
@@ -4,18 +4,12 @@ import android.app.ProgressDialog;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
-import android.os.Environment;
import android.os.IBinder;
import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.storage.IMountService;
-import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
-import android.util.Log;
+import android.util.Slog;
import android.view.WindowManager;
import android.widget.Toast;
@@ -24,8 +18,7 @@ import com.android.internal.R;
/**
* Takes care of unmounting and formatting external storage.
*/
-public class ExternalStorageFormatter extends Service
- implements DialogInterface.OnCancelListener {
+public class ExternalStorageFormatter extends Service {
static final String TAG = "ExternalStorageFormatter";
public static final String FORMAT_ONLY = "com.android.internal.os.storage.FORMAT_ONLY";
@@ -33,16 +26,10 @@ public class ExternalStorageFormatter extends Service
public static final String EXTRA_ALWAYS_RESET = "always_reset";
- // If non-null, the volume to format. Otherwise, will use the default external storage directory
- private StorageVolume mStorageVolume;
-
public static final ComponentName COMPONENT_NAME
= new ComponentName("android", ExternalStorageFormatter.class.getName());
- // Access using getMountService()
- private IMountService mMountService = null;
-
- private StorageManager mStorageManager = null;
+ private StorageManager mStorageManager;
private PowerManager.WakeLock mWakeLock;
@@ -52,24 +39,11 @@ public class ExternalStorageFormatter extends Service
private boolean mAlwaysReset = false;
private String mReason = null;
- StorageEventListener mStorageListener = new StorageEventListener() {
- @Override
- public void onStorageStateChanged(String path, String oldState, String newState) {
- Log.i(TAG, "Received storage state changed notification that " +
- path + " changed state from " + oldState +
- " to " + newState);
- updateProgressState();
- }
- };
-
@Override
public void onCreate() {
super.onCreate();
- if (mStorageManager == null) {
- mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
- mStorageManager.registerListener(mStorageListener);
- }
+ mStorageManager = getSystemService(StorageManager.class);
mWakeLock = ((PowerManager)getSystemService(Context.POWER_SERVICE))
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ExternalStorageFormatter");
@@ -86,170 +60,83 @@ public class ExternalStorageFormatter extends Service
}
mReason = intent.getStringExtra(Intent.EXTRA_REASON);
- mStorageVolume = intent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME);
-
- if (mProgressDialog == null) {
- mProgressDialog = new ProgressDialog(this);
- mProgressDialog.setIndeterminate(true);
- mProgressDialog.setCancelable(true);
- mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
- if (!mAlwaysReset) {
- mProgressDialog.setOnCancelListener(this);
- }
- updateProgressState();
- mProgressDialog.show();
+ StorageVolume userVol = intent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME);
+ if (userVol == null) {
+ Slog.w(TAG, "Missing explicit storage volume; assuming default");
+ userVol = mStorageManager.getPrimaryVolume();
}
- return Service.START_REDELIVER_INTENT;
- }
+ final String volumeId = userVol.getId();
- @Override
- public void onDestroy() {
- if (mStorageManager != null) {
- mStorageManager.unregisterListener(mStorageListener);
- }
- if (mProgressDialog != null) {
- mProgressDialog.dismiss();
- }
- mWakeLock.release();
- super.onDestroy();
- }
+ mProgressDialog = new ProgressDialog(this);
+ mProgressDialog.setIndeterminate(true);
+ mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ mProgressDialog.setMessage(getText(R.string.progress_unmounting));
+ mProgressDialog.show();
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
+ new FormatTask(volumeId).start();
- @Override
- public void onCancel(DialogInterface dialog) {
- IMountService mountService = getMountService();
- String extStoragePath = mStorageVolume == null ?
- Environment.getLegacyExternalStorageDirectory().toString() :
- mStorageVolume.getPath();
- try {
- mountService.mountVolume(extStoragePath);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed talking with mount service", e);
- }
- stopSelf();
+ return Service.START_REDELIVER_INTENT;
}
- void fail(int msg) {
- Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
- if (mAlwaysReset) {
- Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_REASON, mReason);
- sendBroadcast(intent);
+ private class FormatTask extends Thread {
+ private final String mVolumeId;
+
+ public FormatTask(String volumeId) {
+ mVolumeId = volumeId;
}
- stopSelf();
- }
- void updateProgressState() {
- String status = mStorageVolume == null ?
- Environment.getExternalStorageState() :
- mStorageManager.getVolumeState(mStorageVolume.getPath());
- if (Environment.MEDIA_MOUNTED.equals(status)
- || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status)) {
- updateProgressDialog(R.string.progress_unmounting);
- IMountService mountService = getMountService();
- final String extStoragePath = mStorageVolume == null ?
- Environment.getLegacyExternalStorageDirectory().toString() :
- mStorageVolume.getPath();
+ @Override
+ public void run() {
+ boolean success = false;
try {
- // Remove encryption mapping if this is an unmount for a factory reset.
- mountService.unmountVolume(extStoragePath, true, mFactoryReset);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed talking with mount service", e);
+ mStorageManager.format(mVolumeId);
+ success = true;
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to format", e);
+ Toast.makeText(ExternalStorageFormatter.this,
+ R.string.format_error, Toast.LENGTH_LONG).show();
+ }
+ if (success) {
+ if (mFactoryReset) {
+ Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_REASON, mReason);
+ sendBroadcast(intent);
+ // Intent handling is asynchronous -- assume it will happen soon.
+ stopSelf();
+ return;
+ }
}
- } else if (Environment.MEDIA_NOFS.equals(status)
- || Environment.MEDIA_UNMOUNTED.equals(status)
- || Environment.MEDIA_UNMOUNTABLE.equals(status)) {
- updateProgressDialog(R.string.progress_erasing);
- final IMountService mountService = getMountService();
- final String extStoragePath = mStorageVolume == null ?
- Environment.getLegacyExternalStorageDirectory().toString() :
- mStorageVolume.getPath();
- if (mountService != null) {
- new Thread() {
- @Override
- public void run() {
- boolean success = false;
- try {
- mountService.formatVolume(extStoragePath);
- success = true;
- } catch (Exception e) {
- Toast.makeText(ExternalStorageFormatter.this,
- R.string.format_error, Toast.LENGTH_LONG).show();
- }
- if (success) {
- if (mFactoryReset) {
- Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_REASON, mReason);
- sendBroadcast(intent);
- // Intent handling is asynchronous -- assume it will happen soon.
- stopSelf();
- return;
- }
- }
- // If we didn't succeed, or aren't doing a full factory
- // reset, then it is time to remount the storage.
- if (!success && mAlwaysReset) {
- Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_REASON, mReason);
- sendBroadcast(intent);
- } else {
- try {
- mountService.mountVolume(extStoragePath);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed talking with mount service", e);
- }
- }
- stopSelf();
- return;
- }
- }.start();
+ // If we didn't succeed, or aren't doing a full factory
+ // reset, then it is time to remount the storage.
+ if (!success && mAlwaysReset) {
+ Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_REASON, mReason);
+ sendBroadcast(intent);
} else {
- Log.w(TAG, "Unable to locate IMountService");
+ try {
+ mStorageManager.mount(mVolumeId);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to mount", e);
+ }
}
- } else if (Environment.MEDIA_BAD_REMOVAL.equals(status)) {
- fail(R.string.media_bad_removal);
- } else if (Environment.MEDIA_CHECKING.equals(status)) {
- fail(R.string.media_checking);
- } else if (Environment.MEDIA_REMOVED.equals(status)) {
- fail(R.string.media_removed);
- } else if (Environment.MEDIA_SHARED.equals(status)) {
- fail(R.string.media_shared);
- } else {
- fail(R.string.media_unknown_state);
- Log.w(TAG, "Unknown storage state: " + status);
stopSelf();
}
}
- public void updateProgressDialog(int msg) {
- if (mProgressDialog == null) {
- mProgressDialog = new ProgressDialog(this);
- mProgressDialog.setIndeterminate(true);
- mProgressDialog.setCancelable(false);
- mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
- mProgressDialog.show();
+ @Override
+ public void onDestroy() {
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
}
-
- mProgressDialog.setMessage(getText(msg));
+ mWakeLock.release();
+ super.onDestroy();
}
- IMountService getMountService() {
- if (mMountService == null) {
- IBinder service = ServiceManager.getService("mount");
- if (service != null) {
- mMountService = IMountService.Stub.asInterface(service);
- } else {
- Log.e(TAG, "Can't get mount service");
- }
- }
- return mMountService;
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
}
}
diff --git a/core/java/com/android/internal/util/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java
index c153904..7d56e9e 100644
--- a/core/java/com/android/internal/util/ImageUtils.java
+++ b/core/java/com/android/internal/util/ImageUtils.java
@@ -17,10 +17,13 @@
package com.android.internal.util;
import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
/**
* Utility class for image analysis and processing.
@@ -117,4 +120,40 @@ public class ImageUtils {
&& Math.abs(r - b) < TOLERANCE
&& Math.abs(g - b) < TOLERANCE;
}
+
+ /**
+ * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight.
+ */
+ public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
+ int maxHeight) {
+ if (drawable == null) {
+ return null;
+ }
+ int originalWidth = drawable.getIntrinsicWidth();
+ int originalHeight = drawable.getIntrinsicHeight();
+
+ if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight) &&
+ (drawable instanceof BitmapDrawable)) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+ if (originalHeight <= 0 || originalWidth <= 0) {
+ return null;
+ }
+
+ // create a new bitmap, scaling down to fit the max dimensions of
+ // a large notification icon if necessary
+ float ratio = Math.min((float) maxWidth / (float) originalWidth,
+ (float) maxHeight / (float) originalHeight);
+ ratio = Math.min(1.0f, ratio);
+ int scaledWidth = (int) (ratio * originalWidth);
+ int scaledHeight = (int) (ratio * originalHeight);
+ Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888);
+
+ // and paint our app bitmap on it
+ Canvas canvas = new Canvas(result);
+ drawable.setBounds(0, 0, scaledWidth, scaledHeight);
+ drawable.draw(canvas);
+
+ return result;
+ }
}
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index 6fddd09..f1add27 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -18,6 +18,7 @@ package com.android.internal.util;
import java.io.PrintWriter;
import java.io.Writer;
+import java.util.Arrays;
/**
* Lightweight wrapper around {@link PrintWriter} that automatically indents
@@ -68,6 +69,10 @@ public class IndentingPrintWriter extends PrintWriter {
print(key + "=" + String.valueOf(value) + " ");
}
+ public void printPair(String key, Object[] value) {
+ print(key + "=" + Arrays.toString(value) + " ");
+ }
+
public void printHexPair(String key, int value) {
print(key + "=0x" + Integer.toHexString(value) + " ");
}
diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java
new file mode 100644
index 0000000..aacdb34
--- /dev/null
+++ b/core/java/com/android/internal/view/FloatingActionMode.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 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.internal.view;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.widget.FloatingToolbar;
+
+public class FloatingActionMode extends ActionMode {
+
+ private final Context mContext;
+ private final ActionMode.Callback2 mCallback;
+ private final MenuBuilder mMenu;
+ private final FloatingToolbar mFloatingToolbar;
+ private final Rect mContentRect;
+ private final Rect mContentRectOnWindow;
+ private final Rect mPreviousContentRectOnWindow;
+ private final int[] mViewPosition;
+ private final View mOriginatingView;
+
+ public FloatingActionMode(
+ Context context, ActionMode.Callback2 callback, View originatingView,
+ FloatingToolbar floatingToolbar) {
+ mContext = context;
+ mCallback = callback;
+ mMenu = new MenuBuilder(context).setDefaultShowAsAction(
+ MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ mFloatingToolbar = floatingToolbar
+ .setMenu(mMenu)
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ return mCallback.onActionItemClicked(FloatingActionMode.this, item);
+ }
+ });
+ setType(ActionMode.TYPE_FLOATING);
+ mContentRect = new Rect();
+ mContentRectOnWindow = new Rect();
+ mPreviousContentRectOnWindow = new Rect();
+ mViewPosition = new int[2];
+ mOriginatingView = originatingView;
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {}
+
+ @Override
+ public void setTitle(int resId) {}
+
+ @Override
+ public void setSubtitle(CharSequence subtitle) {}
+
+ @Override
+ public void setSubtitle(int resId) {}
+
+ @Override
+ public void setCustomView(View view) {}
+
+ @Override
+ public void invalidate() {
+ mCallback.onPrepareActionMode(this, mMenu);
+ mFloatingToolbar.updateLayout();
+ invalidateContentRect();
+ }
+
+ @Override
+ public void invalidateContentRect() {
+ mCallback.onGetContentRect(this, mOriginatingView, mContentRect);
+ repositionToolbar();
+ }
+
+ public void updateViewLocationInWindow() {
+ mOriginatingView.getLocationInWindow(mViewPosition);
+ repositionToolbar();
+ }
+
+ private void repositionToolbar() {
+ mContentRectOnWindow.set(
+ mContentRect.left + mViewPosition[0],
+ mContentRect.top + mViewPosition[1],
+ mContentRect.right + mViewPosition[0],
+ mContentRect.bottom + mViewPosition[1]);
+ if (!mContentRectOnWindow.equals(mPreviousContentRectOnWindow)) {
+ mFloatingToolbar.setContentRect(mContentRectOnWindow);
+ mFloatingToolbar.updateLayout();
+ }
+ mPreviousContentRectOnWindow.set(mContentRectOnWindow);
+ }
+
+ @Override
+ public void finish() {
+ mCallback.onDestroyActionMode(this);
+ }
+
+ @Override
+ public Menu getMenu() {
+ return mMenu;
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getSubtitle() {
+ return null;
+ }
+
+ @Override
+ public View getCustomView() {
+ return null;
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ return new MenuInflater(mContext);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
new file mode 100644
index 0000000..be9945d
--- /dev/null
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2015 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.internal.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A floating toolbar for showing contextual menu items.
+ * This view shows as many menu item buttons as can fit in the horizontal toolbar and the
+ * the remaining menu items in a vertical overflow view when the overflow button is clicked.
+ * The horizontal toolbar morphs into the vertical overflow view.
+ */
+public final class FloatingToolbar {
+
+ private static final MenuItem.OnMenuItemClickListener NO_OP_MENUITEM_CLICK_LISTENER =
+ new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ return false;
+ }
+ };
+
+ private final Context mContext;
+ private final FloatingToolbarPopup mPopup;
+ private final ViewGroup mMenuItemButtonsContainer;
+ private final View.OnClickListener mMenuItemButtonOnClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (v.getTag() instanceof MenuItem) {
+ mMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
+ mPopup.dismiss();
+ }
+ }
+ };
+
+ private final Rect mContentRect = new Rect();
+ private final Point mCoordinates = new Point();
+
+ private Menu mMenu;
+ private List<CharSequence> mShowingTitles = new ArrayList<CharSequence>();
+ private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;
+ private View mOpenOverflowButton;
+
+ private int mSuggestedWidth;
+
+ /**
+ * Initializes a floating toolbar.
+ */
+ public FloatingToolbar(Context context, Window window) {
+ mContext = Preconditions.checkNotNull(context);
+ mPopup = new FloatingToolbarPopup(Preconditions.checkNotNull(window.getDecorView()));
+ mMenuItemButtonsContainer = createMenuButtonsContainer(context);
+ }
+
+ /**
+ * Sets the menu to be shown in this floating toolbar.
+ * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the
+ * toolbar.
+ */
+ public FloatingToolbar setMenu(Menu menu) {
+ mMenu = Preconditions.checkNotNull(menu);
+ return this;
+ }
+
+ /**
+ * Sets the custom listener for invocation of menu items in this floating
+ * toolbar.
+ */
+ public FloatingToolbar setOnMenuItemClickListener(
+ MenuItem.OnMenuItemClickListener menuItemClickListener) {
+ if (menuItemClickListener != null) {
+ mMenuItemClickListener = menuItemClickListener;
+ } else {
+ mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;
+ }
+ return this;
+ }
+
+ /**
+ * Sets the content rectangle. This is the area of the interesting content that this toolbar
+ * should avoid obstructing.
+ * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the
+ * toolbar.
+ */
+ public FloatingToolbar setContentRect(Rect rect) {
+ mContentRect.set(Preconditions.checkNotNull(rect));
+ return this;
+ }
+
+ /**
+ * Sets the suggested width of this floating toolbar.
+ * The actual width will be about this size but there are no guarantees that it will be exactly
+ * the suggested width.
+ * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the
+ * toolbar.
+ */
+ public FloatingToolbar setSuggestedWidth(int suggestedWidth) {
+ mSuggestedWidth = suggestedWidth;
+ return this;
+ }
+
+ /**
+ * Shows this floating toolbar.
+ */
+ public FloatingToolbar show() {
+ List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
+ if (hasContentChanged(menuItems) || hasWidthChanged()) {
+ mPopup.dismiss();
+ layoutMenuItemButtons(menuItems);
+ mShowingTitles = getMenuItemTitles(menuItems);
+ }
+ refreshCoordinates();
+ mPopup.updateCoordinates(mCoordinates.x, mCoordinates.y);
+ if (!mPopup.isShowing()) {
+ mPopup.show(mCoordinates.x, mCoordinates.y);
+ }
+ return this;
+ }
+
+ /**
+ * Updates this floating toolbar to reflect recent position and view updates.
+ * NOTE: This method is a no-op if the toolbar isn't showing.
+ */
+ public FloatingToolbar updateLayout() {
+ if (mPopup.isShowing()) {
+ // show() performs all the logic we need here.
+ show();
+ }
+ return this;
+ }
+
+ /**
+ * Dismisses this floating toolbar.
+ */
+ public void dismiss() {
+ mPopup.dismiss();
+ }
+
+ /**
+ * Returns {@code true} if this popup is currently showing. {@code false} otherwise.
+ */
+ public boolean isShowing() {
+ return mPopup.isShowing();
+ }
+
+ /**
+ * Refreshes {@link #mCoordinates} with values based on {@link #mContentRect}.
+ */
+ private void refreshCoordinates() {
+ int popupWidth = mPopup.getWidth();
+ int popupHeight = mPopup.getHeight();
+ if (!mPopup.isShowing()) {
+ // Popup isn't yet shown, get estimated size from the menu item buttons container.
+ mMenuItemButtonsContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ popupWidth = mMenuItemButtonsContainer.getMeasuredWidth();
+ popupHeight = mMenuItemButtonsContainer.getMeasuredHeight();
+ }
+ int x = mContentRect.centerX() - popupWidth / 2;
+ int y;
+ if (shouldDisplayAtTopOfContent()) {
+ y = mContentRect.top - popupHeight;
+ } else {
+ y = mContentRect.bottom;
+ }
+ mCoordinates.set(x, y);
+ }
+
+ /**
+ * Returns true if this floating toolbar's menu items have been reordered or changed.
+ */
+ private boolean hasContentChanged(List<MenuItem> menuItems) {
+ return !mShowingTitles.equals(getMenuItemTitles(menuItems));
+ }
+
+ /**
+ * Returns true if there is a significant change in width of the toolbar.
+ */
+ private boolean hasWidthChanged() {
+ int actualWidth = mPopup.getWidth();
+ int difference = Math.abs(actualWidth - mSuggestedWidth);
+ return difference > (actualWidth * 0.2);
+ }
+
+ /**
+ * Returns true if the preferred positioning of the toolbar is above the content rect.
+ */
+ private boolean shouldDisplayAtTopOfContent() {
+ return mContentRect.top - getMinimumOverflowHeight(mContext) > 0;
+ }
+
+ /**
+ * Returns the visible and enabled menu items in the specified menu.
+ * This method is recursive.
+ */
+ private List<MenuItem> getVisibleAndEnabledMenuItems(Menu menu) {
+ List<MenuItem> menuItems = new ArrayList<MenuItem>();
+ for (int i = 0; (menu != null) && (i < menu.size()); i++) {
+ MenuItem menuItem = menu.getItem(i);
+ if (menuItem.isVisible() && menuItem.isEnabled()) {
+ Menu subMenu = menuItem.getSubMenu();
+ if (subMenu != null) {
+ menuItems.addAll(getVisibleAndEnabledMenuItems(subMenu));
+ } else {
+ menuItems.add(menuItem);
+ }
+ }
+ }
+ return menuItems;
+ }
+
+ private List<CharSequence> getMenuItemTitles(List<MenuItem> menuItems) {
+ List<CharSequence> titles = new ArrayList<CharSequence>();
+ for (MenuItem menuItem : menuItems) {
+ titles.add(menuItem.getTitle());
+ }
+ return titles;
+ }
+
+ private void layoutMenuItemButtons(List<MenuItem> menuItems) {
+ final int toolbarWidth = getAdjustedToolbarWidth(mContext, mSuggestedWidth)
+ // Reserve space for the "open overflow" button.
+ - getEstimatedOpenOverflowButtonWidth(mContext);
+
+ int availableWidth = toolbarWidth;
+ LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems);
+
+ mMenuItemButtonsContainer.removeAllViews();
+
+ boolean isFirstItem = true;
+ while (!remainingMenuItems.isEmpty()) {
+ final MenuItem menuItem = remainingMenuItems.peek();
+ Button menuItemButton = createMenuItemButton(mContext, menuItem);
+
+ // Adding additional left padding for the first button to even out button spacing.
+ if (isFirstItem) {
+ menuItemButton.setPadding(
+ 2 * menuItemButton.getPaddingLeft(),
+ menuItemButton.getPaddingTop(),
+ menuItemButton.getPaddingRight(),
+ menuItemButton.getPaddingBottom());
+ isFirstItem = false;
+ }
+
+ // Adding additional right padding for the last button to even out button spacing.
+ if (remainingMenuItems.size() == 1) {
+ menuItemButton.setPadding(
+ menuItemButton.getPaddingLeft(),
+ menuItemButton.getPaddingTop(),
+ 2 * menuItemButton.getPaddingRight(),
+ menuItemButton.getPaddingBottom());
+ }
+
+ menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
+ if (menuItemButtonWidth <= availableWidth) {
+ menuItemButton.setTag(menuItem);
+ menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
+ mMenuItemButtonsContainer.addView(menuItemButton);
+ menuItemButton.getLayoutParams().width = menuItemButtonWidth;
+ availableWidth -= menuItemButtonWidth;
+ remainingMenuItems.pop();
+ } else {
+ // The "open overflow" button launches the vertical overflow from the
+ // floating toolbar.
+ createOpenOverflowButtonIfNotExists();
+ mMenuItemButtonsContainer.addView(mOpenOverflowButton);
+ break;
+ }
+ }
+ mPopup.setContentView(mMenuItemButtonsContainer);
+ }
+
+ /**
+ * Creates and returns the button that opens the vertical overflow.
+ */
+ private void createOpenOverflowButtonIfNotExists() {
+ mOpenOverflowButton = (ImageButton) LayoutInflater.from(mContext)
+ .inflate(R.layout.floating_popup_open_overflow_button, null);
+ mOpenOverflowButton.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Open the overflow.
+ }
+ });
+ }
+
+ /**
+ * Creates and returns a floating toolbar menu buttons container.
+ */
+ private static ViewGroup createMenuButtonsContainer(Context context) {
+ return (ViewGroup) LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_container, null);
+ }
+
+ /**
+ * Creates and returns a menu button for the specified menu item.
+ */
+ private static Button createMenuItemButton(Context context, MenuItem menuItem) {
+ Button menuItemButton = (Button) LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_menu_button, null);
+ menuItemButton.setText(menuItem.getTitle());
+ menuItemButton.setContentDescription(menuItem.getTitle());
+ return menuItemButton;
+ }
+
+ private static int getMinimumOverflowHeight(Context context) {
+ return context.getResources().
+ getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height);
+ }
+
+ private static int getEstimatedOpenOverflowButtonWidth(Context context) {
+ return context.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_menu_button_minimum_width);
+ }
+
+ private static int getAdjustedToolbarWidth(Context context, int width) {
+ if (width <= 0 || width > getScreenWidth(context)) {
+ width = context.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_default_width);
+ }
+ return width;
+ }
+
+ /**
+ * Returns the device's screen width.
+ */
+ public static int getScreenWidth(Context context) {
+ return context.getResources().getDisplayMetrics().widthPixels;
+ }
+
+ /**
+ * Returns the device's screen height.
+ */
+ public static int getScreenHeight(Context context) {
+ return context.getResources().getDisplayMetrics().heightPixels;
+ }
+
+
+ /**
+ * A popup window used by the floating toolbar.
+ */
+ private static final class FloatingToolbarPopup {
+
+ private final View mParent;
+ private final PopupWindow mPopupWindow;
+ private final ViewGroup mContentContainer;
+ private final Animator.AnimatorListener mOnDismissEnd =
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPopupWindow.dismiss();
+ mDismissAnimating = false;
+ }
+ };
+ private final AnimatorSet mGrowFadeInFromBottomAnimation;
+ private final AnimatorSet mShrinkFadeOutFromBottomAnimation;
+
+ private boolean mDismissAnimating;
+
+ /**
+ * Initializes a new floating bar popup.
+ *
+ * @param parent A parent view to get the {@link View#getWindowToken()} token from.
+ */
+ public FloatingToolbarPopup(View parent) {
+ mParent = Preconditions.checkNotNull(parent);
+ mContentContainer = createContentContainer(parent.getContext());
+ mPopupWindow = createPopupWindow(mContentContainer);
+ mGrowFadeInFromBottomAnimation = createGrowFadeInFromBottom(mContentContainer);
+ mShrinkFadeOutFromBottomAnimation =
+ createShrinkFadeOutFromBottomAnimation(mContentContainer, mOnDismissEnd);
+ }
+
+ /**
+ * Shows this popup at the specified coordinates.
+ * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
+ * If this popup is already showing, this will be a no-op.
+ */
+ public void show(int x, int y) {
+ if (isShowing()) {
+ updateCoordinates(x, y);
+ return;
+ }
+
+ mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, 0, 0);
+ positionOnScreen(x, y);
+ growFadeInFromBottom();
+
+ mDismissAnimating = false;
+ }
+
+ /**
+ * Gets rid of this popup. If the popup isn't currently showing, this will be a no-op.
+ */
+ public void dismiss() {
+ if (!isShowing()) {
+ return;
+ }
+
+ if (mDismissAnimating) {
+ // This window is already dismissing. Don't restart the animation.
+ return;
+ }
+ mDismissAnimating = true;
+ shrinkFadeOutFromBottom();
+ }
+
+ /**
+ * Returns {@code true} if this popup is currently showing. {@code false} otherwise.
+ */
+ public boolean isShowing() {
+ return mPopupWindow.isShowing() && !mDismissAnimating;
+ }
+
+ /**
+ * Updates the coordinates of this popup.
+ * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
+ */
+ public void updateCoordinates(int x, int y) {
+ if (isShowing()) {
+ positionOnScreen(x, y);
+ }
+ }
+
+ /**
+ * Sets the content of this popup.
+ */
+ public void setContentView(View view) {
+ Preconditions.checkNotNull(view);
+ mContentContainer.removeAllViews();
+ mContentContainer.addView(view);
+ }
+
+ /**
+ * Returns the width of this popup.
+ */
+ public int getWidth() {
+ return mContentContainer.getWidth();
+ }
+
+ /**
+ * Returns the height of this popup.
+ */
+ public int getHeight() {
+ return mContentContainer.getHeight();
+ }
+
+ /**
+ * Returns the context this popup is running in.
+ */
+ public Context getContext() {
+ return mContentContainer.getContext();
+ }
+
+ private void positionOnScreen(int x, int y) {
+ if (getWidth() == 0) {
+ // content size is yet to be measured.
+ mContentContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ }
+ x = clamp(x, 0, getScreenWidth(getContext()) - getWidth());
+ y = clamp(y, 0, getScreenHeight(getContext()) - getHeight());
+
+ // Position the view w.r.t. the window.
+ mContentContainer.setX(x);
+ mContentContainer.setY(y);
+ }
+
+ /**
+ * Performs the "grow and fade in from the bottom" animation on the floating popup.
+ */
+ private void growFadeInFromBottom() {
+ setPivot();
+ mGrowFadeInFromBottomAnimation.start();
+ }
+
+ /**
+ * Performs the "shrink and fade out from bottom" animation on the floating popup.
+ */
+ private void shrinkFadeOutFromBottom() {
+ setPivot();
+ mShrinkFadeOutFromBottomAnimation.start();
+ }
+
+ /**
+ * Sets the popup content container's pivot.
+ */
+ private void setPivot() {
+ mContentContainer.setPivotX(mContentContainer.getMeasuredWidth() / 2);
+ mContentContainer.setPivotY(mContentContainer.getMeasuredHeight());
+ }
+
+ private static ViewGroup createContentContainer(Context context) {
+ return (ViewGroup) LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_container, null);
+ }
+
+ private static PopupWindow createPopupWindow(View content) {
+ ViewGroup popupContentHolder = new LinearLayout(content.getContext());
+ PopupWindow popupWindow = new PopupWindow(popupContentHolder);
+ popupWindow.setAnimationStyle(0);
+ popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ popupWindow.setWidth(getScreenWidth(content.getContext()));
+ popupWindow.setHeight(getScreenHeight(content.getContext()));
+ content.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ popupContentHolder.addView(content);
+ return popupWindow;
+ }
+
+ /**
+ * Creates a "grow and fade in from the bottom" animation for the specified view.
+ *
+ * @param view The view to animate
+ */
+ private static AnimatorSet createGrowFadeInFromBottom(View view) {
+ AnimatorSet growFadeInFromBottomAnimation = new AnimatorSet();
+ growFadeInFromBottomAnimation.playTogether(
+ ObjectAnimator.ofFloat(view, View.SCALE_X, 0.5f, 1).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.5f, 1).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(75));
+ return growFadeInFromBottomAnimation;
+ }
+
+ /**
+ * Creates a "shrink and fade out from bottom" animation for the specified view.
+ *
+ * @param view The view to animate
+ * @param listener The animation listener
+ */
+ private static AnimatorSet createShrinkFadeOutFromBottomAnimation(
+ View view, Animator.AnimatorListener listener) {
+ AnimatorSet shrinkFadeOutFromBottomAnimation = new AnimatorSet();
+ shrinkFadeOutFromBottomAnimation.playTogether(
+ ObjectAnimator.ofFloat(view, View.SCALE_Y, 1, 0.5f).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(75));
+ shrinkFadeOutFromBottomAnimation.setStartDelay(150);
+ shrinkFadeOutFromBottomAnimation.addListener(listener);
+ return shrinkFadeOutFromBottomAnimation;
+ }
+
+ /**
+ * Returns value, restricted to the range min->max (inclusive).
+ * If maximum is less than minimum, the result is undefined.
+ *
+ * @param value The value to clamp.
+ * @param minimum The minimum value in the range.
+ * @param maximum The maximum value in the range. Must not be less than minimum.
+ */
+ private static int clamp(int value, int minimum, int maximum) {
+ return Math.max(minimum, Math.min(value, maximum));
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 90821dc..2967876 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -449,29 +449,29 @@ public class LockPatternUtils {
* @param disable Disables lock screen when true
*/
public void setLockScreenDisabled(boolean disable) {
- setBoolean(DISABLE_LOCKSCREEN_KEY, disable, getCurrentOrCallingUserId());
+ setLockScreenDisabled(disable, getCurrentOrCallingUserId());
}
/**
- * Determine if LockScreen can be disabled. This is used, for example, to tell if we should
- * show LockScreen or go straight to the home screen.
+ * Disable showing lock screen at all for a given user.
+ * This is only meaningful if pattern, pin or password are not set.
+ *
+ * @param disable Disables lock screen when true
+ * @param userId User ID of the user this has effect on
+ */
+ public void setLockScreenDisabled(boolean disable, int userId) {
+ setBoolean(DISABLE_LOCKSCREEN_KEY, disable, userId);
+ }
+
+ /**
+ * Determine if LockScreen is disabled for the current user. This is used to decide whether
+ * LockScreen is shown after reboot or after screen timeout / short press on power.
*
- * @return true if lock screen is can be disabled
+ * @return true if lock screen is disabled
*/
public boolean isLockScreenDisabled() {
- if (!isSecure() && getBoolean(DISABLE_LOCKSCREEN_KEY, false, getCurrentOrCallingUserId())) {
- // Check if the number of switchable users forces the lockscreen.
- final List<UserInfo> users = UserManager.get(mContext).getUsers(true);
- final int userCount = users.size();
- int switchableUsers = 0;
- for (int i = 0; i < userCount; i++) {
- if (users.get(i).supportsSwitchTo()) {
- switchableUsers++;
- }
- }
- return switchableUsers < 2;
- }
- return false;
+ return !isSecure() &&
+ getBoolean(DISABLE_LOCKSCREEN_KEY, false, getCurrentOrCallingUserId());
}
/**
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
index 8018942..8d66191 100644
--- a/core/java/com/android/internal/widget/ViewPager.java
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -42,6 +42,7 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityRecord;
import android.view.animation.Interpolator;
import android.widget.EdgeEffect;
@@ -371,8 +372,6 @@ public class ViewPager extends ViewGroup {
mCloseEnough = (int) (CLOSE_ENOUGH * density);
mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
- setAccessibilityDelegate(new MyAccessibilityDelegate());
-
if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
@@ -2695,29 +2694,6 @@ public class ViewPager extends ViewGroup {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- // Dispatch scroll events from this ViewPager.
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
- return super.dispatchPopulateAccessibilityEvent(event);
- }
-
- // Dispatch all other accessibility events from the current page.
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == VISIBLE) {
- final ItemInfo ii = infoForChild(child);
- if (ii != null && ii.position == mCurItem &&
- child.dispatchPopulateAccessibilityEvent(event)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- @Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
@@ -2737,60 +2713,63 @@ public class ViewPager extends ViewGroup {
return new LayoutParams(getContext(), attrs);
}
- class MyAccessibilityDelegate extends AccessibilityDelegate {
- @Override
- public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(host, event);
- event.setClassName(ViewPager.class.getName());
- final AccessibilityRecord record = AccessibilityRecord.obtain();
- record.setScrollable(canScroll());
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED
- && mAdapter != null) {
- record.setItemCount(mAdapter.getCount());
- record.setFromIndex(mCurItem);
- record.setToIndex(mCurItem);
- }
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+
+ event.setClassName(ViewPager.class.getName());
+ event.setScrollable(canScroll());
+
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED && mAdapter != null) {
+ event.setItemCount(mAdapter.getCount());
+ event.setFromIndex(mCurItem);
+ event.setToIndex(mCurItem);
}
+ }
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- info.setClassName(ViewPager.class.getName());
- info.setScrollable(canScroll());
- if (canScrollHorizontally(1)) {
- info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
- }
- if (canScrollHorizontally(-1)) {
- info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
- }
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+
+ info.setClassName(ViewPager.class.getName());
+ info.setScrollable(canScroll());
+
+ if (canScrollHorizontally(1)) {
+ info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
}
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (super.performAccessibilityAction(host, action, args)) {
- return true;
- }
- switch (action) {
- case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
- if (canScrollHorizontally(1)) {
- setCurrentItem(mCurItem + 1);
- return true;
- }
- } return false;
- case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
- if (canScrollHorizontally(-1)) {
- setCurrentItem(mCurItem - 1);
- return true;
- }
- } return false;
- }
- return false;
+ if (canScrollHorizontally(-1)) {
+ info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
}
+ }
- private boolean canScroll() {
- return (mAdapter != null) && (mAdapter.getCount() > 1);
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle args) {
+ if (super.performAccessibilityAction(action, args)) {
+ return true;
}
+
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
+ if (canScrollHorizontally(1)) {
+ setCurrentItem(mCurItem + 1);
+ return true;
+ }
+ return false;
+ case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
+ if (canScrollHorizontally(-1)) {
+ setCurrentItem(mCurItem - 1);
+ return true;
+ }
+ return false;
+ }
+
+ return false;
+ }
+
+ private boolean canScroll() {
+ return mAdapter != null && mAdapter.getCount() > 1;
}
private class PagerObserver extends DataSetObserver {
diff --git a/core/java/com/android/server/backup/PreferredActivityBackupHelper.java b/core/java/com/android/server/backup/PreferredActivityBackupHelper.java
new file mode 100644
index 0000000..6ac0d89
--- /dev/null
+++ b/core/java/com/android/server/backup/PreferredActivityBackupHelper.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 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.backup;
+
+import android.app.AppGlobals;
+import android.app.backup.BackupDataInputStream;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupHelper;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.org.bouncycastle.util.Arrays;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class PreferredActivityBackupHelper implements BackupHelper {
+ private static final String TAG = "PreferredBackup";
+ private static final boolean DEBUG = true;
+
+ // current schema of the backup state blob
+ private static final int STATE_VERSION = 1;
+
+ // key under which the preferred-activity state blob is committed to backup
+ private static final String KEY_PREFERRED = "preferred-activity";
+
+ final Context mContext;
+
+ public PreferredActivityBackupHelper(Context context) {
+ mContext = context;
+ }
+
+ // The fds passed here are shared among all helpers, so we mustn't close them
+ private void writeState(ParcelFileDescriptor stateFile, byte[] payload) {
+ try {
+ FileOutputStream fos = new FileOutputStream(stateFile.getFileDescriptor());
+
+ // We explicitly don't close 'out' because we must not close the backing fd.
+ // The FileOutputStream will not close it implicitly.
+ @SuppressWarnings("resource")
+ DataOutputStream out = new DataOutputStream(fos);
+
+ out.writeInt(STATE_VERSION);
+ if (payload == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(payload.length);
+ out.write(payload);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to write updated state", e);
+ }
+ }
+
+ private byte[] readState(ParcelFileDescriptor oldStateFd) {
+ FileInputStream fis = new FileInputStream(oldStateFd.getFileDescriptor());
+ BufferedInputStream bis = new BufferedInputStream(fis);
+
+ @SuppressWarnings("resource")
+ DataInputStream in = new DataInputStream(bis);
+
+ byte[] oldState = null;
+ try {
+ int version = in.readInt();
+ if (version == STATE_VERSION) {
+ int size = in.readInt();
+ if (size > 0) {
+ if (size > 200*1024) {
+ Slog.w(TAG, "Suspiciously large state blog; ignoring. N=" + size);
+ } else {
+ // size looks okay; make the return buffer and fill it
+ oldState = new byte[size];
+ in.read(oldState);
+ }
+ }
+ } else {
+ Slog.w(TAG, "Prior state from unrecognized version " + version);
+ }
+ } catch (EOFException e) {
+ // Empty file is expected on first backup, so carry on. If the state
+ // is truncated we just treat it the same way.
+ oldState = null;
+ } catch (Exception e) {
+ Slog.w(TAG, "Error examing prior backup state " + e.getMessage());
+ oldState = null;
+ }
+
+ return oldState;
+ }
+
+ @Override
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ byte[] payload = null;
+ try {
+ byte[] oldPayload = readState(oldState);
+
+ IPackageManager pm = AppGlobals.getPackageManager();
+ byte[] newPayload = pm.getPreferredActivityBackup(UserHandle.USER_OWNER);
+ if (!Arrays.areEqual(oldPayload, newPayload)) {
+ if (DEBUG) {
+ Slog.i(TAG, "State has changed => writing new preferred app payload");
+ }
+ data.writeEntityHeader(KEY_PREFERRED, newPayload.length);
+ data.writeEntityData(newPayload, newPayload.length);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "No change to state => not writing to wire");
+ }
+ }
+
+ // Always need to re-record the state, even if nothing changed
+ payload = newPayload;
+ } catch (Exception e) {
+ // On failures we'll wind up committing a zero-size state payload. This is
+ // a forward-safe situation because we know we commit the entire new payload
+ // on prior-state mismatch.
+ Slog.w(TAG, "Unable to record preferred activities", e);
+ } finally {
+ writeState(newState, payload);
+ }
+ }
+
+ @Override
+ public void restoreEntity(BackupDataInputStream data) {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ try {
+ byte[] payload = new byte[data.size()];
+ data.read(payload);
+ if (DEBUG) {
+ Slog.i(TAG, "Restoring preferred activities; size=" + payload.length);
+ }
+ pm.restorePreferredActivities(payload, UserHandle.USER_OWNER);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception reading restore data", e);
+ }
+ }
+
+ @Override
+ public void writeNewStateDescription(ParcelFileDescriptor newState) {
+ writeState(newState, null);
+ }
+
+}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index b5f2f37..19d9e29 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -16,7 +16,6 @@
package com.android.server.backup;
-
import android.app.ActivityManagerNative;
import android.app.IWallpaperManager;
import android.app.backup.BackupDataInput;
@@ -43,6 +42,13 @@ import java.io.IOException;
public class SystemBackupAgent extends BackupAgentHelper {
private static final String TAG = "SystemBackupAgent";
+ // Names of the helper tags within the dataset. Changing one of these names will
+ // break the ability to restore from datasets that predate the change.
+ private static final String WALLPAPER_HELPER = "wallpaper";
+ private static final String RECENTS_HELPER = "recents";
+ private static final String SYNC_SETTINGS_HELPER = "account_sync_settings";
+ private static final String PREFERRED_HELPER = "preferred_activities";
+
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
// taken to support the legacy backed-up datasets.
@@ -84,10 +90,10 @@ public class SystemBackupAgent extends BackupAgentHelper {
Slog.e(TAG, "Couldn't get wallpaper name\n" + re);
}
}
- addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files, keys));
- addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this));
- addHelper("account_sync_settings",
- new AccountSyncSettingsBackupHelper(SystemBackupAgent.this));
+ addHelper(WALLPAPER_HELPER, new WallpaperBackupHelper(this, files, keys));
+ addHelper(RECENTS_HELPER, new RecentsBackupHelper(this));
+ addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
+ addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(this));
super.onBackup(oldState, data, newState);
}
@@ -104,24 +110,24 @@ public class SystemBackupAgent extends BackupAgentHelper {
// steps during restore; the restore will happen properly when the individual
// files are restored piecemeal.
FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null,
- WALLPAPER_INFO_DIR, WALLPAPER_INFO, output.getData());
+ WALLPAPER_INFO_DIR, WALLPAPER_INFO, output);
FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null,
- WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output.getData());
+ WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output);
}
@Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
// On restore, we also support a previous data schema "system_files"
- addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this,
+ addHelper(WALLPAPER_HELPER, new WallpaperBackupHelper(this,
new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO },
new String[] { WALLPAPER_IMAGE_KEY, WALLPAPER_INFO_KEY} ));
- addHelper("system_files", new WallpaperBackupHelper(SystemBackupAgent.this,
+ addHelper("system_files", new WallpaperBackupHelper(this,
new String[] { WALLPAPER_IMAGE },
new String[] { WALLPAPER_IMAGE_KEY} ));
- addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this));
- addHelper("account_sync_settings",
- new AccountSyncSettingsBackupHelper(SystemBackupAgent.this));
+ addHelper(RECENTS_HELPER, new RecentsBackupHelper(this));
+ addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
+ addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(this));
try {
super.onRestore(data, appVersionCode, newState);