diff options
Diffstat (limited to 'core/java/com')
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); |
