diff options
Diffstat (limited to 'core/java/com')
104 files changed, 9720 insertions, 5171 deletions
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java index 19c0a44..5547a10 100644 --- a/core/java/com/android/internal/app/AlertController.java +++ b/core/java/com/android/internal/app/AlertController.java @@ -72,6 +72,8 @@ public class AlertController { private View mView; + private int mViewLayoutResId; + private int mViewSpacingLeft; private int mViewSpacingTop; @@ -102,7 +104,7 @@ public class AlertController { private ScrollView mScrollView; - private int mIconId = -1; + private int mIconId = 0; private Drawable mIcon; @@ -121,23 +123,30 @@ public class AlertController { private int mCheckedItem = -1; private int mAlertDialogLayout; + private int mButtonPanelSideLayout; private int mListLayout; private int mMultiChoiceItemLayout; private int mSingleChoiceItemLayout; private int mListItemLayout; + private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE; + private Handler mHandler; - View.OnClickListener mButtonHandler = new View.OnClickListener() { + private final View.OnClickListener mButtonHandler = new View.OnClickListener() { + @Override public void onClick(View v) { - Message m = null; + final Message m; if (v == mButtonPositive && mButtonPositiveMessage != null) { m = Message.obtain(mButtonPositiveMessage); } else if (v == mButtonNegative && mButtonNegativeMessage != null) { m = Message.obtain(mButtonNegativeMessage); } else if (v == mButtonNeutral && mButtonNeutralMessage != null) { m = Message.obtain(mButtonNeutralMessage); + } else { + m = null; } + if (m != null) { m.sendToTarget(); } @@ -193,6 +202,9 @@ public class AlertController { mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout, com.android.internal.R.layout.alert_dialog); + mButtonPanelSideLayout = a.getResourceId( + com.android.internal.R.styleable.AlertDialog_buttonPanelSideLayout, 0); + mListLayout = a.getResourceId( com.android.internal.R.styleable.AlertDialog_listLayout, com.android.internal.R.layout.select_dialog); @@ -234,15 +246,22 @@ public class AlertController { public void installContent() { /* We use a custom title so never request a window title */ mWindow.requestFeature(Window.FEATURE_NO_TITLE); - - if (mView == null || !canTextInput(mView)) { - mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, - WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - } - mWindow.setContentView(mAlertDialogLayout); + int contentView = selectContentView(); + mWindow.setContentView(contentView); setupView(); setupDecor(); } + + private int selectContentView() { + if (mButtonPanelSideLayout == 0) { + return mAlertDialogLayout; + } + if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) { + return mButtonPanelSideLayout; + } + // TODO: use layout hint side for long messages/lists + return mAlertDialogLayout; + } public void setTitle(CharSequence title) { mTitle = title; @@ -266,10 +285,20 @@ public class AlertController { } /** + * Set the view resource to display in the dialog. + */ + public void setView(int layoutResId) { + mView = null; + mViewLayoutResId = layoutResId; + mViewSpacingSpecified = false; + } + + /** * Set the view to display in the dialog. */ public void setView(View view) { mView = view; + mViewLayoutResId = 0; mViewSpacingSpecified = false; } @@ -279,6 +308,7 @@ public class AlertController { public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight, int viewSpacingBottom) { mView = view; + mViewLayoutResId = 0; mViewSpacingSpecified = true; mViewSpacingLeft = viewSpacingLeft; mViewSpacingTop = viewSpacingTop; @@ -287,6 +317,13 @@ public class AlertController { } /** + * Sets a hint for the best button panel layout. + */ + public void setButtonPanelLayoutHint(int layoutHint) { + mButtonPanelLayoutHint = layoutHint; + } + + /** * Sets a click listener or a message to be sent when the button is clicked. * You only need to pass one of {@code listener} or {@code msg}. * @@ -328,25 +365,39 @@ public class AlertController { } /** - * Set resId to 0 if you don't want an icon. - * @param resId the resourceId of the drawable to use as the icon or 0 - * if you don't want an icon. + * Specifies the icon to display next to the alert title. + * + * @param resId the resource identifier of the drawable to use as the icon, + * or 0 for no icon */ public void setIcon(int resId) { + mIcon = null; mIconId = resId; + if (mIconView != null) { - if (resId > 0) { + if (resId != 0) { mIconView.setImageResource(mIconId); - } else if (resId == 0) { + } else { mIconView.setVisibility(View.GONE); } } } - + + /** + * Specifies the icon to display next to the alert title. + * + * @param icon the drawable to use as the icon or null for no icon + */ public void setIcon(Drawable icon) { mIcon = icon; - if ((mIconView != null) && (mIcon != null)) { - mIconView.setImageDrawable(icon); + mIconId = 0; + + if (mIconView != null) { + if (icon != null) { + mIconView.setImageDrawable(icon); + } else { + mIconView.setVisibility(View.GONE); + } } } @@ -430,28 +481,44 @@ public class AlertController { mWindow.setCloseOnTouchOutsideIfNotSet(true); } - FrameLayout customPanel = null; + final FrameLayout customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel); + final View customView; if (mView != null) { - customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel); - FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom); - custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT)); + customView = mView; + } else if (mViewLayoutResId != 0) { + final LayoutInflater inflater = LayoutInflater.from(mContext); + customView = inflater.inflate(mViewLayoutResId, customPanel, false); + } else { + customView = null; + } + + final boolean hasCustomView = customView != null; + if (!hasCustomView || !canTextInput(customView)) { + mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + } + + if (hasCustomView) { + final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom); + custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT)); + if (mViewSpacingSpecified) { - custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, - mViewSpacingBottom); + custom.setPadding( + mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom); } + if (mListView != null) { ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0; } } else { - mWindow.findViewById(R.id.customPanel).setVisibility(View.GONE); + customPanel.setVisibility(View.GONE); } - - /* Only display the divider if we have a title and a - * custom view or a message. - */ + + // Only display the divider if we have a title and a custom view or a + // message. if (hasTitle) { - View divider = null; - if (mMessage != null || mView != null || mListView != null) { + final View divider; + if (mMessage != null || customView != null || mListView != null) { divider = mWindow.findViewById(R.id.titleDivider); } else { divider = mWindow.findViewById(R.id.titleDividerTop); @@ -461,8 +528,9 @@ public class AlertController { divider.setVisibility(View.VISIBLE); } } - - setBackground(topPanel, contentPanel, customPanel, hasButtons, a, hasTitle, buttonPanel); + + setBackground(a, topPanel, contentPanel, customPanel, buttonPanel, hasTitle, hasCustomView, + hasButtons); a.recycle(); } @@ -480,28 +548,24 @@ public class AlertController { View titleTemplate = mWindow.findViewById(R.id.title_template); titleTemplate.setVisibility(View.GONE); } else { - final boolean hasTextTitle = !TextUtils.isEmpty(mTitle); - mIconView = (ImageView) mWindow.findViewById(R.id.icon); + + final boolean hasTextTitle = !TextUtils.isEmpty(mTitle); if (hasTextTitle) { - /* Display the title if a title is supplied, else hide it */ + // Display the title if a title is supplied, else hide it. mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle); - mTitleView.setText(mTitle); - - /* Do this last so that if the user has supplied any - * icons we use them instead of the default ones. If the - * user has specified 0 then make it disappear. - */ - if (mIconId > 0) { + + // Do this last so that if the user has supplied any icons we + // use them instead of the default ones. If the user has + // specified 0 then make it disappear. + if (mIconId != 0) { mIconView.setImageResource(mIconId); } else if (mIcon != null) { mIconView.setImageDrawable(mIcon); - } else if (mIconId == 0) { - - /* Apply the padding from the icon to ensure the - * title is aligned correctly. - */ + } else { + // Apply the padding from the icon to ensure the title is + // aligned correctly. mTitleView.setPadding(mIconView.getPaddingLeft(), mIconView.getPaddingTop(), mIconView.getPaddingRight(), @@ -509,9 +573,8 @@ public class AlertController { mIconView.setVisibility(View.GONE); } } else { - // Hide the title template - View titleTemplate = mWindow.findViewById(R.id.title_template); + final View titleTemplate = mWindow.findViewById(R.id.title_template); titleTemplate.setVisibility(View.GONE); mIconView.setVisibility(View.GONE); topPanel.setVisibility(View.GONE); @@ -620,11 +683,8 @@ public class AlertController { } } - private void setBackground(LinearLayout topPanel, LinearLayout contentPanel, - View customPanel, boolean hasButtons, TypedArray a, boolean hasTitle, - View buttonPanel) { - - /* Get all the different background required */ + private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel, + View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) { int fullDark = 0; int topDark = 0; int centerDark = 0; @@ -645,62 +705,59 @@ public class AlertController { bottomBright = R.drawable.popup_bottom_bright; bottomMedium = R.drawable.popup_bottom_medium; } - fullDark = a.getResourceId(R.styleable.AlertDialog_fullDark, fullDark); - topDark = a.getResourceId(R.styleable.AlertDialog_topDark, topDark); - centerDark = a.getResourceId(R.styleable.AlertDialog_centerDark, centerDark); - bottomDark = a.getResourceId(R.styleable.AlertDialog_bottomDark, bottomDark); - fullBright = a.getResourceId(R.styleable.AlertDialog_fullBright, fullBright); topBright = a.getResourceId(R.styleable.AlertDialog_topBright, topBright); + topDark = a.getResourceId(R.styleable.AlertDialog_topDark, topDark); centerBright = a.getResourceId(R.styleable.AlertDialog_centerBright, centerBright); - bottomBright = a.getResourceId(R.styleable.AlertDialog_bottomBright, bottomBright); - bottomMedium = a.getResourceId(R.styleable.AlertDialog_bottomMedium, bottomMedium); + centerDark = a.getResourceId(R.styleable.AlertDialog_centerDark, centerDark); + - /* - * We now set the background of all of the sections of the alert. + /* We now set the background of all of the sections of the alert. * First collect together each section that is being displayed along * with whether it is on a light or dark background, then run through * them setting their backgrounds. This is complicated because we need * to correctly use the full, top, middle, and bottom graphics depending * on how many views they are and where they appear. */ - - View[] views = new View[4]; - boolean[] light = new boolean[4]; + + final View[] views = new View[4]; + final boolean[] light = new boolean[4]; View lastView = null; boolean lastLight = false; - + int pos = 0; if (hasTitle) { views[pos] = topPanel; light[pos] = false; pos++; } - + /* The contentPanel displays either a custom text message or * a ListView. If it's text we should use the dark background * for ListView we should use the light background. If neither * are there the contentPanel will be hidden so set it as null. */ - views[pos] = (contentPanel.getVisibility() == View.GONE) - ? null : contentPanel; + views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel; light[pos] = mListView != null; pos++; - if (customPanel != null) { + + if (hasCustomView) { views[pos] = customPanel; light[pos] = mForceInverseBackground; pos++; } + if (hasButtons) { views[pos] = buttonPanel; light[pos] = true; } - + boolean setView = false; - for (pos=0; pos<views.length; pos++) { - View v = views[pos]; + for (pos = 0; pos < views.length; pos++) { + final View v = views[pos]; if (v == null) { continue; } + if (lastView != null) { if (!setView) { lastView.setBackgroundResource(lastLight ? topBright : topDark); @@ -709,23 +766,29 @@ public class AlertController { } setView = true; } + lastView = v; lastLight = light[pos]; } - + if (lastView != null) { if (setView) { - - /* ListViews will use the Bright background but buttons use - * the Medium background. - */ + bottomBright = a.getResourceId(R.styleable.AlertDialog_bottomBright, bottomBright); + bottomMedium = a.getResourceId(R.styleable.AlertDialog_bottomMedium, bottomMedium); + bottomDark = a.getResourceId(R.styleable.AlertDialog_bottomDark, bottomDark); + + // ListViews will use the Bright background, but buttons use the + // Medium background. lastView.setBackgroundResource( lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark); } else { + fullBright = a.getResourceId(R.styleable.AlertDialog_fullBright, fullBright); + fullDark = a.getResourceId(R.styleable.AlertDialog_fullDark, fullDark); + lastView.setBackgroundResource(lastLight ? fullBright : fullDark); } } - + /* TODO: uncomment section below. The logic for this should be if * it's a Contextual menu being displayed AND only a Cancel button * is shown then do this. @@ -750,12 +813,14 @@ public class AlertController { mListView.addFooterView(buttonPanel); */ // } - - if ((mListView != null) && (mAdapter != null)) { - mListView.setAdapter(mAdapter); - if (mCheckedItem > -1) { - mListView.setItemChecked(mCheckedItem, true); - mListView.setSelection(mCheckedItem); + + final ListView listView = mListView; + if (listView != null && mAdapter != null) { + listView.setAdapter(mAdapter); + final int checkedItem = mCheckedItem; + if (checkedItem > -1) { + listView.setItemChecked(checkedItem, true); + listView.setSelection(checkedItem); } } } @@ -771,8 +836,13 @@ public class AlertController { super(context, attrs); } - public RecycleListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public RecycleListView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public RecycleListView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } @Override @@ -804,6 +874,7 @@ public class AlertController { public CharSequence[] mItems; public ListAdapter mAdapter; public DialogInterface.OnClickListener mOnClickListener; + public int mViewLayoutResId; public View mView; public int mViewSpacingLeft; public int mViewSpacingTop; @@ -889,8 +960,10 @@ public class AlertController { } else { dialog.setView(mView); } + } else if (mViewLayoutResId != 0) { + dialog.setView(mViewLayoutResId); } - + /* dialog.setCancelable(mCancelable); dialog.setOnCancelListener(mOnCancelListener); @@ -953,7 +1026,7 @@ public class AlertController { ? dialog.mSingleChoiceItemLayout : dialog.mListItemLayout; if (mCursor == null) { adapter = (mAdapter != null) ? mAdapter - : new ArrayAdapter<CharSequence>(mContext, layout, R.id.text1, mItems); + : new CheckedItemAdapter(mContext, layout, R.id.text1, mItems); } else { adapter = new SimpleCursorAdapter(mContext, layout, mCursor, new String[]{mLabelColumn}, new int[]{R.id.text1}); @@ -972,7 +1045,8 @@ public class AlertController { if (mOnClickListener != null) { listView.setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, int position, long id) { + @Override + public void onItemClick(AdapterView<?> parent, View v, int position, long id) { mOnClickListener.onClick(dialog.mDialogInterface, position); if (!mIsSingleChoice) { dialog.mDialogInterface.dismiss(); @@ -981,7 +1055,8 @@ public class AlertController { }); } else if (mOnCheckboxClickListener != null) { listView.setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, int position, long id) { + @Override + public void onItemClick(AdapterView<?> parent, View v, int position, long id) { if (mCheckedItems != null) { mCheckedItems[position] = listView.isItemChecked(position); } @@ -1006,4 +1081,20 @@ public class AlertController { } } + private static class CheckedItemAdapter extends ArrayAdapter<CharSequence> { + public CheckedItemAdapter(Context context, int resource, int textViewResourceId, + CharSequence[] objects) { + super(context, resource, textViewResourceId, objects); + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public long getItemId(int position) { + return position; + } + } } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 1eda373..106ac0b 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -33,6 +33,14 @@ public class ChooserActivity extends ResolverActivity { return; } Intent target = (Intent)targetParcelable; + if (target != null) { + final String action = target.getAction(); + if (Intent.ACTION_SEND.equals(action) || + Intent.ACTION_SEND_MULTIPLE.equals(action)) { + target.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | + Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS); + } + } CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE); if (title == null) { title = getResources().getText(com.android.internal.R.string.chooseActivity); @@ -43,13 +51,19 @@ public class ChooserActivity extends ResolverActivity { initialIntents = new Intent[pa.length]; for (int i=0; i<pa.length; i++) { if (!(pa[i] instanceof Intent)) { - Log.w("ChooserActivity", "Initial intent #" + i - + " not an Intent: " + pa[i]); + Log.w("ChooserActivity", "Initial intent #" + i + " not an Intent: " + pa[i]); finish(); super.onCreate(null); return; } - initialIntents[i] = (Intent)pa[i]; + final Intent in = (Intent) pa[i]; + final String action = in.getAction(); + if (Intent.ACTION_SEND.equals(action) || + Intent.ACTION_SEND_MULTIPLE.equals(action)) { + in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | + Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS); + } + initialIntents[i] = in; } } super.onCreate(savedInstanceState, target, title, initialIntents, null, false); diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java index 3d46cdd..83ad9dc 100644 --- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java +++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java @@ -32,7 +32,6 @@ import android.util.TypedValue; import android.view.View; import android.view.Window; import android.view.View.OnClickListener; -import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 16c41f3..8e6fa58 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -17,6 +17,7 @@ package com.android.internal.app; import android.app.AppOpsManager; +import android.os.Bundle; import com.android.internal.app.IAppOpsCallback; interface IAppOpsService { @@ -36,4 +37,12 @@ interface IAppOpsService { List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops); void setMode(int code, int uid, String packageName, int mode); void resetAllModes(); + int checkAudioOperation(int code, int stream, int uid, String packageName); + void setAudioRestriction(int code, int stream, int uid, int mode, in String[] exceptionPackages); + + void setDeviceOwner(String packageName); + void setProfileOwner(String packageName, int userHandle); + void setUserRestrictions(in Bundle restrictions, int userHandle); + void removeUser(int userHandle); + } diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 5413113..0769b08 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -19,23 +19,42 @@ package com.android.internal.app; import com.android.internal.os.BatteryStatsImpl; import android.os.WorkSource; +import android.telephony.DataConnectionRealTimeInfo; import android.telephony.SignalStrength; interface IBatteryStats { - byte[] getStatistics(); - void noteStartWakelock(int uid, int pid, String name, int type); - void noteStopWakelock(int uid, int pid, String name, int type); - - /* DO NOT CHANGE the position of noteStartSensor without updating - SensorService.cpp */ + // These first methods are also called by native code, so must + // be kept in sync with frameworks/native/include/binder/IBatteryStats.h void noteStartSensor(int uid, int sensor); - - /* DO NOT CHANGE the position of noteStopSensor without updating - SensorService.cpp */ void noteStopSensor(int uid, int sensor); - void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type); - void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type); + // Remaining methods are only used in Java. + byte[] getStatistics(); + + // Return the computed amount of time remaining on battery, in milliseconds. + // Returns -1 if nothing could be computed. + long computeBatteryTimeRemaining(); + + // Return the computed amount of time remaining to fully charge, in milliseconds. + // Returns -1 if nothing could be computed. + long computeChargeTimeRemaining(); + + void addIsolatedUid(int isolatedUid, int appUid); + void removeIsolatedUid(int isolatedUid, int appUid); + + void noteEvent(int code, String name, int uid); + + void noteStartWakelock(int uid, int pid, String name, String historyName, + int type, boolean unimportantForLogging); + void noteStopWakelock(int uid, int pid, String name, String historyName, int type); + + void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, String historyName, + int type, boolean unimportantForLogging); + void noteChangeWakelockFromSource(in WorkSource ws, int pid, String name, String histyoryName, + int type, in WorkSource newWs, int newPid, String newName, + String newHistoryName, int newType, boolean newUnimportantForLogging); + void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, String historyName, + int type); void noteVibratorOn(int uid, long durationMillis); void noteVibratorOff(int uid); @@ -45,6 +64,7 @@ interface IBatteryStats { void noteScreenBrightness(int brightness); void noteUserActivity(int uid, int event); void noteInteractive(boolean interactive); + void noteMobileRadioPowerState(int powerState, long timestampNs); void notePhoneOn(); void notePhoneOff(); void notePhoneSignalStrength(in SignalStrength signalStrength); @@ -55,8 +75,10 @@ interface IBatteryStats { void noteWifiRunning(in WorkSource ws); void noteWifiRunningChanged(in WorkSource oldWs, in WorkSource newWs); void noteWifiStopped(in WorkSource ws); + void noteWifiState(int wifiState, String accessPoint); void noteBluetoothOn(); void noteBluetoothOff(); + void noteBluetoothState(int bluetoothState); void noteFullWifiLockAcquired(int uid); void noteFullWifiLockReleased(int uid); void noteWifiScanStarted(int uid); diff --git a/core/java/com/android/internal/app/IUsageStats.aidl b/core/java/com/android/internal/app/IUsageStats.aidl index 1ea7409..7e7f0e1 100644 --- a/core/java/com/android/internal/app/IUsageStats.aidl +++ b/core/java/com/android/internal/app/IUsageStats.aidl @@ -16,13 +16,17 @@ package com.android.internal.app; +import android.app.UsageStats; import android.content.ComponentName; -import com.android.internal.os.PkgUsageStats; +import android.content.res.Configuration; +import android.os.ParcelableParcel; interface IUsageStats { void noteResumeComponent(in ComponentName componentName); void notePauseComponent(in ComponentName componentName); void noteLaunchTime(in ComponentName componentName, int millis); - PkgUsageStats getPkgUsageStats(in ComponentName componentName); - PkgUsageStats[] getAllPkgUsageStats(); + void noteStartConfig(in Configuration config); + UsageStats.PackageStats getPkgUsageStats(String callingPkg, in ComponentName componentName); + UsageStats.PackageStats[] getAllPkgUsageStats(String callingPkg); + ParcelableParcel getCurrentStats(String callingPkg); } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl new file mode 100644 index 0000000..98e35dd --- /dev/null +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -0,0 +1,32 @@ +/* + * 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.app; + +import android.content.Intent; +import android.os.Bundle; + +import com.android.internal.app.IVoiceInteractor; +import android.service.voice.IVoiceInteractionService; +import android.service.voice.IVoiceInteractionSession; + +interface IVoiceInteractionManagerService { + void startSession(IVoiceInteractionService service, in Bundle sessionArgs); + boolean deliverNewSession(IBinder token, IVoiceInteractionSession session, + IVoiceInteractor interactor); + int startVoiceActivity(IBinder token, in Intent intent, String resolvedType); + void finish(IBinder token); +} diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl new file mode 100644 index 0000000..2900595 --- /dev/null +++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl @@ -0,0 +1,35 @@ +/* + * 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.app; + +import android.os.Bundle; + +import com.android.internal.app.IVoiceInteractorCallback; +import com.android.internal.app.IVoiceInteractorRequest; + +/** + * IPC interface for an application to perform calls through a VoiceInteractor. + */ +interface IVoiceInteractor { + IVoiceInteractorRequest startConfirmation(String callingPackage, + IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras); + IVoiceInteractorRequest startAbortVoice(String callingPackage, + IVoiceInteractorCallback callback, CharSequence message, in Bundle extras); + IVoiceInteractorRequest startCommand(String callingPackage, + IVoiceInteractorCallback callback, String command, in Bundle extras); + boolean[] supportsCommands(String callingPackage, in String[] commands); +} diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl new file mode 100644 index 0000000..8dbf9d4 --- /dev/null +++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl @@ -0,0 +1,32 @@ +/* + * 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.app; + +import android.os.Bundle; + +import com.android.internal.app.IVoiceInteractorRequest; + +/** + * IPC interface for an application to receive callbacks from the voice system. + */ +oneway interface IVoiceInteractorCallback { + void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed, + in Bundle result); + void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result); + void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result); + void deliverCancel(IVoiceInteractorRequest request); +} diff --git a/core/java/com/android/internal/backup/BackupConstants.java b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl index 4c276b7..ce2902d 100644 --- a/core/java/com/android/internal/backup/BackupConstants.java +++ b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * 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. @@ -14,15 +14,11 @@ * limitations under the License. */ -package com.android.internal.backup; +package com.android.internal.app; /** - * Constants used internally between the backup manager and its transports + * IPC interface identifying a request from an application calling through an IVoiceInteractor. */ -public class BackupConstants { - public static final int TRANSPORT_OK = 0; - public static final int TRANSPORT_ERROR = 1; - public static final int TRANSPORT_NOT_INITIALIZED = 2; - public static final int AGENT_ERROR = 3; - public static final int AGENT_UNKNOWN = 4; +interface IVoiceInteractorRequest { + void cancel(); } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java new file mode 100644 index 0000000..01e5d40 --- /dev/null +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -0,0 +1,113 @@ +/* + * 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.app; + +import android.app.Activity; +import android.app.AppGlobals; +import android.os.Bundle; +import android.content.Context; +import android.content.Intent; +import android.content.pm.IPackageManager; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; +import android.app.ActivityManagerNative; +import android.os.RemoteException; +import android.util.Slog; +import java.util.List; +import java.util.Set; + + + + +/* + * This is used in conjunction with the {@link setCrossProfileIntentFilter} method of + * {@link DevicePolicyManager} to enable intents to be passed in and out of a managed profile. + */ + +public class IntentForwarderActivity extends Activity { + + public static String TAG = "IntentForwarderActivity"; + + public static String FORWARD_INTENT_TO_USER_OWNER + = "com.android.internal.app.ForwardIntentToUserOwner"; + + public static String FORWARD_INTENT_TO_MANAGED_PROFILE + = "com.android.internal.app.ForwardIntentToManagedProfile"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intentReceived = getIntent(); + + String className = intentReceived.getComponent().getClassName(); + final UserHandle userDest; + + if (className.equals(FORWARD_INTENT_TO_USER_OWNER)) { + userDest = UserHandle.OWNER; + } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { + userDest = getManagedProfile(); + } else { + Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly"); + userDest = null; + } + if (userDest == null) { // This covers the case where there is no managed profile. + finish(); + return; + } + Intent newIntent = new Intent(intentReceived); + newIntent.setComponent(null); + newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT + |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); + int callingUserId = getUserId(); + IPackageManager ipm = AppGlobals.getPackageManager(); + String resolvedType = newIntent.resolveTypeIfNeeded(getContentResolver()); + boolean canForward = false; + try { + canForward = ipm.canForwardTo(newIntent, resolvedType, callingUserId, + userDest.getIdentifier()); + } catch (RemoteException e) { + Slog.e(TAG, "PackageManagerService is dead?"); + } + if (canForward) { + newIntent.prepareToLeaveUser(callingUserId); + startActivityAsUser(newIntent, userDest); + } else { + Slog.wtf(TAG, "the intent: " + newIntent + "cannot be forwarded from user " + + callingUserId + " to user " + userDest.getIdentifier()); + } + finish(); + } + + /** + * Returns the managed profile for this device or null if there is no managed + * profile. + * + * TODO: Remove the assumption that there is only one managed profile + * on the device. + */ + private UserHandle getManagedProfile() { + UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); + List<UserInfo> relatedUsers = userManager.getProfiles(UserHandle.USER_OWNER); + for (UserInfo userInfo : relatedUsers) { + if (userInfo.isManagedProfile()) return new UserHandle(userInfo.id); + } + Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE + + " has been called, but there is no managed profile"); + return null; + } +} diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java index 043964f..ec2d654 100644 --- a/core/java/com/android/internal/app/LocalePicker.java +++ b/core/java/com/android/internal/app/LocalePicker.java @@ -117,7 +117,7 @@ public class LocalePicker extends ListFragment { /** - TODO: Enable when zz_ZY Pseudolocale is complete * if (!localeList.contains("zz_ZY")) { * localeList.add("zz_ZY"); - * } + * } */ } String[] locales = new String[localeList.size()]; diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java index ae362af..237feed 100644 --- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java +++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java @@ -21,7 +21,6 @@ import android.app.DialogFragment; import android.content.Context; import android.os.Bundle; import android.view.View; -import android.view.View.OnClickListener; /** * Media route chooser dialog fragment. diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java index 8fc99c7..b0e0373 100644 --- a/core/java/com/android/internal/app/MediaRouteControllerDialog.java +++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java @@ -256,13 +256,13 @@ public class MediaRouteControllerDialog extends Dialog { private Drawable getIconDrawable() { if (mRoute.isConnecting()) { if (mMediaRouteConnectingDrawable == null) { - mMediaRouteConnectingDrawable = getContext().getResources().getDrawable( + mMediaRouteConnectingDrawable = getContext().getDrawable( R.drawable.ic_media_route_connecting_holo_dark); } return mMediaRouteConnectingDrawable; } else { if (mMediaRouteOnDrawable == null) { - mMediaRouteOnDrawable = getContext().getResources().getDrawable( + mMediaRouteOnDrawable = getContext().getDrawable( R.drawable.ic_media_route_on_holo_dark); } return mMediaRouteOnDrawable; diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java index 40a705c..abd1791 100644 --- a/core/java/com/android/internal/app/PlatLogoActivity.java +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -18,161 +18,122 @@ package com.android.internal.app; import android.app.Activity; import android.content.ActivityNotFoundException; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.graphics.Typeface; -import android.provider.Settings; import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.text.method.AllCapsTransformationMethod; -import android.text.method.TransformationMethod; +import android.provider.Settings; +import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.AnticipateOvershootInterpolator; -import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; public class PlatLogoActivity extends Activity { - FrameLayout mContent; - int mCount; - final Handler mHandler = new Handler(); - static final int BGCOLOR = 0xffed1d24; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); - - Typeface bold = Typeface.create("sans-serif", Typeface.BOLD); - Typeface light = Typeface.create("sans-serif-light", Typeface.NORMAL); - - mContent = new FrameLayout(this); - mContent.setBackgroundColor(0xC0000000); - - final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT); - lp.gravity = Gravity.CENTER; - - final ImageView logo = new ImageView(this); - logo.setImageResource(com.android.internal.R.drawable.platlogo); - logo.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - logo.setVisibility(View.INVISIBLE); - - final View bg = new View(this); - bg.setBackgroundColor(BGCOLOR); - bg.setAlpha(0f); - - final TextView letter = new TextView(this); - - letter.setTypeface(bold); - letter.setTextSize(300); - letter.setTextColor(0xFFFFFFFF); - letter.setGravity(Gravity.CENTER); - letter.setText(String.valueOf(Build.ID).substring(0, 1)); - - final int p = (int)(4 * metrics.density); - - final TextView tv = new TextView(this); - if (light != null) tv.setTypeface(light); - tv.setTextSize(30); - tv.setPadding(p, p, p, p); - tv.setTextColor(0xFFFFFFFF); - tv.setGravity(Gravity.CENTER); - tv.setTransformationMethod(new AllCapsTransformationMethod(this)); - tv.setText("Android " + Build.VERSION.RELEASE); - tv.setVisibility(View.INVISIBLE); - - mContent.addView(bg); - mContent.addView(letter, lp); - mContent.addView(logo, lp); + private static class Torso extends FrameLayout { + boolean mAnimate = false; + TextView mText; + + public Torso(Context context) { + this(context, null); + } + public Torso(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + public Torso(Context context, AttributeSet attrs, int flags) { + super(context, attrs, flags); + + for (int i=0; i<2; i++) { + final View v = new View(context); + v.setBackgroundColor(i % 2 == 0 ? Color.BLUE : Color.RED); + addView(v); + } - final FrameLayout.LayoutParams lp2 = new FrameLayout.LayoutParams(lp); - lp2.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; - lp2.bottomMargin = 10*p; + mText = new TextView(context); + mText.setTextColor(Color.BLACK); + mText.setTextSize(14 /* sp */); + mText.setTypeface(Typeface.create("monospace", Typeface.BOLD)); - mContent.addView(tv, lp2); + addView(mText, new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT, + Gravity.BOTTOM | Gravity.LEFT + )); + } - mContent.setOnClickListener(new View.OnClickListener() { - int clicks; + private Runnable mRunnable = new Runnable() { @Override - public void onClick(View v) { - clicks++; - if (clicks >= 6) { - mContent.performLongClick(); - return; + public void run() { + mText.setText(String.format("android_%s.flv - build %s", + Build.VERSION.CODENAME, + Build.VERSION.INCREMENTAL)); + final int N = getChildCount(); + final float parentw = getMeasuredWidth(); + final float parenth = getMeasuredHeight(); + for (int i=0; i<N; i++) { + final View v = getChildAt(i); + if (v instanceof TextView) continue; + + final int w = (int) (Math.random() * parentw); + final int h = (int) (Math.random() * parenth); + v.setLayoutParams(new FrameLayout.LayoutParams(w, h)); + + v.setX((float) Math.random() * (parentw - w)); + v.setY((float) Math.random() * (parenth - h)); } - letter.animate().cancel(); - final float offset = (int)letter.getRotation() % 360; - letter.animate() - .rotationBy((Math.random() > 0.5f ? 360 : -360) - offset) - .setInterpolator(new DecelerateInterpolator()) - .setDuration(700).start(); - } - }); - mContent.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (logo.getVisibility() != View.VISIBLE) { - bg.setScaleX(0.01f); - bg.animate().alpha(1f).scaleX(1f).setStartDelay(500).start(); - letter.animate().alpha(0f).scaleY(0.5f).scaleX(0.5f) - .rotationBy(360) - .setInterpolator(new AccelerateInterpolator()) - .setDuration(1000) - .start(); - logo.setAlpha(0f); - logo.setVisibility(View.VISIBLE); - logo.setScaleX(0.5f); - logo.setScaleY(0.5f); - logo.animate().alpha(1f).scaleX(1f).scaleY(1f) - .setDuration(1000).setStartDelay(500) - .setInterpolator(new AnticipateOvershootInterpolator()) - .start(); - tv.setAlpha(0f); - tv.setVisibility(View.VISIBLE); - tv.animate().alpha(1f).setDuration(1000).setStartDelay(1000).start(); - return true; - } - return false; + if (mAnimate) postDelayed(this, 1000); } - }); + }; + @Override + protected void onAttachedToWindow() { + mAnimate = true; + post(mRunnable); + } + @Override + protected void onDetachedFromWindow() { + mAnimate = false; + removeCallbacks(mRunnable); + } + } - logo.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (Settings.System.getLong(getContentResolver(), Settings.System.EGG_MODE, 0) - == 0) { - // For posterity: the moment this user unlocked the easter egg - Settings.System.putLong(getContentResolver(), - Settings.System.EGG_MODE, - System.currentTimeMillis()); - } - try { - startActivity(new Intent(Intent.ACTION_MAIN) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - .addCategory("com.android.internal.category.PLATLOGO")); - } catch (ActivityNotFoundException ex) { - android.util.Log.e("PlatLogoActivity", "Couldn't catch a break."); - } - finish(); - return true; - } - }); - - setContentView(mContent); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Torso t = new Torso(this); + t.setBackgroundColor(Color.WHITE); + + t.getChildAt(0) + .setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + final ContentResolver cr = getContentResolver(); + if (Settings.System.getLong(cr, Settings.System.EGG_MODE, 0) + == 0) { + // For posterity: the moment this user unlocked the easter egg + Settings.System.putLong(cr, + Settings.System.EGG_MODE, + System.currentTimeMillis()); + } + try { + startActivity(new Intent(Intent.ACTION_MAIN) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .addCategory("com.android.internal.category.PLATLOGO")); + } catch (ActivityNotFoundException ex) { + android.util.Log.e("PlatLogoActivity", "Couldn't catch a break."); + } + finish(); + return true; + } + }); + + setContentView(t); } } diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java index cd90cdb..7e11850 100644 --- a/core/java/com/android/internal/app/ProcessStats.java +++ b/core/java/com/android/internal/app/ProcessStats.java @@ -29,8 +29,11 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; import android.webkit.WebViewFactory; -import com.android.internal.util.ArrayUtils; + +import com.android.internal.util.GrowingArrayUtils; + import dalvik.system.VMRuntime; +import libcore.util.EmptyArray; import java.io.IOException; import java.io.InputStream; @@ -171,7 +174,7 @@ public final class ProcessStats implements Parcelable { static final String CSV_SEP = "\t"; // Current version of the parcel format. - private static final int PARCEL_VERSION = 13; + private static final int PARCEL_VERSION = 14; // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0x50535453; @@ -189,7 +192,8 @@ public final class ProcessStats implements Parcelable { public String mTimePeriodStartClockStr; public int mFlags; - public final ProcessMap<PackageState> mPackages = new ProcessMap<PackageState>(); + public final ProcessMap<SparseArray<PackageState>> mPackages + = new ProcessMap<SparseArray<PackageState>>(); public final ProcessMap<ProcessState> mProcesses = new ProcessMap<ProcessState>(); public final long[] mMemFactorDurations = new long[ADJ_COUNT]; @@ -227,40 +231,45 @@ public final class ProcessStats implements Parcelable { } public void add(ProcessStats other) { - ArrayMap<String, SparseArray<PackageState>> pkgMap = other.mPackages.getMap(); + ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = other.mPackages.getMap(); for (int ip=0; ip<pkgMap.size(); ip++) { - String pkgName = pkgMap.keyAt(ip); - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final String pkgName = pkgMap.keyAt(ip); + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); for (int iu=0; iu<uids.size(); iu++) { - int uid = uids.keyAt(iu); - PackageState otherState = uids.valueAt(iu); - final int NPROCS = otherState.mProcesses.size(); - final int NSRVS = otherState.mServices.size(); - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState otherProc = otherState.mProcesses.valueAt(iproc); - if (otherProc.mCommonProcess != otherProc) { - if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid - + " proc " + otherProc.mName); - ProcessState thisProc = getProcessStateLocked(pkgName, uid, - otherProc.mName); - if (thisProc.mCommonProcess == thisProc) { - if (DEBUG) Slog.d(TAG, "Existing process is single-package, splitting"); - thisProc.mMultiPackage = true; - long now = SystemClock.uptimeMillis(); - final PackageState pkgState = getPackageStateLocked(pkgName, uid); - thisProc = thisProc.clone(thisProc.mPackage, now); - pkgState.mProcesses.put(thisProc.mName, thisProc); + final int uid = uids.keyAt(iu); + final SparseArray<PackageState> versions = uids.valueAt(iu); + for (int iv=0; iv<versions.size(); iv++) { + final int vers = versions.keyAt(iv); + final PackageState otherState = versions.valueAt(iv); + final int NPROCS = otherState.mProcesses.size(); + final int NSRVS = otherState.mServices.size(); + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState otherProc = otherState.mProcesses.valueAt(iproc); + if (otherProc.mCommonProcess != otherProc) { + if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid + + " vers " + vers + " proc " + otherProc.mName); + ProcessState thisProc = getProcessStateLocked(pkgName, uid, vers, + otherProc.mName); + if (thisProc.mCommonProcess == thisProc) { + if (DEBUG) Slog.d(TAG, "Existing process is single-package, splitting"); + thisProc.mMultiPackage = true; + long now = SystemClock.uptimeMillis(); + final PackageState pkgState = getPackageStateLocked(pkgName, uid, + vers); + thisProc = thisProc.clone(thisProc.mPackage, now); + pkgState.mProcesses.put(thisProc.mName, thisProc); + } + thisProc.add(otherProc); } - thisProc.add(otherProc); } - } - for (int isvc=0; isvc<NSRVS; isvc++) { - ServiceState otherSvc = otherState.mServices.valueAt(isvc); - if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid - + " service " + otherSvc.mName); - ServiceState thisSvc = getServiceStateLocked(pkgName, uid, - otherSvc.mProcessName, otherSvc.mName); - thisSvc.add(otherSvc); + for (int isvc=0; isvc<NSRVS; isvc++) { + ServiceState otherSvc = otherState.mServices.valueAt(isvc); + if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid + + " service " + otherSvc.mName); + ServiceState thisSvc = getServiceStateLocked(pkgName, uid, vers, + otherSvc.mProcessName, otherSvc.mName); + thisSvc.add(otherSvc); + } } } } @@ -275,9 +284,11 @@ public final class ProcessStats implements Parcelable { if (DEBUG) Slog.d(TAG, "Adding uid " + uid + " proc " + otherProc.mName); if (thisProc == null) { if (DEBUG) Slog.d(TAG, "Creating new process!"); - thisProc = new ProcessState(this, otherProc.mPackage, uid, otherProc.mName); + thisProc = new ProcessState(this, otherProc.mPackage, uid, otherProc.mVersion, + otherProc.mName); mProcesses.put(otherProc.mName, uid, thisProc); - PackageState thisState = getPackageStateLocked(otherProc.mPackage, uid); + PackageState thisState = getPackageStateLocked(otherProc.mPackage, uid, + otherProc.mVersion); if (!thisState.mProcesses.containsKey(otherProc.mName)) { thisState.mProcesses.put(otherProc.mName, thisProc); } @@ -440,7 +451,7 @@ public final class ProcessStats implements Parcelable { } static void dumpServiceTimeCheckin(PrintWriter pw, String label, String packageName, - int uid, String serviceName, ServiceState svc, int serviceType, int opCount, + int uid, int vers, String serviceName, ServiceState svc, int serviceType, int opCount, int curState, long curStartTime, long now) { if (opCount <= 0) { return; @@ -451,6 +462,8 @@ public final class ProcessStats implements Parcelable { pw.print(","); pw.print(uid); pw.print(","); + pw.print(vers); + pw.print(","); pw.print(serviceName); pw.print(","); pw.print(opCount); @@ -775,7 +788,7 @@ public final class ProcessStats implements Parcelable { static void dumpProcessSummaryLocked(PrintWriter pw, String prefix, ArrayList<ProcessState> procs, int[] screenStates, int[] memStates, int[] procStates, - long now, long totalTime) { + boolean inclUidVers, long now, long totalTime) { for (int i=procs.size()-1; i>=0; i--) { ProcessState proc = procs.get(i); pw.print(prefix); @@ -783,6 +796,8 @@ public final class ProcessStats implements Parcelable { pw.print(proc.mName); pw.print(" / "); UserHandle.formatUid(pw, proc.mUid); + pw.print(" / v"); + pw.print(proc.mVersion); pw.println(":"); dumpProcessSummaryDetails(pw, proc, prefix, " TOTAL: ", screenStates, memStates, procStates, now, totalTime, true); @@ -869,6 +884,8 @@ public final class ProcessStats implements Parcelable { pw.print("process"); pw.print(CSV_SEP); pw.print("uid"); + pw.print(CSV_SEP); + pw.print("vers"); dumpStateHeadersCsv(pw, CSV_SEP, sepScreenStates ? screenStates : null, sepMemStates ? memStates : null, sepProcStates ? procStates : null); @@ -878,6 +895,8 @@ public final class ProcessStats implements Parcelable { pw.print(proc.mName); pw.print(CSV_SEP); UserHandle.formatUid(pw, proc.mUid); + pw.print(CSV_SEP); + pw.print(proc.mVersion); dumpProcessStateCsv(pw, proc, sepScreenStates, screenStates, sepMemStates, memStates, sepProcStates, procStates, now); pw.println(); @@ -979,53 +998,88 @@ public final class ProcessStats implements Parcelable { public void resetSafely() { if (DEBUG) Slog.d(TAG, "Safely resetting state of " + mTimePeriodStartClockStr); resetCommon(); - long now = SystemClock.uptimeMillis(); - ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); + + // First initialize use count of all common processes. + final long now = SystemClock.uptimeMillis(); + final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); for (int ip=procMap.size()-1; ip>=0; ip--) { - SparseArray<ProcessState> uids = procMap.valueAt(ip); + final SparseArray<ProcessState> uids = procMap.valueAt(ip); for (int iu=uids.size()-1; iu>=0; iu--) { - ProcessState ps = uids.valueAt(iu); - if (ps.isInUse()) { - uids.valueAt(iu).resetSafely(now); - } else { - uids.valueAt(iu).makeDead(); + uids.valueAt(iu).mTmpNumInUse = 0; + } + } + + // Next reset or prune all per-package processes, and for the ones that are reset + // track this back to the common processes. + final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); + for (int ip=pkgMap.size()-1; ip>=0; ip--) { + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); + for (int iu=uids.size()-1; iu>=0; iu--) { + final SparseArray<PackageState> vpkgs = uids.valueAt(iu); + for (int iv=vpkgs.size()-1; iv>=0; iv--) { + final PackageState pkgState = vpkgs.valueAt(iv); + for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) { + final ProcessState ps = pkgState.mProcesses.valueAt(iproc); + if (ps.isInUse()) { + ps.resetSafely(now); + ps.mCommonProcess.mTmpNumInUse++; + ps.mCommonProcess.mTmpFoundSubProc = ps; + } else { + pkgState.mProcesses.valueAt(iproc).makeDead(); + pkgState.mProcesses.removeAt(iproc); + } + } + for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) { + final ServiceState ss = pkgState.mServices.valueAt(isvc); + if (ss.isInUse()) { + ss.resetSafely(now); + } else { + pkgState.mServices.removeAt(isvc); + } + } + if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) { + vpkgs.removeAt(iv); + } + } + if (vpkgs.size() <= 0) { uids.removeAt(iu); } } if (uids.size() <= 0) { - procMap.removeAt(ip); + pkgMap.removeAt(ip); } } - ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); - for (int ip=pkgMap.size()-1; ip>=0; ip--) { - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + + // Finally prune out any common processes that are no longer in use. + for (int ip=procMap.size()-1; ip>=0; ip--) { + final SparseArray<ProcessState> uids = procMap.valueAt(ip); for (int iu=uids.size()-1; iu>=0; iu--) { - PackageState pkgState = uids.valueAt(iu); - for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) { - ProcessState ps = pkgState.mProcesses.valueAt(iproc); - if (ps.isInUse() || ps.mCommonProcess.isInUse()) { - pkgState.mProcesses.valueAt(iproc).resetSafely(now); - } else { - pkgState.mProcesses.valueAt(iproc).makeDead(); - pkgState.mProcesses.removeAt(iproc); - } - } - for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) { - ServiceState ss = pkgState.mServices.valueAt(isvc); - if (ss.isInUse()) { - pkgState.mServices.valueAt(isvc).resetSafely(now); + ProcessState ps = uids.valueAt(iu); + if (ps.isInUse() || ps.mTmpNumInUse > 0) { + // If this is a process for multiple packages, we could at this point + // be back down to one package. In that case, we want to revert back + // to a single shared ProcessState. We can do this by converting the + // current package-specific ProcessState up to the shared ProcessState, + // throwing away the current one we have here (because nobody else is + // using it). + if (!ps.mActive && ps.mMultiPackage && ps.mTmpNumInUse == 1) { + // Here we go... + ps = ps.mTmpFoundSubProc; + ps.mCommonProcess = ps; + uids.setValueAt(iu, ps); } else { - pkgState.mServices.removeAt(isvc); + ps.resetSafely(now); } - } - if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) { + } else { + ps.makeDead(); uids.removeAt(iu); } } if (uids.size() <= 0) { - pkgMap.removeAt(ip); + procMap.removeAt(ip); } } + mStartTime = now; if (DEBUG) Slog.d(TAG, "State reset; now " + mTimePeriodStartClockStr); } @@ -1054,13 +1108,6 @@ public final class ProcessStats implements Parcelable { mRuntime = runtime; } } - String webview = WebViewFactory.useExperimentalWebView() ? "chromeview" : "webview"; - if (!Objects.equals(webview, mWebView)) { - changed = true; - if (update) { - mWebView = webview; - } - } return changed; } @@ -1193,23 +1240,27 @@ public final class ProcessStats implements Parcelable { uids.valueAt(iu).commitStateTime(now); } } - ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); + final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); final int NPKG = pkgMap.size(); for (int ip=0; ip<NPKG; ip++) { - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); final int NUID = uids.size(); for (int iu=0; iu<NUID; iu++) { - PackageState pkgState = uids.valueAt(iu); - final int NPROCS = pkgState.mProcesses.size(); - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - if (proc.mCommonProcess != proc) { - proc.commitStateTime(now); + final SparseArray<PackageState> vpkgs = uids.valueAt(iu); + final int NVERS = vpkgs.size(); + for (int iv=0; iv<NVERS; iv++) { + PackageState pkgState = vpkgs.valueAt(iv); + final int NPROCS = pkgState.mProcesses.size(); + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (proc.mCommonProcess != proc) { + proc.commitStateTime(now); + } + } + final int NSRVS = pkgState.mServices.size(); + for (int isvc=0; isvc<NSRVS; isvc++) { + pkgState.mServices.valueAt(isvc).commitStateTime(now); } - } - final int NSRVS = pkgState.mServices.size(); - for (int isvc=0; isvc<NSRVS; isvc++) { - pkgState.mServices.valueAt(isvc).commitStateTime(now); } } } @@ -1239,46 +1290,53 @@ public final class ProcessStats implements Parcelable { out.writeInt(NPROC); for (int ip=0; ip<NPROC; ip++) { writeCommonString(out, procMap.keyAt(ip)); - SparseArray<ProcessState> uids = procMap.valueAt(ip); + final SparseArray<ProcessState> uids = procMap.valueAt(ip); final int NUID = uids.size(); out.writeInt(NUID); for (int iu=0; iu<NUID; iu++) { out.writeInt(uids.keyAt(iu)); - ProcessState proc = uids.valueAt(iu); + final ProcessState proc = uids.valueAt(iu); writeCommonString(out, proc.mPackage); + out.writeInt(proc.mVersion); proc.writeToParcel(out, now); } } out.writeInt(NPKG); for (int ip=0; ip<NPKG; ip++) { writeCommonString(out, pkgMap.keyAt(ip)); - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); final int NUID = uids.size(); out.writeInt(NUID); for (int iu=0; iu<NUID; iu++) { out.writeInt(uids.keyAt(iu)); - PackageState pkgState = uids.valueAt(iu); - final int NPROCS = pkgState.mProcesses.size(); - out.writeInt(NPROCS); - for (int iproc=0; iproc<NPROCS; iproc++) { - writeCommonString(out, pkgState.mProcesses.keyAt(iproc)); - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - if (proc.mCommonProcess == proc) { - // This is the same as the common process we wrote above. - out.writeInt(0); - } else { - // There is separate data for this package's process. - out.writeInt(1); - proc.writeToParcel(out, now); + final SparseArray<PackageState> vpkgs = uids.valueAt(iu); + final int NVERS = vpkgs.size(); + out.writeInt(NVERS); + for (int iv=0; iv<NVERS; iv++) { + out.writeInt(vpkgs.keyAt(iv)); + final PackageState pkgState = vpkgs.valueAt(iv); + final int NPROCS = pkgState.mProcesses.size(); + out.writeInt(NPROCS); + for (int iproc=0; iproc<NPROCS; iproc++) { + writeCommonString(out, pkgState.mProcesses.keyAt(iproc)); + final ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (proc.mCommonProcess == proc) { + // This is the same as the common process we wrote above. + out.writeInt(0); + } else { + // There is separate data for this package's process. + out.writeInt(1); + proc.writeToParcel(out, now); + } + } + final int NSRVS = pkgState.mServices.size(); + out.writeInt(NSRVS); + for (int isvc=0; isvc<NSRVS; isvc++) { + out.writeString(pkgState.mServices.keyAt(isvc)); + final ServiceState svc = pkgState.mServices.valueAt(isvc); + writeCommonString(out, svc.mProcessName); + svc.writeToParcel(out, now); } - } - final int NSRVS = pkgState.mServices.size(); - out.writeInt(NSRVS); - for (int isvc=0; isvc<NSRVS; isvc++) { - out.writeString(pkgState.mServices.keyAt(isvc)); - ServiceState svc = pkgState.mServices.valueAt(isvc); - writeCommonString(out, svc.mProcessName); - svc.writeToParcel(out, now); } } } @@ -1396,7 +1454,7 @@ public final class ProcessStats implements Parcelable { } while (NPROC > 0) { NPROC--; - String procName = readCommonString(in, version); + final String procName = readCommonString(in, version); if (procName == null) { mReadError = "bad process name"; return; @@ -1408,23 +1466,24 @@ public final class ProcessStats implements Parcelable { } while (NUID > 0) { NUID--; - int uid = in.readInt(); + final int uid = in.readInt(); if (uid < 0) { mReadError = "bad uid: " + uid; return; } - String pkgName = readCommonString(in, version); + final String pkgName = readCommonString(in, version); if (pkgName == null) { mReadError = "bad process package name"; return; } + final int vers = in.readInt(); ProcessState proc = hadData ? mProcesses.get(procName, uid) : null; if (proc != null) { if (!proc.readFromParcel(in, false)) { return; } } else { - proc = new ProcessState(this, pkgName, uid, procName); + proc = new ProcessState(this, pkgName, uid, vers, procName); if (!proc.readFromParcel(in, true)) { return; } @@ -1444,7 +1503,7 @@ public final class ProcessStats implements Parcelable { } while (NPKG > 0) { NPKG--; - String pkgName = readCommonString(in, version); + final String pkgName = readCommonString(in, version); if (pkgName == null) { mReadError = "bad package name"; return; @@ -1456,83 +1515,98 @@ public final class ProcessStats implements Parcelable { } while (NUID > 0) { NUID--; - int uid = in.readInt(); + final int uid = in.readInt(); if (uid < 0) { mReadError = "bad uid: " + uid; return; } - PackageState pkgState = new PackageState(pkgName, uid); - mPackages.put(pkgName, uid, pkgState); - int NPROCS = in.readInt(); - if (NPROCS < 0) { - mReadError = "bad package process count: " + NPROCS; + int NVERS = in.readInt(); + if (NVERS < 0) { + mReadError = "bad versions count: " + NVERS; return; } - while (NPROCS > 0) { - NPROCS--; - String procName = readCommonString(in, version); - if (procName == null) { - mReadError = "bad package process name"; - return; + while (NVERS > 0) { + NVERS--; + final int vers = in.readInt(); + PackageState pkgState = new PackageState(pkgName, uid); + SparseArray<PackageState> vpkg = mPackages.get(pkgName, uid); + if (vpkg == null) { + vpkg = new SparseArray<PackageState>(); + mPackages.put(pkgName, uid, vpkg); } - int hasProc = in.readInt(); - if (DEBUG_PARCEL) Slog.d(TAG, "Reading package " + pkgName + " " + uid - + " process " + procName + " hasProc=" + hasProc); - ProcessState commonProc = mProcesses.get(procName, uid); - if (DEBUG_PARCEL) Slog.d(TAG, "Got common proc " + procName + " " + uid - + ": " + commonProc); - if (commonProc == null) { - mReadError = "no common proc: " + procName; + vpkg.put(vers, pkgState); + int NPROCS = in.readInt(); + if (NPROCS < 0) { + mReadError = "bad package process count: " + NPROCS; return; } - if (hasProc != 0) { - // The process for this package is unique to the package; we - // need to load it. We don't need to do anything about it if - // it is not unique because if someone later looks for it - // they will find and use it from the global procs. - ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null; - if (proc != null) { - if (!proc.readFromParcel(in, false)) { - return; + while (NPROCS > 0) { + NPROCS--; + String procName = readCommonString(in, version); + if (procName == null) { + mReadError = "bad package process name"; + return; + } + int hasProc = in.readInt(); + if (DEBUG_PARCEL) Slog.d(TAG, "Reading package " + pkgName + " " + uid + + " process " + procName + " hasProc=" + hasProc); + ProcessState commonProc = mProcesses.get(procName, uid); + if (DEBUG_PARCEL) Slog.d(TAG, "Got common proc " + procName + " " + uid + + ": " + commonProc); + if (commonProc == null) { + mReadError = "no common proc: " + procName; + return; + } + if (hasProc != 0) { + // The process for this package is unique to the package; we + // need to load it. We don't need to do anything about it if + // it is not unique because if someone later looks for it + // they will find and use it from the global procs. + ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null; + if (proc != null) { + if (!proc.readFromParcel(in, false)) { + return; + } + } else { + proc = new ProcessState(commonProc, pkgName, uid, vers, procName, + 0); + if (!proc.readFromParcel(in, true)) { + return; + } } + if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: " + + procName + " " + uid + " " + proc); + pkgState.mProcesses.put(procName, proc); } else { - proc = new ProcessState(commonProc, pkgName, uid, procName, 0); - if (!proc.readFromParcel(in, true)) { - return; - } + if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: " + + procName + " " + uid + " " + commonProc); + pkgState.mProcesses.put(procName, commonProc); } - if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: " - + procName + " " + uid + " " + proc); - pkgState.mProcesses.put(procName, proc); - } else { - if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: " - + procName + " " + uid + " " + commonProc); - pkgState.mProcesses.put(procName, commonProc); } - } - int NSRVS = in.readInt(); - if (NSRVS < 0) { - mReadError = "bad package service count: " + NSRVS; - return; - } - while (NSRVS > 0) { - NSRVS--; - String serviceName = in.readString(); - if (serviceName == null) { - mReadError = "bad package service name"; + int NSRVS = in.readInt(); + if (NSRVS < 0) { + mReadError = "bad package service count: " + NSRVS; return; } - String processName = version > 9 ? readCommonString(in, version) : null; - ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null; - if (serv == null) { - serv = new ServiceState(this, pkgName, serviceName, processName, null); - } - if (!serv.readFromParcel(in)) { - return; + while (NSRVS > 0) { + NSRVS--; + String serviceName = in.readString(); + if (serviceName == null) { + mReadError = "bad package service name"; + return; + } + String processName = version > 9 ? readCommonString(in, version) : null; + ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null; + if (serv == null) { + serv = new ServiceState(this, pkgName, serviceName, processName, null); + } + if (!serv.readFromParcel(in)) { + return; + } + if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " service: " + + serviceName + " " + uid + " " + serv); + pkgState.mServices.put(serviceName, serv); } - if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " service: " - + serviceName + " " + uid + " " + serv); - pkgState.mServices.put(serviceName, serv); } } } @@ -1543,21 +1617,10 @@ public final class ProcessStats implements Parcelable { } int addLongData(int index, int type, int num) { - int tableLen = mAddLongTable != null ? mAddLongTable.length : 0; - if (mAddLongTableSize >= tableLen) { - int newSize = ArrayUtils.idealIntArraySize(tableLen + 1); - int[] newTable = new int[newSize]; - if (tableLen > 0) { - System.arraycopy(mAddLongTable, 0, newTable, 0, tableLen); - } - mAddLongTable = newTable; - } - if (mAddLongTableSize > 0 && mAddLongTableSize - index != 0) { - System.arraycopy(mAddLongTable, index, mAddLongTable, index + 1, - mAddLongTableSize - index); - } int off = allocLongData(num); - mAddLongTable[index] = type | off; + mAddLongTable = GrowingArrayUtils.insert( + mAddLongTable != null ? mAddLongTable : EmptyArray.INT, + mAddLongTableSize, index, type | off); mAddLongTableSize++; return off; } @@ -1627,30 +1690,36 @@ public final class ProcessStats implements Parcelable { return ~lo; // value not present } - public PackageState getPackageStateLocked(String packageName, int uid) { - PackageState as = mPackages.get(packageName, uid); + public PackageState getPackageStateLocked(String packageName, int uid, int vers) { + SparseArray<PackageState> vpkg = mPackages.get(packageName, uid); + if (vpkg == null) { + vpkg = new SparseArray<PackageState>(); + mPackages.put(packageName, uid, vpkg); + } + PackageState as = vpkg.get(vers); if (as != null) { return as; } as = new PackageState(packageName, uid); - mPackages.put(packageName, uid, as); + vpkg.put(vers, as); return as; } - public ProcessState getProcessStateLocked(String packageName, int uid, String processName) { - final PackageState pkgState = getPackageStateLocked(packageName, uid); + public ProcessState getProcessStateLocked(String packageName, int uid, int vers, + String processName) { + final PackageState pkgState = getPackageStateLocked(packageName, uid, vers); ProcessState ps = pkgState.mProcesses.get(processName); if (ps != null) { return ps; } ProcessState commonProc = mProcesses.get(processName, uid); if (commonProc == null) { - commonProc = new ProcessState(this, packageName, uid, processName); + commonProc = new ProcessState(this, packageName, uid, vers, processName); mProcesses.put(processName, uid, commonProc); if (DEBUG) Slog.d(TAG, "GETPROC created new common " + commonProc); } if (!commonProc.mMultiPackage) { - if (packageName.equals(commonProc.mPackage)) { + if (packageName.equals(commonProc.mPackage) && vers == commonProc.mVersion) { // This common process is not in use by multiple packages, and // is for the calling package, so we can just use it directly. ps = commonProc; @@ -1668,7 +1737,8 @@ public final class ProcessStats implements Parcelable { long now = SystemClock.uptimeMillis(); // First let's make a copy of the current process state and put // that under the now unique state for its original package name. - final PackageState commonPkgState = getPackageStateLocked(commonProc.mPackage, uid); + final PackageState commonPkgState = getPackageStateLocked(commonProc.mPackage, + uid, commonProc.mVersion); if (commonPkgState != null) { ProcessState cloned = commonProc.clone(commonProc.mPackage, now); if (DEBUG) Slog.d(TAG, "GETPROC setting clone to pkg " + commonProc.mPackage @@ -1691,13 +1761,13 @@ public final class ProcessStats implements Parcelable { + "/" + uid + " for proc " + commonProc.mName); } // And now make a fresh new process state for the new package name. - ps = new ProcessState(commonProc, packageName, uid, processName, now); + ps = new ProcessState(commonProc, packageName, uid, vers, processName, now); if (DEBUG) Slog.d(TAG, "GETPROC created new pkg " + ps); } } else { // The common process is for multiple packages, we need to create a // separate object for the per-package data. - ps = new ProcessState(commonProc, packageName, uid, processName, + ps = new ProcessState(commonProc, packageName, uid, vers, processName, SystemClock.uptimeMillis()); if (DEBUG) Slog.d(TAG, "GETPROC created new pkg " + ps); } @@ -1706,16 +1776,16 @@ public final class ProcessStats implements Parcelable { return ps; } - public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid, + public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid, int vers, String processName, String className) { - final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid); + final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid, vers); ProcessStats.ServiceState ss = as.mServices.get(className); if (ss != null) { if (DEBUG) Slog.d(TAG, "GETSVC: returning existing " + ss); return ss; } final ProcessStats.ProcessState ps = processName != null - ? getProcessStateLocked(packageName, uid, processName) : null; + ? getProcessStateLocked(packageName, uid, vers, processName) : null; ss = new ProcessStats.ServiceState(this, packageName, className, processName, ps); as.mServices.put(className, ss); if (DEBUG) Slog.d(TAG, "GETSVC: creating " + ss + " in " + ps); @@ -1756,119 +1826,124 @@ public final class ProcessStats implements Parcelable { boolean dumpAll, boolean activeOnly) { long totalTime = dumpSingleTime(null, null, mMemFactorDurations, mMemFactor, mStartTime, now); - ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); + ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); boolean printedHeader = false; boolean sepNeeded = false; for (int ip=0; ip<pkgMap.size(); ip++) { final String pkgName = pkgMap.keyAt(ip); - final SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); for (int iu=0; iu<uids.size(); iu++) { final int uid = uids.keyAt(iu); - final PackageState pkgState = uids.valueAt(iu); - final int NPROCS = pkgState.mProcesses.size(); - final int NSRVS = pkgState.mServices.size(); - final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); - if (!pkgMatch) { - boolean procMatch = false; - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - if (reqPackage.equals(proc.mName)) { - procMatch = true; - break; + final SparseArray<PackageState> vpkgs = uids.valueAt(iu); + for (int iv=0; iv<vpkgs.size(); iv++) { + final int vers = vpkgs.keyAt(iv); + final PackageState pkgState = vpkgs.valueAt(iv); + final int NPROCS = pkgState.mProcesses.size(); + final int NSRVS = pkgState.mServices.size(); + final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); + if (!pkgMatch) { + boolean procMatch = false; + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (reqPackage.equals(proc.mName)) { + procMatch = true; + break; + } + } + if (!procMatch) { + continue; } } - if (!procMatch) { - continue; + if (NPROCS > 0 || NSRVS > 0) { + if (!printedHeader) { + pw.println("Per-Package Stats:"); + printedHeader = true; + sepNeeded = true; + } + pw.print(" * "); pw.print(pkgName); pw.print(" / "); + UserHandle.formatUid(pw, uid); pw.print(" / v"); + pw.print(vers); pw.println(":"); } - } - if (NPROCS > 0 || NSRVS > 0) { - if (!printedHeader) { - pw.println("Per-Package Stats:"); - printedHeader = true; - sepNeeded = true; + if (!dumpSummary || dumpAll) { + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } + if (activeOnly && !proc.isInUse()) { + pw.print(" (Not active: "); + pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")"); + continue; + } + pw.print(" Process "); + pw.print(pkgState.mProcesses.keyAt(iproc)); + if (proc.mCommonProcess.mMultiPackage) { + pw.print(" (multi, "); + } else { + pw.print(" (unique, "); + } + pw.print(proc.mDurationsTableSize); + pw.print(" entries)"); + pw.println(":"); + dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, + ALL_PROC_STATES, now); + dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, + ALL_PROC_STATES); + dumpProcessInternalLocked(pw, " ", proc, dumpAll); + } + } else { + ArrayList<ProcessState> procs = new ArrayList<ProcessState>(); + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } + if (activeOnly && !proc.isInUse()) { + continue; + } + procs.add(proc); + } + dumpProcessSummaryLocked(pw, " ", procs, ALL_SCREEN_ADJ, ALL_MEM_ADJ, + NON_CACHED_PROC_STATES, false, now, totalTime); } - pw.print(" * "); pw.print(pkgName); pw.print(" / "); - UserHandle.formatUid(pw, uid); pw.println(":"); - } - if (!dumpSummary || dumpAll) { - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - if (!pkgMatch && !reqPackage.equals(proc.mName)) { + for (int isvc=0; isvc<NSRVS; isvc++) { + ServiceState svc = pkgState.mServices.valueAt(isvc); + if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) { continue; } - if (activeOnly && !proc.isInUse()) { + if (activeOnly && !svc.isInUse()) { pw.print(" (Not active: "); - pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")"); + pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")"); continue; } - pw.print(" Process "); - pw.print(pkgState.mProcesses.keyAt(iproc)); - if (proc.mCommonProcess.mMultiPackage) { - pw.print(" (multi, "); + if (dumpAll) { + pw.print(" Service "); } else { - pw.print(" (unique, "); + pw.print(" * "); } - pw.print(proc.mDurationsTableSize); - pw.print(" entries)"); + pw.print(pkgState.mServices.keyAt(isvc)); pw.println(":"); - dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, - ALL_PROC_STATES, now); - dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, - ALL_PROC_STATES); - dumpProcessInternalLocked(pw, " ", proc, dumpAll); - } - } else { - ArrayList<ProcessState> procs = new ArrayList<ProcessState>(); - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - if (!pkgMatch && !reqPackage.equals(proc.mName)) { - continue; - } - if (activeOnly && !proc.isInUse()) { - continue; - } - procs.add(proc); - } - dumpProcessSummaryLocked(pw, " ", procs, ALL_SCREEN_ADJ, ALL_MEM_ADJ, - NON_CACHED_PROC_STATES, now, totalTime); - } - for (int isvc=0; isvc<NSRVS; isvc++) { - ServiceState svc = pkgState.mServices.valueAt(isvc); - if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) { - continue; - } - if (activeOnly && !svc.isInUse()) { - pw.print(" (Not active: "); - pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")"); - continue; - } - if (dumpAll) { - pw.print(" Service "); - } else { - pw.print(" * "); - } - pw.print(pkgState.mServices.keyAt(isvc)); - pw.println(":"); - pw.print(" Process: "); pw.println(svc.mProcessName); - dumpServiceStats(pw, " ", " ", " ", "Running", svc, - svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState, - svc.mRunStartTime, now, totalTime, !dumpSummary || dumpAll); - dumpServiceStats(pw, " ", " ", " ", "Started", svc, - svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState, - svc.mStartedStartTime, now, totalTime, !dumpSummary || dumpAll); - dumpServiceStats(pw, " ", " ", " ", "Bound", svc, - svc.mBoundCount, ServiceState.SERVICE_BOUND, svc.mBoundState, - svc.mBoundStartTime, now, totalTime, !dumpSummary || dumpAll); - dumpServiceStats(pw, " ", " ", " ", "Executing", svc, - svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState, - svc.mExecStartTime, now, totalTime, !dumpSummary || dumpAll); - if (dumpAll) { - if (svc.mOwner != null) { - pw.print(" mOwner="); pw.println(svc.mOwner); - } - if (svc.mStarted || svc.mRestarting) { - pw.print(" mStarted="); pw.print(svc.mStarted); - pw.print(" mRestarting="); pw.println(svc.mRestarting); + pw.print(" Process: "); pw.println(svc.mProcessName); + dumpServiceStats(pw, " ", " ", " ", "Running", svc, + svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState, + svc.mRunStartTime, now, totalTime, !dumpSummary || dumpAll); + dumpServiceStats(pw, " ", " ", " ", "Started", svc, + svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState, + svc.mStartedStartTime, now, totalTime, !dumpSummary || dumpAll); + dumpServiceStats(pw, " ", " ", " ", "Bound", svc, + svc.mBoundCount, ServiceState.SERVICE_BOUND, svc.mBoundState, + svc.mBoundStartTime, now, totalTime, !dumpSummary || dumpAll); + dumpServiceStats(pw, " ", " ", " ", "Executing", svc, + svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState, + svc.mExecStartTime, now, totalTime, !dumpSummary || dumpAll); + if (dumpAll) { + if (svc.mOwner != null) { + pw.print(" mOwner="); pw.println(svc.mOwner); + } + if (svc.mStarted || svc.mRestarting) { + pw.print(" mStarted="); pw.print(svc.mStarted); + pw.print(" mRestarting="); pw.println(svc.mRestarting); + } } } } @@ -2059,7 +2134,7 @@ public final class ProcessStats implements Parcelable { pw.println(header); } dumpProcessSummaryLocked(pw, prefix, procs, screenStates, memStates, - sortProcStates, now, totalTime); + sortProcStates, true, now, totalTime); } } @@ -2067,23 +2142,27 @@ public final class ProcessStats implements Parcelable { int[] procStates, int sortProcStates[], long now, String reqPackage, boolean activeOnly) { final ArraySet<ProcessState> foundProcs = new ArraySet<ProcessState>(); - final ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); + final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); for (int ip=0; ip<pkgMap.size(); ip++) { final String pkgName = pkgMap.keyAt(ip); - final SparseArray<PackageState> procs = pkgMap.valueAt(ip); + final SparseArray<SparseArray<PackageState>> procs = pkgMap.valueAt(ip); for (int iu=0; iu<procs.size(); iu++) { - final PackageState state = procs.valueAt(iu); - final int NPROCS = state.mProcesses.size(); - final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); - for (int iproc=0; iproc<NPROCS; iproc++) { - final ProcessState proc = state.mProcesses.valueAt(iproc); - if (!pkgMatch && !reqPackage.equals(proc.mName)) { - continue; - } - if (activeOnly && !proc.isInUse()) { - continue; + final SparseArray<PackageState> vpkgs = procs.valueAt(iu); + final int NVERS = vpkgs.size(); + for (int iv=0; iv<NVERS; iv++) { + final PackageState state = vpkgs.valueAt(iv); + final int NPROCS = state.mProcesses.size(); + final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); + for (int iproc=0; iproc<NPROCS; iproc++) { + final ProcessState proc = state.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } + if (activeOnly && !proc.isInUse()) { + continue; + } + foundProcs.add(proc.mCommonProcess); } - foundProcs.add(proc.mCommonProcess); } } } @@ -2128,8 +2207,8 @@ public final class ProcessStats implements Parcelable { public void dumpCheckinLocked(PrintWriter pw, String reqPackage) { final long now = SystemClock.uptimeMillis(); - ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); - pw.println("vers,3"); + final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap(); + pw.println("vers,4"); pw.print("period,"); pw.print(mTimePeriodStartClockStr); pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(","); pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime); @@ -2152,75 +2231,85 @@ public final class ProcessStats implements Parcelable { pw.println(); pw.print("config,"); pw.print(mRuntime); pw.print(','); pw.println(mWebView); for (int ip=0; ip<pkgMap.size(); ip++) { - String pkgName = pkgMap.keyAt(ip); + final String pkgName = pkgMap.keyAt(ip); if (reqPackage != null && !reqPackage.equals(pkgName)) { continue; } - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip); for (int iu=0; iu<uids.size(); iu++) { - int uid = uids.keyAt(iu); - PackageState pkgState = uids.valueAt(iu); - final int NPROCS = pkgState.mProcesses.size(); - final int NSRVS = pkgState.mServices.size(); - for (int iproc=0; iproc<NPROCS; iproc++) { - ProcessState proc = pkgState.mProcesses.valueAt(iproc); - pw.print("pkgproc,"); - pw.print(pkgName); - pw.print(","); - pw.print(uid); - pw.print(","); - pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); - dumpAllProcessStateCheckin(pw, proc, now); - pw.println(); - if (proc.mPssTableSize > 0) { - pw.print("pkgpss,"); + final int uid = uids.keyAt(iu); + final SparseArray<PackageState> vpkgs = uids.valueAt(iu); + for (int iv=0; iv<vpkgs.size(); iv++) { + final int vers = vpkgs.keyAt(iv); + final PackageState pkgState = vpkgs.valueAt(iv); + final int NPROCS = pkgState.mProcesses.size(); + final int NSRVS = pkgState.mServices.size(); + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + pw.print("pkgproc,"); pw.print(pkgName); pw.print(","); pw.print(uid); pw.print(","); - pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); - dumpAllProcessPssCheckin(pw, proc); - pw.println(); - } - if (proc.mNumExcessiveWake > 0 || proc.mNumExcessiveCpu > 0 - || proc.mNumCachedKill > 0) { - pw.print("pkgkills,"); - pw.print(pkgName); - pw.print(","); - pw.print(uid); + pw.print(vers); pw.print(","); pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); - pw.print(","); - pw.print(proc.mNumExcessiveWake); - pw.print(","); - pw.print(proc.mNumExcessiveCpu); - pw.print(","); - pw.print(proc.mNumCachedKill); - pw.print(","); - pw.print(proc.mMinCachedKillPss); - pw.print(":"); - pw.print(proc.mAvgCachedKillPss); - pw.print(":"); - pw.print(proc.mMaxCachedKillPss); + dumpAllProcessStateCheckin(pw, proc, now); pw.println(); + if (proc.mPssTableSize > 0) { + pw.print("pkgpss,"); + pw.print(pkgName); + pw.print(","); + pw.print(uid); + pw.print(","); + pw.print(vers); + pw.print(","); + pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); + dumpAllProcessPssCheckin(pw, proc); + pw.println(); + } + if (proc.mNumExcessiveWake > 0 || proc.mNumExcessiveCpu > 0 + || proc.mNumCachedKill > 0) { + pw.print("pkgkills,"); + pw.print(pkgName); + pw.print(","); + pw.print(uid); + pw.print(","); + pw.print(vers); + pw.print(","); + pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); + pw.print(","); + pw.print(proc.mNumExcessiveWake); + pw.print(","); + pw.print(proc.mNumExcessiveCpu); + pw.print(","); + pw.print(proc.mNumCachedKill); + pw.print(","); + pw.print(proc.mMinCachedKillPss); + pw.print(":"); + pw.print(proc.mAvgCachedKillPss); + pw.print(":"); + pw.print(proc.mMaxCachedKillPss); + pw.println(); + } + } + for (int isvc=0; isvc<NSRVS; isvc++) { + String serviceName = collapseString(pkgName, + pkgState.mServices.keyAt(isvc)); + ServiceState svc = pkgState.mServices.valueAt(isvc); + dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, vers, serviceName, + svc, ServiceState.SERVICE_RUN, svc.mRunCount, + svc.mRunState, svc.mRunStartTime, now); + dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, vers, serviceName, + svc, ServiceState.SERVICE_STARTED, svc.mStartedCount, + svc.mStartedState, svc.mStartedStartTime, now); + dumpServiceTimeCheckin(pw, "pkgsvc-bound", pkgName, uid, vers, serviceName, + svc, ServiceState.SERVICE_BOUND, svc.mBoundCount, + svc.mBoundState, svc.mBoundStartTime, now); + dumpServiceTimeCheckin(pw, "pkgsvc-exec", pkgName, uid, vers, serviceName, + svc, ServiceState.SERVICE_EXEC, svc.mExecCount, + svc.mExecState, svc.mExecStartTime, now); } - } - for (int isvc=0; isvc<NSRVS; isvc++) { - String serviceName = collapseString(pkgName, - pkgState.mServices.keyAt(isvc)); - ServiceState svc = pkgState.mServices.valueAt(isvc); - dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, serviceName, - svc, ServiceState.SERVICE_RUN, svc.mRunCount, - svc.mRunState, svc.mRunStartTime, now); - dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, serviceName, - svc, ServiceState.SERVICE_STARTED, svc.mStartedCount, - svc.mStartedState, svc.mStartedStartTime, now); - dumpServiceTimeCheckin(pw, "pkgsvc-bound", pkgName, uid, serviceName, - svc, ServiceState.SERVICE_BOUND, svc.mBoundCount, - svc.mBoundState, svc.mBoundStartTime, now); - dumpServiceTimeCheckin(pw, "pkgsvc-exec", pkgName, uid, serviceName, - svc, ServiceState.SERVICE_EXEC, svc.mExecCount, - svc.mExecState, svc.mExecStartTime, now); } } } @@ -2364,9 +2453,10 @@ public final class ProcessStats implements Parcelable { } public static final class ProcessState extends DurationsTable { - public final ProcessState mCommonProcess; + public ProcessState mCommonProcess; public final String mPackage; public final int mUid; + public final int mVersion; //final long[] mDurations = new long[STATE_COUNT*ADJ_COUNT]; int mCurState = STATE_NOTHING; @@ -2393,16 +2483,19 @@ public final class ProcessStats implements Parcelable { boolean mDead; public long mTmpTotalTime; + int mTmpNumInUse; + ProcessState mTmpFoundSubProc; /** * Create a new top-level process state, for the initial case where there is only * a single package running in a process. The initial state is not running. */ - public ProcessState(ProcessStats processStats, String pkg, int uid, String name) { + public ProcessState(ProcessStats processStats, String pkg, int uid, int vers, String name) { super(processStats, name); mCommonProcess = this; mPackage = pkg; mUid = uid; + mVersion = vers; } /** @@ -2410,18 +2503,19 @@ public final class ProcessStats implements Parcelable { * state. The current running state of the top-level process is also copied, * marked as started running at 'now'. */ - public ProcessState(ProcessState commonProcess, String pkg, int uid, String name, + public ProcessState(ProcessState commonProcess, String pkg, int uid, int vers, String name, long now) { super(commonProcess.mStats, name); mCommonProcess = commonProcess; mPackage = pkg; mUid = uid; + mVersion = vers; mCurState = commonProcess.mCurState; mStartTime = now; } ProcessState clone(String pkg, long now) { - ProcessState pnew = new ProcessState(this, pkg, mUid, mName, now); + ProcessState pnew = new ProcessState(this, pkg, mUid, mVersion, mName, now); copyDurationsTo(pnew); if (mPssTable != null) { mStats.mAddLongTable = new int[mPssTable.length]; @@ -2811,9 +2905,20 @@ public final class ProcessStats implements Parcelable { // The array map is still pointing to a common process state // that is now shared across packages. Update it to point to // the new per-package state. - ProcessState proc = mStats.mPackages.get(pkgName, mUid).mProcesses.get(mName); + SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgName, mUid); + if (vpkg == null) { + throw new IllegalStateException("Didn't find package " + pkgName + + " / " + mUid); + } + PackageState pkg = vpkg.get(mVersion); + if (pkg == null) { + throw new IllegalStateException("Didn't find package " + pkgName + + " / " + mUid + " vers " + mVersion); + } + ProcessState proc = pkg.mProcesses.get(mName); if (proc == null) { - throw new IllegalStateException("Didn't create per-package process"); + throw new IllegalStateException("Didn't create per-package process " + + mName + " in pkg " + pkgName + " / " + mUid + " vers " + mVersion); } return proc; } @@ -2829,18 +2934,26 @@ public final class ProcessStats implements Parcelable { // are losing whatever data we had in the old process state. Log.wtf(TAG, "Pulling dead proc: name=" + mName + " pkg=" + mPackage + " uid=" + mUid + " common.name=" + mCommonProcess.mName); - proc = mStats.getProcessStateLocked(proc.mPackage, proc.mUid, proc.mName); + proc = mStats.getProcessStateLocked(proc.mPackage, proc.mUid, proc.mVersion, + proc.mName); } if (proc.mMultiPackage) { // The array map is still pointing to a common process state // that is now shared across packages. Update it to point to // the new per-package state. - PackageState pkg = mStats.mPackages.get(pkgList.keyAt(index), proc.mUid); - if (pkg == null) { + SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgList.keyAt(index), + proc.mUid); + if (vpkg == null) { throw new IllegalStateException("No existing package " + pkgList.keyAt(index) + "/" + proc.mUid + " for multi-proc " + proc.mName); } + PackageState pkg = vpkg.get(proc.mVersion); + if (pkg == null) { + throw new IllegalStateException("No existing package " + + pkgList.keyAt(index) + "/" + proc.mUid + + " for multi-proc " + proc.mName + " version " + proc.mVersion); + } proc = pkg.mProcesses.get(proc.mName); if (proc == null) { throw new IllegalStateException("Didn't create per-package process " @@ -3014,7 +3127,7 @@ public final class ProcessStats implements Parcelable { } public boolean isInUse() { - return mOwner != null; + return mOwner != null || mRestarting; } void add(ServiceState other) { diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java new file mode 100644 index 0000000..6056bf2 --- /dev/null +++ b/core/java/com/android/internal/app/ToolbarActionBar.java @@ -0,0 +1,450 @@ +/* + * 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.app; + +import android.annotation.Nullable; +import android.app.ActionBar; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.drawable.Drawable; +import android.view.ActionMode; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.widget.SpinnerAdapter; +import android.widget.Toolbar; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.widget.DecorToolbar; +import com.android.internal.widget.ToolbarWidgetWrapper; + +import java.util.ArrayList; + +public class ToolbarActionBar extends ActionBar { + private Toolbar mToolbar; + private DecorToolbar mDecorToolbar; + private Window.Callback mWindowCallback; + + private boolean mLastMenuVisibility; + private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners = + new ArrayList<OnMenuVisibilityListener>(); + + private final Runnable mMenuInvalidator = new Runnable() { + @Override + public void run() { + populateOptionsMenu(); + } + }; + + private final Toolbar.OnMenuItemClickListener mMenuClicker = + new Toolbar.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); + } + }; + + public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) { + mToolbar = toolbar; + mDecorToolbar = new ToolbarWidgetWrapper(toolbar); + mWindowCallback = windowCallback; + toolbar.setOnMenuItemClickListener(mMenuClicker); + mDecorToolbar.setWindowTitle(title); + } + + @Override + public void setCustomView(View view) { + setCustomView(view, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + } + + @Override + public void setCustomView(View view, LayoutParams layoutParams) { + view.setLayoutParams(layoutParams); + mDecorToolbar.setCustomView(view); + } + + @Override + public void setCustomView(int resId) { + final LayoutInflater inflater = LayoutInflater.from(mToolbar.getContext()); + setCustomView(inflater.inflate(resId, mToolbar, false)); + } + + @Override + public void setIcon(int resId) { + mDecorToolbar.setIcon(resId); + } + + @Override + public void setIcon(Drawable icon) { + mDecorToolbar.setIcon(icon); + } + + @Override + public void setLogo(int resId) { + mDecorToolbar.setLogo(resId); + } + + @Override + public void setLogo(Drawable logo) { + mDecorToolbar.setLogo(logo); + } + + @Override + public void setStackedBackgroundDrawable(Drawable d) { + // This space for rent (do nothing) + } + + @Override + public void setSplitBackgroundDrawable(Drawable d) { + // This space for rent (do nothing) + } + + @Override + public void setHomeButtonEnabled(boolean enabled) { + // If the nav button on a Toolbar is present, it's enabled. No-op. + } + + @Override + public Context getThemedContext() { + return mToolbar.getContext(); + } + + @Override + public boolean isTitleTruncated() { + return super.isTitleTruncated(); + } + + @Override + public void setHomeAsUpIndicator(Drawable indicator) { + mToolbar.setNavigationIcon(indicator); + } + + @Override + public void setHomeAsUpIndicator(int resId) { + mToolbar.setNavigationIcon(resId); + } + + @Override + public void setHomeActionContentDescription(CharSequence description) { + mToolbar.setNavigationDescription(description); + } + + @Override + public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) { + // Do nothing + } + + @Override + public void setHomeActionContentDescription(int resId) { + mToolbar.setNavigationDescription(resId); + } + + @Override + public void setShowHideAnimationEnabled(boolean enabled) { + // This space for rent; no-op. + } + + @Override + public void onConfigurationChanged(Configuration config) { + super.onConfigurationChanged(config); + } + + @Override + public ActionMode startActionMode(ActionMode.Callback callback) { + return mToolbar.startActionMode(callback); + } + + @Override + public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public void setSelectedNavigationItem(int position) { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public int getSelectedNavigationIndex() { + return -1; + } + + @Override + public int getNavigationItemCount() { + return 0; + } + + @Override + public void setTitle(CharSequence title) { + mDecorToolbar.setTitle(title); + } + + @Override + public void setTitle(int resId) { + mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null); + } + + @Override + public void setSubtitle(CharSequence subtitle) { + mDecorToolbar.setSubtitle(subtitle); + } + + @Override + public void setSubtitle(int resId) { + mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null); + } + + @Override + public void setDisplayOptions(@DisplayOptions int options) { + setDisplayOptions(options, 0xffffffff); + } + + @Override + public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) { + mDecorToolbar.setDisplayOptions((options & mask) | + mDecorToolbar.getDisplayOptions() & ~mask); + } + + @Override + public void setDisplayUseLogoEnabled(boolean useLogo) { + setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO); + } + + @Override + public void setDisplayShowHomeEnabled(boolean showHome) { + setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME); + } + + @Override + public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) { + setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP); + } + + @Override + public void setDisplayShowTitleEnabled(boolean showTitle) { + setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE); + } + + @Override + public void setDisplayShowCustomEnabled(boolean showCustom) { + setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM); + } + + @Override + public void setBackgroundDrawable(@Nullable Drawable d) { + mToolbar.setBackground(d); + } + + @Override + public View getCustomView() { + return mDecorToolbar.getCustomView(); + } + + @Override + public CharSequence getTitle() { + return mToolbar.getTitle(); + } + + @Override + public CharSequence getSubtitle() { + return mToolbar.getSubtitle(); + } + + @Override + public int getNavigationMode() { + return NAVIGATION_MODE_STANDARD; + } + + @Override + public void setNavigationMode(@NavigationMode int mode) { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public int getDisplayOptions() { + return mDecorToolbar.getDisplayOptions(); + } + + @Override + public Tab newTab() { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public void addTab(Tab tab) { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public void addTab(Tab tab, boolean setSelected) { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public void addTab(Tab tab, int position) { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public void addTab(Tab tab, int position, boolean setSelected) { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public void removeTab(Tab tab) { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public void removeTabAt(int position) { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public void removeAllTabs() { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public void selectTab(Tab tab) { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public Tab getSelectedTab() { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public Tab getTabAt(int index) { + throw new UnsupportedOperationException( + "Navigation modes are not supported in toolbar action bars"); + } + + @Override + public int getTabCount() { + return 0; + } + + @Override + public int getHeight() { + return mToolbar.getHeight(); + } + + @Override + public void show() { + // TODO: Consider a better transition for this. + // Right now use no automatic transition so that the app can supply one if desired. + mToolbar.setVisibility(View.VISIBLE); + } + + @Override + public void hide() { + // TODO: Consider a better transition for this. + // Right now use no automatic transition so that the app can supply one if desired. + mToolbar.setVisibility(View.GONE); + } + + @Override + public boolean isShowing() { + return mToolbar.getVisibility() == View.VISIBLE; + } + + @Override + public boolean openOptionsMenu() { + return mToolbar.showOverflowMenu(); + } + + @Override + public boolean invalidateOptionsMenu() { + mToolbar.removeCallbacks(mMenuInvalidator); + mToolbar.postOnAnimation(mMenuInvalidator); + return true; + } + + @Override + public boolean collapseActionView() { + if (mToolbar.hasExpandedActionView()) { + mToolbar.collapseActionView(); + return true; + } + return false; + } + + void populateOptionsMenu() { + final Menu menu = mToolbar.getMenu(); + final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null; + if (mb != null) { + mb.stopDispatchingItemsChanged(); + } + try { + menu.clear(); + if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) || + !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) { + menu.clear(); + } + } finally { + if (mb != null) { + mb.startDispatchingItemsChanged(); + } + } + } + + @Override + public boolean onMenuKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP) { + openOptionsMenu(); + } + return true; + } + + public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) { + mMenuVisibilityListeners.add(listener); + } + + public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) { + mMenuVisibilityListeners.remove(listener); + } + + public void dispatchMenuVisibilityChanged(boolean isVisible) { + if (isVisible == mLastMenuVisibility) { + return; + } + mLastMenuVisibility = isVisible; + + final int count = mMenuVisibilityListeners.size(); + for (int i = 0; i < count; i++) { + mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible); + } + } +} diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index ad45894..c0b5b97 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -17,7 +17,12 @@ package com.android.internal.app; import android.animation.ValueAnimator; +import android.content.res.TypedArray; +import android.view.ViewGroup; import android.view.ViewParent; +import android.widget.AdapterView; +import android.widget.Toolbar; +import com.android.internal.R; import com.android.internal.view.ActionBarPolicy; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuPopupHelper; @@ -26,6 +31,7 @@ import com.android.internal.widget.ActionBarContainer; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.ActionBarOverlayLayout; import com.android.internal.widget.ActionBarView; +import com.android.internal.widget.DecorToolbar; import com.android.internal.widget.ScrollingTabContainerView; import android.animation.Animator; @@ -41,8 +47,6 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.util.Log; import android.util.TypedValue; import android.view.ActionMode; import android.view.ContextThemeWrapper; @@ -51,24 +55,25 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.view.accessibility.AccessibilityEvent; import android.view.animation.AnimationUtils; import android.widget.SpinnerAdapter; +import com.android.internal.widget.ToolbarWidgetWrapper; import java.lang.ref.WeakReference; import java.util.ArrayList; /** - * ActionBarImpl is the ActionBar implementation used - * by devices of all screen sizes. If it detects a compatible decor, - * it will split contextual modes across both the ActionBarView at - * the top of the screen and a horizontal LinearLayout at the bottom - * which is normally hidden. + * WindowDecorActionBar is the ActionBar implementation used + * by devices of all screen sizes as part of the window decor layout. + * If it detects a compatible decor, it will split contextual modes + * across both the ActionBarView at the top of the screen and + * a horizontal LinearLayout at the bottom which is normally hidden. */ -public class ActionBarImpl extends ActionBar { - private static final String TAG = "ActionBarImpl"; +public class WindowDecorActionBar extends ActionBar implements + ActionBarOverlayLayout.ActionBarVisibilityCallback { + private static final String TAG = "WindowDecorActionBar"; private Context mContext; private Context mThemedContext; @@ -77,7 +82,7 @@ public class ActionBarImpl extends ActionBar { private ActionBarOverlayLayout mOverlayLayout; private ActionBarContainer mContainerView; - private ActionBarView mActionView; + private DecorToolbar mDecorToolbar; private ActionBarContextView mContextView; private ActionBarContainer mSplitView; private View mContentView; @@ -106,9 +111,6 @@ public class ActionBarImpl extends ActionBar { private int mContextDisplayMode; private boolean mHasEmbeddedTabs; - final Handler mHandler = new Handler(); - Runnable mTabSelector; - private int mCurWindowVisibility = View.VISIBLE; private boolean mContentAnimations = true; @@ -120,6 +122,7 @@ public class ActionBarImpl extends ActionBar { private Animator mCurrentShowAnim; private boolean mShowHideAnimationEnabled; + boolean mHideOnContentScroll; final AnimatorListener mHideListener = new AnimatorListenerAdapter() { @Override @@ -136,7 +139,7 @@ public class ActionBarImpl extends ActionBar { mCurrentShowAnim = null; completeDeferredDestroyActionMode(); if (mOverlayLayout != null) { - mOverlayLayout.requestFitSystemWindows(); + mOverlayLayout.requestApplyInsets(); } } }; @@ -158,7 +161,7 @@ public class ActionBarImpl extends ActionBar { } }; - public ActionBarImpl(Activity activity) { + public WindowDecorActionBar(Activity activity) { mActivity = activity; Window window = activity.getWindow(); View decor = window.getDecorView(); @@ -169,7 +172,7 @@ public class ActionBarImpl extends ActionBar { } } - public ActionBarImpl(Dialog dialog) { + public WindowDecorActionBar(Dialog dialog) { mDialog = dialog; init(dialog.getWindow().getDecorView()); } @@ -178,19 +181,18 @@ public class ActionBarImpl extends ActionBar { * Only for edit mode. * @hide */ - public ActionBarImpl(View layout) { + public WindowDecorActionBar(View layout) { assert layout.isInEditMode(); init(layout); } private void init(View decor) { - mContext = decor.getContext(); mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById( - com.android.internal.R.id.action_bar_overlay_layout); + com.android.internal.R.id.decor_content_parent); if (mOverlayLayout != null) { - mOverlayLayout.setActionBar(this); + mOverlayLayout.setActionBarVisibilityCallback(this); } - mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar); + mDecorToolbar = getDecorToolbar(decor.findViewById(com.android.internal.R.id.action_bar)); mContextView = (ActionBarContextView) decor.findViewById( com.android.internal.R.id.action_context_bar); mContainerView = (ActionBarContainer) decor.findViewById( @@ -198,17 +200,17 @@ public class ActionBarImpl extends ActionBar { mSplitView = (ActionBarContainer) decor.findViewById( com.android.internal.R.id.split_action_bar); - if (mActionView == null || mContextView == null || mContainerView == null) { + if (mDecorToolbar == null || mContextView == null || mContainerView == null) { throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + "with a compatible window decor layout"); } - mActionView.setContextView(mContextView); - mContextDisplayMode = mActionView.isSplitActionBar() ? + mContext = mDecorToolbar.getContext(); + mContextDisplayMode = mDecorToolbar.isSplit() ? CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL; // This was initially read from the action bar style - final int current = mActionView.getDisplayOptions(); + final int current = mDecorToolbar.getDisplayOptions(); final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0; if (homeAsUp) { mDisplayHomeAsUpSet = true; @@ -217,6 +219,25 @@ public class ActionBarImpl extends ActionBar { ActionBarPolicy abp = ActionBarPolicy.get(mContext); setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp); setHasEmbeddedTabs(abp.hasEmbeddedTabs()); + + final TypedArray a = mContext.obtainStyledAttributes(null, + com.android.internal.R.styleable.ActionBar, + com.android.internal.R.attr.actionBarStyle, 0); + if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) { + setHideOnContentScrollEnabled(true); + } + a.recycle(); + } + + private DecorToolbar getDecorToolbar(View view) { + if (view instanceof DecorToolbar) { + return (DecorToolbar) view; + } else if (view instanceof Toolbar) { + return ((Toolbar) view).getWrapper(); + } else { + throw new IllegalStateException("Can't make a decor toolbar out of " + + view.getClass().getSimpleName()); + } } public void onConfigurationChanged(Configuration newConfig) { @@ -227,28 +248,25 @@ public class ActionBarImpl extends ActionBar { mHasEmbeddedTabs = hasEmbeddedTabs; // Switch tab layout configuration if needed if (!mHasEmbeddedTabs) { - mActionView.setEmbeddedTabView(null); + mDecorToolbar.setEmbeddedTabView(null); mContainerView.setTabContainer(mTabScrollView); } else { mContainerView.setTabContainer(null); - mActionView.setEmbeddedTabView(mTabScrollView); + mDecorToolbar.setEmbeddedTabView(mTabScrollView); } final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS; if (mTabScrollView != null) { if (isInTabMode) { mTabScrollView.setVisibility(View.VISIBLE); if (mOverlayLayout != null) { - mOverlayLayout.requestFitSystemWindows(); + mOverlayLayout.requestApplyInsets(); } } else { mTabScrollView.setVisibility(View.GONE); } } - mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode); - } - - public boolean hasNonEmbeddedTabs() { - return !mHasEmbeddedTabs && getNavigationMode() == NAVIGATION_MODE_TABS; + mDecorToolbar.setCollapsible(!mHasEmbeddedTabs && isInTabMode); + mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode); } private void ensureTabsExist() { @@ -260,12 +278,12 @@ public class ActionBarImpl extends ActionBar { if (mHasEmbeddedTabs) { tabScroller.setVisibility(View.VISIBLE); - mActionView.setEmbeddedTabView(tabScroller); + mDecorToolbar.setEmbeddedTabView(tabScroller); } else { if (getNavigationMode() == NAVIGATION_MODE_TABS) { tabScroller.setVisibility(View.VISIBLE); if (mOverlayLayout != null) { - mOverlayLayout.requestFitSystemWindows(); + mOverlayLayout.requestApplyInsets(); } } else { tabScroller.setVisibility(View.GONE); @@ -283,7 +301,7 @@ public class ActionBarImpl extends ActionBar { } } - public void setWindowVisibility(int visibility) { + public void onWindowVisibilityChanged(int visibility) { mCurWindowVisibility = visibility; } @@ -323,7 +341,8 @@ public class ActionBarImpl extends ActionBar { @Override public void setCustomView(int resId) { - setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, mActionView, false)); + setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, + mDecorToolbar.getViewGroup(), false)); } @Override @@ -353,7 +372,7 @@ public class ActionBarImpl extends ActionBar { @Override public void setHomeButtonEnabled(boolean enable) { - mActionView.setHomeButtonEnabled(enable); + mDecorToolbar.setHomeButtonEnabled(enable); } @Override @@ -367,12 +386,12 @@ public class ActionBarImpl extends ActionBar { } public void setSelectedNavigationItem(int position) { - switch (mActionView.getNavigationMode()) { + switch (mDecorToolbar.getNavigationMode()) { case NAVIGATION_MODE_TABS: selectTab(mTabs.get(position)); break; case NAVIGATION_MODE_LIST: - mActionView.setDropdownSelectedPosition(position); + mDecorToolbar.setDropdownSelectedPosition(position); break; default: throw new IllegalStateException( @@ -396,26 +415,26 @@ public class ActionBarImpl extends ActionBar { } public void setTitle(CharSequence title) { - mActionView.setTitle(title); + mDecorToolbar.setTitle(title); } public void setSubtitle(CharSequence subtitle) { - mActionView.setSubtitle(subtitle); + mDecorToolbar.setSubtitle(subtitle); } public void setDisplayOptions(int options) { if ((options & DISPLAY_HOME_AS_UP) != 0) { mDisplayHomeAsUpSet = true; } - mActionView.setDisplayOptions(options); + mDecorToolbar.setDisplayOptions(options); } public void setDisplayOptions(int options, int mask) { - final int current = mActionView.getDisplayOptions(); + final int current = mDecorToolbar.getDisplayOptions(); if ((mask & DISPLAY_HOME_AS_UP) != 0) { mDisplayHomeAsUpSet = true; } - mActionView.setDisplayOptions((options & mask) | (current & ~mask)); + mDecorToolbar.setDisplayOptions((options & mask) | (current & ~mask)); } public void setBackgroundDrawable(Drawable d) { @@ -433,23 +452,23 @@ public class ActionBarImpl extends ActionBar { } public View getCustomView() { - return mActionView.getCustomNavigationView(); + return mDecorToolbar.getCustomView(); } public CharSequence getTitle() { - return mActionView.getTitle(); + return mDecorToolbar.getTitle(); } public CharSequence getSubtitle() { - return mActionView.getSubtitle(); + return mDecorToolbar.getSubtitle(); } public int getNavigationMode() { - return mActionView.getNavigationMode(); + return mDecorToolbar.getNavigationMode(); } public int getDisplayOptions() { - return mActionView.getDisplayOptions(); + return mDecorToolbar.getDisplayOptions(); } public ActionMode startActionMode(ActionMode.Callback callback) { @@ -457,6 +476,7 @@ public class ActionBarImpl extends ActionBar { mActionMode.finish(); } + mOverlayLayout.setHideOnContentScrollEnabled(false); mContextView.killMode(); ActionModeImpl mode = new ActionModeImpl(callback); if (mode.dispatchOnCreate()) { @@ -468,7 +488,7 @@ public class ActionBarImpl extends ActionBar { if (mSplitView.getVisibility() != View.VISIBLE) { mSplitView.setVisibility(View.VISIBLE); if (mOverlayLayout != null) { - mOverlayLayout.requestFitSystemWindows(); + mOverlayLayout.requestApplyInsets(); } } } @@ -568,7 +588,7 @@ public class ActionBarImpl extends ActionBar { return; } - final FragmentTransaction trans = mActionView.isInEditMode() ? null : + final FragmentTransaction trans = mDecorToolbar.getViewGroup().isInEditMode() ? null : mActivity.getFragmentManager().beginTransaction().disallowAddToBackStack(); if (mSelectedTab == tab) { @@ -656,6 +676,35 @@ public class ActionBarImpl extends ActionBar { } } + @Override + public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { + if (hideOnContentScroll && !mOverlayLayout.isInOverlayMode()) { + throw new IllegalStateException("Action bar must be in overlay mode " + + "(Window.FEATURE_OVERLAY_ACTION_BAR) to enable hide on content scroll"); + } + mHideOnContentScroll = hideOnContentScroll; + mOverlayLayout.setHideOnContentScrollEnabled(hideOnContentScroll); + } + + @Override + public boolean isHideOnContentScrollEnabled() { + return mOverlayLayout.isHideOnContentScrollEnabled(); + } + + @Override + public int getHideOffset() { + return mOverlayLayout.getActionBarHideOffset(); + } + + @Override + public void setHideOffset(int offset) { + if (offset != 0 && !mOverlayLayout.isInOverlayMode()) { + throw new IllegalStateException("Action bar must be in overlay mode " + + "(Window.FEATURE_OVERLAY_ACTION_BAR) to set a non-zero hide offset"); + } + mOverlayLayout.setActionBarHideOffset(offset); + } + private static boolean checkShowingFlags(boolean hiddenByApp, boolean hiddenBySystem, boolean showingForMode) { if (showingForMode) { @@ -741,7 +790,7 @@ public class ActionBarImpl extends ActionBar { mShowListener.onAnimationEnd(null); } if (mOverlayLayout != null) { - mOverlayLayout.requestFitSystemWindows(); + mOverlayLayout.requestApplyInsets(); } } @@ -785,11 +834,7 @@ public class ActionBarImpl extends ActionBar { } public boolean isShowing() { - return mNowShowing; - } - - public boolean isSystemShowing() { - return !mHiddenBySystem; + return mNowShowing && getHideOffset() < getHeight(); } void animateToMode(boolean toActionMode) { @@ -799,13 +844,18 @@ public class ActionBarImpl extends ActionBar { hideForActionMode(); } - mActionView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); + mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE); - if (mTabScrollView != null && !mActionView.hasEmbeddedTabs() && mActionView.isCollapsed()) { + if (mTabScrollView != null && !mDecorToolbar.hasEmbeddedTabs() && + isCollapsed(mDecorToolbar.getViewGroup())) { mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); } } + private boolean isCollapsed(View view) { + return view == null || view.getVisibility() == View.GONE || view.getMeasuredHeight() == 0; + } + public Context getThemedContext() { if (mThemedContext == null) { TypedValue outValue = new TypedValue(); @@ -825,27 +875,39 @@ public class ActionBarImpl extends ActionBar { @Override public boolean isTitleTruncated() { - return mActionView != null && mActionView.isTitleTruncated(); + return mDecorToolbar != null && mDecorToolbar.isTitleTruncated(); } @Override public void setHomeAsUpIndicator(Drawable indicator) { - mActionView.setHomeAsUpIndicator(indicator); + mDecorToolbar.setNavigationIcon(indicator); } @Override public void setHomeAsUpIndicator(int resId) { - mActionView.setHomeAsUpIndicator(resId); + mDecorToolbar.setNavigationIcon(resId); } @Override public void setHomeActionContentDescription(CharSequence description) { - mActionView.setHomeActionContentDescription(description); + mDecorToolbar.setNavigationContentDescription(description); } @Override public void setHomeActionContentDescription(int resId) { - mActionView.setHomeActionContentDescription(resId); + mDecorToolbar.setNavigationContentDescription(resId); + } + + @Override + public void onContentScrollStarted() { + if (mCurrentShowAnim != null) { + mCurrentShowAnim.cancel(); + mCurrentShowAnim = null; + } + } + + @Override + public void onContentScrollStopped() { } /** @@ -897,7 +959,9 @@ public class ActionBarImpl extends ActionBar { // Clear out the context mode views after the animation finishes mContextView.closeMode(); - mActionView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + mDecorToolbar.getViewGroup().sendAccessibilityEvent( + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll); mActionMode = null; } @@ -1092,7 +1156,7 @@ public class ActionBarImpl extends ActionBar { @Override public Tab setIcon(int resId) { - return setIcon(mContext.getResources().getDrawable(resId)); + return setIcon(mContext.getDrawable(resId)); } @Override @@ -1136,28 +1200,27 @@ public class ActionBarImpl extends ActionBar { @Override public void setCustomView(View view) { - mActionView.setCustomNavigationView(view); + mDecorToolbar.setCustomView(view); } @Override public void setCustomView(View view, LayoutParams layoutParams) { view.setLayoutParams(layoutParams); - mActionView.setCustomNavigationView(view); + mDecorToolbar.setCustomView(view); } @Override public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { - mActionView.setDropdownAdapter(adapter); - mActionView.setCallback(callback); + mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback)); } @Override public int getSelectedNavigationIndex() { - switch (mActionView.getNavigationMode()) { + switch (mDecorToolbar.getNavigationMode()) { case NAVIGATION_MODE_TABS: return mSelectedTab != null ? mSelectedTab.getPosition() : -1; case NAVIGATION_MODE_LIST: - return mActionView.getDropdownSelectedPosition(); + return mDecorToolbar.getDropdownSelectedPosition(); default: return -1; } @@ -1165,12 +1228,11 @@ public class ActionBarImpl extends ActionBar { @Override public int getNavigationItemCount() { - switch (mActionView.getNavigationMode()) { + switch (mDecorToolbar.getNavigationMode()) { case NAVIGATION_MODE_TABS: return mTabs.size(); case NAVIGATION_MODE_LIST: - SpinnerAdapter adapter = mActionView.getDropdownAdapter(); - return adapter != null ? adapter.getCount() : 0; + return mDecorToolbar.getDropdownItemCount(); default: return 0; } @@ -1183,7 +1245,7 @@ public class ActionBarImpl extends ActionBar { @Override public void setNavigationMode(int mode) { - final int oldMode = mActionView.getNavigationMode(); + final int oldMode = mDecorToolbar.getNavigationMode(); switch (oldMode) { case NAVIGATION_MODE_TABS: mSavedTabPosition = getSelectedNavigationIndex(); @@ -1196,7 +1258,7 @@ public class ActionBarImpl extends ActionBar { mOverlayLayout.requestFitSystemWindows(); } } - mActionView.setNavigationMode(mode); + mDecorToolbar.setNavigationMode(mode); switch (mode) { case NAVIGATION_MODE_TABS: ensureTabsExist(); @@ -1207,7 +1269,8 @@ public class ActionBarImpl extends ActionBar { } break; } - mActionView.setCollapsable(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); + mDecorToolbar.setCollapsible(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); + mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); } @Override @@ -1218,30 +1281,30 @@ public class ActionBarImpl extends ActionBar { @Override public void setIcon(int resId) { - mActionView.setIcon(resId); + mDecorToolbar.setIcon(resId); } @Override public void setIcon(Drawable icon) { - mActionView.setIcon(icon); + mDecorToolbar.setIcon(icon); } public boolean hasIcon() { - return mActionView.hasIcon(); + return mDecorToolbar.hasIcon(); } @Override public void setLogo(int resId) { - mActionView.setLogo(resId); + mDecorToolbar.setLogo(resId); } @Override public void setLogo(Drawable logo) { - mActionView.setLogo(logo); + mDecorToolbar.setLogo(logo); } public boolean hasLogo() { - return mActionView.hasLogo(); + return mDecorToolbar.hasLogo(); } public void setDefaultDisplayHomeAsUpEnabled(boolean enable) { @@ -1249,4 +1312,24 @@ public class ActionBarImpl extends ActionBar { setDisplayHomeAsUpEnabled(enable); } } + + static class NavItemSelectedListener implements AdapterView.OnItemSelectedListener { + private final OnNavigationListener mListener; + + public NavItemSelectedListener(OnNavigationListener listener) { + mListener = listener; + } + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + if (mListener != null) { + mListener.onNavigationItemSelected(position, id); + } + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + // Do nothing + } + } } diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl index 7ddd5d2..5214dd9 100644 --- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl +++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl @@ -59,6 +59,5 @@ interface IAppWidgetService { void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection, int userId); void unbindRemoteViewsService(int appWidgetId, in Intent intent, int userId); int[] getAppWidgetIds(in ComponentName provider, int userId); - } diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 1e37fd9..d10451b 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -178,7 +178,7 @@ interface IBackupTransport { /** * Get the data for the application returned by {@link #nextRestorePackage}. * @param data An open, writable file into which the backup data should be stored. - * @return the same error codes as {@link #nextRestorePackage}. + * @return the same error codes as {@link #startRestore}. */ int getRestoreData(in ParcelFileDescriptor outFd); diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 494bc78..7292116 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -18,34 +18,37 @@ package com.android.internal.backup; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; +import android.app.backup.BackupTransport; import android.app.backup.RestoreSet; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.os.RemoteException; import android.os.SELinux; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructStat; import android.util.Log; import com.android.org.bouncycastle.util.encoders.Base64; import java.io.File; -import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; + +import static android.system.OsConstants.*; /** * Backup transport for stashing stuff into a known location on disk, and * later restoring from there. For testing only. */ -public class LocalTransport extends IBackupTransport.Stub { +public class LocalTransport extends BackupTransport { private static final String TAG = "LocalTransport"; private static final boolean DEBUG = true; @@ -55,20 +58,24 @@ public class LocalTransport extends IBackupTransport.Stub { private static final String TRANSPORT_DESTINATION_STRING = "Backing up to debug-only private cache"; - // The single hardcoded restore set always has the same (nonzero!) token - private static final long RESTORE_TOKEN = 1; + // The currently-active restore set always has the same (nonzero!) token + private static final long CURRENT_SET_TOKEN = 1; private Context mContext; private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup"); + private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN)); + private PackageInfo[] mRestorePackages = null; private int mRestorePackage = -1; // Index into mRestorePackages + private File mRestoreDataDir; + private long mRestoreToken; public LocalTransport(Context context) { mContext = context; - mDataDir.mkdirs(); - if (!SELinux.restorecon(mDataDir)) { - Log.e(TAG, "SELinux restorecon failed for " + mDataDir); + mCurrentSetDir.mkdirs(); + if (!SELinux.restorecon(mCurrentSetDir)) { + Log.e(TAG, "SELinux restorecon failed for " + mCurrentSetDir); } } @@ -96,14 +103,23 @@ public class LocalTransport extends IBackupTransport.Stub { public int initializeDevice() { if (DEBUG) Log.v(TAG, "wiping all data"); - deleteContents(mDataDir); - return BackupConstants.TRANSPORT_OK; + deleteContents(mCurrentSetDir); + return BackupTransport.TRANSPORT_OK; } public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) { - if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName); + if (DEBUG) { + try { + StructStat ss = Os.fstat(data.getFileDescriptor()); + Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName + + " size=" + ss.st_size); + } catch (ErrnoException e) { + Log.w(TAG, "Unable to stat input file in performBackup() on " + + packageInfo.packageName); + } + } - File packageDir = new File(mDataDir, packageInfo.packageName); + File packageDir = new File(mCurrentSetDir, packageInfo.packageName); packageDir.mkdirs(); // Each 'record' in the restore set is kept in its own file, named by @@ -135,13 +151,22 @@ public class LocalTransport extends IBackupTransport.Stub { buf = new byte[bufSize]; } changeSet.readEntityData(buf, 0, dataSize); - if (DEBUG) Log.v(TAG, " data size " + dataSize); + if (DEBUG) { + try { + long cur = Os.lseek(data.getFileDescriptor(), 0, SEEK_CUR); + Log.v(TAG, " read entity data; new pos=" + cur); + } + catch (ErrnoException e) { + Log.w(TAG, "Unable to stat input file in performBackup() on " + + packageInfo.packageName); + } + } try { entity.write(buf, 0, dataSize); } catch (IOException e) { Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath()); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } finally { entity.close(); } @@ -149,11 +174,11 @@ public class LocalTransport extends IBackupTransport.Stub { entityFile.delete(); } } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } catch (IOException e) { // oops, something went wrong. abort the operation and return error. Log.v(TAG, "Exception reading backup input:", e); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } } @@ -175,7 +200,7 @@ public class LocalTransport extends IBackupTransport.Stub { public int clearBackupData(PackageInfo packageInfo) { if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName); - File packageDir = new File(mDataDir, packageInfo.packageName); + File packageDir = new File(mCurrentSetDir, packageInfo.packageName); final File[] fileset = packageDir.listFiles(); if (fileset != null) { for (File f : fileset) { @@ -183,39 +208,57 @@ public class LocalTransport extends IBackupTransport.Stub { } packageDir.delete(); } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } public int finishBackup() { if (DEBUG) Log.v(TAG, "finishBackup()"); - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } // Restore handling - public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException { - // one hardcoded restore set - RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN); - RestoreSet[] array = { set }; - return array; + static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 }; + public RestoreSet[] getAvailableRestoreSets() { + long[] existing = new long[POSSIBLE_SETS.length + 1]; + int num = 0; + + // see which possible non-current sets exist, then put the current set at the end + for (long token : POSSIBLE_SETS) { + if ((new File(mDataDir, Long.toString(token))).exists()) { + existing[num++] = token; + } + } + // and always the currently-active set last + existing[num++] = CURRENT_SET_TOKEN; + + RestoreSet[] available = new RestoreSet[num]; + for (int i = 0; i < available.length; i++) { + available[i] = new RestoreSet("Local disk image", "flash", existing[i]); + } + return available; } public long getCurrentRestoreSet() { - // The hardcoded restore set always has the same token - return RESTORE_TOKEN; + // The current restore set always has the same token + return CURRENT_SET_TOKEN; } public int startRestore(long token, PackageInfo[] packages) { if (DEBUG) Log.v(TAG, "start restore " + token); mRestorePackages = packages; mRestorePackage = -1; - return BackupConstants.TRANSPORT_OK; + mRestoreToken = token; + mRestoreDataDir = new File(mDataDir, Long.toString(token)); + return BackupTransport.TRANSPORT_OK; } public String nextRestorePackage() { if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); while (++mRestorePackage < mRestorePackages.length) { String name = mRestorePackages[mRestorePackage].packageName; - if (new File(mDataDir, name).isDirectory()) { + // skip packages where we have a data dir but no actual contents + String[] contents = (new File(mRestoreDataDir, name)).list(); + if (contents != null && contents.length > 0) { if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name); return name; } @@ -228,39 +271,75 @@ public class LocalTransport extends IBackupTransport.Stub { public int getRestoreData(ParcelFileDescriptor outFd) { if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called"); - File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName); + File packageDir = new File(mRestoreDataDir, mRestorePackages[mRestorePackage].packageName); // The restore set is the concatenation of the individual record blobs, - // each of which is a file in the package's directory - File[] blobs = packageDir.listFiles(); + // each of which is a file in the package's directory. We return the + // data in lexical order sorted by key, so that apps which use synthetic + // keys like BLOB_1, BLOB_2, etc will see the date in the most obvious + // order. + ArrayList<DecodedFilename> blobs = contentsByKey(packageDir); if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error - Log.e(TAG, "Error listing directory: " + packageDir); - return BackupConstants.TRANSPORT_ERROR; + Log.e(TAG, "No keys for package: " + packageDir); + return BackupTransport.TRANSPORT_ERROR; } // We expect at least some data if the directory exists in the first place - if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files"); + if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.size() + " key files"); BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor()); try { - for (File f : blobs) { + for (DecodedFilename keyEntry : blobs) { + File f = keyEntry.file; FileInputStream in = new FileInputStream(f); try { int size = (int) f.length(); byte[] buf = new byte[size]; in.read(buf); - String key = new String(Base64.decode(f.getName())); - if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size); - out.writeEntityHeader(key, size); + if (DEBUG) Log.v(TAG, " ... key=" + keyEntry.key + " size=" + size); + out.writeEntityHeader(keyEntry.key, size); out.writeEntityData(buf, size); } finally { in.close(); } } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } catch (IOException e) { Log.e(TAG, "Unable to read backup records", e); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; + } + } + + static class DecodedFilename implements Comparable<DecodedFilename> { + public File file; + public String key; + + public DecodedFilename(File f) { + file = f; + key = new String(Base64.decode(f.getName())); + } + + @Override + public int compareTo(DecodedFilename other) { + // sorts into ascending lexical order by decoded key + return key.compareTo(other.key); + } + } + + // Return a list of the files in the given directory, sorted lexically by + // the Base64-decoded file name, not by the on-disk filename + private ArrayList<DecodedFilename> contentsByKey(File dir) { + File[] allFiles = dir.listFiles(); + if (allFiles == null || allFiles.length == 0) { + return null; + } + + // Decode the filenames into keys then sort lexically by key + ArrayList<DecodedFilename> contents = new ArrayList<DecodedFilename>(); + for (File f : allFiles) { + contents.add(new DecodedFilename(f)); } + Collections.sort(contents); + return contents; } public void finishRestore() { diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/core/java/com/android/internal/backup/LocalTransportService.java index d05699a..77ac313 100644 --- a/core/java/com/android/internal/backup/LocalTransportService.java +++ b/core/java/com/android/internal/backup/LocalTransportService.java @@ -32,6 +32,6 @@ public class LocalTransportService extends Service { @Override public IBinder onBind(Intent intent) { - return sTransport; + return sTransport.getBinder(); } } diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java index 942995b..9df8ad5 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Handler; -import android.os.HandlerThread; import android.os.Looper; import android.os.UserHandle; import com.android.internal.os.BackgroundThread; @@ -243,7 +242,11 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { public boolean anyPackagesDisappearing() { return mDisappearingPackages != null; } - + + public boolean isReplacing() { + return mChangeType == PACKAGE_UPDATING; + } + public boolean isPackageModified(String packageName) { if (mModifiedPackages != null) { for (int i=mModifiedPackages.length-1; i>=0; i--) { diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java new file mode 100644 index 0000000..7dbde69 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2013 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.inputmethod; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.text.TextUtils; +import android.util.Log; +import android.util.Slog; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.TreeMap; + +/** + * InputMethodSubtypeSwitchingController controls the switching behavior of the subtypes. + * <p> + * This class is designed to be used from and only from {@link InputMethodManagerService} by using + * {@link InputMethodManagerService#mMethodMap} as a global lock. + * </p> + */ +public class InputMethodSubtypeSwitchingController { + private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName(); + private static final boolean DEBUG = false; + private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; + + public static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> { + public final CharSequence mImeName; + public final CharSequence mSubtypeName; + public final InputMethodInfo mImi; + public final int mSubtypeId; + private final boolean mIsSystemLocale; + private final boolean mIsSystemLanguage; + + public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName, + InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) { + mImeName = imeName; + mSubtypeName = subtypeName; + mImi = imi; + mSubtypeId = subtypeId; + if (TextUtils.isEmpty(subtypeLocale)) { + mIsSystemLocale = false; + mIsSystemLanguage = false; + } else { + mIsSystemLocale = subtypeLocale.equals(systemLocale); + mIsSystemLanguage = mIsSystemLocale + || subtypeLocale.startsWith(systemLocale.substring(0, 2)); + } + } + + @Override + public int compareTo(ImeSubtypeListItem other) { + if (TextUtils.isEmpty(mImeName)) { + return 1; + } + if (TextUtils.isEmpty(other.mImeName)) { + return -1; + } + if (!TextUtils.equals(mImeName, other.mImeName)) { + return mImeName.toString().compareTo(other.mImeName.toString()); + } + if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) { + return 0; + } + if (mIsSystemLocale) { + return -1; + } + if (other.mIsSystemLocale) { + return 1; + } + if (mIsSystemLanguage) { + return -1; + } + if (other.mIsSystemLanguage) { + return 1; + } + if (TextUtils.isEmpty(mSubtypeName)) { + return 1; + } + if (TextUtils.isEmpty(other.mSubtypeName)) { + return -1; + } + return mSubtypeName.toString().compareTo(other.mSubtypeName.toString()); + } + + @Override + public String toString() { + return "ImeSubtypeListItem{" + + "mImeName=" + mImeName + + " mSubtypeName=" + mSubtypeName + + " mSubtypeId=" + mSubtypeId + + " mIsSystemLocale=" + mIsSystemLocale + + " mIsSystemLanguage=" + mIsSystemLanguage + + "}"; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof ImeSubtypeListItem) { + final ImeSubtypeListItem that = (ImeSubtypeListItem)o; + if (!Objects.equals(this.mImi, that.mImi)) { + return false; + } + if (this.mSubtypeId != that.mSubtypeId) { + return false; + } + return true; + } + return false; + } + } + + private static class InputMethodAndSubtypeList { + private final Context mContext; + // Used to load label + private final PackageManager mPm; + private final String mSystemLocaleStr; + private final InputMethodSettings mSettings; + + public InputMethodAndSubtypeList(Context context, InputMethodSettings settings) { + mContext = context; + mSettings = settings; + mPm = context.getPackageManager(); + final Locale locale = context.getResources().getConfiguration().locale; + mSystemLocaleStr = locale != null ? locale.toString() : ""; + } + + private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis = + new TreeMap<InputMethodInfo, List<InputMethodSubtype>>( + new Comparator<InputMethodInfo>() { + @Override + public int compare(InputMethodInfo imi1, InputMethodInfo imi2) { + if (imi2 == null) + return 0; + if (imi1 == null) + return 1; + if (mPm == null) { + return imi1.getId().compareTo(imi2.getId()); + } + CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId(); + CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId(); + return imiId1.toString().compareTo(imiId2.toString()); + } + }); + + public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() { + return getSortedInputMethodAndSubtypeList(true, false, false); + } + + public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList( + boolean showSubtypes, boolean inputShown, boolean isScreenLocked) { + final ArrayList<ImeSubtypeListItem> imList = + new ArrayList<ImeSubtypeListItem>(); + final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = + mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked( + mContext); + if (immis == null || immis.size() == 0) { + return Collections.emptyList(); + } + mSortedImmis.clear(); + mSortedImmis.putAll(immis); + for (InputMethodInfo imi : mSortedImmis.keySet()) { + if (imi == null) { + continue; + } + List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi); + HashSet<String> enabledSubtypeSet = new HashSet<String>(); + for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) { + enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); + } + final CharSequence imeLabel = imi.loadLabel(mPm); + if (showSubtypes && enabledSubtypeSet.size() > 0) { + final int subtypeCount = imi.getSubtypeCount(); + if (DEBUG) { + Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId()); + } + for (int j = 0; j < subtypeCount; ++j) { + final InputMethodSubtype subtype = imi.getSubtypeAt(j); + final String subtypeHashCode = String.valueOf(subtype.hashCode()); + // We show all enabled IMEs and subtypes when an IME is shown. + if (enabledSubtypeSet.contains(subtypeHashCode) + && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) { + final CharSequence subtypeLabel = + subtype.overridesImplicitlyEnabledSubtype() ? null : subtype + .getDisplayName(mContext, imi.getPackageName(), + imi.getServiceInfo().applicationInfo); + imList.add(new ImeSubtypeListItem(imeLabel, + subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr)); + + // Removing this subtype from enabledSubtypeSet because we no + // longer need to add an entry of this subtype to imList to avoid + // duplicated entries. + enabledSubtypeSet.remove(subtypeHashCode); + } + } + } else { + imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null, + mSystemLocaleStr)); + } + } + Collections.sort(imList); + return imList; + } + } + + private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) { + return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, + subtype.hashCode()) : NOT_A_SUBTYPE_ID; + } + + private static class StaticRotationList { + private final List<ImeSubtypeListItem> mImeSubtypeList; + public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) { + mImeSubtypeList = imeSubtypeList; + } + + /** + * Returns the index of the specified input method and subtype in the given list. + * @param imi The {@link InputMethodInfo} to be searched. + * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method + * does not have a subtype. + * @return The index in the given list. -1 if not found. + */ + private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentSubtypeId = calculateSubtypeId(imi, subtype); + final int N = mImeSubtypeList.size(); + for (int i = 0; i < N; ++i) { + final ImeSubtypeListItem isli = mImeSubtypeList.get(i); + // Skip until the current IME/subtype is found. + if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) { + return i; + } + } + return -1; + } + + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, + InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return null; + } + if (mImeSubtypeList.size() <= 1) { + return null; + } + final int currentIndex = getIndex(imi, subtype); + if (currentIndex < 0) { + return null; + } + final int N = mImeSubtypeList.size(); + for (int offset = 1; offset < N; ++offset) { + // Start searching the next IME/subtype from the next of the current index. + final int candidateIndex = (currentIndex + offset) % N; + final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex); + // Skip if searching inside the current IME only, but the candidate is not + // the current IME. + if (onlyCurrentIme && !imi.equals(candidate.mImi)) { + continue; + } + return candidate; + } + return null; + } + } + + private static class DynamicRotationList { + private static final String TAG = DynamicRotationList.class.getSimpleName(); + private final List<ImeSubtypeListItem> mImeSubtypeList; + private final int[] mUsageHistoryOfSubtypeListItemIndex; + + private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) { + mImeSubtypeList = imeSubtypeListItems; + mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()]; + final int N = mImeSubtypeList.size(); + for (int i = 0; i < N; i++) { + mUsageHistoryOfSubtypeListItemIndex[i] = i; + } + } + + /** + * Returns the index of the specified object in + * {@link #mUsageHistoryOfSubtypeListItemIndex}. + * <p>We call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank" + * so as not to be confused with the index in {@link #mImeSubtypeList}. + * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually. + */ + private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentSubtypeId = calculateSubtypeId(imi, subtype); + final int N = mUsageHistoryOfSubtypeListItemIndex.length; + for (int usageRank = 0; usageRank < N; usageRank++) { + final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank]; + final ImeSubtypeListItem subtypeListItem = + mImeSubtypeList.get(subtypeListItemIndex); + if (subtypeListItem.mImi.equals(imi) && + subtypeListItem.mSubtypeId == currentSubtypeId) { + return usageRank; + } + } + // Not found in the known IME/Subtype list. + return -1; + } + + public void onUserAction(InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentUsageRank = getUsageRank(imi, subtype); + // Do nothing if currentUsageRank == -1 (not found), or currentUsageRank == 0 + if (currentUsageRank <= 0) { + return; + } + final int currentItemIndex = mUsageHistoryOfSubtypeListItemIndex[currentUsageRank]; + System.arraycopy(mUsageHistoryOfSubtypeListItemIndex, 0, + mUsageHistoryOfSubtypeListItemIndex, 1, currentUsageRank); + mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex; + } + + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, + InputMethodInfo imi, InputMethodSubtype subtype) { + int currentUsageRank = getUsageRank(imi, subtype); + if (currentUsageRank < 0) { + if (DEBUG) { + Slog.d(TAG, "IME/subtype is not found: " + imi.getId() + ", " + subtype); + } + return null; + } + final int N = mUsageHistoryOfSubtypeListItemIndex.length; + for (int i = 1; i < N; i++) { + final int subtypeListItemRank = (currentUsageRank + i) % N; + final int subtypeListItemIndex = + mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank]; + final ImeSubtypeListItem subtypeListItem = + mImeSubtypeList.get(subtypeListItemIndex); + if (onlyCurrentIme && !imi.equals(subtypeListItem.mImi)) { + continue; + } + return subtypeListItem; + } + return null; + } + } + + @VisibleForTesting + public static class ControllerImpl { + private final DynamicRotationList mSwitchingAwareRotationList; + private final StaticRotationList mSwitchingUnawareRotationList; + + public static ControllerImpl createFrom(final ControllerImpl currentInstance, + final List<ImeSubtypeListItem> sortedEnabledItems) { + DynamicRotationList switchingAwareRotationList = null; + { + final List<ImeSubtypeListItem> switchingAwareImeSubtypes = + filterImeSubtypeList(sortedEnabledItems, + true /* supportsSwitchingToNextInputMethod */); + if (currentInstance != null && + currentInstance.mSwitchingAwareRotationList != null && + Objects.equals(currentInstance.mSwitchingAwareRotationList.mImeSubtypeList, + switchingAwareImeSubtypes)) { + // Can reuse the current instance. + switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList; + } + if (switchingAwareRotationList == null) { + switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes); + } + } + + StaticRotationList switchingUnawareRotationList = null; + { + final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList( + sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */); + if (currentInstance != null && + currentInstance.mSwitchingUnawareRotationList != null && + Objects.equals( + currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList, + switchingUnawareImeSubtypes)) { + // Can reuse the current instance. + switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList; + } + if (switchingUnawareRotationList == null) { + switchingUnawareRotationList = + new StaticRotationList(switchingUnawareImeSubtypes); + } + } + + return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList); + } + + private ControllerImpl(final DynamicRotationList switchingAwareRotationList, + final StaticRotationList switchingUnawareRotationList) { + mSwitchingAwareRotationList = switchingAwareRotationList; + mSwitchingUnawareRotationList = switchingUnawareRotationList; + } + + public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, + InputMethodSubtype subtype) { + if (imi == null) { + return null; + } + if (imi.supportsSwitchingToNextInputMethod()) { + return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } else { + return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } + } + + public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return; + } + if (imi.supportsSwitchingToNextInputMethod()) { + mSwitchingAwareRotationList.onUserAction(imi, subtype); + } + } + + private static List<ImeSubtypeListItem> filterImeSubtypeList( + final List<ImeSubtypeListItem> items, + final boolean supportsSwitchingToNextInputMethod) { + final ArrayList<ImeSubtypeListItem> result = new ArrayList<>(); + final int ALL_ITEMS_COUNT = items.size(); + for (int i = 0; i < ALL_ITEMS_COUNT; i++) { + final ImeSubtypeListItem item = items.get(i); + if (item.mImi.supportsSwitchingToNextInputMethod() == + supportsSwitchingToNextInputMethod) { + result.add(item); + } + } + return result; + } + } + + private final InputMethodSettings mSettings; + private InputMethodAndSubtypeList mSubtypeList; + private ControllerImpl mController; + + private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) { + mSettings = settings; + resetCircularListLocked(context); + } + + public static InputMethodSubtypeSwitchingController createInstanceLocked( + InputMethodSettings settings, Context context) { + return new InputMethodSubtypeSwitchingController(settings, context); + } + + public void onCommitTextLocked(InputMethodInfo imi, InputMethodSubtype subtype) { + if (mController == null) { + if (DEBUG) { + Log.e(TAG, "mController shouldn't be null."); + } + return; + } + mController.onUserActionLocked(imi, subtype); + } + + public void resetCircularListLocked(Context context) { + mSubtypeList = new InputMethodAndSubtypeList(context, mSettings); + mController = ControllerImpl.createFrom(mController, + mSubtypeList.getSortedInputMethodAndSubtypeList()); + } + + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, + InputMethodSubtype subtype) { + if (mController == null) { + if (DEBUG) { + Log.e(TAG, "mController shouldn't be null."); + } + return null; + } + return mController.getNextInputMethod(onlyCurrentIme, imi, subtype); + } + + public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes, + boolean inputShown, boolean isScreenLocked) { + return mSubtypeList.getSortedInputMethodAndSubtypeList( + showSubtypes, inputShown, isScreenLocked); + } +} diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java index 63d018f..ac3274d 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -504,6 +504,7 @@ public class InputMethodUtils { private String mEnabledInputMethodsStrCache; private int mCurrentUserId; + private int[] mCurrentProfileIds = new int[0]; private static void buildEnabledInputMethodsSettingString( StringBuilder builder, Pair<String, ArrayList<String>> pair) { @@ -536,6 +537,22 @@ public class InputMethodUtils { mCurrentUserId = userId; } + public void setCurrentProfileIds(int[] currentProfileIds) { + synchronized (this) { + mCurrentProfileIds = currentProfileIds; + } + } + + public boolean isCurrentProfile(int userId) { + synchronized (this) { + if (userId == mCurrentUserId) return true; + for (int i = 0; i < mCurrentProfileIds.length; i++) { + if (userId == mCurrentProfileIds[i]) return true; + } + return false; + } + } + public List<InputMethodInfo> getEnabledInputMethodListLocked() { return createEnabledInputMethodListLocked( getEnabledInputMethodsAndSubtypeListLocked()); @@ -959,5 +976,16 @@ public class InputMethodUtils { addSubtypeToHistory(curMethodId, subtypeId); } } + + public HashMap<InputMethodInfo, List<InputMethodSubtype>> + getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(Context context) { + HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes = + new HashMap<InputMethodInfo, List<InputMethodSubtype>>(); + for (InputMethodInfo imi: getEnabledInputMethodListLocked()) { + enabledInputMethodAndSubtypes.put( + imi, getEnabledInputMethodSubtypeListLocked(context, imi, true)); + } + return enabledInputMethodAndSubtypes; + } } } diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index 8282d23..e2a2b1e 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -17,6 +17,7 @@ package com.android.internal.net; import static android.net.NetworkStats.SET_ALL; +import static android.net.NetworkStats.TAG_ALL; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static com.android.server.NetworkManagementSocketTagger.kernelToTag; @@ -26,6 +27,7 @@ import android.os.StrictMode; import android.os.SystemClock; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.ProcFileReader; import java.io.File; @@ -165,22 +167,32 @@ public class NetworkStatsFactory { } public NetworkStats readNetworkStatsDetail() throws IOException { - return readNetworkStatsDetail(UID_ALL); + return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null); } - public NetworkStats readNetworkStatsDetail(int limitUid) throws IOException { + public NetworkStats readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag, + NetworkStats lastStats) + throws IOException { if (USE_NATIVE_PARSING) { - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); - if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid) != 0) { + final NetworkStats stats; + if (lastStats != null) { + stats = lastStats; + stats.setElapsedRealtime(SystemClock.elapsedRealtime()); + } else { + stats = new NetworkStats(SystemClock.elapsedRealtime(), -1); + } + if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid, + limitIfaces, limitTag) != 0) { throw new IOException("Failed to parse network stats"); } if (SANITY_CHECK_NATIVE) { - final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid); + final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid, + limitIfaces, limitTag); assertEquals(javaStats, stats); } return stats; } else { - return javaReadNetworkStatsDetail(mStatsXtUid, limitUid); + return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag); } } @@ -189,7 +201,8 @@ public class NetworkStatsFactory { * expected to monotonically increase since device boot. */ @VisibleForTesting - public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid) + public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid, + String[] limitIfaces, int limitTag) throws IOException { final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); @@ -222,7 +235,9 @@ public class NetworkStatsFactory { entry.txBytes = reader.nextLong(); entry.txPackets = reader.nextLong(); - if (limitUid == UID_ALL || limitUid == entry.uid) { + if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface)) + && (limitUid == UID_ALL || limitUid == entry.uid) + && (limitTag == TAG_ALL || limitTag == entry.tag)) { stats.addValues(entry); } @@ -264,5 +279,5 @@ public class NetworkStatsFactory { */ @VisibleForTesting public static native int nativeReadNetworkStatsDetail( - NetworkStats stats, String path, int limitUid); + NetworkStats stats, String path, int limitUid, String[] limitIfaces, int limitTag); } diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 98599d0..0d00f41 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -25,8 +25,6 @@ import android.os.UserHandle; import android.net.RouteInfo; import android.net.LinkAddress; -import com.android.internal.util.Preconditions; - import java.net.InetAddress; import java.util.List; import java.util.ArrayList; diff --git a/core/java/com/android/internal/notification/DemoContactNotificationScorer.java b/core/java/com/android/internal/notification/DemoContactNotificationScorer.java deleted file mode 100644 index f484724..0000000 --- a/core/java/com/android/internal/notification/DemoContactNotificationScorer.java +++ /dev/null @@ -1,188 +0,0 @@ -/* -* Copyright (C) 2013 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.notification; - -import android.app.Notification; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.provider.ContactsContract; -import android.provider.Settings; -import android.text.SpannableString; -import android.util.Slog; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * This NotificationScorer bumps up the priority of notifications that contain references to the - * display names of starred contacts. The references it picks up are spannable strings which, in - * their entirety, match the display name of some starred contact. The magnitude of the bump ranges - * from 0 to 15 (assuming NOTIFICATION_PRIORITY_MULTIPLIER = 10) depending on the initial score, and - * the mapping is defined by priorityBumpMap. In a production version of this scorer, a notification - * extra will be used to specify contact identifiers. - */ - -public class DemoContactNotificationScorer implements NotificationScorer { - private static final String TAG = "DemoContactNotificationScorer"; - private static final boolean DBG = false; - - protected static final boolean ENABLE_CONTACT_SCORER = true; - private static final String SETTING_ENABLE_SCORER = "contact_scorer_enabled"; - protected boolean mEnabled; - - // see NotificationManagerService - private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; - - private Context mContext; - - private static final List<String> RELEVANT_KEYS_LIST = Arrays.asList( - Notification.EXTRA_INFO_TEXT, Notification.EXTRA_TEXT, Notification.EXTRA_TEXT_LINES, - Notification.EXTRA_SUB_TEXT, Notification.EXTRA_TITLE - ); - - private static final String[] PROJECTION = new String[] { - ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME - }; - - private static final Uri CONTACTS_URI = ContactsContract.Contacts.CONTENT_URI; - - private static List<String> extractSpannedStrings(CharSequence charSequence) { - if (charSequence == null) return Collections.emptyList(); - if (!(charSequence instanceof SpannableString)) { - return Arrays.asList(charSequence.toString()); - } - SpannableString spannableString = (SpannableString)charSequence; - // get all spans - Object[] ssArr = spannableString.getSpans(0, spannableString.length(), Object.class); - // spanned string sequences - ArrayList<String> sss = new ArrayList<String>(); - for (Object spanObj : ssArr) { - try { - sss.add(spannableString.subSequence(spannableString.getSpanStart(spanObj), - spannableString.getSpanEnd(spanObj)).toString()); - } catch(StringIndexOutOfBoundsException e) { - Slog.e(TAG, "Bad indices when extracting spanned subsequence", e); - } - } - return sss; - }; - - private static String getQuestionMarksInParens(int n) { - StringBuilder sb = new StringBuilder("("); - for (int i = 0; i < n; i++) { - if (sb.length() > 1) sb.append(','); - sb.append('?'); - } - sb.append(")"); - return sb.toString(); - } - - private boolean hasStarredContact(Bundle extras) { - if (extras == null) return false; - ArrayList<String> qStrings = new ArrayList<String>(); - // build list to query against the database for display names. - for (String rk: RELEVANT_KEYS_LIST) { - if (extras.get(rk) == null) { - continue; - } else if (extras.get(rk) instanceof CharSequence) { - qStrings.addAll(extractSpannedStrings((CharSequence) extras.get(rk))); - } else if (extras.get(rk) instanceof CharSequence[]) { - // this is intended for Notification.EXTRA_TEXT_LINES - for (CharSequence line: (CharSequence[]) extras.get(rk)){ - qStrings.addAll(extractSpannedStrings(line)); - } - } else { - Slog.w(TAG, "Strange, the extra " + rk + " is of unexpected type."); - } - } - if (qStrings.isEmpty()) return false; - String[] qStringsArr = qStrings.toArray(new String[qStrings.size()]); - - String selection = ContactsContract.Contacts.DISPLAY_NAME + " IN " - + getQuestionMarksInParens(qStringsArr.length) + " AND " - + ContactsContract.Contacts.STARRED+" ='1'"; - - Cursor c = null; - try { - c = mContext.getContentResolver().query( - CONTACTS_URI, PROJECTION, selection, qStringsArr, null); - if (c != null) return c.getCount() > 0; - } catch(Throwable t) { - Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t); - } finally { - if (c != null) { - c.close(); - } - } - return false; - } - - private final static int clamp(int x, int low, int high) { - return (x < low) ? low : ((x > high) ? high : x); - } - - private static int priorityBumpMap(int incomingScore) { - //assumption is that scale runs from [-2*pm, 2*pm] - int pm = NOTIFICATION_PRIORITY_MULTIPLIER; - int theScore = incomingScore; - // enforce input in range - theScore = clamp(theScore, -2 * pm, 2 * pm); - if (theScore != incomingScore) return incomingScore; - // map -20 -> -20 and -10 -> 5 (when pm = 10) - if (theScore <= -pm) { - theScore += 1.5 * (theScore + 2 * pm); - } else { - // map 0 -> 10, 10 -> 15, 20 -> 20; - theScore += 0.5 * (2 * pm - theScore); - } - if (DBG) Slog.v(TAG, "priorityBumpMap: score before: " + incomingScore - + ", score after " + theScore + "."); - return theScore; - } - - @Override - public void initialize(Context context) { - if (DBG) Slog.v(TAG, "Initializing " + getClass().getSimpleName() + "."); - mContext = context; - mEnabled = ENABLE_CONTACT_SCORER && 1 == Settings.Global.getInt( - mContext.getContentResolver(), SETTING_ENABLE_SCORER, 0); - } - - @Override - public int getScore(Notification notification, int score) { - if (notification == null || !mEnabled) { - if (DBG) Slog.w(TAG, "empty notification? scorer disabled?"); - return score; - } - boolean hasStarredPriority = hasStarredContact(notification.extras); - - if (DBG) { - if (hasStarredPriority) { - Slog.v(TAG, "Notification references starred contact. Promoted!"); - } else { - Slog.v(TAG, "Notification lacks any starred contact reference. Not promoted!"); - } - } - if (hasStarredPriority) score = priorityBumpMap(score); - return score; - } -} - diff --git a/core/java/com/android/internal/notification/NotificationScorer.java b/core/java/com/android/internal/notification/NotificationScorer.java deleted file mode 100644 index 863c08c..0000000 --- a/core/java/com/android/internal/notification/NotificationScorer.java +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright (C) 2013 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.notification; - -import android.app.Notification; -import android.content.Context; - -public interface NotificationScorer { - - public void initialize(Context context); - public int getScore(Notification notification, int score); - -} diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java new file mode 100644 index 0000000..6ca24d7 --- /dev/null +++ b/core/java/com/android/internal/os/BatterySipper.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2009 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.Uid; + +/** + * Contains power usage of an application, system service, or hardware type. + */ +public class BatterySipper implements Comparable<BatterySipper> { + public int userId; + public Uid uidObj; + public double value; + public double[] values; + public DrainType drainType; + public long usageTime; + public long cpuTime; + public long gpsTime; + public long wifiRunningTime; + public long cpuFgTime; + public long wakeLockTime; + public long mobileRxPackets; + public long mobileTxPackets; + public long mobileActive; + public int mobileActiveCount; + public double mobilemspp; // milliseconds per packet + public long wifiRxPackets; + public long wifiTxPackets; + public long mobileRxBytes; + public long mobileTxBytes; + public long wifiRxBytes; + public long wifiTxBytes; + public double percent; + public double noCoveragePercent; + public String[] mPackages; + public String packageWithHighestDrain; + + public enum DrainType { + IDLE, + CELL, + PHONE, + WIFI, + BLUETOOTH, + SCREEN, + APP, + USER, + UNACCOUNTED, + OVERCOUNTED + } + + public BatterySipper(DrainType drainType, Uid uid, double[] values) { + this.values = values; + if (values != null) value = values[0]; + this.drainType = drainType; + uidObj = uid; + } + + public double[] getValues() { + return values; + } + + public void computeMobilemspp() { + long packets = mobileRxPackets+mobileTxPackets; + mobilemspp = packets > 0 ? (mobileActive / (double)packets) : 0; + } + + @Override + public int compareTo(BatterySipper other) { + // Return the flipped value because we want the items in descending order + return Double.compare(other.value, value); + } + + /** + * Gets a list of packages associated with the current user + */ + public String[] getPackages() { + return mPackages; + } + + public int getUid() { + // Bail out if the current sipper is not an App sipper. + if (uidObj == null) { + return 0; + } + return uidObj.getUid(); + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java new file mode 100644 index 0000000..7ff949e --- /dev/null +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -0,0 +1,838 @@ +/* + * Copyright (C) 2009 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 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.os.BatteryStats; +import android.os.BatteryStats.Uid; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.telephony.SignalStrength; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.os.BatterySipper.DrainType; + +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. + * + * The caller must initialize this class as soon as activity object is ready to use (for example, in + * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy(). + */ +public class BatteryStatsHelper { + + private static final boolean DEBUG = false; + + private static final String TAG = BatteryStatsHelper.class.getSimpleName(); + + private static BatteryStats sStatsXfer; + private static Intent sBatteryBroadcastXfer; + + final private Context mContext; + final private boolean mCollectBatteryBroadcast; + + private IBatteryStats mBatteryInfo; + private BatteryStats mStats; + 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>(); + + private final List<BatterySipper> mMobilemsppList = new ArrayList<BatterySipper>(); + + private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; + private int mAsUser = 0; + + long mRawRealtime; + long mRawUptime; + long mBatteryRealtime; + long mBatteryUptime; + long mTypeBatteryRealtime; + long mTypeBatteryUptime; + long mBatteryTimeRemaining; + long mChargeTimeRemaining; + + private long mStatsPeriod = 0; + private double mMaxPower = 1; + 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; + + // How much the apps together have left WIFI running. + private long mAppWifiRunning; + + public BatteryStatsHelper(Context context) { + this(context, true); + } + + public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) { + mContext = context; + mCollectBatteryBroadcast = collectBatteryBroadcast; + } + + /** Clears the current stats and forces recreating for future use. */ + public void clearStats() { + mStats = null; + } + + public BatteryStats getStats() { + if (mStats == null) { + load(); + } + return mStats; + } + + public Intent getBatteryBroadcast() { + if (mBatteryBroadcast == null && mCollectBatteryBroadcast) { + load(); + } + return mBatteryBroadcast; + } + + public PowerProfile getPowerProfile() { + return mPowerProfile; + } + + public void create(BatteryStats stats) { + mPowerProfile = new PowerProfile(mContext); + mStats = stats; + } + + public void create(Bundle icicle) { + if (icicle != null) { + mStats = sStatsXfer; + mBatteryBroadcast = sBatteryBroadcastXfer; + } + mBatteryInfo = IBatteryStats.Stub.asInterface( + ServiceManager.getService(BatteryStats.SERVICE_NAME)); + mPowerProfile = new PowerProfile(mContext); + } + + public void storeState() { + sStatsXfer = mStats; + sBatteryBroadcastXfer = mBatteryBroadcast; + } + + public static String makemAh(double power) { + if (power < .00001) return String.format("%.8f", power); + else if (power < .0001) return String.format("%.7f", power); + else if (power < .001) return String.format("%.6f", power); + else if (power < .01) return String.format("%.5f", power); + else if (power < .1) return String.format("%.4f", power); + else if (power < 1) return String.format("%.3f", power); + else if (power < 10) return String.format("%.2f", power); + else if (power < 100) return String.format("%.1f", power); + else return String.format("%.0f", power); + } + + /** + * Refreshes the power usage list. + */ + public void refreshStats(int statsType, int asUser) { + refreshStats(statsType, asUser, SystemClock.elapsedRealtime() * 1000, + SystemClock.uptimeMillis() * 1000); + } + + public void refreshStats(int statsType, int asUser, long rawRealtimeUs, long rawUptimeUs) { + // Initialize mStats if necessary. + getStats(); + + mMaxPower = 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; + } + + mStatsType = statsType; + mAsUser = asUser; + mRawUptime = rawUptimeUs; + mRawRealtime = rawRealtimeUs; + mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs); + mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeUs); + mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeUs, mStatsType); + mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType); + mBatteryTimeRemaining = mStats.computeBatteryTimeRemaining(rawRealtimeUs); + mChargeTimeRemaining = mStats.computeChargeTimeRemaining(rawRealtimeUs); + + if (DEBUG) { + Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs/1000) + " uptime=" + + (rawUptimeUs/1000)); + Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime=" + + (mBatteryUptime/1000)); + Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime=" + + (mTypeBatteryUptime/1000)); + } + mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge() + * mPowerProfile.getBatteryCapacity()) / 100; + mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge() + * mPowerProfile.getBatteryCapacity()) / 100; + + processAppUsage(); + + // Before aggregating apps in to users, collect all apps to sort by their ms per packet. + for (int i=0; i<mUsageList.size(); i++) { + BatterySipper bs = mUsageList.get(i); + bs.computeMobilemspp(); + if (bs.mobilemspp != 0) { + mMobilemsppList.add(bs); + } + } + for (int i=0; i<mUserSippers.size(); i++) { + List<BatterySipper> user = mUserSippers.valueAt(i); + for (int j=0; j<user.size(); j++) { + BatterySipper bs = user.get(j); + bs.computeMobilemspp(); + if (bs.mobilemspp != 0) { + mMobilemsppList.add(bs); + } + } + } + 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; + } + }); + + processMiscUsage(); + + 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); + } else if (mMaxDrainedPower < mComputedPower) { + double amount = mComputedPower - mMaxDrainedPower; + addEntryNoTotal(BatterySipper.DrainType.OVERCOUNTED, 0, amount); + } + } + + Collections.sort(mUsageList); + } + + private void processAppUsage() { + 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 appWakelockTimeUs = 0; + BatterySipper osApp = null; + mStatsPeriod = mTypeBatteryRealtime; + SparseArray<? extends Uid> uidStats = mStats.getUidStats(); + final int NU = uidStats.size(); + for (int iu = 0; iu < NU; iu++) { + Uid u = uidStats.valueAt(iu); + double p; // in mAs + double power = 0; // in mAs + double highestDrain = 0; + String packageWithHighestDrain = null; + Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats(); + long cpuTime = 0; + long cpuFgTime = 0; + long wakelockTime = 0; + long gpsTime = 0; + if (processStats.size() > 0) { + // Process CPU time + for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent + : processStats.entrySet()) { + Uid.Proc ps = ent.getValue(); + final long userTime = ps.getUserTime(which); + final long systemTime = ps.getSystemTime(which); + final long foregroundTime = ps.getForegroundTime(which); + cpuFgTime += foregroundTime * 10; // convert to millis + final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis + int totalTimeAtSpeeds = 0; + // Get the total first + for (int step = 0; step < speedSteps; step++) { + cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which); + totalTimeAtSpeeds += cpuSpeedStepTimes[step]; + } + if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1; + // Then compute the ratio of time spent at each speed + double processPower = 0; + for (int step = 0; step < speedSteps; step++) { + double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds; + if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #" + + step + " ratio=" + makemAh(ratio) + " power=" + + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000))); + processPower += ratio * tmpCpuTime * powerCpuNormal[step]; + } + cpuTime += tmpCpuTime; + if (DEBUG && processPower != 0) { + Log.d(TAG, String.format("process %s, cpu power=%s", + ent.getKey(), makemAh(processPower / (60*60*1000)))); + } + power += processPower; + if (packageWithHighestDrain == null + || packageWithHighestDrain.startsWith("*")) { + highestDrain = processPower; + packageWithHighestDrain = ent.getKey(); + } else if (highestDrain < processPower + && !ent.getKey().startsWith("*")) { + highestDrain = processPower; + packageWithHighestDrain = ent.getKey(); + } + } + } + if (cpuFgTime > cpuTime) { + if (DEBUG && cpuFgTime > cpuTime + 10000) { + Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time"); + } + cpuTime = cpuFgTime; // Statistics may not have been gathered yet. + } + power /= (60*60*1000); + + // Process wake lock usage + Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats(); + for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry + : wakelockStats.entrySet()) { + 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) { + wakelockTime += timer.getTotalTimeLocked(mRawRealtime, which); + } + } + appWakelockTimeUs += wakelockTime; + wakelockTime /= 1000; // convert to millis + + // Add cost of holding a wake lock + p = (wakelockTime + * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake " + + wakelockTime + " power=" + makemAh(p)); + power += p; + + // Add cost of mobile traffic + final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType); + final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType); + final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType); + final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType); + final long mobileActive = u.getMobileRadioActiveTime(mStatsType); + if (mobileActive > 0) { + // We are tracking when the radio is up, so can use the active time to + // determine power use. + mAppMobileActive += mobileActive; + p = (mobilePowerPerMs * mobileActive) / 1000; + } else { + // We are not tracking when the radio is up, so must approximate power use + // based on the number of packets. + p = (mobileRx + mobileTx) * mobilePowerPerPacket; + } + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets " + + (mobileRx+mobileTx) + " active time " + mobileActive + + " power=" + makemAh(p)); + power += p; + + // Add cost of wifi traffic + final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType); + final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType); + final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType); + final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType); + p = (wifiRx + wifiTx) * wifiPowerPerPacket; + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets " + + (mobileRx+mobileTx) + " power=" + makemAh(p)); + power += p; + + // Add cost of keeping WIFI running. + long wifiRunningTimeMs = u.getWifiRunningTime(mRawRealtime, which) / 1000; + mAppWifiRunning += wifiRunningTimeMs; + p = (wifiRunningTimeMs + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running " + + wifiRunningTimeMs + " power=" + makemAh(p)); + power += p; + + // Add cost of WIFI scans + long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000; + p = (wifiScanTimeMs + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000); + if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs + + " power=" + makemAh(p)); + power += p; + for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { + long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000; + p = ((batchScanTimeMs + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin)) + ) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin + + " time=" + batchScanTimeMs + " power=" + makemAh(p)); + power += p; + } + + // Process Sensor usage + Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats(); + for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry + : sensorStats.entrySet()) { + Uid.Sensor sensor = sensorEntry.getValue(); + int sensorHandle = sensor.getHandle(); + BatteryStats.Timer timer = sensor.getSensorTime(); + long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000; + double multiplier = 0; + switch (sensorHandle) { + case Uid.Sensor.GPS: + multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON); + gpsTime = sensorTime; + break; + default: + List<Sensor> sensorList = sensorManager.getSensorList( + android.hardware.Sensor.TYPE_ALL); + for (android.hardware.Sensor s : sensorList) { + if (s.getHandle() == sensorHandle) { + multiplier = s.getPower(); + break; + } + } + } + p = (multiplier * sensorTime) / (60*60*1000); + if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle + + " time=" + sensorTime + " power=" + makemAh(p)); + power += p; + } + + if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s", + u.getUid(), makemAh(power))); + + // Add the app to the list if it is consuming power + final int userId = UserHandle.getUserId(u.getUid()); + if (power != 0 || u.getUid() == 0) { + BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, + new double[] {power}); + app.cpuTime = cpuTime; + app.gpsTime = gpsTime; + app.wifiRunningTime = wifiRunningTimeMs; + app.cpuFgTime = cpuFgTime; + app.wakeLockTime = wakelockTime; + app.mobileRxPackets = mobileRx; + app.mobileTxPackets = mobileTx; + app.mobileActive = mobileActive / 1000; + app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType); + app.wifiRxPackets = wifiRx; + app.wifiTxPackets = wifiTx; + app.mobileRxBytes = mobileRxB; + app.mobileTxBytes = mobileTxB; + app.wifiRxBytes = wifiRxB; + app.wifiTxBytes = wifiTxB; + app.packageWithHighestDrain = packageWithHighestDrain; + if (u.getUid() == Process.WIFI_UID) { + mWifiSippers.add(app); + mWifiPower += power; + } else if (u.getUid() == Process.BLUETOOTH_UID) { + mBluetoothSippers.add(app); + mBluetoothPower += power; + } else if (mAsUser != UserHandle.USER_ALL && userId != mAsUser + && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) { + List<BatterySipper> list = mUserSippers.get(userId); + if (list == null) { + list = new ArrayList<BatterySipper>(); + mUserSippers.put(userId, list); + } + list.add(app); + if (power != 0) { + Double userPower = mUserPower.get(userId); + if (userPower == null) { + userPower = power; + } else { + userPower += power; + } + mUserPower.put(userId, userPower); + } + } else { + mUsageList.add(app); + if (power > mMaxPower) mMaxPower = power; + mComputedPower += power; + } + if (u.getUid() == 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 -= (appWakelockTimeUs / 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; + mComputedPower += power; + } + } + } + + private void addPhoneUsage() { + long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtime, mStatsType) / 1000; + double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) + * phoneOnTimeMs / (60*60*1000); + if (phoneOnPower != 0) { + BatterySipper bs = addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower); + } + } + + private void addScreenUsage() { + double power = 0; + long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtime, mStatsType) / 1000; + power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON); + final double screenFullPower = + mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); + for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) { + double screenBinPower = screenFullPower * (i + 0.5f) + / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; + long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtime, mStatsType) + / 1000; + double p = screenBinPower*brightnessTime; + if (DEBUG && p != 0) { + Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime + + " power=" + makemAh(p / (60 * 60 * 1000))); + } + power += p; + } + power /= (60*60*1000); // To hours + if (power != 0) { + addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power); + } + } + + 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); + } + } + + 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); + bs.cpuTime += wbs.cpuTime; + bs.gpsTime += wbs.gpsTime; + bs.wifiRunningTime += wbs.wifiRunningTime; + bs.cpuFgTime += wbs.cpuFgTime; + bs.wakeLockTime += wbs.wakeLockTime; + bs.mobileRxPackets += wbs.mobileRxPackets; + bs.mobileTxPackets += wbs.mobileTxPackets; + bs.mobileActive += wbs.mobileActive; + bs.mobileActiveCount += wbs.mobileActiveCount; + bs.wifiRxPackets += wbs.wifiRxPackets; + bs.wifiTxPackets += wbs.wifiTxPackets; + bs.mobileRxBytes += wbs.mobileRxBytes; + bs.mobileTxBytes += wbs.mobileTxBytes; + bs.wifiRxBytes += wbs.wifiRxBytes; + bs.wifiTxBytes += wbs.wifiTxBytes; + } + bs.computeMobilemspp(); + } + + private void addWiFiUsage() { + long onTimeMs = mStats.getWifiOnTime(mRawRealtime, mStatsType) / 1000; + long runningTimeMs = mStats.getGlobalWifiRunningTime(mRawRealtime, mStatsType) / 1000; + if (DEBUG) Log.d(TAG, "WIFI runningTime=" + runningTimeMs + + " app runningTime=" + mAppWifiRunning); + runningTimeMs -= mAppWifiRunning; + if (runningTimeMs < 0) runningTimeMs = 0; + double wifiPower = (onTimeMs * 0 /* TODO */ + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON) + + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) + / (60*60*1000); + if (DEBUG && wifiPower != 0) { + Log.d(TAG, "Wifi: time=" + runningTimeMs + " power=" + makemAh(wifiPower)); + } + if ((wifiPower+mWifiPower) != 0) { + BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, runningTimeMs, + wifiPower + mWifiPower); + aggregateSippers(bs, mWifiSippers, "WIFI"); + } + } + + private void addIdleUsage() { + long idleTimeMs = (mTypeBatteryRealtime + - mStats.getScreenOnTime(mRawRealtime, mStatsType)) / 1000; + double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE)) + / (60*60*1000); + if (DEBUG && idlePower != 0) { + Log.d(TAG, "Idle: time=" + idleTimeMs + " power=" + makemAh(idlePower)); + } + if (idlePower != 0) { + addEntry(BatterySipper.DrainType.IDLE, idleTimeMs, idlePower); + } + } + + private void addBluetoothUsage() { + long btOnTimeMs = mStats.getBluetoothOnTime(mRawRealtime, mStatsType) / 1000; + double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON) + / (60*60*1000); + if (DEBUG && btPower != 0) { + Log.d(TAG, "Bluetooth: time=" + btOnTimeMs + " power=" + makemAh(btPower)); + } + int btPingCount = mStats.getBluetoothPingCount(); + double pingPower = (btPingCount + * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD)) + / (60*60*1000); + if (DEBUG && pingPower != 0) { + Log.d(TAG, "Bluetooth ping: count=" + btPingCount + " power=" + makemAh(pingPower)); + } + btPower += pingPower; + if ((btPower+mBluetoothPower) != 0) { + BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, btOnTimeMs, + btPower + mBluetoothPower); + aggregateSippers(bs, mBluetoothSippers, "Bluetooth"); + } + } + + private void addUserUsage() { + 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); + bs.userId = userId; + aggregateSippers(bs, sippers, "User"); + } + } + + /** + * 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(); + addScreenUsage(); + addWiFiUsage(); + addBluetoothUsage(); + addIdleUsage(); // Not including cellular idle power + // Don't compute radio usage if it's a wifi-only device + ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) { + addRadioUsage(); + } + } + + private BatterySipper addEntry(DrainType drainType, long time, double power) { + mComputedPower += 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; + mUsageList.add(bs); + return bs; + } + + public List<BatterySipper> getUsageList() { + return mUsageList; + } + + public List<BatterySipper> getMobilemsppList() { + return mMobilemsppList; + } + + public long getStatsPeriod() { return mStatsPeriod; } + + public int getStatsType() { return mStatsType; }; + + public double getMaxPower() { return mMaxPower; } + + public double getTotalPower() { return mTotalPower; } + + public double getComputedPower() { return mComputedPower; } + + public double getMinDrainedPower() { + return mMinDrainedPower; + } + + public double getMaxDrainedPower() { + return mMaxDrainedPower; + } + + public long getBatteryTimeRemaining() { return mBatteryTimeRemaining; } + + public long getChargeTimeRemaining() { return mChargeTimeRemaining; } + + private void load() { + if (mBatteryInfo == null) { + return; + } + try { + byte[] data = mBatteryInfo.getStatistics(); + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(data, 0, data.length); + parcel.setDataPosition(0); + BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR + .createFromParcel(parcel); + stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED); + mStats = stats; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException:", e); + } + if (mCollectBatteryBroadcast) { + mBatteryBroadcast = mContext.registerReceiver(null, + new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + } + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 9c82fac..ed9f9bc 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -16,12 +16,15 @@ package com.android.internal.os; +import static android.net.NetworkStats.UID_ALL; import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; +import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkStats; +import android.os.BadParcelableException; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.FileUtils; @@ -35,6 +38,7 @@ import android.os.Process; import android.os.SystemClock; import android.os.SystemProperties; import android.os.WorkSource; +import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; @@ -44,25 +48,23 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseIntArray; import android.util.TimeUtils; 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.JournaledFile; -import com.google.android.collect.Sets; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -86,7 +88,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 68 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 106 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; @@ -142,6 +144,11 @@ public final class BatteryStatsImpl extends BatteryStats { private BatteryCallback mCallback; /** + * Mapping isolated uids to the actual owning app uid. + */ + final SparseIntArray mIsolatedUids = new SparseIntArray(); + + /** * The statistics we have collected organized by uids. */ final SparseArray<BatteryStatsImpl.Uid> mUidStats = @@ -168,13 +175,23 @@ public final class BatteryStatsImpl extends BatteryStats { // These are the objects that will want to do something when the device // is unplugged from power. - final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>(); + final TimeBase mOnBatteryTimeBase = new TimeBase(); + + // These are the objects that will want to do something when the device + // is unplugged from power *and* the screen is off. + final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase(); + + // Set to true when we want to distribute CPU across wakelocks for the next + // CPU update, even if we aren't currently running wake locks. + boolean mDistributeWakelockCpu; boolean mShuttingDown; + final HistoryEventTracker mActiveEvents = new HistoryEventTracker(); + long mHistoryBaseTime; boolean mHaveBatteryLevel = false; - boolean mRecordingHistory = true; + boolean mRecordingHistory = false; int mNumHistoryItems; static final int MAX_HISTORY_BUFFER = 128*1024; // 128KB @@ -183,9 +200,18 @@ public final class BatteryStatsImpl extends BatteryStats { final HistoryItem mHistoryLastWritten = new HistoryItem(); final HistoryItem mHistoryLastLastWritten = new HistoryItem(); final HistoryItem mHistoryReadTmp = new HistoryItem(); + final HistoryItem mHistoryAddTmp = new HistoryItem(); + final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<HistoryTag, Integer>(); + String[] mReadHistoryStrings; + int[] mReadHistoryUids; + int mReadHistoryChars; + int mNextHistoryTagIdx = 0; + int mNumHistoryTagChars = 0; int mHistoryBufferLastPos = -1; boolean mHistoryOverflow = false; - long mLastHistoryTime = 0; + long mLastHistoryElapsedRealtime = 0; + long mTrackRunningHistoryElapsedRealtime = 0; + long mTrackRunningHistoryUptime = 0; final HistoryItem mHistoryCur = new HistoryItem(); @@ -200,17 +226,17 @@ public final class BatteryStatsImpl extends BatteryStats { int mStartCount; - long mBatteryUptime; - long mBatteryLastUptime; - long mBatteryRealtime; - long mBatteryLastRealtime; + long mStartClockTime; long mUptime; long mUptimeStart; - long mLastUptime; long mRealtime; long mRealtimeStart; - long mLastRealtime; + + int mWakeLockNesting; + boolean mWakeLockImportant; + boolean mRecordAllWakeLocks; + boolean mNoAutoReset; int mScreenState = Display.STATE_UNKNOWN; StopwatchTimer mScreenOnTimer; @@ -221,6 +247,9 @@ public final class BatteryStatsImpl extends BatteryStats { boolean mInteractive; StopwatchTimer mInteractiveTimer; + boolean mLowPowerModeEnabled; + StopwatchTimer mLowPowerModeEnabledTimer; + boolean mPhoneOn; StopwatchTimer mPhoneOnTimer; @@ -241,19 +270,33 @@ public final class BatteryStatsImpl extends BatteryStats { final StopwatchTimer[] mPhoneDataConnectionsTimer = new StopwatchTimer[NUM_DATA_CONNECTION_TYPES]; - final LongSamplingCounter[] mNetworkActivityCounters = + final LongSamplingCounter[] mNetworkByteActivityCounters = + new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; + final LongSamplingCounter[] mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; boolean mWifiOn; StopwatchTimer mWifiOnTimer; - int mWifiOnUid = -1; boolean mGlobalWifiRunning; StopwatchTimer mGlobalWifiRunningTimer; + int mWifiState = -1; + final StopwatchTimer[] mWifiStateTimer = new StopwatchTimer[NUM_WIFI_STATES]; + boolean mBluetoothOn; StopwatchTimer mBluetoothOnTimer; + int mBluetoothState = -1; + final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES]; + + int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + StopwatchTimer mMobileRadioActiveTimer; + StopwatchTimer mMobileRadioActivePerAppTimer; + LongSamplingCounter mMobileRadioActiveAdjustedTime; + LongSamplingCounter mMobileRadioActiveUnknownTime; + LongSamplingCounter mMobileRadioActiveUnknownCount; + /** Bluetooth headset object */ BluetoothHeadset mBtHeadset; @@ -263,20 +306,15 @@ public final class BatteryStatsImpl extends BatteryStats { */ boolean mOnBattery; boolean mOnBatteryInternal; - long mTrackBatteryPastUptime; - long mTrackBatteryUptimeStart; - long mTrackBatteryPastRealtime; - long mTrackBatteryRealtimeStart; - - long mUnpluggedBatteryUptime; - long mUnpluggedBatteryRealtime; /* * These keep track of battery levels (1-100) at the last plug event and the last unplug event. */ int mDischargeStartLevel; int mDischargeUnplugLevel; + int mDischargePlugLevel; int mDischargeCurrentLevel; + int mCurrentBatteryLevel; int mLowDischargeAmountSinceCharge; int mHighDischargeAmountSinceCharge; int mDischargeScreenOnUnplugLevel; @@ -286,10 +324,21 @@ public final class BatteryStatsImpl extends BatteryStats { int mDischargeAmountScreenOff; int mDischargeAmountScreenOffSinceCharge; - long mLastWriteTime = 0; // Milliseconds + static final int MAX_LEVEL_STEPS = 100; + + int mLastDischargeStepLevel; + long mLastDischargeStepTime; + int mMinDischargeStepLevel; + int mNumDischargeStepDurations; + final long[] mDischargeStepDurations = new long[MAX_LEVEL_STEPS]; - private long mRadioDataUptime; - private long mRadioDataStart; + int mLastChargeStepLevel; + long mLastChargeStepTime; + int mMaxChargeStepLevel; + int mNumChargeStepDurations; + final long[] mChargeStepDurations = new long[MAX_LEVEL_STEPS]; + + long mLastWriteTime = 0; // Milliseconds private int mBluetoothPingCount; private int mBluetoothPingStart = -1; @@ -304,12 +353,21 @@ public final class BatteryStatsImpl extends BatteryStats { private final HashMap<String, SamplingTimer> mKernelWakelockStats = new HashMap<String, SamplingTimer>(); - public Map<String, ? extends SamplingTimer> getKernelWakelockStats() { + public Map<String, ? extends Timer> getKernelWakelockStats() { return mKernelWakelockStats; } private static int sKernelWakelockUpdateVersion = 0; + String mLastWakeupReason = null; + long mLastWakeupUptimeMs = 0; + private final HashMap<String, LongSamplingCounter> mWakeupReasonStats = + new HashMap<String, LongSamplingCounter>(); + + public Map<String, ? extends LongCounter> getWakeupReasonStats() { + return mWakeupReasonStats; + } + private static final int[] PROC_WAKELOCKS_FORMAT = new int[] { Process.PROC_TAB_TERM|Process.PROC_OUT_STRING| // 0: name Process.PROC_QUOTES, @@ -342,15 +400,18 @@ public final class BatteryStatsImpl extends BatteryStats { private final Map<String, KernelWakelockStats> mProcWakelockFileStats = new HashMap<String, KernelWakelockStats>(); - private HashMap<String, Integer> mUidCache = new HashMap<String, Integer>(); - private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory(); - private NetworkStats mLastSnapshot; + 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 HashSet<String> mMobileIfaces = Sets.newHashSet(); + private String[] mMobileIfaces = new String[0]; @GuardedBy("this") - private HashSet<String> mWifiIfaces = Sets.newHashSet(); + private String[] mWifiIfaces = new String[0]; // For debugging public BatteryStatsImpl() { @@ -358,35 +419,228 @@ public final class BatteryStatsImpl extends BatteryStats { mHandler = null; } - public static interface Unpluggable { - void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime); - void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime); + public static interface TimeBaseObs { + void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime); + void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime); + } + + static class TimeBase { + private final ArrayList<TimeBaseObs> mObservers = new ArrayList<TimeBaseObs>(); + + private long mUptime; + private long mRealtime; + + private boolean mRunning; + + private long mPastUptime; + private long mUptimeStart; + private long mPastRealtime; + private long mRealtimeStart; + private long mUnpluggedUptime; + private long mUnpluggedRealtime; + + public void dump(PrintWriter pw, String prefix) { + StringBuilder sb = new StringBuilder(128); + pw.print(prefix); pw.print("mRunning="); pw.println(mRunning); + sb.setLength(0); + sb.append(prefix); + sb.append("mUptime="); + formatTimeMs(sb, mUptime / 1000); + pw.println(sb.toString()); + sb.setLength(0); + sb.append(prefix); + sb.append("mRealtime="); + formatTimeMs(sb, mRealtime / 1000); + pw.println(sb.toString()); + sb.setLength(0); + sb.append(prefix); + sb.append("mPastUptime="); + formatTimeMs(sb, mPastUptime / 1000); sb.append("mUptimeStart="); + formatTimeMs(sb, mUptimeStart / 1000); + sb.append("mUnpluggedUptime="); formatTimeMs(sb, mUnpluggedUptime / 1000); + pw.println(sb.toString()); + sb.setLength(0); + sb.append(prefix); + sb.append("mPastRealtime="); + formatTimeMs(sb, mPastRealtime / 1000); sb.append("mRealtimeStart="); + formatTimeMs(sb, mRealtimeStart / 1000); + sb.append("mUnpluggedRealtime="); formatTimeMs(sb, mUnpluggedRealtime / 1000); + pw.println(sb.toString()); + } + + public void add(TimeBaseObs observer) { + mObservers.add(observer); + } + + public void remove(TimeBaseObs observer) { + if (!mObservers.remove(observer)) { + Slog.wtf(TAG, "Removed unknown observer: " + observer); + } + } + + public void init(long uptime, long realtime) { + mRealtime = 0; + mUptime = 0; + mPastUptime = 0; + mPastRealtime = 0; + mUptimeStart = uptime; + mRealtimeStart = realtime; + mUnpluggedUptime = getUptime(mUptimeStart); + mUnpluggedRealtime = getRealtime(mRealtimeStart); + } + + public void reset(long uptime, long realtime) { + if (!mRunning) { + mPastUptime = 0; + mPastRealtime = 0; + } else { + mUptimeStart = uptime; + mRealtimeStart = realtime; + mUnpluggedUptime = getUptime(uptime); + mUnpluggedRealtime = getRealtime(realtime); + } + } + + public long computeUptime(long curTime, int which) { + switch (which) { + case STATS_SINCE_CHARGED: + return mUptime + getUptime(curTime); + case STATS_CURRENT: + return getUptime(curTime); + case STATS_SINCE_UNPLUGGED: + return getUptime(curTime) - mUnpluggedUptime; + } + return 0; + } + + public long computeRealtime(long curTime, int which) { + switch (which) { + case STATS_SINCE_CHARGED: + return mRealtime + getRealtime(curTime); + case STATS_CURRENT: + return getRealtime(curTime); + case STATS_SINCE_UNPLUGGED: + return getRealtime(curTime) - mUnpluggedRealtime; + } + return 0; + } + + public long getUptime(long curTime) { + long time = mPastUptime; + if (mRunning) { + time += curTime - mUptimeStart; + } + return time; + } + + public long getRealtime(long curTime) { + long time = mPastRealtime; + if (mRunning) { + time += curTime - mRealtimeStart; + } + return time; + } + + public long getUptimeStart() { + return mUptimeStart; + } + + public long getRealtimeStart() { + return mRealtimeStart; + } + + public boolean isRunning() { + return mRunning; + } + + public boolean setRunning(boolean running, long uptime, long realtime) { + if (mRunning != running) { + mRunning = running; + if (running) { + mUptimeStart = uptime; + mRealtimeStart = realtime; + long batteryUptime = mUnpluggedUptime = getUptime(uptime); + long batteryRealtime = mUnpluggedRealtime = getRealtime(realtime); + + for (int i = mObservers.size() - 1; i >= 0; i--) { + mObservers.get(i).onTimeStarted(realtime, batteryUptime, batteryRealtime); + } + } else { + mPastUptime += uptime - mUptimeStart; + mPastRealtime += realtime - mRealtimeStart; + + long batteryUptime = getUptime(uptime); + long batteryRealtime = getRealtime(realtime); + + for (int i = mObservers.size() - 1; i >= 0; i--) { + mObservers.get(i).onTimeStopped(realtime, batteryUptime, batteryRealtime); + } + } + return true; + } + return false; + } + + public void readSummaryFromParcel(Parcel in) { + mUptime = in.readLong(); + mRealtime = in.readLong(); + } + + public void writeSummaryToParcel(Parcel out, long uptime, long realtime) { + out.writeLong(computeUptime(uptime, STATS_SINCE_CHARGED)); + out.writeLong(computeRealtime(realtime, STATS_SINCE_CHARGED)); + } + + public void readFromParcel(Parcel in) { + mRunning = false; + mUptime = in.readLong(); + mPastUptime = in.readLong(); + mUptimeStart = in.readLong(); + mRealtime = in.readLong(); + mPastRealtime = in.readLong(); + mRealtimeStart = in.readLong(); + mUnpluggedUptime = in.readLong(); + mUnpluggedRealtime = in.readLong(); + } + + public void writeToParcel(Parcel out, long uptime, long realtime) { + final long runningUptime = getUptime(uptime); + final long runningRealtime = getRealtime(realtime); + out.writeLong(mUptime); + out.writeLong(runningUptime); + out.writeLong(mUptimeStart); + out.writeLong(mRealtime); + out.writeLong(runningRealtime); + out.writeLong(mRealtimeStart); + out.writeLong(mUnpluggedUptime); + out.writeLong(mUnpluggedRealtime); + } } /** * State for keeping track of counting information. */ - public static class Counter extends BatteryStats.Counter implements Unpluggable { + public static class Counter extends BatteryStats.Counter implements TimeBaseObs { final AtomicInteger mCount = new AtomicInteger(); - final ArrayList<Unpluggable> mUnpluggables; + final TimeBase mTimeBase; int mLoadedCount; int mLastCount; int mUnpluggedCount; int mPluggedCount; - Counter(ArrayList<Unpluggable> unpluggables, Parcel in) { - mUnpluggables = unpluggables; + Counter(TimeBase timeBase, Parcel in) { + mTimeBase = timeBase; mPluggedCount = in.readInt(); mCount.set(mPluggedCount); mLoadedCount = in.readInt(); mLastCount = 0; mUnpluggedCount = in.readInt(); - unpluggables.add(this); + timeBase.add(this); } - Counter(ArrayList<Unpluggable> unpluggables) { - mUnpluggables = unpluggables; - unpluggables.add(this); + Counter(TimeBase timeBase) { + mTimeBase = timeBase; + timeBase.add(this); } public void writeToParcel(Parcel out) { @@ -395,12 +649,12 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mUnpluggedCount); } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { mUnpluggedCount = mPluggedCount; mCount.set(mPluggedCount); } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { mPluggedCount = mCount.get(); } @@ -422,16 +676,11 @@ public final class BatteryStatsImpl extends BatteryStats { @Override public int getCountLocked(int which) { - int val; - if (which == STATS_LAST) { - val = mLastCount; - } else { - val = mCount.get(); - if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedCount; - } else if (which != STATS_SINCE_CHARGED) { - val -= mLoadedCount; - } + int val = mCount.get(); + if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedCount; + } else if (which != STATS_SINCE_CHARGED) { + val -= mLoadedCount; } return val; @@ -460,7 +709,7 @@ public final class BatteryStatsImpl extends BatteryStats { } void detach() { - mUnpluggables.remove(this); + mTimeBase.remove(this); } void writeSummaryFromParcelLocked(Parcel out) { @@ -477,12 +726,12 @@ public final class BatteryStatsImpl extends BatteryStats { } public static class SamplingCounter extends Counter { - SamplingCounter(ArrayList<Unpluggable> unpluggables, Parcel in) { - super(unpluggables, in); + SamplingCounter(TimeBase timeBase, Parcel in) { + super(timeBase, in); } - SamplingCounter(ArrayList<Unpluggable> unpluggables) { - super(unpluggables); + SamplingCounter(TimeBase timeBase) { + super(timeBase); } public void addCountAtomic(long count) { @@ -490,27 +739,27 @@ public final class BatteryStatsImpl extends BatteryStats { } } - public static class LongSamplingCounter implements Unpluggable { - final ArrayList<Unpluggable> mUnpluggables; + public static class LongSamplingCounter extends LongCounter implements TimeBaseObs { + final TimeBase mTimeBase; long mCount; long mLoadedCount; long mLastCount; long mUnpluggedCount; long mPluggedCount; - LongSamplingCounter(ArrayList<Unpluggable> unpluggables, Parcel in) { - mUnpluggables = unpluggables; + LongSamplingCounter(TimeBase timeBase, Parcel in) { + mTimeBase = timeBase; mPluggedCount = in.readLong(); mCount = mPluggedCount; mLoadedCount = in.readLong(); mLastCount = 0; mUnpluggedCount = in.readLong(); - unpluggables.add(this); + timeBase.add(this); } - LongSamplingCounter(ArrayList<Unpluggable> unpluggables) { - mUnpluggables = unpluggables; - unpluggables.add(this); + LongSamplingCounter(TimeBase timeBase) { + mTimeBase = timeBase; + timeBase.add(this); } public void writeToParcel(Parcel out) { @@ -520,32 +769,35 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { mUnpluggedCount = mPluggedCount; mCount = mPluggedCount; } @Override - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { mPluggedCount = mCount; } public long getCountLocked(int which) { - long val; - if (which == STATS_LAST) { - val = mLastCount; - } else { - val = mCount; - if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedCount; - } else if (which != STATS_SINCE_CHARGED) { - val -= mLoadedCount; - } + long val = mCount; + if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedCount; + } else if (which != STATS_SINCE_CHARGED) { + val -= mLoadedCount; } return val; } + @Override + public void logState(Printer pw, String prefix) { + pw.println(prefix + "mCount=" + mCount + + " mLoadedCount=" + mLoadedCount + " mLastCount=" + mLastCount + + " mUnpluggedCount=" + mUnpluggedCount + + " mPluggedCount=" + mPluggedCount); + } + void addCountLocked(long count) { mCount += count; } @@ -562,7 +814,7 @@ public final class BatteryStatsImpl extends BatteryStats { } void detach() { - mUnpluggables.remove(this); + mTimeBase.remove(this); } void writeSummaryFromParcelLocked(Parcel out) { @@ -580,9 +832,9 @@ public final class BatteryStatsImpl extends BatteryStats { /** * State for keeping track of timing information. */ - public static abstract class Timer extends BatteryStats.Timer implements Unpluggable { + public static abstract class Timer extends BatteryStats.Timer implements TimeBaseObs { final int mType; - final ArrayList<Unpluggable> mUnpluggables; + final TimeBase mTimeBase; int mCount; int mLoadedCount; @@ -621,12 +873,12 @@ public final class BatteryStatsImpl extends BatteryStats { /** * Constructs from a parcel. * @param type - * @param unpluggables + * @param timeBase * @param in */ - Timer(int type, ArrayList<Unpluggable> unpluggables, Parcel in) { + Timer(int type, TimeBase timeBase, Parcel in) { mType = type; - mUnpluggables = unpluggables; + mTimeBase = timeBase; mCount = in.readInt(); mLoadedCount = in.readInt(); @@ -636,13 +888,14 @@ public final class BatteryStatsImpl extends BatteryStats { mLoadedTime = in.readLong(); mLastTime = 0; mUnpluggedTime = in.readLong(); - unpluggables.add(this); + timeBase.add(this); + if (DEBUG) Log.i(TAG, "**** READ TIMER #" + mType + ": mTotalTime=" + mTotalTime); } - Timer(int type, ArrayList<Unpluggable> unpluggables) { + Timer(int type, TimeBase timeBase) { mType = type; - mUnpluggables = unpluggables; - unpluggables.add(this); + mTimeBase = timeBase; + timeBase.add(this); } protected abstract long computeRunTimeLocked(long curBatteryRealtime); @@ -653,7 +906,7 @@ public final class BatteryStatsImpl extends BatteryStats { * Clear state of this timer. Returns true if the timer is inactive * so can be completely dropped. */ - boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { + boolean reset(boolean detachIfReset) { mTotalTime = mLoadedTime = mLastTime = 0; mCount = mLoadedCount = mLastCount = 0; if (detachIfReset) { @@ -663,25 +916,27 @@ public final class BatteryStatsImpl extends BatteryStats { } void detach() { - mUnpluggables.remove(this); + mTimeBase.remove(this); } - public void writeToParcel(Parcel out, long batteryRealtime) { + public void writeToParcel(Parcel out, long elapsedRealtimeUs) { + if (DEBUG) Log.i(TAG, "**** WRITING TIMER #" + mType + ": mTotalTime=" + + computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs))); out.writeInt(mCount); out.writeInt(mLoadedCount); out.writeInt(mUnpluggedCount); - out.writeLong(computeRunTimeLocked(batteryRealtime)); + out.writeLong(computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs))); out.writeLong(mLoadedTime); out.writeLong(mUnpluggedTime); } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long timeBaseUptime, long baseRealtime) { if (DEBUG && mType < 0) { - Log.v(TAG, "unplug #" + mType + ": realtime=" + batteryRealtime + Log.v(TAG, "unplug #" + mType + ": realtime=" + baseRealtime + " old mUnpluggedTime=" + mUnpluggedTime + " old mUnpluggedCount=" + mUnpluggedCount); } - mUnpluggedTime = computeRunTimeLocked(batteryRealtime); + mUnpluggedTime = computeRunTimeLocked(baseRealtime); mUnpluggedCount = mCount; if (DEBUG && mType < 0) { Log.v(TAG, "unplug #" + mType @@ -690,12 +945,12 @@ public final class BatteryStatsImpl extends BatteryStats { } } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { if (DEBUG && mType < 0) { - Log.v(TAG, "plug #" + mType + ": realtime=" + batteryRealtime + Log.v(TAG, "plug #" + mType + ": realtime=" + baseRealtime + " old mTotalTime=" + mTotalTime); } - mTotalTime = computeRunTimeLocked(batteryRealtime); + mTotalTime = computeRunTimeLocked(baseRealtime); mCount = computeCurrentCountLocked(); if (DEBUG && mType < 0) { Log.v(TAG, "plug #" + mType @@ -709,29 +964,23 @@ public final class BatteryStatsImpl extends BatteryStats { * @param out the Parcel to be written to. * @param timer a Timer, or null. */ - public static void writeTimerToParcel(Parcel out, Timer timer, - long batteryRealtime) { + public static void writeTimerToParcel(Parcel out, Timer timer, long elapsedRealtimeUs) { if (timer == null) { out.writeInt(0); // indicates null return; } out.writeInt(1); // indicates non-null - timer.writeToParcel(out, batteryRealtime); + timer.writeToParcel(out, elapsedRealtimeUs); } @Override - public long getTotalTimeLocked(long batteryRealtime, int which) { - long val; - if (which == STATS_LAST) { - val = mLastTime; - } else { - val = computeRunTimeLocked(batteryRealtime); - if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedTime; - } else if (which != STATS_SINCE_CHARGED) { - val -= mLoadedTime; - } + public long getTotalTimeLocked(long elapsedRealtimeUs, int which) { + long val = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)); + if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedTime; + } else if (which != STATS_SINCE_CHARGED) { + val -= mLoadedTime; } return val; @@ -739,16 +988,11 @@ public final class BatteryStatsImpl extends BatteryStats { @Override public int getCountLocked(int which) { - int val; - if (which == STATS_LAST) { - val = mLastCount; - } else { - val = computeCurrentCountLocked(); - if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedCount; - } else if (which != STATS_SINCE_CHARGED) { - val -= mLoadedCount; - } + int val = computeCurrentCountLocked(); + if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedCount; + } else if (which != STATS_SINCE_CHARGED) { + val -= mLoadedCount; } return val; @@ -765,16 +1009,15 @@ public final class BatteryStatsImpl extends BatteryStats { } - void writeSummaryFromParcelLocked(Parcel out, long batteryRealtime) { - long runTime = computeRunTimeLocked(batteryRealtime); - // Divide by 1000 for backwards compatibility - out.writeLong((runTime + 500) / 1000); + void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) { + long runTime = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)); + out.writeLong(runTime); out.writeInt(mCount); } void readSummaryFromParcelLocked(Parcel in) { // Multiply by 1000 for backwards compatibility - mTotalTime = mLoadedTime = in.readLong() * 1000; + mTotalTime = mLoadedTime = in.readLong(); mLastTime = 0; mUnpluggedTime = mTotalTime; mCount = mLoadedCount = in.readInt(); @@ -811,7 +1054,7 @@ public final class BatteryStatsImpl extends BatteryStats { /** * Whether we are currently in a discharge cycle. */ - boolean mInDischarge; + boolean mTimeBaseRunning; /** * Whether we are currently recording reported values. @@ -823,21 +1066,20 @@ public final class BatteryStatsImpl extends BatteryStats { */ int mUpdateVersion; - SamplingTimer(ArrayList<Unpluggable> unpluggables, boolean inDischarge, Parcel in) { - super(0, unpluggables, in); + SamplingTimer(TimeBase timeBase, Parcel in) { + super(0, timeBase, in); mCurrentReportedCount = in.readInt(); mUnpluggedReportedCount = in.readInt(); mCurrentReportedTotalTime = in.readLong(); mUnpluggedReportedTotalTime = in.readLong(); mTrackingReportedValues = in.readInt() == 1; - mInDischarge = inDischarge; + mTimeBaseRunning = timeBase.isRunning(); } - SamplingTimer(ArrayList<Unpluggable> unpluggables, boolean inDischarge, - boolean trackReportedValues) { - super(0, unpluggables); + SamplingTimer(TimeBase timeBase, boolean trackReportedValues) { + super(0, timeBase); mTrackingReportedValues = trackReportedValues; - mInDischarge = inDischarge; + mTimeBaseRunning = timeBase.isRunning(); } public void setStale() { @@ -855,7 +1097,7 @@ public final class BatteryStatsImpl extends BatteryStats { } public void updateCurrentReportedCount(int count) { - if (mInDischarge && mUnpluggedReportedCount == 0) { + if (mTimeBaseRunning && mUnpluggedReportedCount == 0) { // Updating the reported value for the first time. mUnpluggedReportedCount = count; // If we are receiving an update update mTrackingReportedValues; @@ -865,7 +1107,7 @@ public final class BatteryStatsImpl extends BatteryStats { } public void updateCurrentReportedTotalTime(long totalTime) { - if (mInDischarge && mUnpluggedReportedTotalTime == 0) { + if (mTimeBaseRunning && mUnpluggedReportedTotalTime == 0) { // Updating the reported value for the first time. mUnpluggedReportedTotalTime = totalTime; // If we are receiving an update update mTrackingReportedValues; @@ -874,18 +1116,18 @@ public final class BatteryStatsImpl extends BatteryStats { mCurrentReportedTotalTime = totalTime; } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { - super.unplug(elapsedRealtime, batteryUptime, batteryRealtime); + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { + super.onTimeStarted(elapsedRealtime, baseUptime, baseRealtime); if (mTrackingReportedValues) { mUnpluggedReportedTotalTime = mCurrentReportedTotalTime; mUnpluggedReportedCount = mCurrentReportedCount; } - mInDischarge = true; + mTimeBaseRunning = true; } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { - super.plug(elapsedRealtime, batteryUptime, batteryRealtime); - mInDischarge = false; + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { + super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime); + mTimeBaseRunning = false; } public void logState(Printer pw, String prefix) { @@ -897,17 +1139,17 @@ public final class BatteryStatsImpl extends BatteryStats { } protected long computeRunTimeLocked(long curBatteryRealtime) { - return mTotalTime + (mInDischarge && mTrackingReportedValues + return mTotalTime + (mTimeBaseRunning && mTrackingReportedValues ? mCurrentReportedTotalTime - mUnpluggedReportedTotalTime : 0); } protected int computeCurrentCountLocked() { - return mCount + (mInDischarge && mTrackingReportedValues + return mCount + (mTimeBaseRunning && mTrackingReportedValues ? mCurrentReportedCount - mUnpluggedReportedCount : 0); } - public void writeToParcel(Parcel out, long batteryRealtime) { - super.writeToParcel(out, batteryRealtime); + public void writeToParcel(Parcel out, long elapsedRealtimeUs) { + super.writeToParcel(out, elapsedRealtimeUs); out.writeInt(mCurrentReportedCount); out.writeInt(mUnpluggedReportedCount); out.writeLong(mCurrentReportedTotalTime); @@ -915,8 +1157,8 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mTrackingReportedValues ? 1 : 0); } - boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { - super.reset(stats, detachIfReset); + boolean reset(boolean detachIfReset) { + super.reset(detachIfReset); setStale(); return true; } @@ -958,45 +1200,43 @@ public final class BatteryStatsImpl extends BatteryStats { */ boolean mInDischarge; - BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables, - boolean inDischarge, Parcel in) { - super(type, unpluggables, in); + BatchTimer(Uid uid, int type, TimeBase timeBase, Parcel in) { + super(type, timeBase, in); mUid = uid; mLastAddedTime = in.readLong(); mLastAddedDuration = in.readLong(); - mInDischarge = inDischarge; + mInDischarge = timeBase.isRunning(); } - BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables, - boolean inDischarge) { - super(type, unpluggables); + BatchTimer(Uid uid, int type, TimeBase timeBase) { + super(type, timeBase); mUid = uid; - mInDischarge = inDischarge; + mInDischarge = timeBase.isRunning(); } @Override - public void writeToParcel(Parcel out, long batteryRealtime) { - super.writeToParcel(out, batteryRealtime); + public void writeToParcel(Parcel out, long elapsedRealtimeUs) { + super.writeToParcel(out, elapsedRealtimeUs); out.writeLong(mLastAddedTime); out.writeLong(mLastAddedDuration); } @Override - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { recomputeLastDuration(SystemClock.elapsedRealtime() * 1000, false); mInDischarge = false; - super.plug(elapsedRealtime, batteryUptime, batteryRealtime); + super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime); } @Override - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { recomputeLastDuration(elapsedRealtime, false); mInDischarge = true; // If we are still within the last added duration, then re-added whatever remains. if (mLastAddedTime == elapsedRealtime) { mTotalTime += mLastAddedDuration; } - super.unplug(elapsedRealtime, batteryUptime, batteryRealtime); + super.onTimeStarted(elapsedRealtime, baseUptime, baseRealtime); } @Override @@ -1062,11 +1302,11 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override - boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { + boolean reset(boolean detachIfReset) { final long now = SystemClock.elapsedRealtime() * 1000; recomputeLastDuration(now, true); boolean stillActive = mLastAddedTime == now; - super.reset(stats, !stillActive && detachIfReset); + super.reset(!stillActive && detachIfReset); return !stillActive; } } @@ -1102,16 +1342,16 @@ public final class BatteryStatsImpl extends BatteryStats { boolean mInList; StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool, - ArrayList<Unpluggable> unpluggables, Parcel in) { - super(type, unpluggables, in); + TimeBase timeBase, Parcel in) { + super(type, timeBase, in); mUid = uid; mTimerPool = timerPool; mUpdateTime = in.readLong(); } StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool, - ArrayList<Unpluggable> unpluggables) { - super(type, unpluggables); + TimeBase timeBase) { + super(type, timeBase); mUid = uid; mTimerPool = timerPool; } @@ -1120,18 +1360,18 @@ public final class BatteryStatsImpl extends BatteryStats { mTimeout = timeout; } - public void writeToParcel(Parcel out, long batteryRealtime) { - super.writeToParcel(out, batteryRealtime); + public void writeToParcel(Parcel out, long elapsedRealtimeUs) { + super.writeToParcel(out, elapsedRealtimeUs); out.writeLong(mUpdateTime); } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { if (mNesting > 0) { if (DEBUG && mType < 0) { Log.v(TAG, "old mUpdateTime=" + mUpdateTime); } - super.plug(elapsedRealtime, batteryUptime, batteryRealtime); - mUpdateTime = batteryRealtime; + super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime); + mUpdateTime = baseRealtime; if (DEBUG && mType < 0) { Log.v(TAG, "new mUpdateTime=" + mUpdateTime); } @@ -1144,14 +1384,14 @@ public final class BatteryStatsImpl extends BatteryStats { + " mAcquireTime=" + mAcquireTime); } - void startRunningLocked(BatteryStatsImpl stats) { + void startRunningLocked(long elapsedRealtimeMs) { if (mNesting++ == 0) { - mUpdateTime = stats.getBatteryRealtimeLocked( - SystemClock.elapsedRealtime() * 1000); + final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000); + mUpdateTime = batteryRealtime; if (mTimerPool != null) { // Accumulate time to all currently active timers before adding // this new one to the pool. - refreshTimersLocked(stats, mTimerPool); + refreshTimersLocked(batteryRealtime, mTimerPool, null); // Add this timer to the active pool mTimerPool.add(this); } @@ -1170,21 +1410,39 @@ public final class BatteryStatsImpl extends BatteryStats { return mNesting > 0; } - void stopRunningLocked(BatteryStatsImpl stats) { + 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; + } + + long getLastUpdateTimeMs() { + return mUpdateTime; + } + + void stopRunningLocked(long elapsedRealtimeMs) { // Ignore attempt to stop a timer that isn't running if (mNesting == 0) { return; } if (--mNesting == 0) { + final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000); if (mTimerPool != null) { // Accumulate time to all active counters, scaled by the total // active in the pool, before taking this one out of the pool. - refreshTimersLocked(stats, mTimerPool); + refreshTimersLocked(batteryRealtime, mTimerPool, null); // Remove this timer from the active pool mTimerPool.remove(this); } else { - final long realtime = SystemClock.elapsedRealtime() * 1000; - final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime); mNesting = 1; mTotalTime = computeRunTimeLocked(batteryRealtime); mNesting = 0; @@ -1206,19 +1464,23 @@ public final class BatteryStatsImpl extends BatteryStats { // Update the total time for all other running Timers with the same type as this Timer // due to a change in timer count - private static void refreshTimersLocked(final BatteryStatsImpl stats, - final ArrayList<StopwatchTimer> pool) { - final long realtime = SystemClock.elapsedRealtime() * 1000; - final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime); + private static long refreshTimersLocked(long batteryRealtime, + final ArrayList<StopwatchTimer> pool, StopwatchTimer self) { + long selfTime = 0; final int N = pool.size(); for (int i=N-1; i>= 0; i--) { final StopwatchTimer t = pool.get(i); long heldTime = batteryRealtime - t.mUpdateTime; if (heldTime > 0) { - t.mTotalTime += heldTime / N; + final long myTime = heldTime / N; + if (t == self) { + selfTime = myTime; + } + t.mTotalTime += myTime; } t.mUpdateTime = batteryRealtime; } + return selfTime; } @Override @@ -1237,12 +1499,11 @@ public final class BatteryStatsImpl extends BatteryStats { return mCount; } - boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { + boolean reset(boolean detachIfReset) { boolean canDetach = mNesting <= 0; - super.reset(stats, canDetach && detachIfReset); + super.reset(canDetach && detachIfReset); if (mNesting > 0) { - mUpdateTime = stats.getBatteryRealtimeLocked( - SystemClock.elapsedRealtime() * 1000); + mUpdateTime = mTimeBase.getRealtime(SystemClock.elapsedRealtime() * 1000); } mAcquireTime = mTotalTime; return canDetach; @@ -1261,6 +1522,19 @@ public final class BatteryStatsImpl extends BatteryStats { } } + /* + * Get the wakeup reason counter, and create a new one if one + * doesn't already exist. + */ + public LongSamplingCounter getWakeupReasonCounterLocked(String name) { + LongSamplingCounter counter = mWakeupReasonStats.get(name); + if (counter == null) { + counter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase); + mWakeupReasonStats.put(name, counter); + } + return counter; + } + private final Map<String, KernelWakelockStats> readKernelWakelockStats() { FileInputStream is; @@ -1405,51 +1679,12 @@ public final class BatteryStatsImpl extends BatteryStats { public SamplingTimer getKernelWakelockTimerLocked(String name) { SamplingTimer kwlt = mKernelWakelockStats.get(name); if (kwlt == null) { - kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal, - true /* track reported values */); + kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase, true /* track reported values */); mKernelWakelockStats.put(name, kwlt); } return kwlt; } - /** - * Radio uptime in microseconds when transferring data. This value is very approximate. - * @return - */ - private long getCurrentRadioDataUptime() { - try { - File awakeTimeFile = new File("/sys/devices/virtual/net/rmnet0/awake_time_ms"); - if (!awakeTimeFile.exists()) return 0; - BufferedReader br = new BufferedReader(new FileReader(awakeTimeFile)); - String line = br.readLine(); - br.close(); - return Long.parseLong(line) * 1000; - } catch (NumberFormatException nfe) { - // Nothing - } catch (IOException ioe) { - // Nothing - } - return 0; - } - - /** - * @deprecated use getRadioDataUptime - */ - public long getRadioDataUptimeMs() { - return getRadioDataUptime() / 1000; - } - - /** - * Returns the duration that the cell radio was up for data transfers. - */ - public long getRadioDataUptime() { - if (mRadioDataStart == -1) { - return mRadioDataUptime; - } else { - return getCurrentRadioDataUptime() - mRadioDataStart; - } - } - private int getCurrentBluetoothPingCount() { if (mBtHeadset != null) { List<BluetoothDevice> deviceList = mBtHeadset.getConnectedDevices(); @@ -1476,83 +1711,434 @@ public final class BatteryStatsImpl extends BatteryStats { mBtHeadset = headset; } - int mChangedBufferStates = 0; + private int writeHistoryTag(HistoryTag tag) { + Integer idxObj = mHistoryTagPool.get(tag); + int idx; + if (idxObj != null) { + idx = idxObj; + } else { + idx = mNextHistoryTagIdx; + HistoryTag key = new HistoryTag(); + key.setTo(tag); + tag.poolIdx = idx; + mHistoryTagPool.put(key, idx); + mNextHistoryTagIdx++; + mNumHistoryTagChars += key.string.length() + 1; + } + return idx; + } + + private void readHistoryTag(int index, HistoryTag tag) { + tag.string = mReadHistoryStrings[index]; + tag.uid = mReadHistoryUids[index]; + tag.poolIdx = index; + } + + // Part of initial delta int that specifies the time delta. + static final int DELTA_TIME_MASK = 0x7ffff; + static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long + static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int + static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update. + // Flag in delta int: a new battery level int follows. + static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000; + // Flag in delta int: a new full state and battery status int follows. + static final int DELTA_STATE_FLAG = 0x00100000; + // Flag in delta int: a new full state2 int follows. + static final int DELTA_STATE2_FLAG = 0x00200000; + // Flag in delta int: contains a wakelock or wakeReason tag. + static final int DELTA_WAKELOCK_FLAG = 0x00400000; + // Flag in delta int: contains an event description. + static final int DELTA_EVENT_FLAG = 0x00800000; + // These upper bits are the frequently changing state bits. + static final int DELTA_STATE_MASK = 0xff000000; + + // These are the pieces of battery state that are packed in to the upper bits of + // the state int that have been packed in to the first delta int. They must fit + // in DELTA_STATE_MASK. + static final int STATE_BATTERY_STATUS_MASK = 0x00000007; + static final int STATE_BATTERY_STATUS_SHIFT = 29; + static final int STATE_BATTERY_HEALTH_MASK = 0x00000007; + static final int STATE_BATTERY_HEALTH_SHIFT = 26; + static final int STATE_BATTERY_PLUG_MASK = 0x00000003; + static final int STATE_BATTERY_PLUG_SHIFT = 24; + + public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) { + if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) { + dest.writeInt(DELTA_TIME_ABS); + cur.writeToParcel(dest, 0); + return; + } + + final long deltaTime = cur.time - last.time; + final int lastBatteryLevelInt = buildBatteryLevelInt(last); + final int lastStateInt = buildStateInt(last); + + int deltaTimeToken; + if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) { + deltaTimeToken = DELTA_TIME_LONG; + } else if (deltaTime >= DELTA_TIME_ABS) { + deltaTimeToken = DELTA_TIME_INT; + } else { + deltaTimeToken = (int)deltaTime; + } + int firstToken = deltaTimeToken | (cur.states&DELTA_STATE_MASK); + final int batteryLevelInt = buildBatteryLevelInt(cur); + final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt; + if (batteryLevelIntChanged) { + firstToken |= DELTA_BATTERY_LEVEL_FLAG; + } + final int stateInt = buildStateInt(cur); + final boolean stateIntChanged = stateInt != lastStateInt; + if (stateIntChanged) { + firstToken |= DELTA_STATE_FLAG; + } + final boolean state2IntChanged = cur.states2 != last.states2; + if (state2IntChanged) { + firstToken |= DELTA_STATE2_FLAG; + } + if (cur.wakelockTag != null || cur.wakeReasonTag != null) { + firstToken |= DELTA_WAKELOCK_FLAG; + } + if (cur.eventCode != HistoryItem.EVENT_NONE) { + firstToken |= DELTA_EVENT_FLAG; + } + dest.writeInt(firstToken); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken) + + " deltaTime=" + deltaTime); - void addHistoryBufferLocked(long curTime) { + if (deltaTimeToken >= DELTA_TIME_INT) { + if (deltaTimeToken == DELTA_TIME_INT) { + if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime); + dest.writeInt((int)deltaTime); + } else { + if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime); + dest.writeLong(deltaTime); + } + } + if (batteryLevelIntChanged) { + dest.writeInt(batteryLevelInt); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x" + + Integer.toHexString(batteryLevelInt) + + " batteryLevel=" + cur.batteryLevel + + " batteryTemp=" + cur.batteryTemperature + + " batteryVolt=" + (int)cur.batteryVoltage); + } + if (stateIntChanged) { + dest.writeInt(stateInt); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x" + + Integer.toHexString(stateInt) + + " batteryStatus=" + cur.batteryStatus + + " batteryHealth=" + cur.batteryHealth + + " batteryPlugType=" + cur.batteryPlugType + + " states=0x" + Integer.toHexString(cur.states)); + } + if (state2IntChanged) { + dest.writeInt(cur.states2); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: states2=0x" + + Integer.toHexString(cur.states2)); + } + if (cur.wakelockTag != null || cur.wakeReasonTag != null) { + int wakeLockIndex; + int wakeReasonIndex; + if (cur.wakelockTag != null) { + wakeLockIndex = writeHistoryTag(cur.wakelockTag); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx + + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string); + } else { + wakeLockIndex = 0xffff; + } + if (cur.wakeReasonTag != null) { + wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx + + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string); + } else { + wakeReasonIndex = 0xffff; + } + dest.writeInt((wakeReasonIndex<<16) | wakeLockIndex); + } + if (cur.eventCode != HistoryItem.EVENT_NONE) { + int index = writeHistoryTag(cur.eventTag); + int codeAndIndex = (cur.eventCode&0xffff) | (index<<16); + dest.writeInt(codeAndIndex); + if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#" + + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":" + + cur.eventTag.string); + } + } + + private int buildBatteryLevelInt(HistoryItem h) { + return ((((int)h.batteryLevel)<<25)&0xfe000000) + | ((((int)h.batteryTemperature)<<14)&0x01ffc000) + | (((int)h.batteryVoltage)&0x00003fff); + } + + private int buildStateInt(HistoryItem h) { + int plugType = 0; + if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_AC) != 0) { + plugType = 1; + } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_USB) != 0) { + plugType = 2; + } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) { + plugType = 3; + } + return ((h.batteryStatus&STATE_BATTERY_STATUS_MASK)<<STATE_BATTERY_STATUS_SHIFT) + | ((h.batteryHealth&STATE_BATTERY_HEALTH_MASK)<<STATE_BATTERY_HEALTH_SHIFT) + | ((plugType&STATE_BATTERY_PLUG_MASK)<<STATE_BATTERY_PLUG_SHIFT) + | (h.states&(~DELTA_STATE_MASK)); + } + + public void readHistoryDelta(Parcel src, HistoryItem cur) { + int firstToken = src.readInt(); + int deltaTimeToken = firstToken&DELTA_TIME_MASK; + cur.cmd = HistoryItem.CMD_UPDATE; + cur.numReadInts = 1; + if (DEBUG) Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken) + + " deltaTimeToken=" + deltaTimeToken); + + if (deltaTimeToken < DELTA_TIME_ABS) { + cur.time += deltaTimeToken; + } else if (deltaTimeToken == DELTA_TIME_ABS) { + cur.time = src.readLong(); + cur.numReadInts += 2; + if (DEBUG) Slog.i(TAG, "READ DELTA: ABS time=" + cur.time); + cur.readFromParcel(src); + return; + } else if (deltaTimeToken == DELTA_TIME_INT) { + int delta = src.readInt(); + cur.time += delta; + cur.numReadInts += 1; + if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time); + } else { + long delta = src.readLong(); + if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time); + cur.time += delta; + cur.numReadInts += 2; + } + + if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) { + int batteryLevelInt = src.readInt(); + cur.batteryLevel = (byte)((batteryLevelInt>>25)&0x7f); + cur.batteryTemperature = (short)((batteryLevelInt<<7)>>21); + cur.batteryVoltage = (char)(batteryLevelInt&0x3fff); + cur.numReadInts += 1; + if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x" + + Integer.toHexString(batteryLevelInt) + + " batteryLevel=" + cur.batteryLevel + + " batteryTemp=" + cur.batteryTemperature + + " batteryVolt=" + (int)cur.batteryVoltage); + } + + if ((firstToken&DELTA_STATE_FLAG) != 0) { + int stateInt = src.readInt(); + cur.states = (firstToken&DELTA_STATE_MASK) | (stateInt&(~DELTA_STATE_MASK)); + cur.batteryStatus = (byte)((stateInt>>STATE_BATTERY_STATUS_SHIFT) + & STATE_BATTERY_STATUS_MASK); + cur.batteryHealth = (byte)((stateInt>>STATE_BATTERY_HEALTH_SHIFT) + & STATE_BATTERY_HEALTH_MASK); + cur.batteryPlugType = (byte)((stateInt>>STATE_BATTERY_PLUG_SHIFT) + & STATE_BATTERY_PLUG_MASK); + switch (cur.batteryPlugType) { + case 1: + cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_AC; + break; + case 2: + cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_USB; + break; + case 3: + cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS; + break; + } + cur.numReadInts += 1; + if (DEBUG) Slog.i(TAG, "READ DELTA: stateToken=0x" + + Integer.toHexString(stateInt) + + " batteryStatus=" + cur.batteryStatus + + " batteryHealth=" + cur.batteryHealth + + " batteryPlugType=" + cur.batteryPlugType + + " states=0x" + Integer.toHexString(cur.states)); + } else { + cur.states = (firstToken&DELTA_STATE_MASK) | (cur.states&(~DELTA_STATE_MASK)); + } + + if ((firstToken&DELTA_STATE2_FLAG) != 0) { + cur.states2 = src.readInt(); + if (DEBUG) Slog.i(TAG, "READ DELTA: states2=0x" + + Integer.toHexString(cur.states2)); + } + + if ((firstToken&DELTA_WAKELOCK_FLAG) != 0) { + int indexes = src.readInt(); + int wakeLockIndex = indexes&0xffff; + int wakeReasonIndex = (indexes>>16)&0xffff; + if (wakeLockIndex != 0xffff) { + cur.wakelockTag = cur.localWakelockTag; + readHistoryTag(wakeLockIndex, cur.wakelockTag); + if (DEBUG) Slog.i(TAG, "READ DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx + + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string); + } else { + cur.wakelockTag = null; + } + if (wakeReasonIndex != 0xffff) { + cur.wakeReasonTag = cur.localWakeReasonTag; + readHistoryTag(wakeReasonIndex, cur.wakeReasonTag); + if (DEBUG) Slog.i(TAG, "READ DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx + + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string); + } else { + cur.wakeReasonTag = null; + } + cur.numReadInts += 1; + } else { + cur.wakelockTag = null; + cur.wakeReasonTag = null; + } + + if ((firstToken&DELTA_EVENT_FLAG) != 0) { + cur.eventTag = cur.localEventTag; + final int codeAndIndex = src.readInt(); + cur.eventCode = (codeAndIndex&0xffff); + final int index = ((codeAndIndex>>16)&0xffff); + readHistoryTag(index, cur.eventTag); + cur.numReadInts += 1; + if (DEBUG) Slog.i(TAG, "READ DELTA: event=" + cur.eventCode + " tag=#" + + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":" + + cur.eventTag.string); + } else { + cur.eventCode = HistoryItem.EVENT_NONE; + } + } + + void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) { if (!mHaveBatteryLevel || !mRecordingHistory) { return; } - final long timeDiff = (mHistoryBaseTime+curTime) - mHistoryLastWritten.time; + final long timeDiff = (mHistoryBaseTime+elapsedRealtimeMs) - mHistoryLastWritten.time; + final int diffStates = mHistoryLastWritten.states^cur.states; + final int diffStates2 = mHistoryLastWritten.states2^cur.states2; + final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states; + final int lastDiffStates2 = mHistoryLastWritten.states2^mHistoryLastLastWritten.states2; + if (DEBUG) Slog.i(TAG, "ADD: tdelta=" + timeDiff + " diff=" + + Integer.toHexString(diffStates) + " lastDiff=" + + Integer.toHexString(lastDiffStates) + " diff2=" + + Integer.toHexString(diffStates2) + " lastDiff2=" + + Integer.toHexString(lastDiffStates2)); if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE - && timeDiff < 2000 - && ((mHistoryLastWritten.states^mHistoryCur.states)&mChangedBufferStates) == 0) { - // If the current is the same as the one before, then we no - // longer need the entry. + && timeDiff < 1000 && (diffStates&lastDiffStates) == 0 + && (diffStates2&lastDiffStates2) == 0 + && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null) + && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null) + && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE + || cur.eventCode == HistoryItem.EVENT_NONE) + && mHistoryLastWritten.batteryLevel == cur.batteryLevel + && mHistoryLastWritten.batteryStatus == cur.batteryStatus + && mHistoryLastWritten.batteryHealth == cur.batteryHealth + && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType + && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature + && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) { + // We can merge this new change in with the last one. Merging is + // allowed as long as only the states have changed, and within those states + // as long as no bit has changed both between now and the last entry, as + // well as the last entry and the one before it (so we capture any toggles). + if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos); mHistoryBuffer.setDataSize(mHistoryBufferLastPos); mHistoryBuffer.setDataPosition(mHistoryBufferLastPos); mHistoryBufferLastPos = -1; - if (mHistoryLastLastWritten.cmd == HistoryItem.CMD_UPDATE - && timeDiff < 500 && mHistoryLastLastWritten.same(mHistoryCur)) { - // If this results in us returning to the state written - // prior to the last one, then we can just delete the last - // written one and drop the new one. Nothing more to do. - mHistoryLastWritten.setTo(mHistoryLastLastWritten); - mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL; - return; + elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTime; + // If the last written history had a wakelock tag, we need to retain it. + // Note that the condition above made sure that we aren't in a case where + // both it and the current history item have a wakelock tag. + if (mHistoryLastWritten.wakelockTag != null) { + cur.wakelockTag = cur.localWakelockTag; + cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag); + } + // If the last written history had a wake reason tag, we need to retain it. + // Note that the condition above made sure that we aren't in a case where + // both it and the current history item have a wakelock tag. + if (mHistoryLastWritten.wakeReasonTag != null) { + cur.wakeReasonTag = cur.localWakeReasonTag; + cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag); + } + // If the last written history had an event, we need to retain it. + // Note that the condition above made sure that we aren't in a case where + // both it and the current history item have an event. + if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) { + cur.eventCode = mHistoryLastWritten.eventCode; + cur.eventTag = cur.localEventTag; + cur.eventTag.setTo(mHistoryLastWritten.eventTag); } - mChangedBufferStates |= mHistoryLastWritten.states^mHistoryCur.states; - curTime = mHistoryLastWritten.time - mHistoryBaseTime; mHistoryLastWritten.setTo(mHistoryLastLastWritten); - } else { - mChangedBufferStates = 0; } final int dataSize = mHistoryBuffer.dataSize(); if (dataSize >= MAX_HISTORY_BUFFER) { if (!mHistoryOverflow) { mHistoryOverflow = true; - addHistoryBufferLocked(curTime, HistoryItem.CMD_OVERFLOW); + addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); + addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur); + return; } // Once we've reached the maximum number of items, we only // record changes to the battery level and the most interesting states. // Once we've reached the maximum maximum number of items, we only // record changes to the battery level. - if (mHistoryLastWritten.batteryLevel == mHistoryCur.batteryLevel && + if (mHistoryLastWritten.batteryLevel == cur.batteryLevel && (dataSize >= MAX_MAX_HISTORY_BUFFER - || ((mHistoryLastWritten.states^mHistoryCur.states) + || ((mHistoryLastWritten.states^cur.states) & HistoryItem.MOST_INTERESTING_STATES) == 0)) { return; } + + addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); + return; } - addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE); + addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); } - void addHistoryBufferLocked(long curTime, byte cmd) { - int origPos = 0; + private void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, byte cmd, + HistoryItem cur) { if (mIteratingHistory) { - origPos = mHistoryBuffer.dataPosition(); - mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize()); + throw new IllegalStateException("Can't do this while iterating history!"); } mHistoryBufferLastPos = mHistoryBuffer.dataPosition(); mHistoryLastLastWritten.setTo(mHistoryLastWritten); - mHistoryLastWritten.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur); - mHistoryLastWritten.writeDelta(mHistoryBuffer, mHistoryLastLastWritten); - mLastHistoryTime = curTime; + mHistoryLastWritten.setTo(mHistoryBaseTime + elapsedRealtimeMs, cmd, cur); + writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten); + mLastHistoryElapsedRealtime = elapsedRealtimeMs; + cur.wakelockTag = null; + cur.wakeReasonTag = null; + cur.eventCode = HistoryItem.EVENT_NONE; + cur.eventTag = null; if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos + " now " + mHistoryBuffer.dataPosition() + " size is now " + mHistoryBuffer.dataSize()); - if (mIteratingHistory) { - mHistoryBuffer.setDataPosition(origPos); - } } int mChangedStates = 0; + int mChangedStates2 = 0; + + void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs) { + if (mTrackRunningHistoryElapsedRealtime != 0) { + final long diffElapsed = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtime; + final long diffUptime = uptimeMs - mTrackRunningHistoryUptime; + if (diffUptime < (diffElapsed-20)) { + final long wakeElapsedTime = elapsedRealtimeMs - (diffElapsed - diffUptime); + mHistoryAddTmp.setTo(mHistoryLastWritten); + mHistoryAddTmp.wakelockTag = null; + mHistoryAddTmp.wakeReasonTag = null; + mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE; + mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG; + addHistoryRecordInnerLocked(wakeElapsedTime, uptimeMs, mHistoryAddTmp); + } + } + mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG; + mTrackRunningHistoryElapsedRealtime = elapsedRealtimeMs; + mTrackRunningHistoryUptime = uptimeMs; + addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur); + } - void addHistoryRecordLocked(long curTime) { - addHistoryBufferLocked(curTime); + void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) { + addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur); if (!USE_OLD_HISTORY) { return; @@ -1567,30 +2153,33 @@ public final class BatteryStatsImpl extends BatteryStats { // are now resetting back to their original value, then just collapse // into one record. if (mHistoryEnd != null && mHistoryEnd.cmd == HistoryItem.CMD_UPDATE - && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+2000) - && ((mHistoryEnd.states^mHistoryCur.states)&mChangedStates) == 0) { + && (mHistoryBaseTime+elapsedRealtimeMs) < (mHistoryEnd.time+1000) + && ((mHistoryEnd.states^cur.states)&mChangedStates) == 0 + && ((mHistoryEnd.states2^cur.states2)&mChangedStates2) == 0) { // If the current is the same as the one before, then we no // longer need the entry. if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE - && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500) - && mHistoryLastEnd.same(mHistoryCur)) { + && (mHistoryBaseTime+elapsedRealtimeMs) < (mHistoryEnd.time+500) + && mHistoryLastEnd.sameNonEvent(cur)) { mHistoryLastEnd.next = null; mHistoryEnd.next = mHistoryCache; mHistoryCache = mHistoryEnd; mHistoryEnd = mHistoryLastEnd; mHistoryLastEnd = null; } else { - mChangedStates |= mHistoryEnd.states^mHistoryCur.states; - mHistoryEnd.setTo(mHistoryEnd.time, HistoryItem.CMD_UPDATE, mHistoryCur); + mChangedStates |= mHistoryEnd.states^cur.states; + mChangedStates2 |= mHistoryEnd.states^cur.states2; + mHistoryEnd.setTo(mHistoryEnd.time, HistoryItem.CMD_UPDATE, cur); } return; } mChangedStates = 0; + mChangedStates2 = 0; if (mNumHistoryItems == MAX_HISTORY_ITEMS || mNumHistoryItems == MAX_MAX_HISTORY_ITEMS) { - addHistoryRecordLocked(curTime, HistoryItem.CMD_OVERFLOW); + addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW); } if (mNumHistoryItems >= MAX_HISTORY_ITEMS) { @@ -1599,25 +2188,34 @@ public final class BatteryStatsImpl extends BatteryStats { // Once we've reached the maximum maximum number of items, we only // record changes to the battery level. if (mHistoryEnd != null && mHistoryEnd.batteryLevel - == mHistoryCur.batteryLevel && + == cur.batteryLevel && (mNumHistoryItems >= MAX_MAX_HISTORY_ITEMS - || ((mHistoryEnd.states^mHistoryCur.states) + || ((mHistoryEnd.states^cur.states) & HistoryItem.MOST_INTERESTING_STATES) == 0)) { return; } } - addHistoryRecordLocked(curTime, HistoryItem.CMD_UPDATE); + addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE); + } + + void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code, + String name, int uid) { + mHistoryCur.eventCode = code; + mHistoryCur.eventTag = mHistoryCur.localEventTag; + mHistoryCur.eventTag.string = name; + mHistoryCur.eventTag.uid = uid; + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } - void addHistoryRecordLocked(long curTime, byte cmd) { + void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs, byte cmd, HistoryItem cur) { HistoryItem rec = mHistoryCache; if (rec != null) { mHistoryCache = rec.next; } else { rec = new HistoryItem(); } - rec.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur); + rec.setTo(mHistoryBaseTime + elapsedRealtimeMs, cmd, cur); addHistoryRecordLocked(rec); } @@ -1646,114 +2244,247 @@ public final class BatteryStatsImpl extends BatteryStats { } mHistoryBaseTime = 0; - mLastHistoryTime = 0; + mLastHistoryElapsedRealtime = 0; + mTrackRunningHistoryElapsedRealtime = 0; + mTrackRunningHistoryUptime = 0; mHistoryBuffer.setDataSize(0); mHistoryBuffer.setDataPosition(0); - mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER/2); - mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL; - mHistoryLastWritten.cmd = HistoryItem.CMD_NULL; + mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER / 2); + mHistoryLastLastWritten.clear(); + mHistoryLastWritten.clear(); + mHistoryTagPool.clear(); + mNextHistoryTagIdx = 0; + mNumHistoryTagChars = 0; mHistoryBufferLastPos = -1; mHistoryOverflow = false; } - public void doUnplugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) { - for (int i = mUnpluggables.size() - 1; i >= 0; i--) { - mUnpluggables.get(i).unplug(elapsedRealtime, batteryUptime, batteryRealtime); + public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime, + long realtime) { + if (mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime)) { + if (unplugged) { + // Track bt headset ping count + mBluetoothPingStart = getCurrentBluetoothPingCount(); + mBluetoothPingCount = 0; + } else { + // Track bt headset ping count + mBluetoothPingCount = getBluetoothPingCount(); + mBluetoothPingStart = -1; + } + } + + boolean unpluggedScreenOff = unplugged && screenOff; + if (unpluggedScreenOff != mOnBatteryScreenOffTimeBase.isRunning()) { + updateKernelWakelocksLocked(); + requestWakelockCpuUpdate(); + if (!unpluggedScreenOff) { + // We are switching to no longer tracking wake locks, but we want + // the next CPU update we receive to take them in to account. + mDistributeWakelockCpu = true; + } + mOnBatteryScreenOffTimeBase.setRunning(unpluggedScreenOff, uptime, realtime); + } + } + + public void addIsolatedUidLocked(int isolatedUid, int appUid) { + mIsolatedUids.put(isolatedUid, appUid); + } + + public void removeIsolatedUidLocked(int isolatedUid, int appUid) { + int curUid = mIsolatedUids.get(isolatedUid, -1); + if (curUid == appUid) { + mIsolatedUids.delete(isolatedUid); } + } - // Track radio awake time - mRadioDataStart = getCurrentRadioDataUptime(); - mRadioDataUptime = 0; + public int mapUid(int uid) { + int isolated = mIsolatedUids.get(uid, -1); + return isolated > 0 ? isolated : uid; + } - // Track bt headset ping count - mBluetoothPingStart = getCurrentBluetoothPingCount(); - mBluetoothPingCount = 0; + public void noteEventLocked(int code, String name, int uid) { + uid = mapUid(uid); + if (!mActiveEvents.updateState(code, name, uid, 0)) { + return; + } + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + addHistoryEventLocked(elapsedRealtime, uptime, code, name, uid); } - public void doPlugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) { - for (int i = mUnpluggables.size() - 1; i >= 0; i--) { - mUnpluggables.get(i).plug(elapsedRealtime, batteryUptime, batteryRealtime); + private void requestWakelockCpuUpdate() { + if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) { + Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS); + mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS); } + } - // Track radio awake time - mRadioDataUptime = getRadioDataUptime(); - mRadioDataStart = -1; + public void setRecordAllWakeLocksLocked(boolean enabled) { + mRecordAllWakeLocks = enabled; + if (!enabled) { + // Clear out any existing state. + mActiveEvents.removeEvents(HistoryItem.EVENT_WAKE_LOCK); + } + } - // Track bt headset ping count - mBluetoothPingCount = getBluetoothPingCount(); - mBluetoothPingStart = -1; + public void setNoAutoReset(boolean enabled) { + mNoAutoReset = enabled; } - int mWakeLockNesting; + private String mInitialAcquireWakeName; + private int mInitialAcquireWakeUid = -1; - public void noteStartWakeLocked(int uid, int pid, String name, int type) { + public void noteStartWakeLocked(int uid, int pid, String name, String historyName, int type, + boolean unimportantForLogging, long elapsedRealtime, long uptime) { + uid = mapUid(uid); if (type == WAKE_TYPE_PARTIAL) { // Only care about partial wake locks, since full wake locks // will be canceled when the user puts the screen to sleep. + aggregateLastWakeupUptimeLocked(uptime); + if (mRecordAllWakeLocks) { + if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, name, uid, 0)) { + addHistoryEventLocked(elapsedRealtime, uptime, + HistoryItem.EVENT_WAKE_LOCK_START, name, uid); + } + } + historyName = historyName == null ? name : historyName; if (mWakeLockNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag; + mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName; + mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid; + mWakeLockImportant = !unimportantForLogging; + addHistoryRecordLocked(elapsedRealtime, uptime); + } else if (!mWakeLockImportant && !unimportantForLogging) { + if (mHistoryLastWritten.wakelockTag != null) { + // We'll try to update the last tag. + mHistoryLastWritten.wakelockTag = null; + mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag; + mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName; + mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid; + addHistoryRecordLocked(elapsedRealtime, uptime); + } + mWakeLockImportant = true; } mWakeLockNesting++; } if (uid >= 0) { - if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) { - Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS); - mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS); - } - getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type); + //if (uid == 0) { + // Slog.wtf(TAG, "Acquiring wake lock from root: " + name); + //} + requestWakelockCpuUpdate(); + getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type, elapsedRealtime); } } - public void noteStopWakeLocked(int uid, int pid, String name, int type) { + public void noteStopWakeLocked(int uid, int pid, String name, String historyName, int type, + long elapsedRealtime, long uptime) { + uid = mapUid(uid); if (type == WAKE_TYPE_PARTIAL) { mWakeLockNesting--; + if (mRecordAllWakeLocks) { + if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, name, uid, 0)) { + addHistoryEventLocked(elapsedRealtime, uptime, + HistoryItem.EVENT_WAKE_LOCK_FINISH, name, uid); + } + } if (mWakeLockNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + mInitialAcquireWakeName = null; + mInitialAcquireWakeUid = -1; + addHistoryRecordLocked(elapsedRealtime, uptime); } } if (uid >= 0) { - if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) { - Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS); - mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS); - } - getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type); + requestWakelockCpuUpdate(); + getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime); } } - public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) { - int N = ws.size(); + public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, + String historyName, int type, boolean unimportantForLogging) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + final int N = ws.size(); for (int i=0; i<N; i++) { - noteStartWakeLocked(ws.get(i), pid, name, type); + noteStartWakeLocked(ws.get(i), pid, name, historyName, type, unimportantForLogging, + elapsedRealtime, uptime); } } - public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) { - int N = ws.size(); + public void noteChangeWakelockFromSourceLocked(WorkSource ws, int pid, String name, + String historyName, int type, WorkSource newWs, int newPid, String newName, + String newHistoryName, int newType, boolean newUnimportantForLogging) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + // For correct semantics, we start the need worksources first, so that we won't + // make inappropriate history items as if all wake locks went away and new ones + // appeared. This is okay because tracking of wake locks allows nesting. + final int NN = newWs.size(); + for (int i=0; i<NN; i++) { + noteStartWakeLocked(newWs.get(i), newPid, newName, newHistoryName, newType, + newUnimportantForLogging, elapsedRealtime, uptime); + } + final int NO = ws.size(); + for (int i=0; i<NO; i++) { + noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime); + } + } + + public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name, + String historyName, int type) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + final int N = ws.size(); for (int i=0; i<N; i++) { - noteStopWakeLocked(ws.get(i), pid, name, type); + noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime); + } + } + + void aggregateLastWakeupUptimeLocked(long uptimeMs) { + if (mLastWakeupReason != null) { + long deltaUptime = uptimeMs - mLastWakeupUptimeMs; + LongSamplingCounter timer = getWakeupReasonCounterLocked(mLastWakeupReason); + timer.addCountLocked(deltaUptime); + mLastWakeupReason = null; } } + public void noteWakeupReasonLocked(String reason) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + if (DEBUG_HISTORY) Slog.v(TAG, "Wakeup reason reason \"" + reason +"\": " + + Integer.toHexString(mHistoryCur.states)); + aggregateLastWakeupUptimeLocked(uptime); + mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag; + mHistoryCur.wakeReasonTag.string = reason; + mHistoryCur.wakeReasonTag.uid = 0; + mLastWakeupReason = reason; + mLastWakeupUptimeMs = uptime; + addHistoryRecordLocked(elapsedRealtime, uptime); + } + public int startAddingCpuLocked() { mHandler.removeMessages(MSG_UPDATE_WAKELOCKS); - if (mScreenState == Display.STATE_ON) { - return 0; - } - final int N = mPartialTimers.size(); if (N == 0) { mLastPartialTimers.clear(); + mDistributeWakelockCpu = false; + return 0; + } + + if (!mOnBatteryScreenOffTimeBase.isRunning() && !mDistributeWakelockCpu) { return 0; } + mDistributeWakelockCpu = false; + // How many timers should consume CPU? Only want to include ones // that have already been in the list. for (int i=0; i<N; i++) { @@ -1840,6 +2571,7 @@ public final class BatteryStatsImpl extends BatteryStats { } public void noteProcessDiedLocked(int uid, int pid) { + uid = mapUid(uid); Uid u = mUidStats.get(uid); if (u != null) { u.mPids.remove(pid); @@ -1847,17 +2579,19 @@ public final class BatteryStatsImpl extends BatteryStats { } public long getProcessWakeTime(int uid, int pid, long realtime) { + uid = mapUid(uid); Uid u = mUidStats.get(uid); if (u != null) { Uid.Pid p = u.mPids.get(pid); if (p != null) { - return p.mWakeSum + (p.mWakeStart != 0 ? (realtime - p.mWakeStart) : 0); + return p.mWakeSumMs + (p.mWakeNesting > 0 ? (realtime - p.mWakeStartMs) : 0); } } return 0; } public void reportExcessiveWakeLocked(int uid, String proc, long overTime, long usedTime) { + uid = mapUid(uid); Uid u = mUidStats.get(uid); if (u != null) { u.reportExcessiveWakeLocked(proc, overTime, usedTime); @@ -1865,6 +2599,7 @@ public final class BatteryStatsImpl extends BatteryStats { } public void reportExcessiveCpuLocked(int uid, String proc, long overTime, long usedTime) { + uid = mapUid(uid); Uid u = mUidStats.get(uid); if (u != null) { u.reportExcessiveCpuLocked(proc, overTime, usedTime); @@ -1874,49 +2609,61 @@ public final class BatteryStatsImpl extends BatteryStats { int mSensorNesting; public void noteStartSensorLocked(int uid, int sensor) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); if (mSensorNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); } mSensorNesting++; - getUidStatsLocked(uid).noteStartSensor(sensor); + getUidStatsLocked(uid).noteStartSensor(sensor, elapsedRealtime); } public void noteStopSensorLocked(int uid, int sensor) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mSensorNesting--; if (mSensorNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); } - getUidStatsLocked(uid).noteStopSensor(sensor); + getUidStatsLocked(uid).noteStopSensor(sensor, elapsedRealtime); } int mGpsNesting; public void noteStartGpsLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); if (mGpsNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); } mGpsNesting++; - getUidStatsLocked(uid).noteStartGps(); + getUidStatsLocked(uid).noteStartGps(elapsedRealtime); } public void noteStopGpsLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mGpsNesting--; if (mGpsNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); } - getUidStatsLocked(uid).noteStopGps(); + getUidStatsLocked(uid).noteStopGps(elapsedRealtime); } public void noteScreenStateLocked(int state) { @@ -1928,18 +2675,24 @@ public final class BatteryStatsImpl extends BatteryStats { if (state == Display.STATE_ON) { // Screen turning on. + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); - mScreenOnTimer.startRunningLocked(this); + addHistoryRecordLocked(elapsedRealtime, uptime); + mScreenOnTimer.startRunningLocked(elapsedRealtime); if (mScreenBrightnessBin >= 0) { - mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(this); + mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(elapsedRealtime); } + updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), false, + SystemClock.uptimeMillis() * 1000, elapsedRealtime * 1000); + // Fake a wake lock, so we consider the device waked as long // as the screen is on. - noteStartWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL); + noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false, + elapsedRealtime, uptime); // Update discharge amounts. if (mOnBatteryInternal) { @@ -1947,16 +2700,22 @@ public final class BatteryStatsImpl extends BatteryStats { } } else if (oldState == Display.STATE_ON) { // Screen turning off or dozing. + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); - mScreenOnTimer.stopRunningLocked(this); + addHistoryRecordLocked(elapsedRealtime, uptime); + mScreenOnTimer.stopRunningLocked(elapsedRealtime); if (mScreenBrightnessBin >= 0) { - mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this); + mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime); } - noteStopWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL); + noteStopWakeLocked(-1, -1, "screen", "screen", WAKE_TYPE_PARTIAL, + elapsedRealtime, uptime); + + updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), true, + SystemClock.uptimeMillis() * 1000, elapsedRealtime * 1000); // Update discharge amounts. if (mOnBatteryInternal) { @@ -1972,66 +2731,136 @@ public final class BatteryStatsImpl extends BatteryStats { if (bin < 0) bin = 0; else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1; if (mScreenBrightnessBin != bin) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK) | (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT); if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); if (mScreenState == Display.STATE_ON) { if (mScreenBrightnessBin >= 0) { - mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this); + mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime); } - mScreenBrightnessTimer[bin].startRunningLocked(this); + mScreenBrightnessTimer[bin].startRunningLocked(elapsedRealtime); } mScreenBrightnessBin = bin; } } public void noteUserActivityLocked(int uid, int event) { - getUidStatsLocked(uid).noteUserActivityLocked(event); + if (mOnBatteryInternal) { + uid = mapUid(uid); + getUidStatsLocked(uid).noteUserActivityLocked(event); + } } public void noteInteractiveLocked(boolean interactive) { if (mInteractive != interactive) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); mInteractive = interactive; if (DEBUG) Slog.v(TAG, "Interactive: " + interactive); if (interactive) { - mInteractiveTimer.startRunningLocked(this); + mInteractiveTimer.startRunningLocked(elapsedRealtime); } else { - mInteractiveTimer.stopRunningLocked(this); + mInteractiveTimer.stopRunningLocked(elapsedRealtime); } } } + public void noteMobileRadioPowerState(int powerState, long timestampNs) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + if (mMobileRadioPowerState != powerState) { + long realElapsedRealtimeMs; + final boolean active = + powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM + || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH; + if (active) { + realElapsedRealtimeMs = elapsedRealtime; + mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG; + } else { + realElapsedRealtimeMs = timestampNs / (1000*1000); + long lastUpdateTimeMs = mMobileRadioActiveTimer.getLastUpdateTimeMs(); + if (realElapsedRealtimeMs < lastUpdateTimeMs) { + Slog.wtf(TAG, "Data connection inactive timestamp " + realElapsedRealtimeMs + + " is before start time " + lastUpdateTimeMs); + realElapsedRealtimeMs = elapsedRealtime; + } else if (realElapsedRealtimeMs < elapsedRealtime) { + mMobileRadioActiveAdjustedTime.addCountLocked(elapsedRealtime + - realElapsedRealtimeMs); + } + mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG; + } + if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtime, uptime); + mMobileRadioPowerState = powerState; + if (active) { + mMobileRadioActiveTimer.startRunningLocked(elapsedRealtime); + mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime); + } else { + mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs); + updateNetworkActivityLocked(NET_UPDATE_MOBILE, realElapsedRealtimeMs); + mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs); + } + } + } + + public void noteLowPowerMode(boolean enabled) { + if (mLowPowerModeEnabled != enabled) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + mLowPowerModeEnabled = enabled; + if (enabled) { + mHistoryCur.states2 |= HistoryItem.STATE2_LOW_POWER_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Low power mode enabled to: " + + Integer.toHexString(mHistoryCur.states2)); + mLowPowerModeEnabledTimer.startRunningLocked(elapsedRealtime); + } else { + mHistoryCur.states2 &= ~HistoryItem.STATE2_LOW_POWER_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Low power mode disabled to: " + + Integer.toHexString(mHistoryCur.states2)); + mLowPowerModeEnabledTimer.stopRunningLocked(elapsedRealtime); + } + addHistoryRecordLocked(elapsedRealtime, uptime); + } + } + public void notePhoneOnLocked() { if (!mPhoneOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mHistoryCur.states |= HistoryItem.STATE_PHONE_IN_CALL_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mPhoneOn = true; - mPhoneOnTimer.startRunningLocked(this); + mPhoneOnTimer.startRunningLocked(elapsedRealtime); } } public void notePhoneOffLocked() { if (mPhoneOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mHistoryCur.states &= ~HistoryItem.STATE_PHONE_IN_CALL_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mPhoneOn = false; - mPhoneOnTimer.stopRunningLocked(this); + mPhoneOnTimer.stopRunningLocked(elapsedRealtime); } } void stopAllSignalStrengthTimersLocked(int except) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { if (i == except) { continue; } while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) { - mPhoneSignalStrengthsTimer[i].stopRunningLocked(this); + mPhoneSignalStrengthsTimer[i].stopRunningLocked(elapsedRealtime); } } } @@ -2049,26 +2878,29 @@ public final class BatteryStatsImpl extends BatteryStats { return state; } - private void updateAllPhoneStateLocked(int state, int simState, int bin) { + private void updateAllPhoneStateLocked(int state, int simState, int strengthBin) { boolean scanning = false; boolean newHistory = false; mPhoneServiceStateRaw = state; mPhoneSimStateRaw = simState; - mPhoneSignalStrengthBinRaw = bin; + mPhoneSignalStrengthBinRaw = strengthBin; + + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); if (simState == TelephonyManager.SIM_STATE_ABSENT) { // In this case we will always be STATE_OUT_OF_SERVICE, so need // to infer that we are scanning from other data. if (state == ServiceState.STATE_OUT_OF_SERVICE - && bin > SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { + && strengthBin > SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { state = ServiceState.STATE_IN_SERVICE; } } // If the phone is powered off, stop all timers. if (state == ServiceState.STATE_POWER_OFF) { - bin = -1; + strengthBin = -1; // If we are in service, make sure the correct signal string timer is running. } else if (state == ServiceState.STATE_IN_SERVICE) { @@ -2078,13 +2910,13 @@ public final class BatteryStatsImpl extends BatteryStats { // bin and have the scanning bit set. } else if (state == ServiceState.STATE_OUT_OF_SERVICE) { scanning = true; - bin = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + strengthBin = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; if (!mPhoneSignalScanningTimer.isRunningLocked()) { mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG; newHistory = true; if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: " + Integer.toHexString(mHistoryCur.states)); - mPhoneSignalScanningTimer.startRunningLocked(this); + mPhoneSignalScanningTimer.startRunningLocked(elapsedRealtime); } } @@ -2095,7 +2927,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: " + Integer.toHexString(mHistoryCur.states)); newHistory = true; - mPhoneSignalScanningTimer.stopRunningLocked(this); + mPhoneSignalScanningTimer.stopRunningLocked(elapsedRealtime); } } @@ -2108,27 +2940,28 @@ public final class BatteryStatsImpl extends BatteryStats { mPhoneServiceState = state; } - if (mPhoneSignalStrengthBin != bin) { + if (mPhoneSignalStrengthBin != strengthBin) { if (mPhoneSignalStrengthBin >= 0) { - mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(this); + mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked( + elapsedRealtime); } - if (bin >= 0) { - if (!mPhoneSignalStrengthsTimer[bin].isRunningLocked()) { - mPhoneSignalStrengthsTimer[bin].startRunningLocked(this); + if (strengthBin >= 0) { + if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) { + mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime); } mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_SIGNAL_STRENGTH_MASK) - | (bin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT); - if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + bin + " to: " + | (strengthBin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: " + Integer.toHexString(mHistoryCur.states)); newHistory = true; } else { stopAllSignalStrengthTimersLocked(-1); } - mPhoneSignalStrengthBin = bin; + mPhoneSignalStrengthBin = strengthBin; } if (newHistory) { - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); } } @@ -2202,120 +3035,142 @@ public final class BatteryStatsImpl extends BatteryStats { } if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData); if (mPhoneDataConnectionType != bin) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK) | (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT); if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); if (mPhoneDataConnectionType >= 0) { - mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(this); + mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked( + elapsedRealtime); } mPhoneDataConnectionType = bin; - mPhoneDataConnectionsTimer[bin].startRunningLocked(this); + mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtime); } } public void noteWifiOnLocked() { if (!mWifiOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mHistoryCur.states |= HistoryItem.STATE_WIFI_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mWifiOn = true; - mWifiOnTimer.startRunningLocked(this); + mWifiOnTimer.startRunningLocked(elapsedRealtime); } } public void noteWifiOffLocked() { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); if (mWifiOn) { mHistoryCur.states &= ~HistoryItem.STATE_WIFI_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mWifiOn = false; - mWifiOnTimer.stopRunningLocked(this); - } - if (mWifiOnUid >= 0) { - getUidStatsLocked(mWifiOnUid).noteWifiStoppedLocked(); - mWifiOnUid = -1; + mWifiOnTimer.stopRunningLocked(elapsedRealtime); } } public void noteAudioOnLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); if (!mAudioOn) { mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mAudioOn = true; - mAudioOnTimer.startRunningLocked(this); + mAudioOnTimer.startRunningLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteAudioTurnedOnLocked(); + getUidStatsLocked(uid).noteAudioTurnedOnLocked(elapsedRealtime); } public void noteAudioOffLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); if (mAudioOn) { mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mAudioOn = false; - mAudioOnTimer.stopRunningLocked(this); + mAudioOnTimer.stopRunningLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteAudioTurnedOffLocked(); + getUidStatsLocked(uid).noteAudioTurnedOffLocked(elapsedRealtime); } public void noteVideoOnLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); if (!mVideoOn) { - mHistoryCur.states |= HistoryItem.STATE_VIDEO_ON_FLAG; + mHistoryCur.states2 |= HistoryItem.STATE2_VIDEO_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mVideoOn = true; - mVideoOnTimer.startRunningLocked(this); + mVideoOnTimer.startRunningLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteVideoTurnedOnLocked(); + getUidStatsLocked(uid).noteVideoTurnedOnLocked(elapsedRealtime); } public void noteVideoOffLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); if (mVideoOn) { - mHistoryCur.states &= ~HistoryItem.STATE_VIDEO_ON_FLAG; + mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mVideoOn = false; - mVideoOnTimer.stopRunningLocked(this); + mVideoOnTimer.stopRunningLocked(elapsedRealtime); } - getUidStatsLocked(uid).noteVideoTurnedOffLocked(); + getUidStatsLocked(uid).noteVideoTurnedOffLocked(elapsedRealtime); } public void noteActivityResumedLocked(int uid) { - getUidStatsLocked(uid).noteActivityResumedLocked(); + uid = mapUid(uid); + getUidStatsLocked(uid).noteActivityResumedLocked(SystemClock.elapsedRealtime()); } public void noteActivityPausedLocked(int uid) { - getUidStatsLocked(uid).noteActivityPausedLocked(); + uid = mapUid(uid); + getUidStatsLocked(uid).noteActivityPausedLocked(SystemClock.elapsedRealtime()); } public void noteVibratorOnLocked(int uid, long durationMillis) { + uid = mapUid(uid); getUidStatsLocked(uid).noteVibratorOnLocked(durationMillis); } public void noteVibratorOffLocked(int uid) { + uid = mapUid(uid); getUidStatsLocked(uid).noteVibratorOffLocked(); } public void noteWifiRunningLocked(WorkSource ws) { if (!mGlobalWifiRunning) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mHistoryCur.states |= HistoryItem.STATE_WIFI_RUNNING_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mGlobalWifiRunning = true; - mGlobalWifiRunningTimer.startRunningLocked(this); + mGlobalWifiRunningTimer.startRunningLocked(elapsedRealtime); int N = ws.size(); for (int i=0; i<N; i++) { - getUidStatsLocked(ws.get(i)).noteWifiRunningLocked(); + int uid = mapUid(ws.get(i)); + getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime); } } else { Log.w(TAG, "noteWifiRunningLocked -- called while WIFI running"); @@ -2324,13 +3179,16 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiRunningChangedLocked(WorkSource oldWs, WorkSource newWs) { if (mGlobalWifiRunning) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); int N = oldWs.size(); for (int i=0; i<N; i++) { - getUidStatsLocked(oldWs.get(i)).noteWifiStoppedLocked(); + int uid = mapUid(oldWs.get(i)); + getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime); } N = newWs.size(); for (int i=0; i<N; i++) { - getUidStatsLocked(newWs.get(i)).noteWifiRunningLocked(); + int uid = mapUid(newWs.get(i)); + getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime); } } else { Log.w(TAG, "noteWifiRunningChangedLocked -- called while WIFI not running"); @@ -2339,121 +3197,174 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiStoppedLocked(WorkSource ws) { if (mGlobalWifiRunning) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RUNNING_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mGlobalWifiRunning = false; - mGlobalWifiRunningTimer.stopRunningLocked(this); + mGlobalWifiRunningTimer.stopRunningLocked(elapsedRealtime); int N = ws.size(); for (int i=0; i<N; i++) { - getUidStatsLocked(ws.get(i)).noteWifiStoppedLocked(); + int uid = mapUid(ws.get(i)); + getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime); } } else { Log.w(TAG, "noteWifiStoppedLocked -- called while WIFI not running"); } } + public void noteWifiStateLocked(int wifiState, String accessPoint) { + if (DEBUG) Log.i(TAG, "WiFi state -> " + wifiState); + if (mWifiState != wifiState) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + if (mWifiState >= 0) { + mWifiStateTimer[mWifiState].stopRunningLocked(elapsedRealtime); + } + mWifiState = wifiState; + mWifiStateTimer[wifiState].startRunningLocked(elapsedRealtime); + } + } + public void noteBluetoothOnLocked() { if (!mBluetoothOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mHistoryCur.states |= HistoryItem.STATE_BLUETOOTH_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mBluetoothOn = true; - mBluetoothOnTimer.startRunningLocked(this); + mBluetoothOnTimer.startRunningLocked(elapsedRealtime); } } public void noteBluetoothOffLocked() { if (mBluetoothOn) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mHistoryCur.states &= ~HistoryItem.STATE_BLUETOOTH_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); mBluetoothOn = false; - mBluetoothOnTimer.stopRunningLocked(this); + mBluetoothOnTimer.stopRunningLocked(elapsedRealtime); + } + } + + public void noteBluetoothStateLocked(int bluetoothState) { + if (DEBUG) Log.i(TAG, "Bluetooth state -> " + bluetoothState); + if (mBluetoothState != bluetoothState) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + if (mBluetoothState >= 0) { + mBluetoothStateTimer[mBluetoothState].stopRunningLocked(elapsedRealtime); + } + mBluetoothState = bluetoothState; + mBluetoothStateTimer[bluetoothState].startRunningLocked(elapsedRealtime); } } int mWifiFullLockNesting = 0; public void noteFullWifiLockAcquiredLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); if (mWifiFullLockNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); } mWifiFullLockNesting++; - getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(); + getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(elapsedRealtime); } public void noteFullWifiLockReleasedLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mWifiFullLockNesting--; if (mWifiFullLockNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); } - getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(); + getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(elapsedRealtime); } int mWifiScanNesting = 0; public void noteWifiScanStartedLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); if (mWifiScanNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan started for: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); } mWifiScanNesting++; - getUidStatsLocked(uid).noteWifiScanStartedLocked(); + getUidStatsLocked(uid).noteWifiScanStartedLocked(elapsedRealtime); } public void noteWifiScanStoppedLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mWifiScanNesting--; if (mWifiScanNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan stopped for: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); } - getUidStatsLocked(uid).noteWifiScanStoppedLocked(); + getUidStatsLocked(uid).noteWifiScanStoppedLocked(elapsedRealtime); } public void noteWifiBatchedScanStartedLocked(int uid, int csph) { - getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph); + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph, elapsedRealtime); } public void noteWifiBatchedScanStoppedLocked(int uid) { - getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked(); + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked(elapsedRealtime); } int mWifiMulticastNesting = 0; public void noteWifiMulticastEnabledLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); if (mWifiMulticastNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); } mWifiMulticastNesting++; - getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(); + getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime); } public void noteWifiMulticastDisabledLocked(int uid) { + uid = mapUid(uid); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); mWifiMulticastNesting--; if (mWifiMulticastNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); } - getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(); + getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime); } public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) { @@ -2512,16 +3423,45 @@ public final class BatteryStatsImpl extends BatteryStats { } } + private static String[] includeInStringArray(String[] array, String str) { + if (ArrayUtils.indexOf(array, str) >= 0) { + return array; + } + String[] newArray = new String[array.length+1]; + System.arraycopy(array, 0, newArray, 0, array.length); + newArray[array.length] = str; + return newArray; + } + + private static String[] excludeFromStringArray(String[] array, String str) { + int index = ArrayUtils.indexOf(array, str); + if (index >= 0) { + String[] newArray = new String[array.length-1]; + if (index > 0) { + System.arraycopy(array, 0, newArray, 0, index); + } + if (index < array.length-1) { + System.arraycopy(array, index+1, newArray, index, array.length-index-1); + } + return newArray; + } + return array; + } + public void noteNetworkInterfaceTypeLocked(String iface, int networkType) { if (ConnectivityManager.isNetworkTypeMobile(networkType)) { - mMobileIfaces.add(iface); + mMobileIfaces = includeInStringArray(mMobileIfaces, iface); + if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mMobileIfaces); } else { - mMobileIfaces.remove(iface); + mMobileIfaces = excludeFromStringArray(mMobileIfaces, iface); + if (DEBUG) Slog.d(TAG, "Note non-mobile iface " + iface + ": " + mMobileIfaces); } if (ConnectivityManager.isNetworkTypeWifi(networkType)) { - mWifiIfaces.add(iface); + mWifiIfaces = includeInStringArray(mWifiIfaces, iface); + if (DEBUG) Slog.d(TAG, "Note wifi iface " + iface + ": " + mWifiIfaces); } else { - mWifiIfaces.remove(iface); + mWifiIfaces = excludeFromStringArray(mWifiIfaces, iface); + if (DEBUG) Slog.d(TAG, "Note non-wifi iface " + iface + ": " + mWifiIfaces); } } @@ -2529,37 +3469,53 @@ 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(); + updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime()); + } + + @Override public long getScreenOnTime(long elapsedRealtimeUs, int which) { + return mScreenOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } - @Override public long getScreenOnTime(long batteryRealtime, int which) { - return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which); + @Override public int getScreenOnCount(int which) { + return mScreenOnTimer.getCountLocked(which); } @Override public long getScreenBrightnessTime(int brightnessBin, - long batteryRealtime, int which) { + long elapsedRealtimeUs, int which) { return mScreenBrightnessTimer[brightnessBin].getTotalTimeLocked( - batteryRealtime, which); + elapsedRealtimeUs, which); } - @Override public long getInteractiveTime(long batteryRealtime, int which) { - return mInteractiveTimer.getTotalTimeLocked(batteryRealtime, which); + @Override public long getInteractiveTime(long elapsedRealtimeUs, int which) { + return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } - @Override public long getPhoneOnTime(long batteryRealtime, int which) { - return mPhoneOnTimer.getTotalTimeLocked(batteryRealtime, which); + @Override public long getLowPowerModeEnabledTime(long elapsedRealtimeUs, int which) { + return mLowPowerModeEnabledTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public int getLowPowerModeEnabledCount(int which) { + return mLowPowerModeEnabledTimer.getCountLocked(which); + } + + @Override public long getPhoneOnTime(long elapsedRealtimeUs, int which) { + return mPhoneOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public int getPhoneOnCount(int which) { + return mPhoneOnTimer.getCountLocked(which); } @Override public long getPhoneSignalStrengthTime(int strengthBin, - long batteryRealtime, int which) { + long elapsedRealtimeUs, int which) { return mPhoneSignalStrengthsTimer[strengthBin].getTotalTimeLocked( - batteryRealtime, which); + elapsedRealtimeUs, which); } @Override public long getPhoneSignalScanningTime( - long batteryRealtime, int which) { + long elapsedRealtimeUs, int which) { return mPhoneSignalScanningTimer.getTotalTimeLocked( - batteryRealtime, which); + elapsedRealtimeUs, which); } @Override public int getPhoneSignalStrengthCount(int strengthBin, int which) { @@ -2567,36 +3523,89 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override public long getPhoneDataConnectionTime(int dataType, - long batteryRealtime, int which) { + long elapsedRealtimeUs, int which) { return mPhoneDataConnectionsTimer[dataType].getTotalTimeLocked( - batteryRealtime, which); + elapsedRealtimeUs, which); } @Override public int getPhoneDataConnectionCount(int dataType, int which) { return mPhoneDataConnectionsTimer[dataType].getCountLocked(which); } - @Override public long getWifiOnTime(long batteryRealtime, int which) { - return mWifiOnTimer.getTotalTimeLocked(batteryRealtime, which); + @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) { + return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public int getMobileRadioActiveCount(int which) { + return mMobileRadioActiveTimer.getCountLocked(which); + } + + @Override public long getMobileRadioActiveAdjustedTime(int which) { + return mMobileRadioActiveAdjustedTime.getCountLocked(which); } - @Override public long getGlobalWifiRunningTime(long batteryRealtime, int which) { - return mGlobalWifiRunningTimer.getTotalTimeLocked(batteryRealtime, which); + @Override public long getMobileRadioActiveUnknownTime(int which) { + return mMobileRadioActiveUnknownTime.getCountLocked(which); } - @Override public long getBluetoothOnTime(long batteryRealtime, int which) { - return mBluetoothOnTimer.getTotalTimeLocked(batteryRealtime, which); + @Override public int getMobileRadioActiveUnknownCount(int which) { + return (int)mMobileRadioActiveUnknownCount.getCountLocked(which); + } + + @Override public long getWifiOnTime(long elapsedRealtimeUs, int which) { + return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which) { + return mGlobalWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public long getWifiStateTime(int wifiState, + long elapsedRealtimeUs, int which) { + return mWifiStateTimer[wifiState].getTotalTimeLocked( + elapsedRealtimeUs, which); + } + + @Override public int getWifiStateCount(int wifiState, int which) { + return mWifiStateTimer[wifiState].getCountLocked(which); + } + + @Override public long getBluetoothOnTime(long elapsedRealtimeUs, int which) { + return mBluetoothOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public long getBluetoothStateTime(int bluetoothState, + long elapsedRealtimeUs, int which) { + return mBluetoothStateTimer[bluetoothState].getTotalTimeLocked( + elapsedRealtimeUs, which); + } + + @Override public int getBluetoothStateCount(int bluetoothState, int which) { + return mBluetoothStateTimer[bluetoothState].getCountLocked(which); + } + + @Override + public long getNetworkActivityBytes(int type, int which) { + if (type >= 0 && type < mNetworkByteActivityCounters.length) { + return mNetworkByteActivityCounters[type].getCountLocked(which); + } else { + return 0; + } } @Override - public long getNetworkActivityCount(int type, int which) { - if (type >= 0 && type < mNetworkActivityCounters.length) { - return mNetworkActivityCounters[type].getCountLocked(which); + public long getNetworkActivityPackets(int type, int which) { + if (type >= 0 && type < mNetworkPacketActivityCounters.length) { + return mNetworkPacketActivityCounters[type].getCountLocked(which); } else { return 0; } } + @Override public long getStartClockTime() { + return mStartClockTime; + } + @Override public boolean getIsOnBattery() { return mOnBattery; } @@ -2640,7 +3649,10 @@ public final class BatteryStatsImpl extends BatteryStats { Counter[] mUserActivityCounters; - LongSamplingCounter[] mNetworkActivityCounters; + LongSamplingCounter[] mNetworkByteActivityCounters; + LongSamplingCounter[] mNetworkPacketActivityCounters; + LongSamplingCounter mMobileRadioActiveTime; + LongSamplingCounter mMobileRadioActiveCount; /** * The statistics we have collected for this uid's wake locks. @@ -2670,14 +3682,14 @@ public final class BatteryStatsImpl extends BatteryStats { public Uid(int uid) { mUid = uid; mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING, - mWifiRunningTimers, mUnpluggables); + mWifiRunningTimers, mOnBatteryTimeBase); mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK, - mFullWifiLockTimers, mUnpluggables); + mFullWifiLockTimers, mOnBatteryTimeBase); mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN, - mWifiScanTimers, mUnpluggables); + mWifiScanTimers, mOnBatteryTimeBase); mWifiBatchedScanTimer = new StopwatchTimer[NUM_WIFI_BATCHED_SCAN_BINS]; mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, - mWifiMulticastTimers, mUnpluggables); + mWifiMulticastTimers, mOnBatteryTimeBase); } @Override @@ -2706,67 +3718,67 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override - public void noteWifiRunningLocked() { + public void noteWifiRunningLocked(long elapsedRealtimeMs) { if (!mWifiRunning) { mWifiRunning = true; if (mWifiRunningTimer == null) { mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING, - mWifiRunningTimers, mUnpluggables); + mWifiRunningTimers, mOnBatteryTimeBase); } - mWifiRunningTimer.startRunningLocked(BatteryStatsImpl.this); + mWifiRunningTimer.startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteWifiStoppedLocked() { + public void noteWifiStoppedLocked(long elapsedRealtimeMs) { if (mWifiRunning) { mWifiRunning = false; - mWifiRunningTimer.stopRunningLocked(BatteryStatsImpl.this); + mWifiRunningTimer.stopRunningLocked(elapsedRealtimeMs); } } @Override - public void noteFullWifiLockAcquiredLocked() { + public void noteFullWifiLockAcquiredLocked(long elapsedRealtimeMs) { if (!mFullWifiLockOut) { mFullWifiLockOut = true; if (mFullWifiLockTimer == null) { mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK, - mFullWifiLockTimers, mUnpluggables); + mFullWifiLockTimers, mOnBatteryTimeBase); } - mFullWifiLockTimer.startRunningLocked(BatteryStatsImpl.this); + mFullWifiLockTimer.startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteFullWifiLockReleasedLocked() { + public void noteFullWifiLockReleasedLocked(long elapsedRealtimeMs) { if (mFullWifiLockOut) { mFullWifiLockOut = false; - mFullWifiLockTimer.stopRunningLocked(BatteryStatsImpl.this); + mFullWifiLockTimer.stopRunningLocked(elapsedRealtimeMs); } } @Override - public void noteWifiScanStartedLocked() { + public void noteWifiScanStartedLocked(long elapsedRealtimeMs) { if (!mWifiScanStarted) { mWifiScanStarted = true; if (mWifiScanTimer == null) { mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN, - mWifiScanTimers, mUnpluggables); + mWifiScanTimers, mOnBatteryTimeBase); } - mWifiScanTimer.startRunningLocked(BatteryStatsImpl.this); + mWifiScanTimer.startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteWifiScanStoppedLocked() { + public void noteWifiScanStoppedLocked(long elapsedRealtimeMs) { if (mWifiScanStarted) { mWifiScanStarted = false; - mWifiScanTimer.stopRunningLocked(BatteryStatsImpl.this); + mWifiScanTimer.stopRunningLocked(elapsedRealtimeMs); } } @Override - public void noteWifiBatchedScanStartedLocked(int csph) { + public void noteWifiBatchedScanStartedLocked(int csph, long elapsedRealtimeMs) { int bin = 0; while (csph > 8 && bin < NUM_WIFI_BATCHED_SCAN_BINS) { csph = csph >> 3; @@ -2777,66 +3789,66 @@ public final class BatteryStatsImpl extends BatteryStats { if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) { mWifiBatchedScanTimer[mWifiBatchedScanBinStarted]. - stopRunningLocked(BatteryStatsImpl.this); + stopRunningLocked(elapsedRealtimeMs); } mWifiBatchedScanBinStarted = bin; if (mWifiBatchedScanTimer[bin] == null) { makeWifiBatchedScanBin(bin, null); } - mWifiBatchedScanTimer[bin].startRunningLocked(BatteryStatsImpl.this); + mWifiBatchedScanTimer[bin].startRunningLocked(elapsedRealtimeMs); } @Override - public void noteWifiBatchedScanStoppedLocked() { + public void noteWifiBatchedScanStoppedLocked(long elapsedRealtimeMs) { if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) { mWifiBatchedScanTimer[mWifiBatchedScanBinStarted]. - stopRunningLocked(BatteryStatsImpl.this); + stopRunningLocked(elapsedRealtimeMs); mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED; } } @Override - public void noteWifiMulticastEnabledLocked() { + public void noteWifiMulticastEnabledLocked(long elapsedRealtimeMs) { if (!mWifiMulticastEnabled) { mWifiMulticastEnabled = true; if (mWifiMulticastTimer == null) { mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, - mWifiMulticastTimers, mUnpluggables); + mWifiMulticastTimers, mOnBatteryTimeBase); } - mWifiMulticastTimer.startRunningLocked(BatteryStatsImpl.this); + mWifiMulticastTimer.startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteWifiMulticastDisabledLocked() { + public void noteWifiMulticastDisabledLocked(long elapsedRealtimeMs) { if (mWifiMulticastEnabled) { mWifiMulticastEnabled = false; - mWifiMulticastTimer.stopRunningLocked(BatteryStatsImpl.this); + mWifiMulticastTimer.stopRunningLocked(elapsedRealtimeMs); } } public StopwatchTimer createAudioTurnedOnTimerLocked() { if (mAudioTurnedOnTimer == null) { mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON, - null, mUnpluggables); + null, mOnBatteryTimeBase); } return mAudioTurnedOnTimer; } @Override - public void noteAudioTurnedOnLocked() { + public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) { if (!mAudioTurnedOn) { mAudioTurnedOn = true; - createAudioTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this); + createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteAudioTurnedOffLocked() { + public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) { if (mAudioTurnedOn) { mAudioTurnedOn = false; if (mAudioTurnedOnTimer != null) { - mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this); + mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); } } } @@ -2844,25 +3856,25 @@ public final class BatteryStatsImpl extends BatteryStats { public StopwatchTimer createVideoTurnedOnTimerLocked() { if (mVideoTurnedOnTimer == null) { mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON, - null, mUnpluggables); + null, mOnBatteryTimeBase); } return mVideoTurnedOnTimer; } @Override - public void noteVideoTurnedOnLocked() { + public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) { if (!mVideoTurnedOn) { mVideoTurnedOn = true; - createVideoTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this); + createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); } } @Override - public void noteVideoTurnedOffLocked() { + public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) { if (mVideoTurnedOn) { mVideoTurnedOn = false; if (mVideoTurnedOnTimer != null) { - mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this); + mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); } } } @@ -2870,28 +3882,27 @@ public final class BatteryStatsImpl extends BatteryStats { public StopwatchTimer createForegroundActivityTimerLocked() { if (mForegroundActivityTimer == null) { mForegroundActivityTimer = new StopwatchTimer( - Uid.this, FOREGROUND_ACTIVITY, null, mUnpluggables); + Uid.this, FOREGROUND_ACTIVITY, null, mOnBatteryTimeBase); } return mForegroundActivityTimer; } @Override - public void noteActivityResumedLocked() { + public void noteActivityResumedLocked(long elapsedRealtimeMs) { // We always start, since we want multiple foreground PIDs to nest - createForegroundActivityTimerLocked().startRunningLocked(BatteryStatsImpl.this); + createForegroundActivityTimerLocked().startRunningLocked(elapsedRealtimeMs); } @Override - public void noteActivityPausedLocked() { + public void noteActivityPausedLocked(long elapsedRealtimeMs) { if (mForegroundActivityTimer != null) { - mForegroundActivityTimer.stopRunningLocked(BatteryStatsImpl.this); + mForegroundActivityTimer.stopRunningLocked(elapsedRealtimeMs); } } public BatchTimer createVibratorOnTimerLocked() { if (mVibratorOnTimer == null) { - mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, - mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal); + mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, mOnBatteryTimeBase); } return mVibratorOnTimer; } @@ -2907,61 +3918,60 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override - public long getWifiRunningTime(long batteryRealtime, int which) { + public long getWifiRunningTime(long elapsedRealtimeUs, int which) { if (mWifiRunningTimer == null) { return 0; } - return mWifiRunningTimer.getTotalTimeLocked(batteryRealtime, which); + return mWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getFullWifiLockTime(long batteryRealtime, int which) { + public long getFullWifiLockTime(long elapsedRealtimeUs, int which) { if (mFullWifiLockTimer == null) { return 0; } - return mFullWifiLockTimer.getTotalTimeLocked(batteryRealtime, which); + return mFullWifiLockTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getWifiScanTime(long batteryRealtime, int which) { + public long getWifiScanTime(long elapsedRealtimeUs, int which) { if (mWifiScanTimer == null) { return 0; } - return mWifiScanTimer.getTotalTimeLocked(batteryRealtime, which); + return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which) { + public long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which) { if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0; if (mWifiBatchedScanTimer[csphBin] == null) { return 0; } - return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(batteryRealtime, which); + return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getWifiMulticastTime(long batteryRealtime, int which) { + public long getWifiMulticastTime(long elapsedRealtimeUs, int which) { if (mWifiMulticastTimer == null) { return 0; } - return mWifiMulticastTimer.getTotalTimeLocked(batteryRealtime, - which); + return mWifiMulticastTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getAudioTurnedOnTime(long batteryRealtime, int which) { + public long getAudioTurnedOnTime(long elapsedRealtimeUs, int which) { if (mAudioTurnedOnTimer == null) { return 0; } - return mAudioTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which); + return mAudioTurnedOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override - public long getVideoTurnedOnTime(long batteryRealtime, int which) { + public long getVideoTurnedOnTime(long elapsedRealtimeUs, int which) { if (mVideoTurnedOnTimer == null) { return 0; } - return mVideoTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which); + return mVideoTurnedOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override @@ -3010,10 +4020,10 @@ public final class BatteryStatsImpl extends BatteryStats { } if (in == null) { mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected, - mUnpluggables); + mOnBatteryTimeBase); } else { mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected, - mUnpluggables, in); + mOnBatteryTimeBase, in); } } @@ -3021,42 +4031,77 @@ public final class BatteryStatsImpl extends BatteryStats { void initUserActivityLocked() { mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES]; for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) { - mUserActivityCounters[i] = new Counter(mUnpluggables); + mUserActivityCounters[i] = new Counter(mOnBatteryTimeBase); } } - void noteNetworkActivityLocked(int type, long delta) { - if (mNetworkActivityCounters == null) { + void noteNetworkActivityLocked(int type, long deltaBytes, long deltaPackets) { + if (mNetworkByteActivityCounters == null) { initNetworkActivityLocked(); } if (type >= 0 && type < NUM_NETWORK_ACTIVITY_TYPES) { - mNetworkActivityCounters[type].addCountLocked(delta); + mNetworkByteActivityCounters[type].addCountLocked(deltaBytes); + mNetworkPacketActivityCounters[type].addCountLocked(deltaPackets); } else { Slog.w(TAG, "Unknown network activity type " + type + " was specified.", new Throwable()); } } + void noteMobileRadioActiveTimeLocked(long batteryUptime) { + if (mNetworkByteActivityCounters == null) { + initNetworkActivityLocked(); + } + mMobileRadioActiveTime.addCountLocked(batteryUptime); + mMobileRadioActiveCount.addCountLocked(1); + } + @Override public boolean hasNetworkActivity() { - return mNetworkActivityCounters != null; + return mNetworkByteActivityCounters != null; } @Override - public long getNetworkActivityCount(int type, int which) { - if (mNetworkActivityCounters != null && type >= 0 - && type < mNetworkActivityCounters.length) { - return mNetworkActivityCounters[type].getCountLocked(which); + public long getNetworkActivityBytes(int type, int which) { + if (mNetworkByteActivityCounters != null && type >= 0 + && type < mNetworkByteActivityCounters.length) { + return mNetworkByteActivityCounters[type].getCountLocked(which); } else { return 0; } } + @Override + public long getNetworkActivityPackets(int type, int which) { + if (mNetworkPacketActivityCounters != null && type >= 0 + && type < mNetworkPacketActivityCounters.length) { + return mNetworkPacketActivityCounters[type].getCountLocked(which); + } else { + return 0; + } + } + + @Override + public long getMobileRadioActiveTime(int which) { + return mMobileRadioActiveTime != null + ? mMobileRadioActiveTime.getCountLocked(which) : 0; + } + + @Override + public int getMobileRadioActiveCount(int which) { + return mMobileRadioActiveCount != null + ? (int)mMobileRadioActiveCount.getCountLocked(which) : 0; + } + void initNetworkActivityLocked() { - mNetworkActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; + mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; + mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables); + mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); + mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); } + mMobileRadioActiveTime = new LongSamplingCounter(mOnBatteryTimeBase); + mMobileRadioActiveCount = new LongSamplingCounter(mOnBatteryTimeBase); } /** @@ -3067,42 +4112,42 @@ public final class BatteryStatsImpl extends BatteryStats { boolean active = false; if (mWifiRunningTimer != null) { - active |= !mWifiRunningTimer.reset(BatteryStatsImpl.this, false); + active |= !mWifiRunningTimer.reset(false); active |= mWifiRunning; } if (mFullWifiLockTimer != null) { - active |= !mFullWifiLockTimer.reset(BatteryStatsImpl.this, false); + active |= !mFullWifiLockTimer.reset(false); active |= mFullWifiLockOut; } if (mWifiScanTimer != null) { - active |= !mWifiScanTimer.reset(BatteryStatsImpl.this, false); + active |= !mWifiScanTimer.reset(false); active |= mWifiScanStarted; } if (mWifiBatchedScanTimer != null) { for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { if (mWifiBatchedScanTimer[i] != null) { - active |= !mWifiBatchedScanTimer[i].reset(BatteryStatsImpl.this, false); + active |= !mWifiBatchedScanTimer[i].reset(false); } } active |= (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED); } if (mWifiMulticastTimer != null) { - active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false); + active |= !mWifiMulticastTimer.reset(false); active |= mWifiMulticastEnabled; } if (mAudioTurnedOnTimer != null) { - active |= !mAudioTurnedOnTimer.reset(BatteryStatsImpl.this, false); + active |= !mAudioTurnedOnTimer.reset(false); active |= mAudioTurnedOn; } if (mVideoTurnedOnTimer != null) { - active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false); + active |= !mVideoTurnedOnTimer.reset(false); active |= mVideoTurnedOn; } if (mForegroundActivityTimer != null) { - active |= !mForegroundActivityTimer.reset(BatteryStatsImpl.this, false); + active |= !mForegroundActivityTimer.reset(false); } if (mVibratorOnTimer != null) { - if (mVibratorOnTimer.reset(BatteryStatsImpl.this, false)) { + if (mVibratorOnTimer.reset(false)) { mVibratorOnTimer.detach(); mVibratorOnTimer = null; } else { @@ -3116,10 +4161,13 @@ public final class BatteryStatsImpl extends BatteryStats { } } - if (mNetworkActivityCounters != null) { + if (mNetworkByteActivityCounters != null) { for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].reset(false); + mNetworkByteActivityCounters[i].reset(false); + mNetworkPacketActivityCounters[i].reset(false); } + mMobileRadioActiveTime.reset(false); + mMobileRadioActiveCount.reset(false); } if (mWakelockStats.size() > 0) { @@ -3155,10 +4203,12 @@ public final class BatteryStatsImpl extends BatteryStats { mProcessStats.clear(); } if (mPids.size() > 0) { - for (int i=0; !active && i<mPids.size(); i++) { + for (int i=mPids.size()-1; i>=0; i--) { Pid pid = mPids.valueAt(i); - if (pid.mWakeStart != 0) { + if (pid.mWakeNesting > 0) { active = true; + } else { + mPids.removeAt(i); } } } @@ -3180,8 +4230,6 @@ public final class BatteryStatsImpl extends BatteryStats { mPackageStats.clear(); } - mPids.clear(); - if (!active) { if (mWifiRunningTimer != null) { mWifiRunningTimer.detach(); @@ -3217,29 +4265,31 @@ public final class BatteryStatsImpl extends BatteryStats { mUserActivityCounters[i].detach(); } } - if (mNetworkActivityCounters != null) { + if (mNetworkByteActivityCounters != null) { for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].detach(); + mNetworkByteActivityCounters[i].detach(); + mNetworkPacketActivityCounters[i].detach(); } } + mPids.clear(); } return !active; } - void writeToParcelLocked(Parcel out, long batteryRealtime) { + void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) { out.writeInt(mWakelockStats.size()); for (Map.Entry<String, Uid.Wakelock> wakelockEntry : mWakelockStats.entrySet()) { out.writeString(wakelockEntry.getKey()); Uid.Wakelock wakelock = wakelockEntry.getValue(); - wakelock.writeToParcelLocked(out, batteryRealtime); + wakelock.writeToParcelLocked(out, elapsedRealtimeUs); } out.writeInt(mSensorStats.size()); for (Map.Entry<Integer, Uid.Sensor> sensorEntry : mSensorStats.entrySet()) { out.writeInt(sensorEntry.getKey()); Uid.Sensor sensor = sensorEntry.getValue(); - sensor.writeToParcelLocked(out, batteryRealtime); + sensor.writeToParcelLocked(out, elapsedRealtimeUs); } out.writeInt(mProcessStats.size()); @@ -3258,57 +4308,57 @@ public final class BatteryStatsImpl extends BatteryStats { if (mWifiRunningTimer != null) { out.writeInt(1); - mWifiRunningTimer.writeToParcel(out, batteryRealtime); + mWifiRunningTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mFullWifiLockTimer != null) { out.writeInt(1); - mFullWifiLockTimer.writeToParcel(out, batteryRealtime); + mFullWifiLockTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mWifiScanTimer != null) { out.writeInt(1); - mWifiScanTimer.writeToParcel(out, batteryRealtime); + mWifiScanTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { if (mWifiBatchedScanTimer[i] != null) { out.writeInt(1); - mWifiBatchedScanTimer[i].writeToParcel(out, batteryRealtime); + mWifiBatchedScanTimer[i].writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } } if (mWifiMulticastTimer != null) { out.writeInt(1); - mWifiMulticastTimer.writeToParcel(out, batteryRealtime); + mWifiMulticastTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mAudioTurnedOnTimer != null) { out.writeInt(1); - mAudioTurnedOnTimer.writeToParcel(out, batteryRealtime); + mAudioTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mVideoTurnedOnTimer != null) { out.writeInt(1); - mVideoTurnedOnTimer.writeToParcel(out, batteryRealtime); + mVideoTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mForegroundActivityTimer != null) { out.writeInt(1); - mForegroundActivityTimer.writeToParcel(out, batteryRealtime); + mForegroundActivityTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } if (mVibratorOnTimer != null) { out.writeInt(1); - mVibratorOnTimer.writeToParcel(out, batteryRealtime); + mVibratorOnTimer.writeToParcel(out, elapsedRealtimeUs); } else { out.writeInt(0); } @@ -3320,23 +4370,26 @@ public final class BatteryStatsImpl extends BatteryStats { } else { out.writeInt(0); } - if (mNetworkActivityCounters != null) { + if (mNetworkByteActivityCounters != null) { out.writeInt(1); for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].writeToParcel(out); + mNetworkByteActivityCounters[i].writeToParcel(out); + mNetworkPacketActivityCounters[i].writeToParcel(out); } + mMobileRadioActiveTime.writeToParcel(out); + mMobileRadioActiveCount.writeToParcel(out); } else { out.writeInt(0); } } - void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) { + void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) { int numWakelocks = in.readInt(); mWakelockStats.clear(); for (int j = 0; j < numWakelocks; j++) { String wakelockName = in.readString(); Uid.Wakelock wakelock = new Wakelock(); - wakelock.readFromParcelLocked(unpluggables, in); + wakelock.readFromParcelLocked(timeBase, screenOffTimeBase, in); // We will just drop some random set of wakelocks if // the previous run of the system was an older version // that didn't impose a limit. @@ -3348,7 +4401,7 @@ public final class BatteryStatsImpl extends BatteryStats { for (int k = 0; k < numSensors; k++) { int sensorNumber = in.readInt(); Uid.Sensor sensor = new Sensor(sensorNumber); - sensor.readFromParcelLocked(mUnpluggables, in); + sensor.readFromParcelLocked(mOnBatteryTimeBase, in); mSensorStats.put(sensorNumber, sensor); } @@ -3373,21 +4426,21 @@ public final class BatteryStatsImpl extends BatteryStats { mWifiRunning = false; if (in.readInt() != 0) { mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING, - mWifiRunningTimers, mUnpluggables, in); + mWifiRunningTimers, mOnBatteryTimeBase, in); } else { mWifiRunningTimer = null; } mFullWifiLockOut = false; if (in.readInt() != 0) { mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK, - mFullWifiLockTimers, mUnpluggables, in); + mFullWifiLockTimers, mOnBatteryTimeBase, in); } else { mFullWifiLockTimer = null; } mWifiScanStarted = false; if (in.readInt() != 0) { mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN, - mWifiScanTimers, mUnpluggables, in); + mWifiScanTimers, mOnBatteryTimeBase, in); } else { mWifiScanTimer = null; } @@ -3402,51 +4455,58 @@ public final class BatteryStatsImpl extends BatteryStats { mWifiMulticastEnabled = false; if (in.readInt() != 0) { mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, - mWifiMulticastTimers, mUnpluggables, in); + mWifiMulticastTimers, mOnBatteryTimeBase, in); } else { mWifiMulticastTimer = null; } mAudioTurnedOn = false; if (in.readInt() != 0) { mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON, - null, mUnpluggables, in); + null, mOnBatteryTimeBase, in); } else { mAudioTurnedOnTimer = null; } mVideoTurnedOn = false; if (in.readInt() != 0) { mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON, - null, mUnpluggables, in); + null, mOnBatteryTimeBase, in); } else { mVideoTurnedOnTimer = null; } if (in.readInt() != 0) { mForegroundActivityTimer = new StopwatchTimer( - Uid.this, FOREGROUND_ACTIVITY, null, mUnpluggables, in); + Uid.this, FOREGROUND_ACTIVITY, null, mOnBatteryTimeBase, in); } else { mForegroundActivityTimer = null; } if (in.readInt() != 0) { - mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, - mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal, in); + mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, mOnBatteryTimeBase, in); } else { mVibratorOnTimer = null; } if (in.readInt() != 0) { mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES]; for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) { - mUserActivityCounters[i] = new Counter(mUnpluggables, in); + mUserActivityCounters[i] = new Counter(mOnBatteryTimeBase, in); } } else { mUserActivityCounters = null; } if (in.readInt() != 0) { - mNetworkActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; + mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; + mNetworkPacketActivityCounters + = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables, in); + mNetworkByteActivityCounters[i] + = new LongSamplingCounter(mOnBatteryTimeBase, in); + mNetworkPacketActivityCounters[i] + = new LongSamplingCounter(mOnBatteryTimeBase, in); } + mMobileRadioActiveTime = new LongSamplingCounter(mOnBatteryTimeBase, in); + mMobileRadioActiveCount = new LongSamplingCounter(mOnBatteryTimeBase, in); } else { - mNetworkActivityCounters = null; + mNetworkByteActivityCounters = null; + mNetworkPacketActivityCounters = null; } } @@ -3477,24 +4537,24 @@ public final class BatteryStatsImpl extends BatteryStats { * return a new Timer, or null. */ private StopwatchTimer readTimerFromParcel(int type, ArrayList<StopwatchTimer> pool, - ArrayList<Unpluggable> unpluggables, Parcel in) { + TimeBase timeBase, Parcel in) { if (in.readInt() == 0) { return null; } - return new StopwatchTimer(Uid.this, type, pool, unpluggables, in); + return new StopwatchTimer(Uid.this, type, pool, timeBase, in); } boolean reset() { boolean wlactive = false; if (mTimerFull != null) { - wlactive |= !mTimerFull.reset(BatteryStatsImpl.this, false); + wlactive |= !mTimerFull.reset(false); } if (mTimerPartial != null) { - wlactive |= !mTimerPartial.reset(BatteryStatsImpl.this, false); + wlactive |= !mTimerPartial.reset(false); } if (mTimerWindow != null) { - wlactive |= !mTimerWindow.reset(BatteryStatsImpl.this, false); + wlactive |= !mTimerWindow.reset(false); } if (!wlactive) { if (mTimerFull != null) { @@ -3513,19 +4573,19 @@ public final class BatteryStatsImpl extends BatteryStats { return !wlactive; } - void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) { + void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) { mTimerPartial = readTimerFromParcel(WAKE_TYPE_PARTIAL, - mPartialTimers, unpluggables, in); + mPartialTimers, screenOffTimeBase, in); mTimerFull = readTimerFromParcel(WAKE_TYPE_FULL, - mFullTimers, unpluggables, in); + mFullTimers, timeBase, in); mTimerWindow = readTimerFromParcel(WAKE_TYPE_WINDOW, - mWindowTimers, unpluggables, in); + mWindowTimers, timeBase, in); } - void writeToParcelLocked(Parcel out, long batteryRealtime) { - Timer.writeTimerToParcel(out, mTimerPartial, batteryRealtime); - Timer.writeTimerToParcel(out, mTimerFull, batteryRealtime); - Timer.writeTimerToParcel(out, mTimerWindow, batteryRealtime); + void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) { + Timer.writeTimerToParcel(out, mTimerPartial, elapsedRealtimeUs); + Timer.writeTimerToParcel(out, mTimerFull, elapsedRealtimeUs); + Timer.writeTimerToParcel(out, mTimerWindow, elapsedRealtimeUs); } @Override @@ -3547,8 +4607,7 @@ public final class BatteryStatsImpl extends BatteryStats { mHandle = handle; } - private StopwatchTimer readTimerFromParcel(ArrayList<Unpluggable> unpluggables, - Parcel in) { + private StopwatchTimer readTimerFromParcel(TimeBase timeBase, Parcel in) { if (in.readInt() == 0) { return null; } @@ -3558,23 +4617,23 @@ public final class BatteryStatsImpl extends BatteryStats { pool = new ArrayList<StopwatchTimer>(); mSensorTimers.put(mHandle, pool); } - return new StopwatchTimer(Uid.this, 0, pool, unpluggables, in); + return new StopwatchTimer(Uid.this, 0, pool, timeBase, in); } boolean reset() { - if (mTimer.reset(BatteryStatsImpl.this, true)) { + if (mTimer.reset(true)) { mTimer = null; return true; } return false; } - void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) { - mTimer = readTimerFromParcel(unpluggables, in); + void readFromParcelLocked(TimeBase timeBase, Parcel in) { + mTimer = readTimerFromParcel(timeBase, in); } - void writeToParcelLocked(Parcel out, long batteryRealtime) { - Timer.writeTimerToParcel(out, mTimer, batteryRealtime); + void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) { + Timer.writeTimerToParcel(out, mTimer, elapsedRealtimeUs); } @Override @@ -3591,7 +4650,12 @@ public final class BatteryStatsImpl extends BatteryStats { /** * The statistics associated with a particular process. */ - public final class Proc extends BatteryStats.Uid.Proc implements Unpluggable { + public final class Proc extends BatteryStats.Uid.Proc implements TimeBaseObs { + /** + * Remains true until removed from the stats. + */ + boolean mActive = true; + /** * Total time (in 1/100 sec) spent executing in user code. */ @@ -3677,26 +4741,27 @@ public final class BatteryStatsImpl extends BatteryStats { ArrayList<ExcessivePower> mExcessivePower; Proc() { - mUnpluggables.add(this); + mOnBatteryTimeBase.add(this); mSpeedBins = new SamplingCounter[getCpuSpeedSteps()]; } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { mUnpluggedUserTime = mUserTime; mUnpluggedSystemTime = mSystemTime; mUnpluggedForegroundTime = mForegroundTime; mUnpluggedStarts = mStarts; } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { } void detach() { - mUnpluggables.remove(this); + mActive = false; + mOnBatteryTimeBase.remove(this); for (int i = 0; i < mSpeedBins.length; i++) { SamplingCounter c = mSpeedBins[i]; if (c != null) { - mUnpluggables.remove(c); + mOnBatteryTimeBase.remove(c); mSpeedBins[i] = null; } } @@ -3825,7 +4890,7 @@ public final class BatteryStatsImpl extends BatteryStats { mSpeedBins = new SamplingCounter[bins >= steps ? bins : steps]; for (int i = 0; i < bins; i++) { if (in.readInt() != 0) { - mSpeedBins[i] = new SamplingCounter(mUnpluggables, in); + mSpeedBins[i] = new SamplingCounter(mOnBatteryTimeBase, in); } } @@ -3850,65 +4915,50 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override + public boolean isActive() { + return mActive; + } + + @Override public long getUserTime(int which) { - long val; - if (which == STATS_LAST) { - val = mLastUserTime; - } else { - val = mUserTime; - if (which == STATS_CURRENT) { - val -= mLoadedUserTime; - } else if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedUserTime; - } + long val = mUserTime; + if (which == STATS_CURRENT) { + val -= mLoadedUserTime; + } else if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedUserTime; } return val; } @Override public long getSystemTime(int which) { - long val; - if (which == STATS_LAST) { - val = mLastSystemTime; - } else { - val = mSystemTime; - if (which == STATS_CURRENT) { - val -= mLoadedSystemTime; - } else if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedSystemTime; - } + long val = mSystemTime; + if (which == STATS_CURRENT) { + val -= mLoadedSystemTime; + } else if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedSystemTime; } return val; } @Override public long getForegroundTime(int which) { - long val; - if (which == STATS_LAST) { - val = mLastForegroundTime; - } else { - val = mForegroundTime; - if (which == STATS_CURRENT) { - val -= mLoadedForegroundTime; - } else if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedForegroundTime; - } + long val = mForegroundTime; + if (which == STATS_CURRENT) { + val -= mLoadedForegroundTime; + } else if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedForegroundTime; } return val; } @Override public int getStarts(int which) { - int val; - if (which == STATS_LAST) { - val = mLastStarts; - } else { - val = mStarts; - if (which == STATS_CURRENT) { - val -= mLoadedStarts; - } else if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedStarts; - } + int val = mStarts; + if (which == STATS_CURRENT) { + val -= mLoadedStarts; + } else if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedStarts; } return val; } @@ -3920,7 +4970,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (amt != 0) { SamplingCounter c = mSpeedBins[i]; if (c == null) { - mSpeedBins[i] = c = new SamplingCounter(mUnpluggables); + mSpeedBins[i] = c = new SamplingCounter(mOnBatteryTimeBase); } c.addCountAtomic(values[i]); } @@ -3941,7 +4991,7 @@ public final class BatteryStatsImpl extends BatteryStats { /** * The statistics associated with a particular package. */ - public final class Pkg extends BatteryStats.Uid.Pkg implements Unpluggable { + public final class Pkg extends BatteryStats.Uid.Pkg implements TimeBaseObs { /** * Number of times this package has done something that could wake up the * device from sleep. @@ -3972,18 +5022,18 @@ public final class BatteryStatsImpl extends BatteryStats { final HashMap<String, Serv> mServiceStats = new HashMap<String, Serv>(); Pkg() { - mUnpluggables.add(this); + mOnBatteryScreenOffTimeBase.add(this); } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { mUnpluggedWakeups = mWakeups; } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { } void detach() { - mUnpluggables.remove(this); + mOnBatteryScreenOffTimeBase.remove(this); } void readFromParcelLocked(Parcel in) { @@ -4024,16 +5074,11 @@ public final class BatteryStatsImpl extends BatteryStats { @Override public int getWakeups(int which) { - int val; - if (which == STATS_LAST) { - val = mLastWakeups; - } else { - val = mWakeups; - if (which == STATS_CURRENT) { - val -= mLoadedWakeups; - } else if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedWakeups; - } + int val = mWakeups; + if (which == STATS_CURRENT) { + val -= mLoadedWakeups; + } else if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedWakeups; } return val; @@ -4042,7 +5087,7 @@ public final class BatteryStatsImpl extends BatteryStats { /** * The statistics associated with a particular service. */ - public final class Serv extends BatteryStats.Uid.Pkg.Serv implements Unpluggable { + public final class Serv extends BatteryStats.Uid.Pkg.Serv implements TimeBaseObs { /** * Total time (ms in battery uptime) the service has been left started. */ @@ -4134,20 +5179,22 @@ public final class BatteryStatsImpl extends BatteryStats { int mUnpluggedLaunches; Serv() { - mUnpluggables.add(this); + mOnBatteryTimeBase.add(this); } - public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { - mUnpluggedStartTime = getStartTimeToNowLocked(batteryUptime); + public void onTimeStarted(long elapsedRealtime, long baseUptime, + long baseRealtime) { + mUnpluggedStartTime = getStartTimeToNowLocked(baseUptime); mUnpluggedStarts = mStarts; mUnpluggedLaunches = mLaunches; } - public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) { + public void onTimeStopped(long elapsedRealtime, long baseUptime, + long baseRealtime) { } void detach() { - mUnpluggables.remove(this); + mOnBatteryTimeBase.remove(this); } void readFromParcelLocked(Parcel in) { @@ -4243,51 +5290,33 @@ public final class BatteryStatsImpl extends BatteryStats { @Override public int getLaunches(int which) { - int val; - - if (which == STATS_LAST) { - val = mLastLaunches; - } else { - val = mLaunches; - if (which == STATS_CURRENT) { - val -= mLoadedLaunches; - } else if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedLaunches; - } + int val = mLaunches; + if (which == STATS_CURRENT) { + val -= mLoadedLaunches; + } else if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedLaunches; } - return val; } @Override public long getStartTime(long now, int which) { - long val; - if (which == STATS_LAST) { - val = mLastStartTime; - } else { - val = getStartTimeToNowLocked(now); - if (which == STATS_CURRENT) { - val -= mLoadedStartTime; - } else if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedStartTime; - } + long val = getStartTimeToNowLocked(now); + if (which == STATS_CURRENT) { + val -= mLoadedStartTime; + } else if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedStartTime; } - return val; } @Override public int getStarts(int which) { - int val; - if (which == STATS_LAST) { - val = mLastStarts; - } else { - val = mStarts; - if (which == STATS_CURRENT) { - val -= mLoadedStarts; - } else if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedStarts; - } + int val = mStarts; + if (which == STATS_CURRENT) { + val -= mLoadedStarts; + } else if (which == STATS_SINCE_UNPLUGGED) { + val -= mUnpluggedStarts; } return val; @@ -4382,7 +5411,7 @@ public final class BatteryStatsImpl extends BatteryStats { t = wl.mTimerPartial; if (t == null) { t = new StopwatchTimer(Uid.this, WAKE_TYPE_PARTIAL, - mPartialTimers, mUnpluggables); + mPartialTimers, mOnBatteryScreenOffTimeBase); wl.mTimerPartial = t; } return t; @@ -4390,7 +5419,7 @@ public final class BatteryStatsImpl extends BatteryStats { t = wl.mTimerFull; if (t == null) { t = new StopwatchTimer(Uid.this, WAKE_TYPE_FULL, - mFullTimers, mUnpluggables); + mFullTimers, mOnBatteryTimeBase); wl.mTimerFull = t; } return t; @@ -4398,7 +5427,7 @@ public final class BatteryStatsImpl extends BatteryStats { t = wl.mTimerWindow; if (t == null) { t = new StopwatchTimer(Uid.this, WAKE_TYPE_WINDOW, - mWindowTimers, mUnpluggables); + mWindowTimers, mOnBatteryTimeBase); wl.mTimerWindow = t; } return t; @@ -4425,34 +5454,36 @@ public final class BatteryStatsImpl extends BatteryStats { timers = new ArrayList<StopwatchTimer>(); mSensorTimers.put(sensor, timers); } - t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mUnpluggables); + t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mOnBatteryTimeBase); se.mTimer = t; return t; } - public void noteStartWakeLocked(int pid, String name, int type) { + public void noteStartWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) { StopwatchTimer t = getWakeTimerLocked(name, type); if (t != null) { - t.startRunningLocked(BatteryStatsImpl.this); + t.startRunningLocked(elapsedRealtimeMs); } if (pid >= 0 && type == WAKE_TYPE_PARTIAL) { Pid p = getPidStatsLocked(pid); - if (p.mWakeStart == 0) { - p.mWakeStart = SystemClock.elapsedRealtime(); + if (p.mWakeNesting++ == 0) { + p.mWakeStartMs = elapsedRealtimeMs; } } } - public void noteStopWakeLocked(int pid, String name, int type) { + public void noteStopWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) { StopwatchTimer t = getWakeTimerLocked(name, type); if (t != null) { - t.stopRunningLocked(BatteryStatsImpl.this); + t.stopRunningLocked(elapsedRealtimeMs); } if (pid >= 0 && type == WAKE_TYPE_PARTIAL) { Pid p = mPids.get(pid); - if (p != null && p.mWakeStart != 0) { - p.mWakeSum += SystemClock.elapsedRealtime() - p.mWakeStart; - p.mWakeStart = 0; + if (p != null && p.mWakeNesting > 0) { + if (p.mWakeNesting-- == 1) { + p.mWakeSumMs += elapsedRealtimeMs - p.mWakeStartMs; + p.mWakeStartMs = 0; + } } } } @@ -4471,32 +5502,32 @@ public final class BatteryStatsImpl extends BatteryStats { } } - public void noteStartSensor(int sensor) { + public void noteStartSensor(int sensor, long elapsedRealtimeMs) { StopwatchTimer t = getSensorTimerLocked(sensor, true); if (t != null) { - t.startRunningLocked(BatteryStatsImpl.this); + t.startRunningLocked(elapsedRealtimeMs); } } - public void noteStopSensor(int sensor) { + public void noteStopSensor(int sensor, long elapsedRealtimeMs) { // Don't create a timer if one doesn't already exist StopwatchTimer t = getSensorTimerLocked(sensor, false); if (t != null) { - t.stopRunningLocked(BatteryStatsImpl.this); + t.stopRunningLocked(elapsedRealtimeMs); } } - public void noteStartGps() { + public void noteStartGps(long elapsedRealtimeMs) { StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, true); if (t != null) { - t.startRunningLocked(BatteryStatsImpl.this); + t.startRunningLocked(elapsedRealtimeMs); } } - public void noteStopGps() { + public void noteStopGps(long elapsedRealtimeMs) { StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, false); if (t != null) { - t.stopRunningLocked(BatteryStatsImpl.this); + t.stopRunningLocked(elapsedRealtimeMs); } } @@ -4509,38 +5540,51 @@ public final class BatteryStatsImpl extends BatteryStats { mFile = new JournaledFile(new File(filename), new File(filename + ".tmp")); mHandler = new MyHandler(handler.getLooper()); mStartCount++; - mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables); + mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mUnpluggables); + mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase); } - mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables); + mInteractiveTimer = new StopwatchTimer(null, -9, null, mOnBatteryTimeBase); + mLowPowerModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase); + mPhoneOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { - mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null, mUnpluggables); + mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null, + mOnBatteryTimeBase); } - mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables); + mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mOnBatteryTimeBase); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null, mUnpluggables); + mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null, + mOnBatteryTimeBase); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables); - } - mWifiOnTimer = new StopwatchTimer(null, -3, null, mUnpluggables); - mGlobalWifiRunningTimer = new StopwatchTimer(null, -4, null, mUnpluggables); - mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mUnpluggables); - mAudioOnTimer = new StopwatchTimer(null, -6, null, mUnpluggables); - mVideoOnTimer = new StopwatchTimer(null, -7, null, mUnpluggables); - mInteractiveTimer = new StopwatchTimer(null, -8, null, mUnpluggables); + mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); + mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); + } + mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mOnBatteryTimeBase); + mMobileRadioActivePerAppTimer = new StopwatchTimer(null, -401, null, mOnBatteryTimeBase); + mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase); + mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase); + mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase); + mWifiOnTimer = new StopwatchTimer(null, -4, null, mOnBatteryTimeBase); + mGlobalWifiRunningTimer = new StopwatchTimer(null, -5, null, mOnBatteryTimeBase); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i] = new StopwatchTimer(null, -600-i, null, mOnBatteryTimeBase); + } + mBluetoothOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i, null, mOnBatteryTimeBase); + } + mAudioOnTimer = new StopwatchTimer(null, -7, null, mOnBatteryTimeBase); + mVideoOnTimer = new StopwatchTimer(null, -8, null, mOnBatteryTimeBase); mOnBattery = mOnBatteryInternal = false; - initTimes(); - mTrackBatteryPastUptime = 0; - mTrackBatteryPastRealtime = 0; - mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000; - mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000; - mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart); - mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart); + long uptime = SystemClock.uptimeMillis() * 1000; + long realtime = SystemClock.elapsedRealtime() * 1000; + initTimes(uptime, realtime); mDischargeStartLevel = 0; mDischargeUnplugLevel = 0; + mDischargePlugLevel = -1; mDischargeCurrentLevel = 0; + mCurrentBatteryLevel = 0; initDischarge(); clearHistoryLocked(); } @@ -4570,18 +5614,21 @@ public final class BatteryStatsImpl extends BatteryStats { public boolean startIteratingOldHistoryLocked() { if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize() + " pos=" + mHistoryBuffer.dataPosition()); + if ((mHistoryIterator = mHistory) == null) { + return false; + } mHistoryBuffer.setDataPosition(0); mHistoryReadTmp.clear(); mReadOverflow = false; mIteratingHistory = true; - return (mHistoryIterator = mHistory) != null; + return true; } @Override public boolean getNextOldHistoryLocked(HistoryItem out) { boolean end = mHistoryBuffer.dataPosition() >= mHistoryBuffer.dataSize(); if (!end) { - mHistoryReadTmp.readDelta(mHistoryBuffer); + readHistoryDelta(mHistoryBuffer, mHistoryReadTmp); mReadOverflow |= mHistoryReadTmp.cmd == HistoryItem.CMD_OVERFLOW; } HistoryItem cur = mHistoryIterator; @@ -4597,13 +5644,13 @@ public final class BatteryStatsImpl extends BatteryStats { if (end) { Slog.w(TAG, "New history ends before old history!"); } else if (!out.same(mHistoryReadTmp)) { - long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); PrintWriter pw = new FastPrintWriter(new LogWriter(android.util.Log.WARN, TAG)); pw.println("Histories differ!"); pw.println("Old history:"); - (new HistoryPrinter()).printNextItem(pw, out, now); + (new HistoryPrinter()).printNextItem(pw, out, 0, false, true); pw.println("New history:"); - (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, now); + (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, 0, false, + true); pw.flush(); } } @@ -4614,16 +5661,60 @@ public final class BatteryStatsImpl extends BatteryStats { public void finishIteratingOldHistoryLocked() { mIteratingHistory = false; mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize()); + mHistoryIterator = null; + } + + public int getHistoryTotalSize() { + return MAX_HISTORY_BUFFER; + } + + public int getHistoryUsedSize() { + return mHistoryBuffer.dataSize(); } @Override public boolean startIteratingHistoryLocked() { if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize() + " pos=" + mHistoryBuffer.dataPosition()); + if (mHistoryBuffer.dataSize() <= 0) { + return false; + } mHistoryBuffer.setDataPosition(0); mReadOverflow = false; mIteratingHistory = true; - return mHistoryBuffer.dataSize() > 0; + mReadHistoryStrings = new String[mHistoryTagPool.size()]; + mReadHistoryUids = new int[mHistoryTagPool.size()]; + mReadHistoryChars = 0; + for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) { + final HistoryTag tag = ent.getKey(); + final int idx = ent.getValue(); + mReadHistoryStrings[idx] = tag.string; + mReadHistoryUids[idx] = tag.uid; + mReadHistoryChars += tag.string.length() + 1; + } + return true; + } + + @Override + public int getHistoryStringPoolSize() { + return mReadHistoryStrings.length; + } + + @Override + public int getHistoryStringPoolBytes() { + // Each entry is a fixed 12 bytes: 4 for index, 4 for uid, 4 for string size + // Each string character is 2 bytes. + return (mReadHistoryStrings.length * 12) + (mReadHistoryChars * 2); + } + + @Override + public String getHistoryTagPoolString(int index) { + return mReadHistoryStrings[index]; + } + + @Override + public int getHistoryTagPoolUid(int index) { + return mReadHistoryUids[index]; } @Override @@ -4637,7 +5728,13 @@ public final class BatteryStatsImpl extends BatteryStats { return false; } - out.readDelta(mHistoryBuffer); + final long lastRealtime = out.time; + final long lastWalltime = out.currentTime; + readHistoryDelta(mHistoryBuffer, out); + if (out.cmd != HistoryItem.CMD_CURRENT_TIME + && out.cmd != HistoryItem.CMD_RESET && lastWalltime != 0) { + out.currentTime = lastWalltime + (out.time - lastRealtime); + } return true; } @@ -4645,6 +5742,7 @@ public final class BatteryStatsImpl extends BatteryStats { public void finishIteratingHistoryLocked() { mIteratingHistory = false; mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize()); + mReadHistoryStrings = null; } @Override @@ -4665,13 +5763,14 @@ public final class BatteryStatsImpl extends BatteryStats { return mScreenState == Display.STATE_ON; } - void initTimes() { - mBatteryRealtime = mTrackBatteryPastUptime = 0; - mBatteryUptime = mTrackBatteryPastRealtime = 0; - mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000; - mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000; - mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart); - mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart); + void initTimes(long uptime, long realtime) { + mStartClockTime = System.currentTimeMillis(); + mOnBatteryTimeBase.init(uptime, realtime); + mOnBatteryScreenOffTimeBase.init(uptime, realtime); + mRealtime = 0; + mUptime = 0; + mRealtimeStart = realtime; + mUptimeStart = uptime; } void initDischarge() { @@ -4681,32 +5780,76 @@ public final class BatteryStatsImpl extends BatteryStats { mDischargeAmountScreenOnSinceCharge = 0; mDischargeAmountScreenOff = 0; mDischargeAmountScreenOffSinceCharge = 0; + mLastDischargeStepTime = -1; + mNumDischargeStepDurations = 0; + mLastChargeStepTime = -1; + mNumChargeStepDurations = 0; } - - public void resetAllStatsLocked() { + + public void resetAllStatsCmdLocked() { + resetAllStatsLocked(); + final long mSecUptime = SystemClock.uptimeMillis(); + long uptime = mSecUptime * 1000; + long mSecRealtime = SystemClock.elapsedRealtime(); + long realtime = mSecRealtime * 1000; + mDischargeStartLevel = mHistoryCur.batteryLevel; + pullPendingStateUpdatesLocked(); + addHistoryRecordLocked(mSecRealtime, mSecUptime); + mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel + = mCurrentBatteryLevel = mHistoryCur.batteryLevel; + mOnBatteryTimeBase.reset(uptime, realtime); + mOnBatteryScreenOffTimeBase.reset(uptime, realtime); + if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) { + if (mScreenState == Display.STATE_ON) { + mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel; + mDischargeScreenOffUnplugLevel = 0; + } else { + mDischargeScreenOnUnplugLevel = 0; + mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel; + } + mDischargeAmountScreenOn = 0; + mDischargeAmountScreenOff = 0; + } + initActiveHistoryEventsLocked(mSecRealtime, mSecUptime); + } + + private void resetAllStatsLocked() { mStartCount = 0; - initTimes(); - mScreenOnTimer.reset(this, false); + initTimes(SystemClock.uptimeMillis() * 1000, SystemClock.elapsedRealtime() * 1000); + mScreenOnTimer.reset(false); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i].reset(this, false); + mScreenBrightnessTimer[i].reset(false); } - mInteractiveTimer.reset(this, false); - mPhoneOnTimer.reset(this, false); - mAudioOnTimer.reset(this, false); - mVideoOnTimer.reset(this, false); + mInteractiveTimer.reset(false); + mLowPowerModeEnabledTimer.reset(false); + mPhoneOnTimer.reset(false); + mAudioOnTimer.reset(false); + mVideoOnTimer.reset(false); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { - mPhoneSignalStrengthsTimer[i].reset(this, false); + mPhoneSignalStrengthsTimer[i].reset(false); } - mPhoneSignalScanningTimer.reset(this, false); + mPhoneSignalScanningTimer.reset(false); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - mPhoneDataConnectionsTimer[i].reset(this, false); + mPhoneDataConnectionsTimer[i].reset(false); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].reset(false); + mNetworkByteActivityCounters[i].reset(false); + mNetworkPacketActivityCounters[i].reset(false); + } + mMobileRadioActiveTimer.reset(false); + mMobileRadioActivePerAppTimer.reset(false); + mMobileRadioActiveAdjustedTime.reset(false); + mMobileRadioActiveUnknownTime.reset(false); + mMobileRadioActiveUnknownCount.reset(false); + mWifiOnTimer.reset(false); + mGlobalWifiRunningTimer.reset(false); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i].reset(false); + } + mBluetoothOnTimer.reset(false); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i].reset(false); } - mWifiOnTimer.reset(this, false); - mGlobalWifiRunningTimer.reset(this, false); - mBluetoothOnTimer.reset(this, false); for (int i=0; i<mUidStats.size(); i++) { if (mUidStats.valueAt(i).reset()) { @@ -4717,16 +5860,39 @@ public final class BatteryStatsImpl extends BatteryStats { if (mKernelWakelockStats.size() > 0) { for (SamplingTimer timer : mKernelWakelockStats.values()) { - mUnpluggables.remove(timer); + mOnBatteryScreenOffTimeBase.remove(timer); } mKernelWakelockStats.clear(); } - + + if (mWakeupReasonStats.size() > 0) { + for (LongSamplingCounter timer : mWakeupReasonStats.values()) { + mOnBatteryScreenOffTimeBase.remove(timer); + } + mWakeupReasonStats.clear(); + } + initDischarge(); clearHistoryLocked(); } + private void initActiveHistoryEventsLocked(long elapsedRealtimeMs, long uptimeMs) { + for (int i=0; i<HistoryItem.EVENT_COUNT; i++) { + HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(i); + if (active == null) { + continue; + } + for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) { + SparseIntArray uids = ent.getValue(); + for (int j=0; j<uids.size(); j++) { + addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, i, ent.getKey(), + uids.keyAt(j)); + } + } + } + } + void updateDischargeScreenLevelsLocked(boolean oldScreenOn, boolean newScreenOn) { if (oldScreenOn) { int diff = mDischargeScreenOnUnplugLevel - mDischargeCurrentLevel; @@ -4750,46 +5916,54 @@ public final class BatteryStatsImpl extends BatteryStats { } } - void setOnBattery(boolean onBattery, int oldStatus, int level) { - synchronized(this) { - setOnBatteryLocked(onBattery, oldStatus, level); + public void pullPendingStateUpdatesLocked() { + updateKernelWakelocksLocked(); + updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime()); + if (mOnBatteryInternal) { + final boolean screenOn = mScreenState == Display.STATE_ON; + updateDischargeScreenLevelsLocked(screenOn, screenOn); } } - void setOnBatteryLocked(boolean onBattery, int oldStatus, int level) { + void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery, + final int oldStatus, final int level) { boolean doWrite = false; Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE); m.arg1 = onBattery ? 1 : 0; mHandler.sendMessage(m); mOnBattery = mOnBatteryInternal = onBattery; - long uptime = SystemClock.uptimeMillis() * 1000; - long mSecRealtime = SystemClock.elapsedRealtime(); - long realtime = mSecRealtime * 1000; + final long uptime = mSecUptime * 1000; + final long realtime = mSecRealtime * 1000; final boolean screenOn = mScreenState == Display.STATE_ON; if (onBattery) { // We will reset our status if we are unplugging after the // battery was last full, or the level is at 100, or // we have gone through a significant charge (from a very low // level to a now very high level). - if (oldStatus == BatteryManager.BATTERY_STATUS_FULL + boolean reset = false; + if (!mNoAutoReset && (oldStatus == BatteryManager.BATTERY_STATUS_FULL || level >= 90 - || (mDischargeCurrentLevel < 20 && level >= 80)) { + || (mDischargeCurrentLevel < 20 && level >= 80))) { doWrite = true; resetAllStatsLocked(); mDischargeStartLevel = level; + reset = true; + mNumDischargeStepDurations = 0; } - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + mLastDischargeStepLevel = level; + mMinDischargeStepLevel = level; + mLastDischargeStepTime = -1; + pullPendingStateUpdatesLocked(); mHistoryCur.batteryLevel = (byte)level; mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(mSecRealtime); - mTrackBatteryUptimeStart = uptime; - mTrackBatteryRealtimeStart = realtime; - mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime); - mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime); + if (reset) { + mRecordingHistory = true; + startRecordingHistory(mSecRealtime, mSecUptime, reset); + } + addHistoryRecordLocked(mSecRealtime, mSecUptime); mDischargeCurrentLevel = mDischargeUnplugLevel = level; if (screenOn) { mDischargeScreenOnUnplugLevel = level; @@ -4800,24 +5974,25 @@ public final class BatteryStatsImpl extends BatteryStats { } mDischargeAmountScreenOn = 0; mDischargeAmountScreenOff = 0; - doUnplugLocked(realtime, mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime); + updateTimeBasesLocked(true, !screenOn, uptime, realtime); } else { - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); mHistoryCur.batteryLevel = (byte)level; mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: " + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(mSecRealtime); - mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart; - mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart; - mDischargeCurrentLevel = level; + addHistoryRecordLocked(mSecRealtime, mSecUptime); + mDischargeCurrentLevel = mDischargePlugLevel = level; if (level < mDischargeUnplugLevel) { mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1; mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level; } updateDischargeScreenLevelsLocked(screenOn, screenOn); - doPlugLocked(realtime, getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime)); + updateTimeBasesLocked(false, !screenOn, uptime, realtime); + mNumChargeStepDurations = 0; + mLastChargeStepLevel = level; + mMaxChargeStepLevel = level; + mLastChargeStepTime = -1; } if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) { if (mFile != null) { @@ -4826,13 +6001,46 @@ public final class BatteryStatsImpl extends BatteryStats { } } + private void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs, + boolean reset) { + mRecordingHistory = true; + mHistoryCur.currentTime = System.currentTimeMillis(); + addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, + reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME, + mHistoryCur); + mHistoryCur.currentTime = 0; + if (reset) { + initActiveHistoryEventsLocked(elapsedRealtimeMs, uptimeMs); + } + } + // This should probably be exposed in the API, though it's not critical private static final int BATTERY_PLUGGED_NONE = 0; + private static int addLevelSteps(long[] steps, int stepCount, long lastStepTime, + int numStepLevels, long elapsedRealtime) { + if (lastStepTime >= 0 && numStepLevels > 0) { + long duration = elapsedRealtime - lastStepTime; + for (int i=0; i<numStepLevels; i++) { + System.arraycopy(steps, 0, steps, 1, steps.length-1); + long thisDuration = duration / (numStepLevels-i); + duration -= thisDuration; + steps[0] = thisDuration; + } + stepCount += numStepLevels; + if (stepCount > steps.length) { + stepCount = steps.length; + } + } + return stepCount; + } + public void setBatteryState(int status, int health, int plugType, int level, int temp, int volt) { synchronized(this) { - boolean onBattery = plugType == BATTERY_PLUGGED_NONE; + final boolean onBattery = plugType == BATTERY_PLUGGED_NONE; + final long uptime = SystemClock.uptimeMillis(); + final long elapsedRealtime = SystemClock.elapsedRealtime(); int oldStatus = mHistoryCur.batteryStatus; if (!mHaveBatteryLevel) { mHaveBatteryLevel = true; @@ -4851,7 +6059,19 @@ public final class BatteryStatsImpl extends BatteryStats { } if (onBattery) { mDischargeCurrentLevel = level; - mRecordingHistory = true; + 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; @@ -4860,7 +6080,7 @@ public final class BatteryStatsImpl extends BatteryStats { mHistoryCur.batteryPlugType = (byte)plugType; mHistoryCur.batteryTemperature = (short)temp; mHistoryCur.batteryVoltage = (char)volt; - setOnBatteryLocked(onBattery, oldStatus, level); + setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level); } else { boolean changed = false; if (mHistoryCur.batteryLevel != level) { @@ -4890,13 +6110,32 @@ public final class BatteryStatsImpl extends BatteryStats { changed = true; } if (changed) { - addHistoryRecordLocked(SystemClock.elapsedRealtime()); + addHistoryRecordLocked(elapsedRealtime, uptime); + } + if (onBattery) { + if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) { + mNumDischargeStepDurations = addLevelSteps(mDischargeStepDurations, + mNumDischargeStepDurations, mLastDischargeStepTime, + mLastDischargeStepLevel - level, elapsedRealtime); + mLastDischargeStepLevel = level; + mMinDischargeStepLevel = level; + mLastDischargeStepTime = elapsedRealtime; + } + } else { + if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) { + mNumChargeStepDurations = addLevelSteps(mChargeStepDurations, + mNumChargeStepDurations, mLastChargeStepTime, + level - mLastChargeStepLevel, elapsedRealtime); + mLastChargeStepLevel = level; + mMaxChargeStepLevel = level; + mLastChargeStepTime = elapsedRealtime; + } } } 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 = false; + mRecordingHistory = DEBUG; } } } @@ -4916,8 +6155,8 @@ public final class BatteryStatsImpl extends BatteryStats { SamplingTimer kwlt = mKernelWakelockStats.get(name); if (kwlt == null) { - kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal, - true /* track reported values */); + kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase, + true /* track reported val */); mKernelWakelockStats.put(name, kwlt); } kwlt.updateCurrentReportedCount(kws.mCount); @@ -4936,48 +6175,124 @@ public final class BatteryStatsImpl extends BatteryStats { } } - private void updateNetworkActivityLocked() { + 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; - final NetworkStats snapshot; - try { - snapshot = mNetworkStatsFactory.readNetworkStatsDetail(); - } catch (IOException e) { - Log.wtf(TAG, "Failed to read network stats", e); - 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 (mLastSnapshot == null) { - mLastSnapshot = snapshot; - return; + 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); + } + } } - final NetworkStats delta = snapshot.subtract(mLastSnapshot); - mLastSnapshot = snapshot; + 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; + } - NetworkStats.Entry entry = null; - final int size = delta.size(); - for (int i = 0; i < size; i++) { - entry = delta.getValues(i, entry); + mCurWifiSnapshot = snapshot; + mLastWifiSnapshot = last; - if (entry.rxBytes == 0 || entry.txBytes == 0) continue; - if (entry.tag != NetworkStats.TAG_NONE) continue; + if (mOnBatteryInternal) { + final NetworkStats delta = NetworkStats.subtract(snapshot, last, + null, null, mTmpNetworkStats); + mTmpNetworkStats = delta; - final Uid u = getUidStatsLocked(entry.uid); + final int size = delta.size(); + for (int i = 0; i < size; i++) { + final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry); - if (mMobileIfaces.contains(entry.iface)) { - u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_BYTES, entry.rxBytes); - u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_BYTES, entry.txBytes); + 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); + } - mNetworkActivityCounters[NETWORK_MOBILE_RX_BYTES].addCountLocked(entry.rxBytes); - mNetworkActivityCounters[NETWORK_MOBILE_TX_BYTES].addCountLocked(entry.txBytes); + if (entry.rxBytes == 0 || entry.txBytes == 0) continue; - } else if (mWifiIfaces.contains(entry.iface)) { - u.noteNetworkActivityLocked(NETWORK_WIFI_RX_BYTES, entry.rxBytes); - u.noteNetworkActivityLocked(NETWORK_WIFI_TX_BYTES, entry.txBytes); + 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); - mNetworkActivityCounters[NETWORK_WIFI_RX_BYTES].addCountLocked(entry.rxBytes); - mNetworkActivityCounters[NETWORK_WIFI_TX_BYTES].addCountLocked(entry.txBytes); + 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); + } } } } @@ -4994,9 +6309,8 @@ public final class BatteryStatsImpl extends BatteryStats { public long computeUptime(long curTime, int which) { switch (which) { case STATS_SINCE_CHARGED: return mUptime + (curTime-mUptimeStart); - case STATS_LAST: return mLastUptime; case STATS_CURRENT: return (curTime-mUptimeStart); - case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart); + case STATS_SINCE_UNPLUGGED: return (curTime-mOnBatteryTimeBase.getUptimeStart()); } return 0; } @@ -5005,71 +6319,155 @@ public final class BatteryStatsImpl extends BatteryStats { public long computeRealtime(long curTime, int which) { switch (which) { case STATS_SINCE_CHARGED: return mRealtime + (curTime-mRealtimeStart); - case STATS_LAST: return mLastRealtime; case STATS_CURRENT: return (curTime-mRealtimeStart); - case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart); + case STATS_SINCE_UNPLUGGED: return (curTime-mOnBatteryTimeBase.getRealtimeStart()); } return 0; } @Override public long computeBatteryUptime(long curTime, int which) { - switch (which) { - case STATS_SINCE_CHARGED: - return mBatteryUptime + getBatteryUptime(curTime); - case STATS_LAST: - return mBatteryLastUptime; - case STATS_CURRENT: - return getBatteryUptime(curTime); - case STATS_SINCE_UNPLUGGED: - return getBatteryUptimeLocked(curTime) - mUnpluggedBatteryUptime; - } - return 0; + return mOnBatteryTimeBase.computeUptime(curTime, which); } @Override public long computeBatteryRealtime(long curTime, int which) { - switch (which) { - case STATS_SINCE_CHARGED: - return mBatteryRealtime + getBatteryRealtimeLocked(curTime); - case STATS_LAST: - return mBatteryLastRealtime; - case STATS_CURRENT: - return getBatteryRealtimeLocked(curTime); - case STATS_SINCE_UNPLUGGED: - return getBatteryRealtimeLocked(curTime) - mUnpluggedBatteryRealtime; + return mOnBatteryTimeBase.computeRealtime(curTime, which); + } + + @Override + public long computeBatteryScreenOffUptime(long curTime, int which) { + return mOnBatteryScreenOffTimeBase.computeUptime(curTime, which); + } + + @Override + public long computeBatteryScreenOffRealtime(long curTime, int which) { + return mOnBatteryScreenOffTimeBase.computeRealtime(curTime, which); + } + + private long computeTimePerLevel(long[] steps, int numSteps) { + // For now we'll do a simple average across all steps. + if (numSteps <= 0) { + return -1; } - return 0; + long total = 0; + for (int i=0; i<numSteps; i++) { + total += steps[i]; + } + return total / numSteps; + /* + long[] buckets = new long[numSteps]; + int numBuckets = 0; + int numToAverage = 4; + int i = 0; + while (i < numSteps) { + long totalTime = 0; + int num = 0; + for (int j=0; j<numToAverage && (i+j)<numSteps; j++) { + totalTime += steps[i+j]; + num++; + } + buckets[numBuckets] = totalTime / num; + numBuckets++; + numToAverage *= 2; + i += num; + } + if (numBuckets < 1) { + return -1; + } + long averageTime = buckets[numBuckets-1]; + for (i=numBuckets-2; i>=0; i--) { + averageTime = (averageTime + buckets[i]) / 2; + } + return averageTime; + */ } - long getBatteryUptimeLocked(long curTime) { - long time = mTrackBatteryPastUptime; - if (mOnBatteryInternal) { - time += curTime - mTrackBatteryUptimeStart; + @Override + public long computeBatteryTimeRemaining(long curTime) { + if (!mOnBattery) { + return -1; } - return time; + /* Simple implementation just looks at the average discharge per level across the + entire sample period. + int discharge = (getLowDischargeAmountSinceCharge()+getHighDischargeAmountSinceCharge())/2; + if (discharge < 2) { + return -1; + } + long duration = computeBatteryRealtime(curTime, STATS_SINCE_CHARGED); + if (duration < 1000*1000) { + return -1; + } + long usPerLevel = duration/discharge; + return usPerLevel * mCurrentBatteryLevel; + */ + if (mNumDischargeStepDurations < 1) { + return -1; + } + long msPerLevel = computeTimePerLevel(mDischargeStepDurations, mNumDischargeStepDurations); + if (msPerLevel <= 0) { + return -1; + } + return (msPerLevel * mCurrentBatteryLevel) * 1000; } - long getBatteryUptimeLocked() { - return getBatteryUptime(SystemClock.uptimeMillis() * 1000); + public int getNumDischargeStepDurations() { + return mNumDischargeStepDurations; } - @Override - public long getBatteryUptime(long curTime) { - return getBatteryUptimeLocked(curTime); + public long[] getDischargeStepDurationsArray() { + return mDischargeStepDurations; } - long getBatteryRealtimeLocked(long curTime) { - long time = mTrackBatteryPastRealtime; - if (mOnBatteryInternal) { - time += curTime - mTrackBatteryRealtimeStart; + @Override + public long computeChargeTimeRemaining(long curTime) { + if (mOnBattery) { + // Not yet working. + return -1; + } + /* Broken + int curLevel = mCurrentBatteryLevel; + int plugLevel = mDischargePlugLevel; + if (plugLevel < 0 || curLevel < (plugLevel+1)) { + return -1; } - return time; + long duration = computeBatteryRealtime(curTime, STATS_SINCE_UNPLUGGED); + if (duration < 1000*1000) { + return -1; + } + long usPerLevel = duration/(curLevel-plugLevel); + return usPerLevel * (100-curLevel); + */ + if (mNumChargeStepDurations < 1) { + return -1; + } + long msPerLevel = computeTimePerLevel(mChargeStepDurations, mNumChargeStepDurations); + if (msPerLevel <= 0) { + return -1; + } + return (msPerLevel * (100-mCurrentBatteryLevel)) * 1000; + } + + public int getNumChargeStepDurations() { + return mNumChargeStepDurations; + } + + public long[] getChargeStepDurationsArray() { + return mChargeStepDurations; + } + + long getBatteryUptimeLocked() { + return mOnBatteryTimeBase.getUptime(SystemClock.uptimeMillis() * 1000); + } + + @Override + public long getBatteryUptime(long curTime) { + return mOnBatteryTimeBase.getUptime(curTime); } @Override public long getBatteryRealtime(long curTime) { - return getBatteryRealtimeLocked(curTime); + return mOnBatteryTimeBase.getRealtime(curTime); } @Override @@ -5115,7 +6513,18 @@ public final class BatteryStatsImpl extends BatteryStats { return val; } } - + + @Override + public int getDischargeAmount(int which) { + int dischargeAmount = which == STATS_SINCE_CHARGED + ? getHighDischargeAmountSinceCharge() + : (getDischargeStartLevel() - getDischargeCurrentLevel()); + if (dischargeAmount < 0) { + dischargeAmount = 0; + } + return dischargeAmount; + } + public int getDischargeAmountScreenOn() { synchronized(this) { int val = mDischargeAmountScreenOn; @@ -5189,24 +6598,7 @@ public final class BatteryStatsImpl extends BatteryStats { * if needed. */ public Uid.Proc getProcessStatsLocked(int uid, String name) { - Uid u = getUidStatsLocked(uid); - return u.getProcessStatsLocked(name); - } - - /** - * Retrieve the statistics object for a particular process, given - * the name of the process. - * @param name process name - * @return the statistics object for the process - */ - public Uid.Proc getProcessStatsLocked(String name, int pid) { - int uid; - if (mUidCache.containsKey(name)) { - uid = mUidCache.get(name); - } else { - uid = Process.getUidForPid(pid); - mUidCache.put(name, uid); - } + uid = mapUid(uid); Uid u = getUidStatsLocked(uid); return u.getProcessStatsLocked(name); } @@ -5216,6 +6608,7 @@ public final class BatteryStatsImpl extends BatteryStats { * if needed. */ public Uid.Pkg getPackageStatsLocked(int uid, String pkg) { + uid = mapUid(uid); Uid u = getUidStatsLocked(uid); return u.getPackageStatsLocked(pkg); } @@ -5225,6 +6618,7 @@ public final class BatteryStatsImpl extends BatteryStats { * if needed. */ public Uid.Pkg.Serv getServiceStatsLocked(int uid, String pkg, String name) { + uid = mapUid(uid); Uid u = getUidStatsLocked(uid); return u.getServiceStatsLocked(pkg, name); } @@ -5265,7 +6659,7 @@ public final class BatteryStatsImpl extends BatteryStats { time = (time*uidRunningTime)/totalRunningTime; SamplingCounter uidSc = uidProc.mSpeedBins[sb]; if (uidSc == null) { - uidSc = new SamplingCounter(mUnpluggables); + uidSc = new SamplingCounter(mOnBatteryTimeBase); uidProc.mSpeedBins[sb] = uidSc; } uidSc.mCount.addAndGet((int)time); @@ -5402,15 +6796,20 @@ public final class BatteryStatsImpl extends BatteryStats { stream.close(); readSummaryFromParcel(in); - } catch(java.io.IOException e) { + } catch(Exception e) { Slog.e("BatteryStats", "Error reading battery statistics", e); } - long now = SystemClock.elapsedRealtime(); - if (USE_OLD_HISTORY) { - addHistoryRecordLocked(now, HistoryItem.CMD_START); + if (mHistoryBuffer.dataPosition() > 0) { + mRecordingHistory = true; + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + if (USE_OLD_HISTORY) { + addHistoryRecordLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur); + } + addHistoryBufferLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur); + startRecordingHistory(elapsedRealtime, uptime, false); } - addHistoryBufferLocked(now, HistoryItem.CMD_START); } public int describeContents() { @@ -5422,6 +6821,25 @@ public final class BatteryStatsImpl extends BatteryStats { mHistoryBuffer.setDataSize(0); mHistoryBuffer.setDataPosition(0); + mHistoryTagPool.clear(); + mNextHistoryTagIdx = 0; + mNumHistoryTagChars = 0; + + int numTags = in.readInt(); + for (int i=0; i<numTags; i++) { + int idx = in.readInt(); + String str = in.readString(); + int uid = in.readInt(); + HistoryTag tag = new HistoryTag(); + tag.string = str; + tag.uid = uid; + tag.poolIdx = idx; + mHistoryTagPool.put(tag, idx); + if (idx >= mNextHistoryTagIdx) { + mNextHistoryTagIdx = idx+1; + } + mNumHistoryTagChars += tag.string.length() + 1; + } int bufSize = in.readInt(); int curPos = in.dataPosition(); @@ -5485,11 +6903,18 @@ public final class BatteryStatsImpl extends BatteryStats { StringBuilder sb = new StringBuilder(128); sb.append("****************** WRITING mHistoryBaseTime: "); TimeUtils.formatDuration(mHistoryBaseTime, sb); - sb.append(" mLastHistoryTime: "); - TimeUtils.formatDuration(mLastHistoryTime, sb); + sb.append(" mLastHistoryElapsedRealtime: "); + TimeUtils.formatDuration(mLastHistoryElapsedRealtime, sb); Slog.i(TAG, sb.toString()); } - out.writeLong(mHistoryBaseTime + mLastHistoryTime); + out.writeLong(mHistoryBaseTime + mLastHistoryElapsedRealtime); + out.writeInt(mHistoryTagPool.size()); + for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) { + HistoryTag tag = ent.getKey(); + out.writeInt(ent.getValue()); + out.writeString(tag.string); + out.writeInt(tag.uid); + } out.writeInt(mHistoryBuffer.dataSize()); if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: " + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition()); @@ -5523,16 +6948,23 @@ public final class BatteryStatsImpl extends BatteryStats { readHistory(in, true); mStartCount = in.readInt(); - mBatteryUptime = in.readLong(); - mBatteryRealtime = in.readLong(); mUptime = in.readLong(); mRealtime = in.readLong(); + mStartClockTime = in.readLong(); + mOnBatteryTimeBase.readSummaryFromParcel(in); + mOnBatteryScreenOffTimeBase.readSummaryFromParcel(in); mDischargeUnplugLevel = in.readInt(); + mDischargePlugLevel = in.readInt(); mDischargeCurrentLevel = in.readInt(); + mCurrentBatteryLevel = in.readInt(); mLowDischargeAmountSinceCharge = in.readInt(); mHighDischargeAmountSinceCharge = in.readInt(); mDischargeAmountScreenOnSinceCharge = in.readInt(); mDischargeAmountScreenOffSinceCharge = in.readInt(); + mNumDischargeStepDurations = in.readInt(); + in.readLongArray(mDischargeStepDurations); + mNumChargeStepDurations = in.readInt(); + in.readLongArray(mChargeStepDurations); mStartCount++; @@ -5544,6 +6976,7 @@ public final class BatteryStatsImpl extends BatteryStats { mInteractive = false; mInteractiveTimer.readSummaryFromParcelLocked(in); mPhoneOn = false; + mLowPowerModeEnabledTimer.readSummaryFromParcelLocked(in); mPhoneOnTimer.readSummaryFromParcelLocked(in); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { mPhoneSignalStrengthsTimer[i].readSummaryFromParcelLocked(in); @@ -5553,14 +6986,27 @@ public final class BatteryStatsImpl extends BatteryStats { mPhoneDataConnectionsTimer[i].readSummaryFromParcelLocked(in); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].readSummaryFromParcelLocked(in); - } + mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in); + mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in); + } + mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + mMobileRadioActiveTimer.readSummaryFromParcelLocked(in); + mMobileRadioActivePerAppTimer.readSummaryFromParcelLocked(in); + mMobileRadioActiveAdjustedTime.readSummaryFromParcelLocked(in); + mMobileRadioActiveUnknownTime.readSummaryFromParcelLocked(in); + mMobileRadioActiveUnknownCount.readSummaryFromParcelLocked(in); mWifiOn = false; mWifiOnTimer.readSummaryFromParcelLocked(in); mGlobalWifiRunning = false; mGlobalWifiRunningTimer.readSummaryFromParcelLocked(in); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i].readSummaryFromParcelLocked(in); + } mBluetoothOn = false; mBluetoothOnTimer.readSummaryFromParcelLocked(in); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i].readSummaryFromParcelLocked(in); + } int NKW = in.readInt(); if (NKW > 10000) { @@ -5574,7 +7020,22 @@ public final class BatteryStatsImpl extends BatteryStats { } } + int NWR = in.readInt(); + if (NWR > 10000) { + Slog.w(TAG, "File corrupt: too many wakeup reasons " + NWR); + return; + } + for (int iwr = 0; iwr < NWR; iwr++) { + if (in.readInt() != 0) { + String reasonName = in.readString(); + getWakeupReasonCounterLocked(reasonName).readSummaryFromParcelLocked(in); + } + } + sNumSpeedSteps = in.readInt(); + if (sNumSpeedSteps < 0 || sNumSpeedSteps > 100) { + throw new BadParcelableException("Bad speed steps in data: " + sNumSpeedSteps); + } final int NU = in.readInt(); if (NU > 10000) { @@ -5634,12 +7095,15 @@ public final class BatteryStatsImpl extends BatteryStats { } if (in.readInt() != 0) { - if (u.mNetworkActivityCounters == null) { + if (u.mNetworkByteActivityCounters == null) { u.initNetworkActivityLocked(); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - u.mNetworkActivityCounters[i].readSummaryFromParcelLocked(in); + u.mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in); + u.mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in); } + u.mMobileRadioActiveTime.readSummaryFromParcelLocked(in); + u.mMobileRadioActiveCount.readSummaryFromParcelLocked(in); } int NW = in.readInt(); @@ -5693,7 +7157,7 @@ public final class BatteryStatsImpl extends BatteryStats { p.mSpeedBins = new SamplingCounter[NSB]; for (int i=0; i<NSB; i++) { if (in.readInt() != 0) { - p.mSpeedBins[i] = new SamplingCounter(mUnpluggables); + p.mSpeedBins[i] = new SamplingCounter(mOnBatteryTimeBase); p.mSpeedBins[i].readSummaryFromParcelLocked(in); } } @@ -5734,50 +7198,66 @@ public final class BatteryStatsImpl extends BatteryStats { * @param out the Parcel to be written to. */ public void writeSummaryToParcel(Parcel out) { - // Need to update with current kernel wake lock counts. - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); final long NOW_SYS = SystemClock.uptimeMillis() * 1000; final long NOWREAL_SYS = SystemClock.elapsedRealtime() * 1000; - final long NOW = getBatteryUptimeLocked(NOW_SYS); - final long NOWREAL = getBatteryRealtimeLocked(NOWREAL_SYS); out.writeInt(VERSION); writeHistory(out, true); out.writeInt(mStartCount); - out.writeLong(computeBatteryUptime(NOW_SYS, STATS_SINCE_CHARGED)); - out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED)); out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED)); out.writeLong(computeRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED)); + out.writeLong(mStartClockTime); + mOnBatteryTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS); + mOnBatteryScreenOffTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS); out.writeInt(mDischargeUnplugLevel); + out.writeInt(mDischargePlugLevel); out.writeInt(mDischargeCurrentLevel); + out.writeInt(mCurrentBatteryLevel); out.writeInt(getLowDischargeAmountSinceCharge()); out.writeInt(getHighDischargeAmountSinceCharge()); out.writeInt(getDischargeAmountScreenOnSinceCharge()); out.writeInt(getDischargeAmountScreenOffSinceCharge()); - - mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + out.writeInt(mNumDischargeStepDurations); + out.writeLongArray(mDischargeStepDurations); + out.writeInt(mNumChargeStepDurations); + out.writeLongArray(mChargeStepDurations); + + mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL); + mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } - mInteractiveTimer.writeSummaryFromParcelLocked(out, NOWREAL); - mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + mInteractiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mLowPowerModeEnabledTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { - mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL); + mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } - mPhoneSignalScanningTimer.writeSummaryFromParcelLocked(out, NOWREAL); + mPhoneSignalScanningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - mPhoneDataConnectionsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL); + mPhoneDataConnectionsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].writeSummaryFromParcelLocked(out); + mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out); + mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out); + } + mMobileRadioActiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mMobileRadioActivePerAppTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mMobileRadioActiveAdjustedTime.writeSummaryFromParcelLocked(out); + mMobileRadioActiveUnknownTime.writeSummaryFromParcelLocked(out); + mMobileRadioActiveUnknownCount.writeSummaryFromParcelLocked(out); + mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); + } + mBluetoothOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } - mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); - mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL); - mBluetoothOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); out.writeInt(mKernelWakelockStats.size()); for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { @@ -5785,7 +7265,19 @@ public final class BatteryStatsImpl extends BatteryStats { if (kwlt != null) { out.writeInt(1); out.writeString(ent.getKey()); - ent.getValue().writeSummaryFromParcelLocked(out, NOWREAL); + kwlt.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + } else { + out.writeInt(0); + } + } + + out.writeInt(mWakeupReasonStats.size()); + for (Map.Entry<String, LongSamplingCounter> ent : mWakeupReasonStats.entrySet()) { + LongSamplingCounter counter = ent.getValue(); + if (counter != null) { + out.writeInt(1); + out.writeString(ent.getKey()); + counter.writeSummaryFromParcelLocked(out); } else { out.writeInt(0); } @@ -5800,57 +7292,57 @@ public final class BatteryStatsImpl extends BatteryStats { if (u.mWifiRunningTimer != null) { out.writeInt(1); - u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mFullWifiLockTimer != null) { out.writeInt(1); - u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mWifiScanTimer != null) { out.writeInt(1); - u.mWifiScanTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mWifiScanTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) { if (u.mWifiBatchedScanTimer[i] != null) { out.writeInt(1); - u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL); + u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } } if (u.mWifiMulticastTimer != null) { out.writeInt(1); - u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mAudioTurnedOnTimer != null) { out.writeInt(1); - u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mVideoTurnedOnTimer != null) { out.writeInt(1); - u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mForegroundActivityTimer != null) { out.writeInt(1); - u.mForegroundActivityTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mForegroundActivityTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (u.mVibratorOnTimer != null) { out.writeInt(1); - u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } @@ -5864,13 +7356,16 @@ public final class BatteryStatsImpl extends BatteryStats { } } - if (u.mNetworkActivityCounters == null) { + if (u.mNetworkByteActivityCounters == null) { out.writeInt(0); } else { out.writeInt(1); for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - u.mNetworkActivityCounters[i].writeSummaryFromParcelLocked(out); + u.mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out); + u.mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out); } + u.mMobileRadioActiveTime.writeSummaryFromParcelLocked(out); + u.mMobileRadioActiveCount.writeSummaryFromParcelLocked(out); } int NW = u.mWakelockStats.size(); @@ -5882,19 +7377,19 @@ public final class BatteryStatsImpl extends BatteryStats { Uid.Wakelock wl = ent.getValue(); if (wl.mTimerFull != null) { out.writeInt(1); - wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL); + wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (wl.mTimerPartial != null) { out.writeInt(1); - wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL); + wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } if (wl.mTimerWindow != null) { out.writeInt(1); - wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL); + wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } @@ -5910,7 +7405,7 @@ public final class BatteryStatsImpl extends BatteryStats { Uid.Sensor se = ent.getValue(); if (se.mTimer != null) { out.writeInt(1); - se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL); + se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); } else { out.writeInt(0); } @@ -5957,7 +7452,8 @@ public final class BatteryStatsImpl extends BatteryStats { : ps.mServiceStats.entrySet()) { out.writeString(sent.getKey()); BatteryStatsImpl.Uid.Pkg.Serv ss = sent.getValue(); - long time = ss.getStartTimeToNowLocked(NOW); + long time = ss.getStartTimeToNowLocked( + mOnBatteryTimeBase.getUptime(NOW_SYS)); out.writeLong(time); out.writeInt(ss.mStarts); out.writeInt(ss.mLaunches); @@ -5981,69 +7477,81 @@ public final class BatteryStatsImpl extends BatteryStats { readHistory(in, false); mStartCount = in.readInt(); - mBatteryUptime = in.readLong(); - mBatteryLastUptime = 0; - mBatteryRealtime = in.readLong(); - mBatteryLastRealtime = 0; + mStartClockTime = in.readLong(); + mUptime = in.readLong(); + mUptimeStart = in.readLong(); + mRealtime = in.readLong(); + mRealtimeStart = in.readLong(); + mOnBattery = in.readInt() != 0; + mOnBatteryInternal = false; // we are no longer really running. + mOnBatteryTimeBase.readFromParcel(in); + mOnBatteryScreenOffTimeBase.readFromParcel(in); + mScreenState = Display.STATE_UNKNOWN; - mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables, in); + mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase, in); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, - null, mUnpluggables, in); + mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase, + in); } + mInteractive = false; + mInteractiveTimer = new StopwatchTimer(null, -9, null, mOnBatteryTimeBase, in); mPhoneOn = false; - mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in); + mLowPowerModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in); + mPhoneOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase, in); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, - null, mUnpluggables, in); + null, mOnBatteryTimeBase, in); } - mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables, in); + mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mOnBatteryTimeBase, in); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, - null, mUnpluggables, in); + null, mOnBatteryTimeBase, in); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables, in); - } + mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in); + mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in); + } + mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mOnBatteryTimeBase, in); + mMobileRadioActivePerAppTimer = new StopwatchTimer(null, -401, null, mOnBatteryTimeBase, + in); + mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in); + mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in); + mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in); mWifiOn = false; - mWifiOnTimer = new StopwatchTimer(null, -3, null, mUnpluggables, in); + mWifiOnTimer = new StopwatchTimer(null, -4, null, mOnBatteryTimeBase, in); mGlobalWifiRunning = false; - mGlobalWifiRunningTimer = new StopwatchTimer(null, -4, null, mUnpluggables, in); + mGlobalWifiRunningTimer = new StopwatchTimer(null, -5, null, mOnBatteryTimeBase, in); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i] = new StopwatchTimer(null, -600-i, + null, mOnBatteryTimeBase, in); + } mBluetoothOn = false; - mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mUnpluggables, in); + mBluetoothOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase, in); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i, + null, mOnBatteryTimeBase, in); + } mAudioOn = false; - mAudioOnTimer = new StopwatchTimer(null, -6, null, mUnpluggables); + mAudioOnTimer = new StopwatchTimer(null, -7, null, mOnBatteryTimeBase); mVideoOn = false; - mVideoOnTimer = new StopwatchTimer(null, -7, null, mUnpluggables); - mInteractive = false; - mInteractiveTimer = new StopwatchTimer(null, -8, null, mUnpluggables, in); - mUptime = in.readLong(); - mUptimeStart = in.readLong(); - mLastUptime = 0; - mRealtime = in.readLong(); - mRealtimeStart = in.readLong(); - mLastRealtime = 0; - mOnBattery = in.readInt() != 0; - mOnBatteryInternal = false; // we are no longer really running. - mTrackBatteryPastUptime = in.readLong(); - mTrackBatteryUptimeStart = in.readLong(); - mTrackBatteryPastRealtime = in.readLong(); - mTrackBatteryRealtimeStart = in.readLong(); - mUnpluggedBatteryUptime = in.readLong(); - mUnpluggedBatteryRealtime = in.readLong(); + mVideoOnTimer = new StopwatchTimer(null, -8, null, mOnBatteryTimeBase); mDischargeUnplugLevel = in.readInt(); + mDischargePlugLevel = in.readInt(); mDischargeCurrentLevel = in.readInt(); + mCurrentBatteryLevel = in.readInt(); mLowDischargeAmountSinceCharge = in.readInt(); mHighDischargeAmountSinceCharge = in.readInt(); mDischargeAmountScreenOn = in.readInt(); mDischargeAmountScreenOnSinceCharge = in.readInt(); mDischargeAmountScreenOff = in.readInt(); mDischargeAmountScreenOffSinceCharge = in.readInt(); + mNumDischargeStepDurations = in.readInt(); + in.readLongArray(mDischargeStepDurations); + mNumChargeStepDurations = in.readInt(); + in.readLongArray(mChargeStepDurations); mLastWriteTime = in.readLong(); - mRadioDataUptime = in.readLong(); - mRadioDataStart = -1; - mBluetoothPingCount = in.readInt(); mBluetoothPingStart = -1; @@ -6052,12 +7560,22 @@ public final class BatteryStatsImpl extends BatteryStats { for (int ikw = 0; ikw < NKW; ikw++) { if (in.readInt() != 0) { String wakelockName = in.readString(); - in.readInt(); // Extra 0/1 written by Timer.writeTimerToParcel - SamplingTimer kwlt = new SamplingTimer(mUnpluggables, mOnBattery, in); + SamplingTimer kwlt = new SamplingTimer(mOnBatteryTimeBase, in); mKernelWakelockStats.put(wakelockName, kwlt); } } + mWakeupReasonStats.clear(); + int NWR = in.readInt(); + for (int iwr = 0; iwr < NWR; iwr++) { + if (in.readInt() != 0) { + String reasonName = in.readString(); + LongSamplingCounter counter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, + in); + mWakeupReasonStats.put(reasonName, counter); + } + } + mPartialTimers.clear(); mFullTimers.clear(); mWindowTimers.clear(); @@ -6074,7 +7592,7 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i = 0; i < numUids; i++) { int uid = in.readInt(); Uid u = new Uid(uid); - u.readFromParcelLocked(mUnpluggables, in); + u.readFromParcelLocked(mOnBatteryTimeBase, mOnBatteryScreenOffTimeBase, in); mUidStats.append(uid, u); } } @@ -6090,64 +7608,75 @@ public final class BatteryStatsImpl extends BatteryStats { @SuppressWarnings("unused") void writeToParcelLocked(Parcel out, boolean inclUids, int flags) { // Need to update with current kernel wake lock counts. - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); final long uSecUptime = SystemClock.uptimeMillis() * 1000; final long uSecRealtime = SystemClock.elapsedRealtime() * 1000; - final long batteryUptime = getBatteryUptimeLocked(uSecUptime); - final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime); + final long batteryRealtime = mOnBatteryTimeBase.getRealtime(uSecRealtime); + final long batteryScreenOffRealtime = mOnBatteryScreenOffTimeBase.getRealtime(uSecRealtime); out.writeInt(MAGIC); writeHistory(out, false); out.writeInt(mStartCount); - out.writeLong(mBatteryUptime); - out.writeLong(mBatteryRealtime); - mScreenOnTimer.writeToParcel(out, batteryRealtime); + out.writeLong(mStartClockTime); + out.writeLong(mUptime); + out.writeLong(mUptimeStart); + out.writeLong(mRealtime); + out.writeLong(mRealtimeStart); + out.writeInt(mOnBattery ? 1 : 0); + mOnBatteryTimeBase.writeToParcel(out, uSecUptime, uSecRealtime); + mOnBatteryScreenOffTimeBase.writeToParcel(out, uSecUptime, uSecRealtime); + + mScreenOnTimer.writeToParcel(out, uSecRealtime); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i].writeToParcel(out, batteryRealtime); + mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime); } - mInteractiveTimer.writeToParcel(out, batteryRealtime); - mPhoneOnTimer.writeToParcel(out, batteryRealtime); + mInteractiveTimer.writeToParcel(out, uSecRealtime); + mLowPowerModeEnabledTimer.writeToParcel(out, uSecRealtime); + mPhoneOnTimer.writeToParcel(out, uSecRealtime); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { - mPhoneSignalStrengthsTimer[i].writeToParcel(out, batteryRealtime); + mPhoneSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime); } - mPhoneSignalScanningTimer.writeToParcel(out, batteryRealtime); + mPhoneSignalScanningTimer.writeToParcel(out, uSecRealtime); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - mPhoneDataConnectionsTimer[i].writeToParcel(out, batteryRealtime); + mPhoneDataConnectionsTimer[i].writeToParcel(out, uSecRealtime); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { - mNetworkActivityCounters[i].writeToParcel(out); + mNetworkByteActivityCounters[i].writeToParcel(out); + mNetworkPacketActivityCounters[i].writeToParcel(out); + } + mMobileRadioActiveTimer.writeToParcel(out, uSecRealtime); + mMobileRadioActivePerAppTimer.writeToParcel(out, uSecRealtime); + mMobileRadioActiveAdjustedTime.writeToParcel(out); + mMobileRadioActiveUnknownTime.writeToParcel(out); + mMobileRadioActiveUnknownCount.writeToParcel(out); + mWifiOnTimer.writeToParcel(out, uSecRealtime); + mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime); + for (int i=0; i<NUM_WIFI_STATES; i++) { + mWifiStateTimer[i].writeToParcel(out, uSecRealtime); + } + mBluetoothOnTimer.writeToParcel(out, uSecRealtime); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + mBluetoothStateTimer[i].writeToParcel(out, uSecRealtime); } - mWifiOnTimer.writeToParcel(out, batteryRealtime); - mGlobalWifiRunningTimer.writeToParcel(out, batteryRealtime); - mBluetoothOnTimer.writeToParcel(out, batteryRealtime); - out.writeLong(mUptime); - out.writeLong(mUptimeStart); - out.writeLong(mRealtime); - out.writeLong(mRealtimeStart); - out.writeInt(mOnBattery ? 1 : 0); - out.writeLong(batteryUptime); - out.writeLong(mTrackBatteryUptimeStart); - out.writeLong(batteryRealtime); - out.writeLong(mTrackBatteryRealtimeStart); - out.writeLong(mUnpluggedBatteryUptime); - out.writeLong(mUnpluggedBatteryRealtime); out.writeInt(mDischargeUnplugLevel); + out.writeInt(mDischargePlugLevel); out.writeInt(mDischargeCurrentLevel); + out.writeInt(mCurrentBatteryLevel); out.writeInt(mLowDischargeAmountSinceCharge); out.writeInt(mHighDischargeAmountSinceCharge); out.writeInt(mDischargeAmountScreenOn); out.writeInt(mDischargeAmountScreenOnSinceCharge); out.writeInt(mDischargeAmountScreenOff); out.writeInt(mDischargeAmountScreenOffSinceCharge); + out.writeInt(mNumDischargeStepDurations); + out.writeLongArray(mDischargeStepDurations); + out.writeInt(mNumChargeStepDurations); + out.writeLongArray(mChargeStepDurations); out.writeLong(mLastWriteTime); - // Write radio uptime for data - out.writeLong(getRadioDataUptime()); - out.writeInt(getBluetoothPingCount()); if (inclUids) { @@ -6157,7 +7686,18 @@ public final class BatteryStatsImpl extends BatteryStats { if (kwlt != null) { out.writeInt(1); out.writeString(ent.getKey()); - Timer.writeTimerToParcel(out, kwlt, batteryRealtime); + kwlt.writeToParcel(out, uSecRealtime); + } else { + out.writeInt(0); + } + } + out.writeInt(mWakeupReasonStats.size()); + for (Map.Entry<String, LongSamplingCounter> ent : mWakeupReasonStats.entrySet()) { + LongSamplingCounter counter = ent.getValue(); + if (counter != null) { + out.writeInt(1); + out.writeString(ent.getKey()); + counter.writeToParcel(out); } else { out.writeInt(0); } @@ -6175,7 +7715,7 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mUidStats.keyAt(i)); Uid uid = mUidStats.valueAt(i); - uid.writeToParcelLocked(out, batteryRealtime); + uid.writeToParcelLocked(out, uSecRealtime); } } else { out.writeInt(0); @@ -6195,12 +7735,15 @@ public final class BatteryStatsImpl extends BatteryStats { public void prepareForDumpLocked() { // Need to retrieve current kernel wake lock stats before printing. - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(); + pullPendingStateUpdatesLocked(); } - public void dumpLocked(PrintWriter pw, boolean isUnpluggedOnly, int reqUid) { + public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) { if (DEBUG) { + pw.println("mOnBatteryTimeBase:"); + mOnBatteryTimeBase.dump(pw, " "); + pw.println("mOnBatteryScreenOffTimeBase:"); + mOnBatteryScreenOffTimeBase.dump(pw, " "); Printer pr = new PrintWriterPrinter(pw); pr.println("*** Screen timer:"); mScreenOnTimer.logState(pr, " "); @@ -6210,6 +7753,8 @@ public final class BatteryStatsImpl extends BatteryStats { } pr.println("*** Interactive timer:"); mInteractiveTimer.logState(pr, " "); + pr.println("*** Low power mode timer:"); + mLowPowerModeEnabledTimer.logState(pr, " "); pr.println("*** Phone timer:"); mPhoneOnTimer.logState(pr, " "); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { @@ -6222,13 +7767,26 @@ public final class BatteryStatsImpl extends BatteryStats { pr.println("*** Data connection type #" + i + ":"); mPhoneDataConnectionsTimer[i].logState(pr, " "); } + pr.println("*** mMobileRadioPowerState=" + mMobileRadioPowerState); + pr.println("*** Mobile network active timer:"); + mMobileRadioActiveTimer.logState(pr, " "); + pr.println("*** Mobile network active adjusted timer:"); + mMobileRadioActiveAdjustedTime.logState(pr, " "); pr.println("*** Wifi timer:"); mWifiOnTimer.logState(pr, " "); pr.println("*** WifiRunning timer:"); mGlobalWifiRunningTimer.logState(pr, " "); + for (int i=0; i<NUM_WIFI_STATES; i++) { + pr.println("*** Wifi state #" + i + ":"); + mWifiStateTimer[i].logState(pr, " "); + } pr.println("*** Bluetooth timer:"); mBluetoothOnTimer.logState(pr, " "); + for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { + pr.println("*** Bluetooth active type #" + i + ":"); + mBluetoothStateTimer[i].logState(pr, " "); + } } - super.dumpLocked(pw, isUnpluggedOnly, reqUid); + super.dumpLocked(context, pw, flags, reqUid, histStart); } } diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java index d9e3ef6..17685fd 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -22,9 +22,6 @@ import android.os.Looper; import android.os.Message; public class HandlerCaller { - - public final Context mContext; - final Looper mMainLooper; final Handler mH; @@ -47,7 +44,6 @@ public class HandlerCaller { public HandlerCaller(Context context, Looper looper, Callback callback, boolean asyncHandler) { - mContext = context; mMainLooper = looper != null ? looper : context.getMainLooper(); mH = new MyHandler(mMainLooper, asyncHandler); mCallback = callback; @@ -85,7 +81,27 @@ public class HandlerCaller { public void sendMessage(Message msg) { mH.sendMessage(msg); } - + + public SomeArgs sendMessageAndWait(Message msg) { + if (Looper.myLooper() == mH.getLooper()) { + throw new IllegalStateException("Can't wait on same thread as looper"); + } + SomeArgs args = (SomeArgs)msg.obj; + args.mWaitState = SomeArgs.WAIT_WAITING; + mH.sendMessage(msg); + synchronized (args) { + while (args.mWaitState == SomeArgs.WAIT_WAITING) { + try { + args.wait(); + } catch (InterruptedException e) { + return null; + } + } + } + args.mWaitState = SomeArgs.WAIT_NONE; + return args; + } + public Message obtainMessage(int what) { return mH.obtainMessage(what); } @@ -136,6 +152,14 @@ public class HandlerCaller { return mH.obtainMessage(what, arg1, 0, args); } + public Message obtainMessageIOOO(int what, int arg1, Object arg2, Object arg3, Object arg4) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = arg2; + args.arg2 = arg3; + args.arg3 = arg4; + return mH.obtainMessage(what, arg1, 0, args); + } + public Message obtainMessageOO(int what, Object arg1, Object arg2) { SomeArgs args = SomeArgs.obtain(); args.arg1 = arg1; @@ -161,6 +185,17 @@ public class HandlerCaller { return mH.obtainMessage(what, 0, 0, args); } + public Message obtainMessageOOOOO(int what, Object arg1, Object arg2, + Object arg3, Object arg4, Object arg5) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = arg1; + args.arg2 = arg2; + args.arg3 = arg3; + args.arg4 = arg4; + args.arg5 = arg5; + return mH.obtainMessage(what, 0, 0, args); + } + public Message obtainMessageIIII(int what, int arg1, int arg2, int arg3, int arg4) { SomeArgs args = SomeArgs.obtain(); diff --git a/core/java/com/android/internal/os/PkgUsageStats.aidl b/core/java/com/android/internal/os/PkgUsageStats.aidl deleted file mode 100644 index 8305271..0000000 --- a/core/java/com/android/internal/os/PkgUsageStats.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* //device/java/android/android/content/Intent.aidl -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -package com.android.internal.os; - -parcelable PkgUsageStats; diff --git a/core/java/com/android/internal/os/PkgUsageStats.java b/core/java/com/android/internal/os/PkgUsageStats.java deleted file mode 100644 index 8c2c405..0000000 --- a/core/java/com/android/internal/os/PkgUsageStats.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2009 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.Parcel; -import android.os.Parcelable; - -import java.util.HashMap; -import java.util.Map; - -/** - * implementation of PkgUsageStats associated with an - * application package. - * @hide - */ -public class PkgUsageStats implements Parcelable { - public String packageName; - public int launchCount; - public long usageTime; - public Map<String, Long> componentResumeTimes; - - public static final Parcelable.Creator<PkgUsageStats> CREATOR - = new Parcelable.Creator<PkgUsageStats>() { - public PkgUsageStats createFromParcel(Parcel in) { - return new PkgUsageStats(in); - } - - public PkgUsageStats[] newArray(int size) { - return new PkgUsageStats[size]; - } - }; - - public String toString() { - return "PkgUsageStats{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + packageName + "}"; - } - - public PkgUsageStats(String pkgName, int count, long time, Map<String, Long> lastResumeTimes) { - packageName = pkgName; - launchCount = count; - usageTime = time; - componentResumeTimes = new HashMap<String, Long>(lastResumeTimes); - } - - public PkgUsageStats(Parcel source) { - packageName = source.readString(); - launchCount = source.readInt(); - usageTime = source.readLong(); - final int N = source.readInt(); - componentResumeTimes = new HashMap<String, Long>(N); - for (int i = 0; i < N; i++) { - String component = source.readString(); - long lastResumeTime = source.readLong(); - componentResumeTimes.put(component, lastResumeTime); - } - } - - public PkgUsageStats(PkgUsageStats pStats) { - packageName = pStats.packageName; - launchCount = pStats.launchCount; - usageTime = pStats.usageTime; - componentResumeTimes = new HashMap<String, Long>(pStats.componentResumeTimes); - } - - public int describeContents() { - return 0; - } - - public void writeToParcel(Parcel dest, int parcelableFlags) { - dest.writeString(packageName); - dest.writeInt(launchCount); - dest.writeLong(usageTime); - dest.writeInt(componentResumeTimes.size()); - for (Map.Entry<String, Long> ent : componentResumeTimes.entrySet()) { - dest.writeString(ent.getKey()); - dest.writeLong(ent.getValue()); - } - } -} diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java index 6fb72f1..7edf4cc 100644 --- a/core/java/com/android/internal/os/SomeArgs.java +++ b/core/java/com/android/internal/os/SomeArgs.java @@ -35,6 +35,11 @@ public final class SomeArgs { private boolean mInPool; + static final int WAIT_NONE = 0; + static final int WAIT_WAITING = 1; + static final int WAIT_FINISHED = 2; + int mWaitState = WAIT_NONE; + public Object arg1; public Object arg2; public Object arg3; @@ -70,6 +75,9 @@ public final class SomeArgs { if (mInPool) { throw new IllegalStateException("Already recycled."); } + if (mWaitState != WAIT_NONE) { + return; + } synchronized (sPoolLock) { clear(); if (sPoolSize < MAX_POOL_SIZE) { diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl index 63ff5a0..a5421f5 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -25,12 +25,24 @@ import android.os.Bundle; interface IKeyguardService { boolean isShowing(); boolean isSecure(); - boolean isShowingAndNotHidden(); + boolean isShowingAndNotOccluded(); boolean isInputRestricted(); boolean isDismissable(); oneway void verifyUnlock(IKeyguardExitCallback callback); oneway void keyguardDone(boolean authenticated, boolean wakeup); - oneway void setHidden(boolean isHidden); + + /** + * Sets the Keyguard as occluded when a window dismisses the Keyguard with flag + * FLAG_SHOW_ON_LOCK_SCREEN. + * + * @param isOccluded Whether the Keyguard is occluded by another window. + * @return See IKeyguardServiceConstants.KEYGUARD_SERVICE_SET_OCCLUDED_*. This is needed because + * PhoneWindowManager needs to set these flags immediately and can't wait for the + * Keyguard thread to pick it up. In the hidden case, PhoneWindowManager is solely + * responsible to make sure that the flags are unset. + */ + int setOccluded(boolean isOccluded); + oneway void dismiss(); oneway void onDreamingStarted(); oneway void onDreamingStopped(); @@ -44,4 +56,13 @@ interface IKeyguardService { oneway void dispatch(in MotionEvent event); oneway void launchCamera(); oneway void onBootCompleted(); + + /** + * Notifies that the activity behind has now been drawn and it's safe to remove the wallpaper + * and keyguard flag. + * + * @param startTime the start time of the animation in uptime milliseconds + * @param fadeoutDuration the duration of the exit animation, in milliseconds + */ + oneway void startKeyguardExitAnimation(long startTime, long fadeoutDuration); } diff --git a/core/java/com/android/internal/policy/IKeyguardServiceConstants.java b/core/java/com/android/internal/policy/IKeyguardServiceConstants.java new file mode 100644 index 0000000..b88174a --- /dev/null +++ b/core/java/com/android/internal/policy/IKeyguardServiceConstants.java @@ -0,0 +1,41 @@ +/* + * 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.policy; + +/** + * @hide + */ +public class IKeyguardServiceConstants { + + /** + * Constant for {@link com.android.internal.policy.IKeyguardService#setHidden(boolean)}: + * Don't change the keyguard window flags. + */ + public static final int KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_NONE = 0; + + /** + * Constant for {@link com.android.internal.policy.IKeyguardService#setHidden(boolean)}: + * Set the keyguard window flags to FLAG_SHOW_WALLPAPER and PRIVATE_FLAG_KEYGUARD. + */ + public static final int KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_SET_FLAGS = 1; + + /** + * Constant for {@link com.android.internal.policy.IKeyguardService#setHidden(boolean)}: + * Unset the keyguard window flags to FLAG_SHOW_WALLPAPER and PRIVATE_FLAG_KEYGUARD. + */ + public static final int KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_UNSET_FLAGS = 2; +} diff --git a/core/java/com/android/internal/preference/YesNoPreference.java b/core/java/com/android/internal/preference/YesNoPreference.java index cf68a58..7abf416 100644 --- a/core/java/com/android/internal/preference/YesNoPreference.java +++ b/core/java/com/android/internal/preference/YesNoPreference.java @@ -31,15 +31,19 @@ import android.util.AttributeSet; */ public class YesNoPreference extends DialogPreference { private boolean mWasPositiveResult; - - public YesNoPreference(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + + public YesNoPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public YesNoPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } public YesNoPreference(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.yesNoPreferenceStyle); } - + public YesNoPreference(Context context) { this(context, null); } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index d1d1a52..a01e9b7 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -24,20 +24,24 @@ oneway interface IStatusBar { void setIcon(int index, in StatusBarIcon icon); void removeIcon(int index); - void addNotification(IBinder key, in StatusBarNotification notification); - void updateNotification(IBinder key, in StatusBarNotification notification); - void removeNotification(IBinder key); + void addNotification(in StatusBarNotification notification); + void updateNotification(in StatusBarNotification notification); + void removeNotification(String key); void disable(int state); void animateExpandNotificationsPanel(); void animateExpandSettingsPanel(); void animateCollapsePanels(); void setSystemUiVisibility(int vis, int mask); void topAppWindowChanged(boolean menuVisible); - void setImeWindowStatus(in IBinder token, int vis, int backDisposition); + void setImeWindowStatus(in IBinder token, int vis, int backDisposition, + boolean showImeSwitcher); void setHardKeyboardStatus(boolean available, boolean enabled); + void setWindowState(int window, int state); + + void showRecentApps(boolean triggeredFromAltTab); + void hideRecentApps(boolean triggeredFromAltTab); void toggleRecentApps(); void preloadRecentApps(); void cancelPreloadRecentApps(); - void setWindowState(int window, int state); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 97ea7d8..a3b417f 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -31,25 +31,32 @@ interface IStatusBarService void setIconVisibility(String slot, boolean visible); void removeIcon(String slot); void topAppWindowChanged(boolean menuVisible); - void setImeWindowStatus(in IBinder token, int vis, int backDisposition); + void setImeWindowStatus(in IBinder token, int vis, int backDisposition, + boolean showImeSwitcher); void expandSettingsPanel(); void setCurrentUser(int newUserId); // ---- Methods below are for use by the status bar policy services ---- // You need the STATUS_BAR_SERVICE permission void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList, - out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications, - out int[] switches, out List<IBinder> binders); + out List<StatusBarNotification> notifications, out int[] switches, + out List<IBinder> binders); void onPanelRevealed(); - void onNotificationClick(String pkg, String tag, int id); + void onPanelHidden(); + void onNotificationClick(String key); void onNotificationError(String pkg, String tag, int id, - int uid, int initialPid, String message); - void onClearAllNotifications(); - void onNotificationClear(String pkg, String tag, int id); + int uid, int initialPid, String message, int userId); + void onClearAllNotifications(int userId); + void onNotificationClear(String pkg, String tag, int id, int userId); + void onNotificationVisibilityChanged( + in String[] newlyVisibleKeys, in String[] noLongerVisibleKeys); void setSystemUiVisibility(int vis, int mask); void setHardKeyboardEnabled(boolean enabled); + void setWindowState(int window, int state); + + void showRecentApps(boolean triggeredFromAltTab); + void hideRecentApps(boolean triggeredFromAltTab); void toggleRecentApps(); void preloadRecentApps(); void cancelPreloadRecentApps(); - void setWindowState(int window, int state); } diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 9137d3c..d66ef83 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -16,11 +16,10 @@ package com.android.internal.util; -import java.lang.reflect.Array; +import dalvik.system.VMRuntime; +import libcore.util.EmptyArray; -// XXX these should be changed to reflect the actual memory allocator we use. -// it looks like right now objects want to be powers of 2 minus 8 -// and the array size eats another 4 bytes +import java.lang.reflect.Array; /** * ArrayUtils contains some methods that you can call to find out @@ -28,46 +27,42 @@ import java.lang.reflect.Array; */ public class ArrayUtils { - private static Object[] EMPTY = new Object[0]; private static final int CACHE_SIZE = 73; private static Object[] sCache = new Object[CACHE_SIZE]; private ArrayUtils() { /* cannot be instantiated */ } - public static int idealByteArraySize(int need) { - for (int i = 4; i < 32; i++) - if (need <= (1 << i) - 12) - return (1 << i) - 12; - - return need; + public static byte[] newUnpaddedByteArray(int minLen) { + return (byte[])VMRuntime.getRuntime().newUnpaddedArray(byte.class, minLen); } - public static int idealBooleanArraySize(int need) { - return idealByteArraySize(need); + public static char[] newUnpaddedCharArray(int minLen) { + return (char[])VMRuntime.getRuntime().newUnpaddedArray(char.class, minLen); } - public static int idealShortArraySize(int need) { - return idealByteArraySize(need * 2) / 2; + public static int[] newUnpaddedIntArray(int minLen) { + return (int[])VMRuntime.getRuntime().newUnpaddedArray(int.class, minLen); } - public static int idealCharArraySize(int need) { - return idealByteArraySize(need * 2) / 2; + public static boolean[] newUnpaddedBooleanArray(int minLen) { + return (boolean[])VMRuntime.getRuntime().newUnpaddedArray(boolean.class, minLen); } - public static int idealIntArraySize(int need) { - return idealByteArraySize(need * 4) / 4; + public static long[] newUnpaddedLongArray(int minLen) { + return (long[])VMRuntime.getRuntime().newUnpaddedArray(long.class, minLen); } - public static int idealFloatArraySize(int need) { - return idealByteArraySize(need * 4) / 4; + public static float[] newUnpaddedFloatArray(int minLen) { + return (float[])VMRuntime.getRuntime().newUnpaddedArray(float.class, minLen); } - public static int idealObjectArraySize(int need) { - return idealByteArraySize(need * 4) / 4; + public static Object[] newUnpaddedObjectArray(int minLen) { + return (Object[])VMRuntime.getRuntime().newUnpaddedArray(Object.class, minLen); } - public static int idealLongArraySize(int need) { - return idealByteArraySize(need * 8) / 8; + @SuppressWarnings("unchecked") + public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) { + return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen); } /** @@ -102,9 +97,10 @@ public class ArrayUtils * it will return the same empty array every time to avoid reallocation, * although this is not guaranteed. */ + @SuppressWarnings("unchecked") public static <T> T[] emptyArray(Class<T> kind) { if (kind == Object.class) { - return (T[]) EMPTY; + return (T[]) EmptyArray.OBJECT; } int bucket = ((System.identityHashCode(kind) / 8) & 0x7FFFFFFF) % CACHE_SIZE; @@ -121,6 +117,13 @@ public class ArrayUtils } /** + * Checks if given array is null or has zero elements. + */ + public static <T> boolean isEmpty(T[] array) { + return array == null || array.length == 0; + } + + /** * Checks that value is present as at least one of the elements of the array. * @param array the array to check in * @param value the value to check for @@ -166,6 +169,15 @@ public class ArrayUtils return false; } + public static boolean contains(long[] array, long value) { + for (long element : array) { + if (element == value) { + return true; + } + } + return false; + } + public static long total(long[] array) { long total = 0; for (long value : array) { @@ -226,6 +238,14 @@ public class ArrayUtils return array; } + /** + * Appends a new value to a copy of the array and returns the copy. If + * the value is already present, the original array is returned + * @param cur The original array, or null to represent an empty array. + * @param val The value to add. + * @return A new array that contains all of the values of the original array + * with the new value added, or the original array. + */ public static int[] appendInt(int[] cur, int val) { if (cur == null) { return new int[] { val }; @@ -261,4 +281,48 @@ public class ArrayUtils } return cur; } + + /** + * Appends a new value to a copy of the array and returns the copy. If + * the value is already present, the original array is returned + * @param cur The original array, or null to represent an empty array. + * @param val The value to add. + * @return A new array that contains all of the values of the original array + * with the new value added, or the original array. + */ + public static long[] appendLong(long[] cur, long val) { + if (cur == null) { + return new long[] { val }; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + return cur; + } + } + long[] ret = new long[N + 1]; + System.arraycopy(cur, 0, ret, 0, N); + ret[N] = val; + return ret; + } + + public static long[] removeLong(long[] cur, long val) { + if (cur == null) { + return null; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + long[] ret = new long[N - 1]; + if (i > 0) { + System.arraycopy(cur, 0, ret, 0, i); + } + if (i < (N - 1)) { + System.arraycopy(cur, i + 1, ret, i, N - i - 1); + } + return ret; + } + } + return cur; + } } diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java index 52281d9..34f62ba 100644 --- a/core/java/com/android/internal/util/AsyncChannel.java +++ b/core/java/com/android/internal/util/AsyncChannel.java @@ -450,6 +450,7 @@ public class AsyncChannel { public void disconnect() { if ((mConnection != null) && (mSrcContext != null)) { mSrcContext.unbindService(mConnection); + mConnection = null; } try { // Send the DISCONNECTED, although it may not be received @@ -463,10 +464,12 @@ public class AsyncChannel { // Tell source we're disconnected. if (mSrcHandler != null) { replyDisconnected(STATUS_SUCCESSFUL); + mSrcHandler = null; } // Unlink only when bindService isn't used if (mConnection == null && mDstMessenger != null && mDeathMonitor!= null) { mDstMessenger.getBinder().unlinkToDeath(mDeathMonitor, 0); + mDeathMonitor = null; } } diff --git a/core/java/com/android/internal/util/GrowingArrayUtils.java b/core/java/com/android/internal/util/GrowingArrayUtils.java new file mode 100644 index 0000000..b4d2d730 --- /dev/null +++ b/core/java/com/android/internal/util/GrowingArrayUtils.java @@ -0,0 +1,196 @@ +/* + * 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.util; + +/** + * A helper class that aims to provide comparable growth performance to ArrayList, but on primitive + * arrays. Common array operations are implemented for efficient use in dynamic containers. + * + * All methods in this class assume that the length of an array is equivalent to its capacity and + * NOT the number of elements in the array. The current size of the array is always passed in as a + * parameter. + * + * @hide + */ +public final class GrowingArrayUtils { + + /** + * Appends an element to the end of the array, growing the array if there is no more room. + * @param array The array to which to append the element. This must NOT be null. + * @param currentSize The number of elements in the array. Must be less than or equal to + * array.length. + * @param element The element to append. + * @return the array to which the element was appended. This may be different than the given + * array. + */ + public static <T> T[] append(T[] array, int currentSize, T element) { + assert currentSize <= array.length; + + if (currentSize + 1 > array.length) { + @SuppressWarnings("unchecked") + T[] newArray = ArrayUtils.newUnpaddedArray( + (Class<T>) array.getClass().getComponentType(), growSize(currentSize)); + System.arraycopy(array, 0, newArray, 0, currentSize); + array = newArray; + } + array[currentSize] = element; + return array; + } + + /** + * Primitive int version of {@link #append(Object[], int, Object)}. + */ + public static int[] append(int[] array, int currentSize, int element) { + assert currentSize <= array.length; + + if (currentSize + 1 > array.length) { + int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize)); + System.arraycopy(array, 0, newArray, 0, currentSize); + array = newArray; + } + array[currentSize] = element; + return array; + } + + /** + * Primitive long version of {@link #append(Object[], int, Object)}. + */ + public static long[] append(long[] array, int currentSize, long element) { + assert currentSize <= array.length; + + if (currentSize + 1 > array.length) { + long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize)); + System.arraycopy(array, 0, newArray, 0, currentSize); + array = newArray; + } + array[currentSize] = element; + return array; + } + + /** + * Primitive boolean version of {@link #append(Object[], int, Object)}. + */ + public static boolean[] append(boolean[] array, int currentSize, boolean element) { + assert currentSize <= array.length; + + if (currentSize + 1 > array.length) { + boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize)); + System.arraycopy(array, 0, newArray, 0, currentSize); + array = newArray; + } + array[currentSize] = element; + return array; + } + + /** + * Inserts an element into the array at the specified index, growing the array if there is no + * more room. + * + * @param array The array to which to append the element. Must NOT be null. + * @param currentSize The number of elements in the array. Must be less than or equal to + * array.length. + * @param element The element to insert. + * @return the array to which the element was appended. This may be different than the given + * array. + */ + public static <T> T[] insert(T[] array, int currentSize, int index, T element) { + assert currentSize <= array.length; + + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + @SuppressWarnings("unchecked") + T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(), + growSize(currentSize)); + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, array.length - index); + return newArray; + } + + /** + * Primitive int version of {@link #insert(Object[], int, int, Object)}. + */ + public static int[] insert(int[] array, int currentSize, int index, int element) { + assert currentSize <= array.length; + + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize)); + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, array.length - index); + return newArray; + } + + /** + * Primitive long version of {@link #insert(Object[], int, int, Object)}. + */ + public static long[] insert(long[] array, int currentSize, int index, long element) { + assert currentSize <= array.length; + + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize)); + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, array.length - index); + return newArray; + } + + /** + * Primitive boolean version of {@link #insert(Object[], int, int, Object)}. + */ + public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) { + assert currentSize <= array.length; + + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize)); + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, array.length - index); + return newArray; + } + + /** + * Given the current size of an array, returns an ideal size to which the array should grow. + * This is typically double the given size, but should not be relied upon to do so in the + * future. + */ + public static int growSize(int currentSize) { + return currentSize <= 4 ? 8 : currentSize * 2; + } + + // Uninstantiable + private GrowingArrayUtils() {} +} diff --git a/core/java/com/android/internal/util/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java new file mode 100644 index 0000000..a5ce6e0 --- /dev/null +++ b/core/java/com/android/internal/util/ImageUtils.java @@ -0,0 +1,84 @@ +/* + * 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.util; + +import android.graphics.Bitmap; + +/** + * Utility class for image analysis and processing. + * + * @hide + */ +public class ImageUtils { + + // Amount (max is 255) that two channels can differ before the color is no longer "gray". + private static final int TOLERANCE = 20; + + // Alpha amount for which values below are considered transparent. + private static final int ALPHA_TOLERANCE = 50; + + private int[] mTempBuffer; + + /** + * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect + * gray". + */ + public boolean isGrayscale(Bitmap bitmap) { + final int height = bitmap.getHeight(); + final int width = bitmap.getWidth(); + int size = height*width; + + ensureBufferSize(size); + bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height); + for (int i = 0; i < size; i++) { + if (!isGrayscale(mTempBuffer[i])) { + return false; + } + } + return true; + } + + /** + * Makes sure that {@code mTempBuffer} has at least length {@code size}. + */ + private void ensureBufferSize(int size) { + if (mTempBuffer == null || mTempBuffer.length < size) { + mTempBuffer = new int[size]; + } + } + + /** + * Classifies a color as grayscale or not. Grayscale here means "very close to a perfect + * gray"; if all three channels are approximately equal, this will return true. + * + * Note that really transparent colors are always grayscale. + */ + public static boolean isGrayscale(int color) { + int alpha = 0xFF & (color >> 24); + if (alpha < ALPHA_TOLERANCE) { + return true; + } + + int r = 0xFF & (color >> 16); + int g = 0xFF & (color >> 8); + int b = 0xFF & color; + + return Math.abs(r - g) < TOLERANCE + && Math.abs(r - b) < TOLERANCE + && Math.abs(g - b) < TOLERANCE; + } +} diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java new file mode 100644 index 0000000..f38cbde --- /dev/null +++ b/core/java/com/android/internal/util/NotificationColorUtil.java @@ -0,0 +1,197 @@ +/* + * 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.util; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.VectorDrawable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.TextAppearanceSpan; +import android.util.Log; +import android.util.Pair; + +import java.util.Arrays; +import java.util.WeakHashMap; + +/** + * Helper class to process legacy (Holo) notifications to make them look like quantum notifications. + * + * @hide + */ +public class NotificationColorUtil { + + private static final String TAG = "NotificationColorUtil"; + + private static final Object sLock = new Object(); + private static NotificationColorUtil sInstance; + + private final ImageUtils mImageUtils = new ImageUtils(); + private final WeakHashMap<Bitmap, Pair<Boolean, Integer>> mGrayscaleBitmapCache = + new WeakHashMap<Bitmap, Pair<Boolean, Integer>>(); + + public static NotificationColorUtil getInstance() { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new NotificationColorUtil(); + } + return sInstance; + } + } + + /** + * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect + * gray". + * + * @param bitmap The bitmap to test. + * @return Whether the bitmap is grayscale. + */ + public boolean isGrayscale(Bitmap bitmap) { + synchronized (sLock) { + Pair<Boolean, Integer> cached = mGrayscaleBitmapCache.get(bitmap); + if (cached != null) { + if (cached.second == bitmap.getGenerationId()) { + return cached.first; + } + } + } + boolean result; + int generationId; + synchronized (mImageUtils) { + result = mImageUtils.isGrayscale(bitmap); + + // generationId and the check whether the Bitmap is grayscale can't be read atomically + // here. However, since the thread is in the process of posting the notification, we can + // assume that it doesn't modify the bitmap while we are checking the pixels. + generationId = bitmap.getGenerationId(); + } + synchronized (sLock) { + mGrayscaleBitmapCache.put(bitmap, Pair.create(result, generationId)); + } + return result; + } + + /** + * Checks whether a drawable is grayscale. Grayscale here means "very close to a perfect + * gray". + * + * @param d The drawable to test. + * @return Whether the drawable is grayscale. + */ + public boolean isGrayscale(Drawable d) { + if (d == null) { + return false; + } else if (d instanceof BitmapDrawable) { + BitmapDrawable bd = (BitmapDrawable) d; + return bd.getBitmap() != null && isGrayscale(bd.getBitmap()); + } else if (d instanceof AnimationDrawable) { + AnimationDrawable ad = (AnimationDrawable) d; + int count = ad.getNumberOfFrames(); + return count > 0 && isGrayscale(ad.getFrame(0)); + } else if (d instanceof VectorDrawable) { + // We just assume you're doing the right thing if using vectors + return true; + } else { + return false; + } + } + + /** + * Checks whether a drawable with a resoure id is grayscale. Grayscale here means "very close + * to a perfect gray". + * + * @param context The context to load the drawable from. + * @return Whether the drawable is grayscale. + */ + public boolean isGrayscale(Context context, int drawableResId) { + if (drawableResId != 0) { + try { + return isGrayscale(context.getDrawable(drawableResId)); + } catch (Resources.NotFoundException ex) { + Log.e(TAG, "Drawable not found: " + drawableResId); + return false; + } + } else { + return false; + } + } + + /** + * Inverts all the grayscale colors set by {@link android.text.style.TextAppearanceSpan}s on + * the text. + * + * @param charSequence The text to process. + * @return The color inverted text. + */ + public CharSequence invertCharSequenceColors(CharSequence charSequence) { + if (charSequence instanceof Spanned) { + Spanned ss = (Spanned) charSequence; + Object[] spans = ss.getSpans(0, ss.length(), Object.class); + SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); + for (Object span : spans) { + Object resultSpan = span; + if (span instanceof TextAppearanceSpan) { + resultSpan = processTextAppearanceSpan((TextAppearanceSpan) span); + } + builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), + ss.getSpanFlags(span)); + } + return builder; + } + return charSequence; + } + + private TextAppearanceSpan processTextAppearanceSpan(TextAppearanceSpan span) { + ColorStateList colorStateList = span.getTextColor(); + if (colorStateList != null) { + int[] colors = colorStateList.getColors(); + boolean changed = false; + for (int i = 0; i < colors.length; i++) { + if (ImageUtils.isGrayscale(colors[i])) { + + // Allocate a new array so we don't change the colors in the old color state + // list. + if (!changed) { + colors = Arrays.copyOf(colors, colors.length); + } + colors[i] = processColor(colors[i]); + changed = true; + } + } + if (changed) { + return new TextAppearanceSpan( + span.getFamily(), span.getTextStyle(), span.getTextSize(), + new ColorStateList(colorStateList.getStates(), colors), + span.getLinkTextColor()); + } + } + return span; + } + + private int processColor(int color) { + return Color.argb(Color.alpha(color), + 255 - Color.red(color), + 255 - Color.green(color), + 255 - Color.blue(color)); + } +} diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index a54b364..c0d1e88 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -30,7 +30,7 @@ public class Preconditions { * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null */ - public static <T> T checkNotNull(T reference) { + public static <T> T checkNotNull(final T reference) { if (reference == null) { throw new NullPointerException(); } @@ -47,7 +47,7 @@ public class Preconditions { * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null */ - public static <T> T checkNotNull(T reference, Object errorMessage) { + public static <T> T checkNotNull(final T reference, final Object errorMessage) { if (reference == null) { throw new NullPointerException(String.valueOf(errorMessage)); } @@ -61,7 +61,7 @@ public class Preconditions { * @param expression a boolean expression * @throws IllegalStateException if {@code expression} is false */ - public static void checkState(boolean expression) { + public static void checkState(final boolean expression) { if (!expression) { throw new IllegalStateException(); } @@ -71,11 +71,205 @@ public class Preconditions { * Check the requested flags, throwing if any requested flags are outside * the allowed set. */ - public static void checkFlagsArgument(int requestedFlags, int allowedFlags) { + public static void checkFlagsArgument(final int requestedFlags, final int allowedFlags) { if ((requestedFlags & allowedFlags) != requestedFlags) { throw new IllegalArgumentException("Requested flags 0x" + Integer.toHexString(requestedFlags) + ", but only 0x" + Integer.toHexString(allowedFlags) + " are allowed"); } } + + /** + * Ensures that that the argument numeric value is non-negative. + * + * @param value a numeric int value + * @param errorMessage the exception message to use if the check fails + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was negative + */ + public static int checkArgumentNonnegative(final int value, final String errorMessage) { + if (value < 0) { + throw new IllegalArgumentException(errorMessage); + } + + return value; + } + + /** + * Ensures that that the argument numeric value is non-negative. + * + * @param value a numeric long value + * @param errorMessage the exception message to use if the check fails + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was negative + */ + public static long checkArgumentNonnegative(final long value, final String errorMessage) { + if (value < 0) { + throw new IllegalArgumentException(errorMessage); + } + + return value; + } + + /** + * Ensures that that the argument numeric value is positive. + * + * @param value a numeric int value + * @param errorMessage the exception message to use if the check fails + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was not positive + */ + public static int checkArgumentPositive(final int value, final String errorMessage) { + if (value <= 0) { + throw new IllegalArgumentException(errorMessage); + } + + return value; + } + + /** + * Ensures that the argument floating point value is a finite number. + * + * <p>A finite number is defined to be both representable (that is, not NaN) and + * not infinite (that is neither positive or negative infinity).</p> + * + * @param value a floating point value + * @param valueName the name of the argument to use if the check fails + * + * @return the validated floating point value + * + * @throws IllegalArgumentException if {@code value} was not finite + */ + public static float checkArgumentFinite(final float value, final String valueName) { + if (Float.isNaN(value)) { + throw new IllegalArgumentException(valueName + " must not be NaN"); + } else if (Float.isInfinite(value)) { + throw new IllegalArgumentException(valueName + " must not be infinite"); + } + + return value; + } + + /** + * Ensures that the argument floating point value is within the inclusive range. + * + * <p>While this can be used to range check against +/- infinity, note that all NaN numbers + * will always be out of range.</p> + * + * @param value a floating point value + * @param lower the lower endpoint of the inclusive range + * @param upper the upper endpoint of the inclusive range + * @param valueName the name of the argument to use if the check fails + * + * @return the validated floating point value + * + * @throws IllegalArgumentException if {@code value} was not within the range + */ + public static float checkArgumentInRange(float value, float lower, float upper, + String valueName) { + if (Float.isNaN(value)) { + throw new IllegalArgumentException(valueName + " must not be NaN"); + } else if (value < lower) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%f, %f] (too low)", valueName, lower, upper)); + } else if (value > upper) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%f, %f] (too high)", valueName, lower, upper)); + } + + return value; + } + + /** + * Ensures that the argument int value is within the inclusive range. + * + * @param value a int value + * @param lower the lower endpoint of the inclusive range + * @param upper the upper endpoint of the inclusive range + * @param valueName the name of the argument to use if the check fails + * + * @return the validated int value + * + * @throws IllegalArgumentException if {@code value} was not within the range + */ + public static int checkArgumentInRange(int value, int lower, int upper, + String valueName) { + if (value < lower) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%d, %d] (too low)", valueName, lower, upper)); + } else if (value > upper) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%d, %d] (too high)", valueName, lower, upper)); + } + + return value; + } + + /** + * Ensures that the array is not {@code null}, and none if its elements are {@code null}. + * + * @param value an array of boxed objects + * @param valueName the name of the argument to use if the check fails + * + * @return the validated array + * + * @throws NullPointerException if the {@code value} or any of its elements were {@code null} + */ + public static <T> T[] checkArrayElementsNotNull(final T[] value, final String valueName) { + if (value == null) { + throw new NullPointerException(valueName + " must not be null"); + } + + for (int i = 0; i < value.length; ++i) { + if (value[i] == null) { + throw new NullPointerException( + String.format("%s[%d] must not be null", valueName, i)); + } + } + + return value; + } + + /** + * Ensures that all elements in the argument floating point array are within the inclusive range + * + * <p>While this can be used to range check against +/- infinity, note that all NaN numbers + * will always be out of range.</p> + * + * @param value a floating point array of values + * @param lower the lower endpoint of the inclusive range + * @param upper the upper endpoint of the inclusive range + * @param valueName the name of the argument to use if the check fails + * + * @return the validated floating point value + * + * @throws IllegalArgumentException if any of the elements in {@code value} were out of range + * @throws NullPointerException if the {@code value} was {@code null} + */ + public static float[] checkArrayElementsInRange(float[] value, float lower, float upper, + String valueName) { + checkNotNull(value, valueName + " must not be null"); + + for (int i = 0; i < value.length; ++i) { + float v = value[i]; + + if (Float.isNaN(v)) { + throw new IllegalArgumentException(valueName + "[" + i + "] must not be NaN"); + } else if (v < lower) { + throw new IllegalArgumentException( + String.format("%s[%d] is out of range of [%f, %f] (too low)", + valueName, i, lower, upper)); + } else if (v > upper) { + throw new IllegalArgumentException( + String.format("%s[%d] is out of range of [%f, %f] (too high)", + valueName, i, lower, upper)); + } + } + + return value; + } } diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index b380403..af966b1 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -46,6 +46,10 @@ public class Protocol { public static final int BASE_WIFI_MONITOR = 0x00024000; public static final int BASE_WIFI_MANAGER = 0x00025000; public static final int BASE_WIFI_CONTROLLER = 0x00026000; + public static final int BASE_WIFI_SCANNER = 0x00027000; + public static final int BASE_WIFI_SCANNER_SERVICE = 0x00027100; + public static final int BASE_WIFI_PASSPOINT_MANAGER = 0x00028000; + public static final int BASE_WIFI_PASSPOINT_SERVICE = 0x00028100; public static final int BASE_DHCP = 0x00030000; public static final int BASE_DATA_CONNECTION = 0x00040000; public static final int BASE_DATA_CONNECTION_AC = 0x00041000; @@ -53,5 +57,9 @@ public class Protocol { public static final int BASE_DNS_PINGER = 0x00050000; public static final int BASE_NSD_MANAGER = 0x00060000; public static final int BASE_NETWORK_STATE_TRACKER = 0x00070000; + public static final int BASE_CONNECTIVITY_MANAGER = 0x00080000; + public static final int BASE_NETWORK_AGENT = 0x00081000; + public static final int BASE_NETWORK_MONITOR = 0x00082000; + public static final int BASE_NETWORK_FACTORY = 0x00083000; //TODO: define all used protocols } diff --git a/core/java/com/android/internal/util/VirtualRefBasePtr.java b/core/java/com/android/internal/util/VirtualRefBasePtr.java new file mode 100644 index 0000000..52306f1 --- /dev/null +++ b/core/java/com/android/internal/util/VirtualRefBasePtr.java @@ -0,0 +1,53 @@ +/* + * 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.util; + +/** + * Helper class that contains a strong reference to a VirtualRefBase native + * object. This will incStrong in the ctor, and decStrong in the finalizer + */ +public final class VirtualRefBasePtr { + private long mNativePtr; + + public VirtualRefBasePtr(long ptr) { + mNativePtr = ptr; + nIncStrong(mNativePtr); + } + + public long get() { + return mNativePtr; + } + + public void release() { + if (mNativePtr != 0) { + nDecStrong(mNativePtr); + mNativePtr = 0; + } + } + + @Override + protected void finalize() throws Throwable { + try { + release(); + } finally { + super.finalize(); + } + } + + private static native void nIncStrong(long ptr); + private static native void nDecStrong(long ptr); +} diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java index b35de93..dca9921 100644 --- a/core/java/com/android/internal/util/XmlUtils.java +++ b/core/java/com/android/internal/util/XmlUtils.java @@ -220,28 +220,74 @@ public class XmlUtils { * @see #readMapXml */ public static final void writeMapXml(Map val, String name, XmlSerializer out) - throws XmlPullParserException, java.io.IOException - { + throws XmlPullParserException, java.io.IOException { + writeMapXml(val, name, out, null); + } + + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). + * + * @param val The map to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the map into. + * @param callback Method to call when an Object type is not recognized. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + * + * @hide + */ + public static final void writeMapXml(Map val, String name, XmlSerializer out, + WriteMapCallback callback) throws XmlPullParserException, java.io.IOException { + if (val == null) { out.startTag(null, "null"); out.endTag(null, "null"); return; } - Set s = val.entrySet(); - Iterator i = s.iterator(); - out.startTag(null, "map"); if (name != null) { out.attribute(null, "name", name); } + writeMapXml(val, out, callback); + + out.endTag(null, "map"); + } + + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). This method presumes that the start tag and + * name attribute have already been written and does not write an end tag. + * + * @param val The map to be flattened. + * @param out XmlSerializer to write the map into. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + * + * @hide + */ + public static final void writeMapXml(Map val, XmlSerializer out, + WriteMapCallback callback) throws XmlPullParserException, java.io.IOException { + if (val == null) { + return; + } + + Set s = val.entrySet(); + Iterator i = s.iterator(); + while (i.hasNext()) { Map.Entry e = (Map.Entry)i.next(); - writeValueXml(e.getValue(), (String)e.getKey(), out); + writeValueXml(e.getValue(), (String)e.getKey(), out, callback); } - - out.endTag(null, "map"); } /** @@ -387,6 +433,123 @@ public class XmlUtils { } /** + * Flatten a long[] into an XmlSerializer. The list can later be read back + * with readThisLongArrayXml(). + * + * @param val The long array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeLongArrayXml(long[] val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "long-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int i=0; i<N; i++) { + out.startTag(null, "item"); + out.attribute(null, "value", Long.toString(val[i])); + out.endTag(null, "item"); + } + + out.endTag(null, "long-array"); + } + + /** + * Flatten a double[] into an XmlSerializer. The list can later be read back + * with readThisDoubleArrayXml(). + * + * @param val The double array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeDoubleArrayXml(double[] val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "double-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int i=0; i<N; i++) { + out.startTag(null, "item"); + out.attribute(null, "value", Double.toString(val[i])); + out.endTag(null, "item"); + } + + out.endTag(null, "double-array"); + } + + /** + * Flatten a String[] into an XmlSerializer. The list can later be read back + * with readThisStringArrayXml(). + * + * @param val The long array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeStringArrayXml(String[] val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "string-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int i=0; i<N; i++) { + out.startTag(null, "item"); + out.attribute(null, "value", val[i]); + out.endTag(null, "item"); + } + + out.endTag(null, "string-array"); + } + + /** * Flatten an object's value into an XmlSerializer. The value can later * be read back with readThisValueXml(). * @@ -403,8 +566,29 @@ public class XmlUtils { * @see #readValueXml */ public static final void writeValueXml(Object v, String name, XmlSerializer out) - throws XmlPullParserException, java.io.IOException - { + throws XmlPullParserException, java.io.IOException { + writeValueXml(v, name, out, null); + } + + /** + * Flatten an object's value into an XmlSerializer. The value can later + * be read back with readThisValueXml(). + * + * Currently supported value types are: null, String, Integer, Long, + * Float, Double Boolean, Map, List. + * + * @param v The object to be flattened. + * @param name Name attribute to include with this value's tag, or null + * for none. + * @param out XmlSerializer to write the object into. + * @param callback Handler for Object types not recognized. + * + * @see #writeMapXml + * @see #writeListXml + * @see #readValueXml + */ + private static final void writeValueXml(Object v, String name, XmlSerializer out, + WriteMapCallback callback) throws XmlPullParserException, java.io.IOException { String typeStr; if (v == null) { out.startTag(null, "null"); @@ -437,14 +621,23 @@ public class XmlUtils { } else if (v instanceof int[]) { writeIntArrayXml((int[])v, name, out); return; + } else if (v instanceof long[]) { + writeLongArrayXml((long[])v, name, out); + return; + } else if (v instanceof double[]) { + writeDoubleArrayXml((double[])v, name, out); + return; + } else if (v instanceof String[]) { + writeStringArrayXml((String[])v, name, out); + return; } else if (v instanceof Map) { writeMapXml((Map)v, name, out); return; } else if (v instanceof List) { - writeListXml((List)v, name, out); + writeListXml((List) v, name, out); return; } else if (v instanceof Set) { - writeSetXml((Set)v, name, out); + writeSetXml((Set) v, name, out); return; } else if (v instanceof CharSequence) { // XXX This is to allow us to at least write something if @@ -457,6 +650,9 @@ public class XmlUtils { out.text(v.toString()); out.endTag(null, "string"); return; + } else if (callback != null) { + callback.writeUnknownObject(v, name, out); + return; } else { throw new RuntimeException("writeValueXml: unable to write value " + v); } @@ -550,14 +746,35 @@ public class XmlUtils { * @see #readMapXml */ public static final HashMap<String, ?> readThisMapXml(XmlPullParser parser, String endTag, - String[] name) throws XmlPullParserException, java.io.IOException + String[] name) throws XmlPullParserException, java.io.IOException { + return readThisMapXml(parser, endTag, name, null); + } + + /** + * Read a HashMap object from an XmlPullParser. The XML data could + * previously have been generated by writeMapXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the map. + * + * @param parser The XmlPullParser from which to read the map data. + * @param endTag Name of the tag that will end the map, usually "map". + * @param name An array of one string, used to return the name attribute + * of the map's tag. + * + * @return HashMap The newly generated map. + * + * @see #readMapXml + * @hide + */ + public static final HashMap<String, ?> readThisMapXml(XmlPullParser parser, String endTag, + String[] name, ReadMapCallback callback) + throws XmlPullParserException, java.io.IOException { HashMap<String, Object> map = new HashMap<String, Object>(); int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - Object val = readThisValueXml(parser, name); + Object val = readThisValueXml(parser, name, callback); map.put(name[0], val); } else if (eventType == parser.END_TAG) { if (parser.getName().equals(endTag)) { @@ -587,15 +804,34 @@ public class XmlUtils { * * @see #readListXml */ - public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name) - throws XmlPullParserException, java.io.IOException - { + public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + return readThisListXml(parser, endTag, name, null); + } + + /** + * Read an ArrayList object from an XmlPullParser. The XML data could + * previously have been generated by writeListXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return HashMap The newly generated list. + * + * @see #readListXml + */ + private static final ArrayList readThisListXml(XmlPullParser parser, String endTag, + String[] name, ReadMapCallback callback) + throws XmlPullParserException, java.io.IOException { ArrayList list = new ArrayList(); int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - Object val = readThisValueXml(parser, name); + Object val = readThisValueXml(parser, name, callback); list.add(val); //System.out.println("Adding to list: " + val); } else if (eventType == parser.END_TAG) { @@ -611,7 +847,29 @@ public class XmlUtils { throw new XmlPullParserException( "Document ended before " + endTag + " end tag"); } - + + /** + * Read a HashSet object from an XmlPullParser. The XML data could previously + * have been generated by writeSetXml(). The XmlPullParser must be positioned + * <em>after</em> the tag that begins the set. + * + * @param parser The XmlPullParser from which to read the set data. + * @param endTag Name of the tag that will end the set, usually "set". + * @param name An array of one string, used to return the name attribute + * of the set's tag. + * + * @return HashSet The newly generated set. + * + * @throws XmlPullParserException + * @throws java.io.IOException + * + * @see #readSetXml + */ + public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + return readThisSetXml(parser, endTag, name, null); + } + /** * Read a HashSet object from an XmlPullParser. The XML data could previously * have been generated by writeSetXml(). The XmlPullParser must be positioned @@ -628,15 +886,16 @@ public class XmlUtils { * @throws java.io.IOException * * @see #readSetXml + * @hide */ - public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name) - throws XmlPullParserException, java.io.IOException { + private static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name, + ReadMapCallback callback) throws XmlPullParserException, java.io.IOException { HashSet set = new HashSet(); int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - Object val = readThisValueXml(parser, name); + Object val = readThisValueXml(parser, name, callback); set.add(val); //System.out.println("Adding to set: " + val); } else if (eventType == parser.END_TAG) { @@ -681,6 +940,7 @@ public class XmlUtils { throw new XmlPullParserException( "Not a number in num attribute in byte-array"); } + parser.next(); int[] array = new int[num]; int i = 0; @@ -722,6 +982,187 @@ public class XmlUtils { } /** + * Read a long[] object from an XmlPullParser. The XML data could + * previously have been generated by writeLongArrayXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated long[]. + * + * @see #readListXml + */ + public static final long[] readThisLongArrayXml(XmlPullParser parser, + String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in long-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in long-array"); + } + parser.next(); + + long[] array = new long[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Long.parseLong(parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + + /** + * Read a double[] object from an XmlPullParser. The XML data could + * previously have been generated by writeDoubleArrayXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "double-array". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated double[]. + * + * @see #readListXml + */ + public static final double[] readThisDoubleArrayXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in double-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in double-array"); + } + parser.next(); + + double[] array = new double[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Double.parseDouble(parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + + /** + * Read a String[] object from an XmlPullParser. The XML data could + * previously have been generated by writeStringArrayXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "string-array". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated String[]. + * + * @see #readListXml + */ + public static final String[] readThisStringArrayXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in string-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in string-array"); + } + parser.next(); + + String[] array = new String[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = parser.getAttributeValue(null, "value"); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + + /** * Read a flattened object from an XmlPullParser. The XML data could * previously have been written with writeMapXml(), writeListXml(), or * writeValueXml(). The XmlPullParser must be positioned <em>at</em> the @@ -743,7 +1184,7 @@ public class XmlUtils { int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - return readThisValueXml(parser, name); + return readThisValueXml(parser, name, null); } else if (eventType == parser.END_TAG) { throw new XmlPullParserException( "Unexpected end tag at: " + parser.getName()); @@ -758,9 +1199,8 @@ public class XmlUtils { "Unexpected end of document"); } - private static final Object readThisValueXml(XmlPullParser parser, String[] name) - throws XmlPullParserException, java.io.IOException - { + private static final Object readThisValueXml(XmlPullParser parser, String[] name, + ReadMapCallback callback) throws XmlPullParserException, java.io.IOException { final String valueName = parser.getAttributeValue(null, "name"); final String tagName = parser.getName(); @@ -794,11 +1234,25 @@ public class XmlUtils { } else if ((res = readThisPrimitiveValueXml(parser, tagName)) != null) { // all work already done by readThisPrimitiveValueXml } else if (tagName.equals("int-array")) { - parser.next(); res = readThisIntArrayXml(parser, "int-array", name); name[0] = valueName; //System.out.println("Returning value for " + valueName + ": " + res); return res; + } else if (tagName.equals("long-array")) { + res = readThisLongArrayXml(parser, "long-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("double-array")) { + res = readThisDoubleArrayXml(parser, "double-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("string-array")) { + res = readThisStringArrayXml(parser, "string-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; } else if (tagName.equals("map")) { parser.next(); res = readThisMapXml(parser, "map", name); @@ -817,9 +1271,12 @@ public class XmlUtils { name[0] = valueName; //System.out.println("Returning value for " + valueName + ": " + res); return res; + } else if (callback != null) { + res = callback.readThisUnknownObjectXml(parser, tagName); + name[0] = valueName; + return res; } else { - throw new XmlPullParserException( - "Unknown tag: " + tagName); + throw new XmlPullParserException("Unknown tag: " + tagName); } // Skip through to end tag. @@ -912,6 +1369,15 @@ public class XmlUtils { } } + public static int readIntAttribute(XmlPullParser in, String name, int defaultValue) { + final String value = in.getAttributeValue(null, name); + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + public static int readIntAttribute(XmlPullParser in, String name) throws IOException { final String value = in.getAttributeValue(null, name); try { @@ -958,4 +1424,39 @@ public class XmlUtils { throws IOException { out.attribute(null, name, Boolean.toString(value)); } + + /** @hide */ + public interface WriteMapCallback { + /** + * Called from writeMapXml when an Object type is not recognized. The implementer + * must write out the entire element including start and end tags. + * + * @param v The object to be written out + * @param name The mapping key for v. Must be written into the "name" attribute of the + * start tag. + * @param out The XML output stream. + * @throws XmlPullParserException on unrecognized Object type. + * @throws IOException on XmlSerializer serialization errors. + * @hide + */ + public void writeUnknownObject(Object v, String name, XmlSerializer out) + throws XmlPullParserException, IOException; + } + + /** @hide */ + public interface ReadMapCallback { + /** + * Called from readThisMapXml when a START_TAG is not recognized. The input stream + * is positioned within the start tag so that attributes can be read using in.getAttribute. + * + * @param in the XML input stream + * @param tag the START_TAG that was not recognized. + * @return the Object parsed from the stream which will be put into the map. + * @throws XmlPullParserException if the START_TAG is not recognized. + * @throws IOException on XmlPullParser serialization errors. + * @hide + */ + public Object readThisUnknownObjectXml(XmlPullParser in, String tag) + throws XmlPullParserException, IOException; + } } diff --git a/core/java/com/android/internal/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java index 25086c5..bee59dc 100644 --- a/core/java/com/android/internal/view/ActionBarPolicy.java +++ b/core/java/com/android/internal/view/ActionBarPolicy.java @@ -19,11 +19,9 @@ package com.android.internal.view; import com.android.internal.R; import android.content.Context; -import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Build; -import android.view.ViewConfiguration; /** * Allows components to query for various configuration policy decisions diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index ce4312d..9e8d12b 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -27,4 +27,5 @@ oneway interface IInputMethodClient { void onBindMethod(in InputBindResult res); void onUnbindMethod(int sequence); void setActive(boolean active); + void setCursorAnchorMonitorMode(int monitorMode); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 3724a08..5336174 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -32,7 +32,9 @@ import com.android.internal.view.IInputMethodClient; * this file. */ interface IInputMethodManager { + // TODO: Use ParceledListSlice instead List<InputMethodInfo> getInputMethodList(); + // TODO: Use ParceledListSlice instead List<InputMethodInfo> getEnabledInputMethodList(); List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId, boolean allowsImplicitlySelectedSubtypes); @@ -74,4 +76,7 @@ interface IInputMethodManager { boolean shouldOfferSwitchingToNextInputMethod(in IBinder token); boolean setInputMethodEnabled(String id, boolean enabled); void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); + int getInputMethodWindowVisibleHeight(); + oneway void notifyTextCommitted(); + void setCursorAnchorMonitorMode(in IBinder token, int monitorMode); } diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl index 90210ce..367b713 100644 --- a/core/java/com/android/internal/view/IInputMethodSession.aidl +++ b/core/java/com/android/internal/view/IInputMethodSession.aidl @@ -21,6 +21,7 @@ import android.os.Bundle; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.ExtractedText; /** @@ -47,4 +48,6 @@ oneway interface IInputMethodSession { void toggleSoftInput(int showFlags, int hideFlags); void finishSession(); + + void updateCursorAnchorInfo(in CursorAnchorInfo cursorAnchorInfo); } diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java index 6295314..b479cb1 100644 --- a/core/java/com/android/internal/view/RotationPolicy.java +++ b/core/java/com/android/internal/view/RotationPolicy.java @@ -18,7 +18,9 @@ package com.android.internal.view; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.database.ContentObserver; +import android.graphics.Point; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; @@ -26,15 +28,20 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; +import android.view.Display; import android.view.IWindowManager; import android.view.Surface; import android.view.WindowManagerGlobal; +import com.android.internal.R; + /** * Provides helper functions for configuring the display rotation policy. */ public final class RotationPolicy { private static final String TAG = "RotationPolicy"; + private static final int CURRENT_ROTATION = -1; + private static final int NATURAL_ROTATION = Surface.ROTATION_0; private RotationPolicy() { } @@ -57,23 +64,33 @@ public final class RotationPolicy { } /** - * Returns true if the device supports the rotation-lock toggle feature - * in the system UI or system bar. + * Returns the orientation that will be used when locking the orientation from system UI + * with {@link #setRotationLock}. * - * When the rotation-lock toggle is supported, the "auto-rotate screen" option in - * Display settings should be hidden, but it should remain available in Accessibility - * settings. + * If the device only supports locking to its natural orientation, this will be either + * Configuration.ORIENTATION_PORTRAIT or Configuration.ORIENTATION_LANDSCAPE, + * otherwise Configuration.ORIENTATION_UNDEFINED if any orientation is lockable. */ - public static boolean isRotationLockToggleSupported(Context context) { - return isRotationSupported(context) - && context.getResources().getConfiguration().smallestScreenWidthDp >= 600; + public static int getRotationLockOrientation(Context context) { + if (!areAllRotationsAllowed(context)) { + final Point size = new Point(); + final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + try { + wm.getInitialDisplaySize(Display.DEFAULT_DISPLAY, size); + return size.x < size.y ? + Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; + } catch (RemoteException e) { + Log.w(TAG, "Unable to get the display size"); + } + } + return Configuration.ORIENTATION_UNDEFINED; } /** - * Returns true if the rotation-lock toggle should be shown in the UI. + * Returns true if the rotation-lock toggle should be shown in system UI. */ public static boolean isRotationLockToggleVisible(Context context) { - return isRotationLockToggleSupported(context) && + return isRotationSupported(context) && Settings.System.getIntForUser(context.getContentResolver(), Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0, UserHandle.USER_CURRENT) == 0; @@ -88,50 +105,42 @@ public final class RotationPolicy { } /** - * Enables or disables rotation lock. - * - * Should be used by the rotation lock toggle. + * Enables or disables rotation lock from the system UI toggle. */ public static void setRotationLock(Context context, final boolean enabled) { Settings.System.putIntForUser(context.getContentResolver(), Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0, UserHandle.USER_CURRENT); - AsyncTask.execute(new Runnable() { - @Override - public void run() { - try { - IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - if (enabled) { - wm.freezeRotation(-1); - } else { - wm.thawRotation(); - } - } catch (RemoteException exc) { - Log.w(TAG, "Unable to save auto-rotate setting"); - } - } - }); + final int rotation = areAllRotationsAllowed(context) ? CURRENT_ROTATION : NATURAL_ROTATION; + setRotationLock(enabled, rotation); } /** - * Enables or disables rotation lock and adjusts whether the rotation lock toggle - * should be hidden for accessibility purposes. + * Enables or disables natural rotation lock from Accessibility settings. * - * Should be used by Display settings and Accessibility settings. + * If rotation is locked for accessibility, the system UI toggle is hidden to avoid confusion. */ public static void setRotationLockForAccessibility(Context context, final boolean enabled) { Settings.System.putIntForUser(context.getContentResolver(), Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, enabled ? 1 : 0, UserHandle.USER_CURRENT); + setRotationLock(enabled, NATURAL_ROTATION); + } + + private static boolean areAllRotationsAllowed(Context context) { + return context.getResources().getBoolean(R.bool.config_allowAllRotations); + } + + private static void setRotationLock(final boolean enabled, final int rotation) { AsyncTask.execute(new Runnable() { @Override public void run() { try { IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); if (enabled) { - wm.freezeRotation(Surface.ROTATION_0); + wm.freezeRotation(rotation); } else { wm.thawRotation(); } diff --git a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java new file mode 100644 index 0000000..06838c9 --- /dev/null +++ b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java @@ -0,0 +1,75 @@ +/* + * 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.view.animation; + +import android.animation.TimeInterpolator; +import android.util.TimeUtils; +import android.view.Choreographer; + +/** + * Interpolator that builds a lookup table to use. This is a fallback for + * building a native interpolator from a TimeInterpolator that is not marked + * with {@link HasNativeInterpolator} + * + * This implements TimeInterpolator to allow for easier interop with Animators + */ +@HasNativeInterpolator +public class FallbackLUTInterpolator implements NativeInterpolatorFactory, TimeInterpolator { + + private TimeInterpolator mSourceInterpolator; + private final float mLut[]; + + /** + * Used to cache the float[] LUT for use across multiple native + * interpolator creation + */ + public FallbackLUTInterpolator(TimeInterpolator interpolator, long duration) { + mSourceInterpolator = interpolator; + mLut = createLUT(interpolator, duration); + } + + private static float[] createLUT(TimeInterpolator interpolator, long duration) { + long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos(); + int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS); + int numAnimFrames = (int) Math.ceil(duration / animIntervalMs); + float values[] = new float[numAnimFrames]; + float lastFrame = numAnimFrames - 1; + for (int i = 0; i < numAnimFrames; i++) { + float inValue = i / lastFrame; + values[i] = interpolator.getInterpolation(inValue); + } + return values; + } + + @Override + public long createNativeInterpolator() { + return NativeInterpolatorFactoryHelper.createLutInterpolator(mLut); + } + + /** + * Used to create a one-shot float[] LUT & native interpolator + */ + public static long createNativeInterpolator(TimeInterpolator interpolator, long duration) { + float[] lut = createLUT(interpolator, duration); + return NativeInterpolatorFactoryHelper.createLutInterpolator(lut); + } + + @Override + public float getInterpolation(float input) { + return mSourceInterpolator.getInterpolation(input); + } +} diff --git a/core/java/com/android/internal/view/animation/HasNativeInterpolator.java b/core/java/com/android/internal/view/animation/HasNativeInterpolator.java new file mode 100644 index 0000000..48ea4da --- /dev/null +++ b/core/java/com/android/internal/view/animation/HasNativeInterpolator.java @@ -0,0 +1,37 @@ +/* + * 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.view.animation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This is a class annotation that signals that it is safe to create + * a native interpolator counterpart via {@link NativeInterpolatorFactory} + * + * The idea here is to prevent subclasses of interpolators from being treated as a + * NativeInterpolatorFactory, and instead have them fall back to the LUT & LERP + * method like a custom interpolator. + * + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface HasNativeInterpolator { +} diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java new file mode 100644 index 0000000..fcacd52 --- /dev/null +++ b/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java @@ -0,0 +1,21 @@ +/* + * 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.view.animation; + +public interface NativeInterpolatorFactory { + long createNativeInterpolator(); +} diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java b/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java new file mode 100644 index 0000000..7cd75f3 --- /dev/null +++ b/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java @@ -0,0 +1,36 @@ +/* + * 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.view.animation; + +/** + * Static utility class for constructing native interpolators to keep the + * JNI simpler + */ +public final class NativeInterpolatorFactoryHelper { + private NativeInterpolatorFactoryHelper() {} + + public static native long createAccelerateDecelerateInterpolator(); + public static native long createAccelerateInterpolator(float factor); + public static native long createAnticipateInterpolator(float tension); + public static native long createAnticipateOvershootInterpolator(float tension); + public static native long createBounceInterpolator(); + public static native long createCycleInterpolator(float cycles); + public static native long createDecelerateInterpolator(float factor); + public static native long createLinearInterpolator(); + public static native long createOvershootInterpolator(float tension); + public static native long createLutInterpolator(float[] values); +} diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java index 7ca6c1b..ed676bb 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItem.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java @@ -163,7 +163,7 @@ public class ActionMenuItem implements MenuItem { public MenuItem setIcon(int iconRes) { mIconResId = iconRes; - mIconDrawable = mContext.getResources().getDrawable(iconRes); + mIconDrawable = mContext.getDrawable(iconRes); return this; } diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java index 238a9c0..891baea 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -28,8 +28,11 @@ import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; +import android.widget.ActionMenuView; +import android.widget.ListPopupWindow; import android.widget.TextView; import android.widget.Toast; +import android.widget.ListPopupWindow.ForwardingListener; /** * @hide @@ -43,6 +46,8 @@ public class ActionMenuItemView extends TextView private CharSequence mTitle; private Drawable mIcon; private MenuBuilder.ItemInvoker mItemInvoker; + private ForwardingListener mForwardingListener; + private PopupCallback mPopupCallback; private boolean mAllowTextWithIcon; private boolean mExpandedFormat; @@ -60,13 +65,17 @@ public class ActionMenuItemView extends TextView this(context, attrs, 0); } - public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); final Resources res = context.getResources(); mAllowTextWithIcon = res.getBoolean( com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon); - TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.ActionMenuItemView, 0, 0); + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ActionMenuItemView, defStyleAttr, defStyleRes); mMinWidth = a.getDimensionPixelSize( com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0); a.recycle(); @@ -99,6 +108,7 @@ public class ActionMenuItemView extends TextView return mItemData; } + @Override public void initialize(MenuItemImpl itemData, int menuType) { mItemData = itemData; @@ -108,8 +118,24 @@ public class ActionMenuItemView extends TextView setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); setEnabled(itemData.isEnabled()); + + if (itemData.hasSubMenu()) { + if (mForwardingListener == null) { + mForwardingListener = new ActionMenuItemForwardingListener(); + } + } } + @Override + public boolean onTouchEvent(MotionEvent e) { + if (mItemData.hasSubMenu() && mForwardingListener != null + && mForwardingListener.onTouch(this, e)) { + return true; + } + return super.onTouchEvent(e); + } + + @Override public void onClick(View v) { if (mItemInvoker != null) { mItemInvoker.invokeItem(mItemData); @@ -120,6 +146,10 @@ public class ActionMenuItemView extends TextView mItemInvoker = invoker; } + public void setPopupCallback(PopupCallback popupCallback) { + mPopupCallback = popupCallback; + } + public boolean prefersCondensedTitle() { return true; } @@ -252,11 +282,6 @@ public class ActionMenuItemView extends TextView @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { - // Fill all available height. - heightMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY); - } final boolean textVisible = hasText(); if (textVisible && mSavedPaddingLeft >= 0) { super.setPadding(mSavedPaddingLeft, getPaddingTop(), @@ -285,4 +310,42 @@ public class ActionMenuItemView extends TextView super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom()); } } + + private class ActionMenuItemForwardingListener extends ForwardingListener { + public ActionMenuItemForwardingListener() { + super(ActionMenuItemView.this); + } + + @Override + public ListPopupWindow getPopup() { + if (mPopupCallback != null) { + return mPopupCallback.getPopup(); + } + return null; + } + + @Override + protected boolean onForwardingStarted() { + // Call the invoker, then check if the expected popup is showing. + if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) { + final ListPopupWindow popup = getPopup(); + return popup != null && popup.isShowing(); + } + return false; + } + + @Override + protected boolean onForwardingStopped() { + final ListPopupWindow popup = getPopup(); + if (popup != null) { + popup.dismiss(); + return true; + } + return false; + } + } + + public static abstract class PopupCallback { + public abstract ListPopupWindow getPopup(); + } } diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java deleted file mode 100644 index fe1cf72..0000000 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ /dev/null @@ -1,756 +0,0 @@ -/* - * Copyright (C) 2011 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.menu; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Parcel; -import android.os.Parcelable; -import android.transition.Transition; -import android.transition.TransitionManager; -import android.util.SparseBooleanArray; -import android.view.ActionProvider; -import android.view.Gravity; -import android.view.MenuItem; -import android.view.SoundEffectConstants; -import android.view.View; -import android.view.View.MeasureSpec; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.ImageButton; -import android.widget.ListPopupWindow; -import android.widget.ListPopupWindow.ForwardingListener; -import com.android.internal.transition.ActionBarTransition; -import com.android.internal.view.ActionBarPolicy; -import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView; - -import java.util.ArrayList; - -/** - * MenuPresenter for building action menus as seen in the action bar and action modes. - */ -public class ActionMenuPresenter extends BaseMenuPresenter - implements ActionProvider.SubUiVisibilityListener { - private static final String TAG = "ActionMenuPresenter"; - - private View mOverflowButton; - private boolean mReserveOverflow; - private boolean mReserveOverflowSet; - private int mWidthLimit; - private int mActionItemWidthLimit; - private int mMaxItems; - private boolean mMaxItemsSet; - private boolean mStrictWidthLimit; - private boolean mWidthLimitSet; - private boolean mExpandedActionViewsExclusive; - - private int mMinCellSize; - - // Group IDs that have been added as actions - used temporarily, allocated here for reuse. - private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray(); - - private View mScrapActionButtonView; - - private OverflowPopup mOverflowPopup; - private ActionButtonSubmenu mActionButtonPopup; - - private OpenOverflowRunnable mPostedOpenRunnable; - - final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback(); - int mOpenSubMenuId; - - public ActionMenuPresenter(Context context) { - super(context, com.android.internal.R.layout.action_menu_layout, - com.android.internal.R.layout.action_menu_item_layout); - } - - @Override - public void initForMenu(Context context, MenuBuilder menu) { - super.initForMenu(context, menu); - - final Resources res = context.getResources(); - - final ActionBarPolicy abp = ActionBarPolicy.get(context); - if (!mReserveOverflowSet) { - mReserveOverflow = abp.showsOverflowMenuButton(); - } - - if (!mWidthLimitSet) { - mWidthLimit = abp.getEmbeddedMenuWidthLimit(); - } - - // Measure for initial configuration - if (!mMaxItemsSet) { - mMaxItems = abp.getMaxActionButtons(); - } - - int width = mWidthLimit; - if (mReserveOverflow) { - if (mOverflowButton == null) { - mOverflowButton = new OverflowMenuButton(mSystemContext); - final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - mOverflowButton.measure(spec, spec); - } - width -= mOverflowButton.getMeasuredWidth(); - } else { - mOverflowButton = null; - } - - mActionItemWidthLimit = width; - - mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density); - - // Drop a scrap view as it may no longer reflect the proper context/config. - mScrapActionButtonView = null; - } - - public void onConfigurationChanged(Configuration newConfig) { - if (!mMaxItemsSet) { - mMaxItems = mContext.getResources().getInteger( - com.android.internal.R.integer.max_action_buttons); - } - if (mMenu != null) { - mMenu.onItemsChanged(true); - } - } - - public void setWidthLimit(int width, boolean strict) { - mWidthLimit = width; - mStrictWidthLimit = strict; - mWidthLimitSet = true; - } - - public void setReserveOverflow(boolean reserveOverflow) { - mReserveOverflow = reserveOverflow; - mReserveOverflowSet = true; - } - - public void setItemLimit(int itemCount) { - mMaxItems = itemCount; - mMaxItemsSet = true; - } - - public void setExpandedActionViewsExclusive(boolean isExclusive) { - mExpandedActionViewsExclusive = isExclusive; - } - - @Override - public MenuView getMenuView(ViewGroup root) { - MenuView result = super.getMenuView(root); - ((ActionMenuView) result).setPresenter(this); - return result; - } - - @Override - public View getItemView(final MenuItemImpl item, View convertView, ViewGroup parent) { - View actionView = item.getActionView(); - if (actionView == null || item.hasCollapsibleActionView()) { - actionView = super.getItemView(item, convertView, parent); - } - actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE); - - final ActionMenuView menuParent = (ActionMenuView) parent; - final ViewGroup.LayoutParams lp = actionView.getLayoutParams(); - if (!menuParent.checkLayoutParams(lp)) { - actionView.setLayoutParams(menuParent.generateLayoutParams(lp)); - } - return actionView; - } - - @Override - public void bindItemView(final MenuItemImpl item, MenuView.ItemView itemView) { - itemView.initialize(item, 0); - - final ActionMenuView menuView = (ActionMenuView) mMenuView; - final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView; - actionItemView.setItemInvoker(menuView); - - if (item.hasSubMenu()) { - actionItemView.setOnTouchListener(new ForwardingListener(actionItemView) { - @Override - public ListPopupWindow getPopup() { - return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null; - } - - @Override - protected boolean onForwardingStarted() { - return onSubMenuSelected((SubMenuBuilder) item.getSubMenu()); - } - - @Override - protected boolean onForwardingStopped() { - return dismissPopupMenus(); - } - }); - } else { - actionItemView.setOnTouchListener(null); - } - } - - @Override - public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) { - return item.isActionButton(); - } - - @Override - public void updateMenuView(boolean cleared) { - final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent(); - if (menuViewParent != null) { - ActionBarTransition.beginDelayedTransition(menuViewParent); - } - super.updateMenuView(cleared); - - ((View) mMenuView).requestLayout(); - - if (mMenu != null) { - final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems(); - final int count = actionItems.size(); - for (int i = 0; i < count; i++) { - final ActionProvider provider = actionItems.get(i).getActionProvider(); - if (provider != null) { - provider.setSubUiVisibilityListener(this); - } - } - } - - final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ? - mMenu.getNonActionItems() : null; - - boolean hasOverflow = false; - if (mReserveOverflow && nonActionItems != null) { - final int count = nonActionItems.size(); - if (count == 1) { - hasOverflow = !nonActionItems.get(0).isActionViewExpanded(); - } else { - hasOverflow = count > 0; - } - } - - if (hasOverflow) { - if (mOverflowButton == null) { - mOverflowButton = new OverflowMenuButton(mSystemContext); - } - ViewGroup parent = (ViewGroup) mOverflowButton.getParent(); - if (parent != mMenuView) { - if (parent != null) { - parent.removeView(mOverflowButton); - } - ActionMenuView menuView = (ActionMenuView) mMenuView; - menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams()); - } - } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) { - ((ViewGroup) mMenuView).removeView(mOverflowButton); - } - - ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow); - } - - @Override - public boolean filterLeftoverView(ViewGroup parent, int childIndex) { - if (parent.getChildAt(childIndex) == mOverflowButton) return false; - return super.filterLeftoverView(parent, childIndex); - } - - public boolean onSubMenuSelected(SubMenuBuilder subMenu) { - if (!subMenu.hasVisibleItems()) return false; - - SubMenuBuilder topSubMenu = subMenu; - while (topSubMenu.getParentMenu() != mMenu) { - topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu(); - } - View anchor = findViewForItem(topSubMenu.getItem()); - if (anchor == null) { - if (mOverflowButton == null) return false; - anchor = mOverflowButton; - } - - mOpenSubMenuId = subMenu.getItem().getItemId(); - mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu); - mActionButtonPopup.setAnchorView(anchor); - mActionButtonPopup.show(); - super.onSubMenuSelected(subMenu); - return true; - } - - private View findViewForItem(MenuItem item) { - final ViewGroup parent = (ViewGroup) mMenuView; - if (parent == null) return null; - - final int count = parent.getChildCount(); - for (int i = 0; i < count; i++) { - final View child = parent.getChildAt(i); - if (child instanceof MenuView.ItemView && - ((MenuView.ItemView) child).getItemData() == item) { - return child; - } - } - return null; - } - - /** - * Display the overflow menu if one is present. - * @return true if the overflow menu was shown, false otherwise. - */ - public boolean showOverflowMenu() { - if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null && - mPostedOpenRunnable == null && !mMenu.getNonActionItems().isEmpty()) { - OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true); - mPostedOpenRunnable = new OpenOverflowRunnable(popup); - // Post this for later; we might still need a layout for the anchor to be right. - ((View) mMenuView).post(mPostedOpenRunnable); - - // ActionMenuPresenter uses null as a callback argument here - // to indicate overflow is opening. - super.onSubMenuSelected(null); - - return true; - } - return false; - } - - /** - * Hide the overflow menu if it is currently showing. - * - * @return true if the overflow menu was hidden, false otherwise. - */ - public boolean hideOverflowMenu() { - if (mPostedOpenRunnable != null && mMenuView != null) { - ((View) mMenuView).removeCallbacks(mPostedOpenRunnable); - mPostedOpenRunnable = null; - return true; - } - - MenuPopupHelper popup = mOverflowPopup; - if (popup != null) { - popup.dismiss(); - return true; - } - return false; - } - - /** - * Dismiss all popup menus - overflow and submenus. - * @return true if popups were dismissed, false otherwise. (This can be because none were open.) - */ - public boolean dismissPopupMenus() { - boolean result = hideOverflowMenu(); - result |= hideSubMenus(); - return result; - } - - /** - * Dismiss all submenu popups. - * - * @return true if popups were dismissed, false otherwise. (This can be because none were open.) - */ - public boolean hideSubMenus() { - if (mActionButtonPopup != null) { - mActionButtonPopup.dismiss(); - return true; - } - return false; - } - - /** - * @return true if the overflow menu is currently showing - */ - public boolean isOverflowMenuShowing() { - return mOverflowPopup != null && mOverflowPopup.isShowing(); - } - - public boolean isOverflowMenuShowPending() { - return mPostedOpenRunnable != null || isOverflowMenuShowing(); - } - - /** - * @return true if space has been reserved in the action menu for an overflow item. - */ - public boolean isOverflowReserved() { - return mReserveOverflow; - } - - public boolean flagActionItems() { - final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems(); - final int itemsSize = visibleItems.size(); - int maxActions = mMaxItems; - int widthLimit = mActionItemWidthLimit; - final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - final ViewGroup parent = (ViewGroup) mMenuView; - - int requiredItems = 0; - int requestedItems = 0; - int firstActionWidth = 0; - boolean hasOverflow = false; - for (int i = 0; i < itemsSize; i++) { - MenuItemImpl item = visibleItems.get(i); - if (item.requiresActionButton()) { - requiredItems++; - } else if (item.requestsActionButton()) { - requestedItems++; - } else { - hasOverflow = true; - } - if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) { - // Overflow everything if we have an expanded action view and we're - // space constrained. - maxActions = 0; - } - } - - // Reserve a spot for the overflow item if needed. - if (mReserveOverflow && - (hasOverflow || requiredItems + requestedItems > maxActions)) { - maxActions--; - } - maxActions -= requiredItems; - - final SparseBooleanArray seenGroups = mActionButtonGroups; - seenGroups.clear(); - - int cellSize = 0; - int cellsRemaining = 0; - if (mStrictWidthLimit) { - cellsRemaining = widthLimit / mMinCellSize; - final int cellSizeRemaining = widthLimit % mMinCellSize; - cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining; - } - - // Flag as many more requested items as will fit. - for (int i = 0; i < itemsSize; i++) { - MenuItemImpl item = visibleItems.get(i); - - if (item.requiresActionButton()) { - View v = getItemView(item, mScrapActionButtonView, parent); - if (mScrapActionButtonView == null) { - mScrapActionButtonView = v; - } - if (mStrictWidthLimit) { - cellsRemaining -= ActionMenuView.measureChildForCells(v, - cellSize, cellsRemaining, querySpec, 0); - } else { - v.measure(querySpec, querySpec); - } - final int measuredWidth = v.getMeasuredWidth(); - widthLimit -= measuredWidth; - if (firstActionWidth == 0) { - firstActionWidth = measuredWidth; - } - final int groupId = item.getGroupId(); - if (groupId != 0) { - seenGroups.put(groupId, true); - } - item.setIsActionButton(true); - } else if (item.requestsActionButton()) { - // Items in a group with other items that already have an action slot - // can break the max actions rule, but not the width limit. - final int groupId = item.getGroupId(); - final boolean inGroup = seenGroups.get(groupId); - boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 && - (!mStrictWidthLimit || cellsRemaining > 0); - - if (isAction) { - View v = getItemView(item, mScrapActionButtonView, parent); - if (mScrapActionButtonView == null) { - mScrapActionButtonView = v; - } - if (mStrictWidthLimit) { - final int cells = ActionMenuView.measureChildForCells(v, - cellSize, cellsRemaining, querySpec, 0); - cellsRemaining -= cells; - if (cells == 0) { - isAction = false; - } - } else { - v.measure(querySpec, querySpec); - } - final int measuredWidth = v.getMeasuredWidth(); - widthLimit -= measuredWidth; - if (firstActionWidth == 0) { - firstActionWidth = measuredWidth; - } - - if (mStrictWidthLimit) { - isAction &= widthLimit >= 0; - } else { - // Did this push the entire first item past the limit? - isAction &= widthLimit + firstActionWidth > 0; - } - } - - if (isAction && groupId != 0) { - seenGroups.put(groupId, true); - } else if (inGroup) { - // We broke the width limit. Demote the whole group, they all overflow now. - seenGroups.put(groupId, false); - for (int j = 0; j < i; j++) { - MenuItemImpl areYouMyGroupie = visibleItems.get(j); - if (areYouMyGroupie.getGroupId() == groupId) { - // Give back the action slot - if (areYouMyGroupie.isActionButton()) maxActions++; - areYouMyGroupie.setIsActionButton(false); - } - } - } - - if (isAction) maxActions--; - - item.setIsActionButton(isAction); - } else { - // Neither requires nor requests an action button. - item.setIsActionButton(false); - } - } - return true; - } - - @Override - public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { - dismissPopupMenus(); - super.onCloseMenu(menu, allMenusAreClosing); - } - - @Override - public Parcelable onSaveInstanceState() { - SavedState state = new SavedState(); - state.openSubMenuId = mOpenSubMenuId; - return state; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - SavedState saved = (SavedState) state; - if (saved.openSubMenuId > 0) { - MenuItem item = mMenu.findItem(saved.openSubMenuId); - if (item != null) { - SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu(); - onSubMenuSelected(subMenu); - } - } - } - - @Override - public void onSubUiVisibilityChanged(boolean isVisible) { - if (isVisible) { - // Not a submenu, but treat it like one. - super.onSubMenuSelected(null); - } else { - mMenu.close(false); - } - } - - private static class SavedState implements Parcelable { - public int openSubMenuId; - - SavedState() { - } - - SavedState(Parcel in) { - openSubMenuId = in.readInt(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(openSubMenuId); - } - - public static final Parcelable.Creator<SavedState> CREATOR - = new Parcelable.Creator<SavedState>() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - private class OverflowMenuButton extends ImageButton implements ActionMenuChildView { - public OverflowMenuButton(Context context) { - super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle); - - setClickable(true); - setFocusable(true); - setVisibility(VISIBLE); - setEnabled(true); - - setOnTouchListener(new ForwardingListener(this) { - @Override - public ListPopupWindow getPopup() { - if (mOverflowPopup == null) { - return null; - } - - return mOverflowPopup.getPopup(); - } - - @Override - public boolean onForwardingStarted() { - showOverflowMenu(); - return true; - } - - @Override - public boolean onForwardingStopped() { - // Displaying the popup occurs asynchronously, so wait for - // the runnable to finish before deciding whether to stop - // forwarding. - if (mPostedOpenRunnable != null) { - return false; - } - - hideOverflowMenu(); - return true; - } - }); - } - - @Override - public boolean performClick() { - if (super.performClick()) { - return true; - } - - playSoundEffect(SoundEffectConstants.CLICK); - showOverflowMenu(); - return true; - } - - @Override - public boolean needsDividerBefore() { - return false; - } - - @Override - public boolean needsDividerAfter() { - return false; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { - // Fill available height - heightMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setCanOpenPopup(true); - } - } - - private class OverflowPopup extends MenuPopupHelper { - public OverflowPopup(Context context, MenuBuilder menu, View anchorView, - boolean overflowOnly) { - super(context, menu, anchorView, overflowOnly); - setGravity(Gravity.END); - setCallback(mPopupPresenterCallback); - } - - @Override - public void onDismiss() { - super.onDismiss(); - mMenu.close(); - mOverflowPopup = null; - } - } - - private class ActionButtonSubmenu extends MenuPopupHelper { - private SubMenuBuilder mSubMenu; - - public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) { - super(context, subMenu); - mSubMenu = subMenu; - - MenuItemImpl item = (MenuItemImpl) subMenu.getItem(); - if (!item.isActionButton()) { - // Give a reasonable anchor to nested submenus. - setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton); - } - - setCallback(mPopupPresenterCallback); - - boolean preserveIconSpacing = false; - final int count = subMenu.size(); - for (int i = 0; i < count; i++) { - MenuItem childItem = subMenu.getItem(i); - if (childItem.isVisible() && childItem.getIcon() != null) { - preserveIconSpacing = true; - break; - } - } - setForceShowIcon(preserveIconSpacing); - } - - @Override - public void onDismiss() { - super.onDismiss(); - mActionButtonPopup = null; - mOpenSubMenuId = 0; - } - } - - private class PopupPresenterCallback implements MenuPresenter.Callback { - - @Override - public boolean onOpenSubMenu(MenuBuilder subMenu) { - if (subMenu == null) return false; - - mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId(); - final MenuPresenter.Callback cb = getCallback(); - return cb != null ? cb.onOpenSubMenu(subMenu) : false; - } - - @Override - public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { - if (menu instanceof SubMenuBuilder) { - ((SubMenuBuilder) menu).getRootMenu().close(false); - } - final MenuPresenter.Callback cb = getCallback(); - if (cb != null) { - cb.onCloseMenu(menu, allMenusAreClosing); - } - } - } - - private class OpenOverflowRunnable implements Runnable { - private OverflowPopup mPopup; - - public OpenOverflowRunnable(OverflowPopup popup) { - mPopup = popup; - } - - public void run() { - mMenu.changeMenuMode(); - final View menuView = (View) mMenuView; - if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) { - mOverflowPopup = mPopup; - } - mPostedOpenRunnable = null; - } - } -} diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java deleted file mode 100644 index 16a2031..0000000 --- a/core/java/com/android/internal/view/menu/ActionMenuView.java +++ /dev/null @@ -1,620 +0,0 @@ -/* - * Copyright (C) 2010 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.menu; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; -import android.view.ViewDebug; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.widget.LinearLayout; -import com.android.internal.R; - -/** - * @hide - */ -public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView { - private static final String TAG = "ActionMenuView"; - - static final int MIN_CELL_SIZE = 56; // dips - static final int GENERATED_ITEM_PADDING = 4; // dips - - private MenuBuilder mMenu; - - private boolean mReserveOverflow; - private ActionMenuPresenter mPresenter; - private boolean mFormatItems; - private int mFormatItemsWidth; - private int mMinCellSize; - private int mGeneratedItemPadding; - private int mMeasuredExtraWidth; - private int mMaxItemHeight; - - public ActionMenuView(Context context) { - this(context, null); - } - - public ActionMenuView(Context context, AttributeSet attrs) { - super(context, attrs); - setBaselineAligned(false); - final float density = context.getResources().getDisplayMetrics().density; - mMinCellSize = (int) (MIN_CELL_SIZE * density); - mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar, - R.attr.actionBarStyle, 0); - mMaxItemHeight = a.getDimensionPixelSize(R.styleable.ActionBar_height, 0); - a.recycle(); - } - - public void setPresenter(ActionMenuPresenter presenter) { - mPresenter = presenter; - } - - public boolean isExpandedFormat() { - return mFormatItems; - } - - public void setMaxItemHeight(int maxItemHeight) { - mMaxItemHeight = maxItemHeight; - requestLayout(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mPresenter.updateMenuView(false); - - if (mPresenter != null && mPresenter.isOverflowMenuShowing()) { - mPresenter.hideOverflowMenu(); - mPresenter.showOverflowMenu(); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // If we've been given an exact size to match, apply special formatting during layout. - final boolean wasFormatted = mFormatItems; - mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY; - - if (wasFormatted != mFormatItems) { - mFormatItemsWidth = 0; // Reset this when switching modes - } - - // Special formatting can change whether items can fit as action buttons. - // Kick the menu and update presenters when this changes. - final int widthSize = MeasureSpec.getSize(widthMeasureSpec); - if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) { - mFormatItemsWidth = widthSize; - mMenu.onItemsChanged(true); - } - - if (mFormatItems) { - onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec); - } else { - // Previous measurement at exact format may have set margins - reset them. - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - lp.leftMargin = lp.rightMargin = 0; - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) { - // We already know the width mode is EXACTLY if we're here. - final int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - final int widthPadding = getPaddingLeft() + getPaddingRight(); - final int heightPadding = getPaddingTop() + getPaddingBottom(); - - final int itemHeightSpec = heightMode == MeasureSpec.EXACTLY - ? MeasureSpec.makeMeasureSpec(heightSize - heightPadding, MeasureSpec.EXACTLY) - : MeasureSpec.makeMeasureSpec( - Math.min(mMaxItemHeight, heightSize - heightPadding), MeasureSpec.AT_MOST); - - widthSize -= widthPadding; - - // Divide the view into cells. - final int cellCount = widthSize / mMinCellSize; - final int cellSizeRemaining = widthSize % mMinCellSize; - - if (cellCount == 0) { - // Give up, nothing fits. - setMeasuredDimension(widthSize, 0); - return; - } - - final int cellSize = mMinCellSize + cellSizeRemaining / cellCount; - - int cellsRemaining = cellCount; - int maxChildHeight = 0; - int maxCellsUsed = 0; - int expandableItemCount = 0; - int visibleItemCount = 0; - boolean hasOverflow = false; - - // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64. - long smallestItemsAt = 0; - - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) continue; - - final boolean isGeneratedItem = child instanceof ActionMenuItemView; - visibleItemCount++; - - if (isGeneratedItem) { - // Reset padding for generated menu item views; it may change below - // and views are recycled. - child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0); - } - - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - lp.expanded = false; - lp.extraPixels = 0; - lp.cellsUsed = 0; - lp.expandable = false; - lp.leftMargin = 0; - lp.rightMargin = 0; - lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText(); - - // Overflow always gets 1 cell. No more, no less. - final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining; - - final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable, - itemHeightSpec, heightPadding); - - maxCellsUsed = Math.max(maxCellsUsed, cellsUsed); - if (lp.expandable) expandableItemCount++; - if (lp.isOverflowButton) hasOverflow = true; - - cellsRemaining -= cellsUsed; - maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight()); - if (cellsUsed == 1) smallestItemsAt |= (1 << i); - } - - // When we have overflow and a single expanded (text) item, we want to try centering it - // visually in the available space even though overflow consumes some of it. - final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2; - - // Divide space for remaining cells if we have items that can expand. - // Try distributing whole leftover cells to smaller items first. - - boolean needsExpansion = false; - while (expandableItemCount > 0 && cellsRemaining > 0) { - int minCells = Integer.MAX_VALUE; - long minCellsAt = 0; // Bit locations are indices of relevant child views - int minCellsItemCount = 0; - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - // Don't try to expand items that shouldn't. - if (!lp.expandable) continue; - - // Mark indices of children that can receive an extra cell. - if (lp.cellsUsed < minCells) { - minCells = lp.cellsUsed; - minCellsAt = 1 << i; - minCellsItemCount = 1; - } else if (lp.cellsUsed == minCells) { - minCellsAt |= 1 << i; - minCellsItemCount++; - } - } - - // Items that get expanded will always be in the set of smallest items when we're done. - smallestItemsAt |= minCellsAt; - - if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop. - - // We have enough cells, all minimum size items will be incremented. - minCells++; - - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if ((minCellsAt & (1 << i)) == 0) { - // If this item is already at our small item count, mark it for later. - if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i; - continue; - } - - if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) { - // Add padding to this item such that it centers. - child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0); - } - lp.cellsUsed++; - lp.expanded = true; - cellsRemaining--; - } - - needsExpansion = true; - } - - // Divide any space left that wouldn't divide along cell boundaries - // evenly among the smallest items - - final boolean singleItem = !hasOverflow && visibleItemCount == 1; - if (cellsRemaining > 0 && smallestItemsAt != 0 && - (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) { - float expandCount = Long.bitCount(smallestItemsAt); - - if (!singleItem) { - // The items at the far edges may only expand by half in order to pin to either side. - if ((smallestItemsAt & 1) != 0) { - LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams(); - if (!lp.preventEdgeOffset) expandCount -= 0.5f; - } - if ((smallestItemsAt & (1 << (childCount - 1))) != 0) { - LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams()); - if (!lp.preventEdgeOffset) expandCount -= 0.5f; - } - } - - final int extraPixels = expandCount > 0 ? - (int) (cellsRemaining * cellSize / expandCount) : 0; - - for (int i = 0; i < childCount; i++) { - if ((smallestItemsAt & (1 << i)) == 0) continue; - - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (child instanceof ActionMenuItemView) { - // If this is one of our views, expand and measure at the larger size. - lp.extraPixels = extraPixels; - lp.expanded = true; - if (i == 0 && !lp.preventEdgeOffset) { - // First item gets part of its new padding pushed out of sight. - // The last item will get this implicitly from layout. - lp.leftMargin = -extraPixels / 2; - } - needsExpansion = true; - } else if (lp.isOverflowButton) { - lp.extraPixels = extraPixels; - lp.expanded = true; - lp.rightMargin = -extraPixels / 2; - needsExpansion = true; - } else { - // If we don't know what it is, give it some margins instead - // and let it center within its space. We still want to pin - // against the edges. - if (i != 0) { - lp.leftMargin = extraPixels / 2; - } - if (i != childCount - 1) { - lp.rightMargin = extraPixels / 2; - } - } - } - - cellsRemaining = 0; - } - - // Remeasure any items that have had extra space allocated to them. - if (needsExpansion) { - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - if (!lp.expanded) continue; - - final int width = lp.cellsUsed * cellSize + lp.extraPixels; - child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - itemHeightSpec); - } - } - - if (heightMode != MeasureSpec.EXACTLY) { - heightSize = maxChildHeight; - } - - setMeasuredDimension(widthSize, heightSize); - mMeasuredExtraWidth = cellsRemaining * cellSize; - } - - /** - * Measure a child view to fit within cell-based formatting. The child's width - * will be measured to a whole multiple of cellSize. - * - * <p>Sets the expandable and cellsUsed fields of LayoutParams. - * - * @param child Child to measure - * @param cellSize Size of one cell - * @param cellsRemaining Number of cells remaining that this view can expand to fill - * @param parentHeightMeasureSpec MeasureSpec used by the parent view - * @param parentHeightPadding Padding present in the parent view - * @return Number of cells this child was measured to occupy - */ - static int measureChildForCells(View child, int cellSize, int cellsRemaining, - int parentHeightMeasureSpec, int parentHeightPadding) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) - - parentHeightPadding; - final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec); - final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode); - - final ActionMenuItemView itemView = child instanceof ActionMenuItemView ? - (ActionMenuItemView) child : null; - final boolean hasText = itemView != null && itemView.hasText(); - - int cellsUsed = 0; - if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) { - final int childWidthSpec = MeasureSpec.makeMeasureSpec( - cellSize * cellsRemaining, MeasureSpec.AT_MOST); - child.measure(childWidthSpec, childHeightSpec); - - final int measuredWidth = child.getMeasuredWidth(); - cellsUsed = measuredWidth / cellSize; - if (measuredWidth % cellSize != 0) cellsUsed++; - if (hasText && cellsUsed < 2) cellsUsed = 2; - } - - final boolean expandable = !lp.isOverflowButton && hasText; - lp.expandable = expandable; - - lp.cellsUsed = cellsUsed; - final int targetWidth = cellsUsed * cellSize; - child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY), - childHeightSpec); - return cellsUsed; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (!mFormatItems) { - super.onLayout(changed, left, top, right, bottom); - return; - } - - final int childCount = getChildCount(); - final int midVertical = (top + bottom) / 2; - final int dividerWidth = getDividerWidth(); - int overflowWidth = 0; - int nonOverflowWidth = 0; - int nonOverflowCount = 0; - int widthRemaining = right - left - getPaddingRight() - getPaddingLeft(); - boolean hasOverflow = false; - final boolean isLayoutRtl = isLayoutRtl(); - for (int i = 0; i < childCount; i++) { - final View v = getChildAt(i); - if (v.getVisibility() == GONE) { - continue; - } - - LayoutParams p = (LayoutParams) v.getLayoutParams(); - if (p.isOverflowButton) { - overflowWidth = v.getMeasuredWidth(); - if (hasDividerBeforeChildAt(i)) { - overflowWidth += dividerWidth; - } - - int height = v.getMeasuredHeight(); - int r; - int l; - if (isLayoutRtl) { - l = getPaddingLeft() + p.leftMargin; - r = l + overflowWidth; - } else { - r = getWidth() - getPaddingRight() - p.rightMargin; - l = r - overflowWidth; - } - int t = midVertical - (height / 2); - int b = t + height; - v.layout(l, t, r, b); - - widthRemaining -= overflowWidth; - hasOverflow = true; - } else { - final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin; - nonOverflowWidth += size; - widthRemaining -= size; - if (hasDividerBeforeChildAt(i)) { - nonOverflowWidth += dividerWidth; - } - nonOverflowCount++; - } - } - - if (childCount == 1 && !hasOverflow) { - // Center a single child - final View v = getChildAt(0); - final int width = v.getMeasuredWidth(); - final int height = v.getMeasuredHeight(); - final int midHorizontal = (right - left) / 2; - final int l = midHorizontal - width / 2; - final int t = midVertical - height / 2; - v.layout(l, t, l + width, t + height); - return; - } - - final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1); - final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0); - - if (isLayoutRtl) { - int startRight = getWidth() - getPaddingRight(); - for (int i = 0; i < childCount; i++) { - final View v = getChildAt(i); - final LayoutParams lp = (LayoutParams) v.getLayoutParams(); - if (v.getVisibility() == GONE || lp.isOverflowButton) { - continue; - } - - startRight -= lp.rightMargin; - int width = v.getMeasuredWidth(); - int height = v.getMeasuredHeight(); - int t = midVertical - height / 2; - v.layout(startRight - width, t, startRight, t + height); - startRight -= width + lp.leftMargin + spacerSize; - } - } else { - int startLeft = getPaddingLeft(); - for (int i = 0; i < childCount; i++) { - final View v = getChildAt(i); - final LayoutParams lp = (LayoutParams) v.getLayoutParams(); - if (v.getVisibility() == GONE || lp.isOverflowButton) { - continue; - } - - startLeft += lp.leftMargin; - int width = v.getMeasuredWidth(); - int height = v.getMeasuredHeight(); - int t = midVertical - height / 2; - v.layout(startLeft, t, startLeft + width, t + height); - startLeft += width + lp.rightMargin + spacerSize; - } - } - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mPresenter.dismissPopupMenus(); - } - - public boolean isOverflowReserved() { - return mReserveOverflow; - } - - public void setOverflowReserved(boolean reserveOverflow) { - mReserveOverflow = reserveOverflow; - } - - @Override - protected LayoutParams generateDefaultLayoutParams() { - LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT); - params.gravity = Gravity.CENTER_VERTICAL; - return params; - } - - @Override - public LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs); - } - - @Override - protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - if (p != null) { - final LayoutParams result = p instanceof LayoutParams - ? new LayoutParams((LayoutParams) p) - : new LayoutParams(p); - if (result.gravity <= Gravity.NO_GRAVITY) { - result.gravity = Gravity.CENTER_VERTICAL; - } - return result; - } - return generateDefaultLayoutParams(); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p != null && p instanceof LayoutParams; - } - - public LayoutParams generateOverflowButtonLayoutParams() { - LayoutParams result = generateDefaultLayoutParams(); - result.isOverflowButton = true; - return result; - } - - public boolean invokeItem(MenuItemImpl item) { - return mMenu.performItemAction(item, 0); - } - - public int getWindowAnimations() { - return 0; - } - - public void initialize(MenuBuilder menu) { - mMenu = menu; - } - - @Override - protected boolean hasDividerBeforeChildAt(int childIndex) { - if (childIndex == 0) { - return false; - } - final View childBefore = getChildAt(childIndex - 1); - final View child = getChildAt(childIndex); - boolean result = false; - if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) { - result |= ((ActionMenuChildView) childBefore).needsDividerAfter(); - } - if (childIndex > 0 && child instanceof ActionMenuChildView) { - result |= ((ActionMenuChildView) child).needsDividerBefore(); - } - return result; - } - - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - return false; - } - - public interface ActionMenuChildView { - public boolean needsDividerBefore(); - public boolean needsDividerAfter(); - } - - public static class LayoutParams extends LinearLayout.LayoutParams { - @ViewDebug.ExportedProperty(category = "layout") - public boolean isOverflowButton; - @ViewDebug.ExportedProperty(category = "layout") - public int cellsUsed; - @ViewDebug.ExportedProperty(category = "layout") - public int extraPixels; - @ViewDebug.ExportedProperty(category = "layout") - public boolean expandable; - @ViewDebug.ExportedProperty(category = "layout") - public boolean preventEdgeOffset; - - public boolean expanded; - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - } - - public LayoutParams(ViewGroup.LayoutParams other) { - super(other); - } - - public LayoutParams(LayoutParams other) { - super((LinearLayout.LayoutParams) other); - isOverflowButton = other.isOverflowButton; - } - - public LayoutParams(int width, int height) { - super(width, height); - isOverflowButton = false; - } - - public LayoutParams(int width, int height, boolean isOverflowButton) { - super(width, height); - this.isOverflowButton = isOverflowButton; - } - } -} diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java index 5d0b25f..de5e279 100644 --- a/core/java/com/android/internal/view/menu/IconMenuItemView.java +++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java @@ -57,8 +57,8 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie private static String sPrependShortcutLabel; - public IconMenuItemView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs); + public IconMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); if (sPrependShortcutLabel == null) { /* @@ -68,10 +68,9 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie sPrependShortcutLabel = getResources().getString( com.android.internal.R.string.prepend_shortcut_label); } - - TypedArray a = - context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.MenuView, defStyle, 0); + + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.MenuView, defStyleAttr, defStyleRes); mDisabledAlpha = a.getFloat( com.android.internal.R.styleable.MenuView_itemIconDisabledAlpha, 0.8f); @@ -81,7 +80,11 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie a.recycle(); } - + + public IconMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + public IconMenuItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java index a2a4acc..692bdac 100644 --- a/core/java/com/android/internal/view/menu/ListMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java @@ -55,13 +55,13 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView private boolean mForceShowIcon; - public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs); - - TypedArray a = - context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.MenuView, defStyle, 0); - + public ListMenuItemView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.MenuView, defStyleAttr, defStyleRes); + mBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground); mTextAppearance = a.getResourceId(com.android.internal.R.styleable. MenuView_itemTextAppearance, -1); @@ -72,6 +72,10 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView a.recycle(); } + public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + public ListMenuItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java index e1bb3621..c476354 100644 --- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java @@ -17,7 +17,6 @@ package com.android.internal.view.menu; import android.content.Context; -import android.database.DataSetObserver; import android.os.Bundle; import android.os.Parcelable; import android.util.SparseArray; diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 5464284..5d7d322 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -926,7 +926,7 @@ public class MenuBuilder implements Menu { * sub menu is about to be shown, <var>allMenusAreClosing</var> * is false. */ - final void close(boolean allMenusAreClosing) { + public final void close(boolean allMenusAreClosing) { if (mIsClosing) return; mIsClosing = true; @@ -953,7 +953,7 @@ public class MenuBuilder implements Menu { * false if only item properties changed. * (Visibility is a structural property since it affects layout.) */ - void onItemsChanged(boolean structureChanged) { + public void onItemsChanged(boolean structureChanged) { if (!mPreventDispatchingItemsChanged) { if (structureChanged) { mIsVisibleItemsStale = true; @@ -1007,7 +1007,7 @@ public class MenuBuilder implements Menu { onItemsChanged(true); } - ArrayList<MenuItemImpl> getVisibleItems() { + public ArrayList<MenuItemImpl> getVisibleItems() { if (!mIsVisibleItemsStale) return mVisibleItems; // Refresh the visible items @@ -1092,12 +1092,12 @@ public class MenuBuilder implements Menu { mIsActionItemsStale = false; } - ArrayList<MenuItemImpl> getActionItems() { + public ArrayList<MenuItemImpl> getActionItems() { flagActionItems(); return mActionItems; } - ArrayList<MenuItemImpl> getNonActionItems() { + public ArrayList<MenuItemImpl> getNonActionItems() { flagActionItems(); return mNonActionItems; } @@ -1128,7 +1128,7 @@ public class MenuBuilder implements Menu { } if (iconRes > 0) { - mHeaderIcon = r.getDrawable(iconRes); + mHeaderIcon = getContext().getDrawable(iconRes); } else if (icon != null) { mHeaderIcon = icon; } diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 4d0a326..61dcaca 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -385,7 +385,7 @@ public final class MenuItemImpl implements MenuItem { } if (mIconResId != NO_ICON) { - Drawable icon = mMenu.getResources().getDrawable(mIconResId); + Drawable icon = mMenu.getContext().getDrawable(mIconResId); mIconResId = NO_ICON; mIconDrawable = icon; return icon; diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 05e9a66..5a12893 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -23,7 +23,6 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; @@ -54,6 +53,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On private final MenuAdapter mAdapter; private final boolean mOverflowOnly; private final int mPopupMaxWidth; + private final int mPopupStyleAttr; private View mAnchorView; private ListPopupWindow mPopup; @@ -73,20 +73,21 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On private int mDropDownGravity = Gravity.NO_GRAVITY; public MenuPopupHelper(Context context, MenuBuilder menu) { - this(context, menu, null, false); + this(context, menu, null, false, com.android.internal.R.attr.popupMenuStyle); } public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) { - this(context, menu, anchorView, false); + this(context, menu, anchorView, false, com.android.internal.R.attr.popupMenuStyle); } - public MenuPopupHelper(Context context, MenuBuilder menu, - View anchorView, boolean overflowOnly) { + public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView, + boolean overflowOnly, int popupStyleAttr) { mContext = context; mInflater = LayoutInflater.from(context); mMenu = menu; mAdapter = new MenuAdapter(mMenu); mOverflowOnly = overflowOnly; + mPopupStyleAttr = popupStyleAttr; final Resources res = context.getResources(); mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2, @@ -120,7 +121,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On } public boolean tryShow() { - mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle); + mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr); mPopup.setOnDismissListener(this); mPopup.setOnItemClickListener(this); mPopup.setAdapter(mAdapter); @@ -273,7 +274,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On @Override public boolean onSubMenuSelected(SubMenuBuilder subMenu) { if (subMenu.hasVisibleItems()) { - MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false); + MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView); subPopup.setCallback(mPresenterCallback); boolean preserveIconSpacing = false; diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java index f3891c7..9e7ff93 100644 --- a/core/java/com/android/internal/widget/AbsActionBarView.java +++ b/core/java/com/android/internal/widget/AbsActionBarView.java @@ -16,8 +16,8 @@ package com.android.internal.widget; import com.android.internal.R; -import com.android.internal.view.menu.ActionMenuPresenter; -import com.android.internal.view.menu.ActionMenuView; +import android.widget.ActionMenuPresenter; +import android.widget.ActionMenuView; import android.animation.Animator; import android.animation.AnimatorSet; @@ -34,7 +34,7 @@ import android.view.animation.DecelerateInterpolator; public abstract class AbsActionBarView extends ViewGroup { protected ActionMenuView mMenuView; protected ActionMenuPresenter mActionMenuPresenter; - protected ActionBarContainer mSplitView; + protected ViewGroup mSplitView; protected boolean mSplitActionBar; protected boolean mSplitWhenNarrow; protected int mContentHeight; @@ -47,15 +47,20 @@ public abstract class AbsActionBarView extends ViewGroup { private static final int FADE_DURATION = 200; public AbsActionBarView(Context context) { - super(context); + this(context, null); } public AbsActionBarView(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); } - public AbsActionBarView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public AbsActionBarView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AbsActionBarView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } @Override @@ -69,7 +74,7 @@ public abstract class AbsActionBarView extends ViewGroup { setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0)); a.recycle(); if (mSplitWhenNarrow) { - setSplitActionBar(getContext().getResources().getBoolean( + setSplitToolbar(getContext().getResources().getBoolean( com.android.internal.R.bool.split_action_bar_is_narrow)); } if (mActionMenuPresenter != null) { @@ -81,7 +86,7 @@ public abstract class AbsActionBarView extends ViewGroup { * Sets whether the bar should be split right now, no questions asked. * @param split true if the bar should split */ - public void setSplitActionBar(boolean split) { + public void setSplitToolbar(boolean split) { mSplitActionBar = split; } @@ -95,9 +100,6 @@ public abstract class AbsActionBarView extends ViewGroup { public void setContentHeight(int height) { mContentHeight = height; - if (mMenuView != null) { - mMenuView.setMaxItemHeight(mContentHeight); - } requestLayout(); } @@ -105,7 +107,7 @@ public abstract class AbsActionBarView extends ViewGroup { return mContentHeight; } - public void setSplitView(ActionBarContainer splitView) { + public void setSplitView(ViewGroup splitView) { mSplitView = splitView; } @@ -212,6 +214,10 @@ public abstract class AbsActionBarView extends ViewGroup { return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved(); } + public boolean canShowOverflowMenu() { + return isOverflowReserved() && getVisibility() == VISIBLE; + } + public void dismissPopupMenus() { if (mActionMenuPresenter != null) { mActionMenuPresenter.dismissPopupMenus(); diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java index 8a49899..790b611 100644 --- a/core/java/com/android/internal/widget/ActionBarContainer.java +++ b/core/java/com/android/internal/widget/ActionBarContainer.java @@ -16,10 +16,10 @@ package com.android.internal.widget; -import android.app.ActionBar; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.ColorFilter; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.ActionMode; @@ -36,13 +36,14 @@ import android.widget.FrameLayout; public class ActionBarContainer extends FrameLayout { private boolean mIsTransitioning; private View mTabContainer; - private ActionBarView mActionBarView; + private View mActionBarView; private Drawable mBackground; private Drawable mStackedBackground; private Drawable mSplitBackground; private boolean mIsSplit; private boolean mIsStacked; + private int mHeight; public ActionBarContainer(Context context) { this(context, null); @@ -51,13 +52,15 @@ public class ActionBarContainer extends FrameLayout { public ActionBarContainer(Context context, AttributeSet attrs) { super(context, attrs); - setBackgroundDrawable(null); + // Set a transparent background so that we project appropriately. + setBackground(new ActionBarBackgroundDrawable()); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ActionBar); mBackground = a.getDrawable(com.android.internal.R.styleable.ActionBar_background); mStackedBackground = a.getDrawable( com.android.internal.R.styleable.ActionBar_backgroundStacked); + mHeight = a.getDimensionPixelSize(com.android.internal.R.styleable.ActionBar_height, -1); if (getId() == com.android.internal.R.id.split_action_bar) { mIsSplit = true; @@ -73,7 +76,7 @@ public class ActionBarContainer extends FrameLayout { @Override public void onFinishInflate() { super.onFinishInflate(); - mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); + mActionBarView = findViewById(com.android.internal.R.id.action_bar); } public void setPrimaryBackground(Drawable bg) { @@ -243,37 +246,28 @@ public class ActionBarContainer extends FrameLayout { } @Override - public void onDraw(Canvas canvas) { - if (getWidth() == 0 || getHeight() == 0) { - return; - } - - if (mIsSplit) { - if (mSplitBackground != null) mSplitBackground.draw(canvas); - } else { - if (mBackground != null) { - mBackground.draw(canvas); - } - if (mStackedBackground != null && mIsStacked) { - mStackedBackground.draw(canvas); - } - } - } - - @Override public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) { // No starting an action mode for an action bar child! (Where would it go?) return null; } + private boolean isCollapsed(View view) { + return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0; + } + @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mActionBarView == null && + MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST && mHeight >= 0) { + heightMeasureSpec = MeasureSpec.makeMeasureSpec( + Math.min(mHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST); + } super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mActionBarView == null) return; final LayoutParams lp = (LayoutParams) mActionBarView.getLayoutParams(); - final int actionBarViewHeight = mActionBarView.isCollapsed() ? 0 : + final int actionBarViewHeight = isCollapsed(mActionBarView) ? 0 : mActionBarView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; if (mTabContainer != null && mTabContainer.getVisibility() != GONE) { @@ -291,12 +285,13 @@ public class ActionBarContainer extends FrameLayout { public void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - final boolean hasTabs = mTabContainer != null && mTabContainer.getVisibility() != GONE; + final View tabContainer = mTabContainer; + final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE; - if (mTabContainer != null && mTabContainer.getVisibility() != GONE) { + if (tabContainer != null && tabContainer.getVisibility() != GONE) { final int containerHeight = getMeasuredHeight(); - final int tabHeight = mTabContainer.getMeasuredHeight(); - mTabContainer.layout(l, containerHeight - tabHeight, r, containerHeight); + final int tabHeight = tabContainer.getMeasuredHeight(); + tabContainer.layout(l, containerHeight - tabHeight, r, containerHeight); } boolean needsInvalidate = false; @@ -311,9 +306,10 @@ public class ActionBarContainer extends FrameLayout { mActionBarView.getRight(), mActionBarView.getBottom()); needsInvalidate = true; } - if ((mIsStacked = hasTabs && mStackedBackground != null)) { - mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(), - mTabContainer.getRight(), mTabContainer.getBottom()); + mIsStacked = hasTabs; + if (hasTabs && mStackedBackground != null) { + mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(), + tabContainer.getRight(), tabContainer.getBottom()); needsInvalidate = true; } } @@ -322,4 +318,37 @@ public class ActionBarContainer extends FrameLayout { invalidate(); } } + + /** + * Dummy drawable so that we don't break background display lists and + * projection surfaces. + */ + private class ActionBarBackgroundDrawable extends Drawable { + @Override + public void draw(Canvas canvas) { + if (mIsSplit) { + if (mSplitBackground != null) mSplitBackground.draw(canvas); + } else { + if (mBackground != null) { + mBackground.draw(canvas); + } + if (mStackedBackground != null && mIsStacked) { + mStackedBackground.draw(canvas); + } + } + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter cf) { + } + + @Override + public int getOpacity() { + return 0; + } + } } diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 8bc1081..6ff77a0 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -16,8 +16,8 @@ package com.android.internal.widget; import com.android.internal.R; -import com.android.internal.view.menu.ActionMenuPresenter; -import com.android.internal.view.menu.ActionMenuView; +import android.widget.ActionMenuPresenter; +import android.widget.ActionMenuView; import com.android.internal.view.menu.MenuBuilder; import android.animation.Animator; @@ -25,7 +25,6 @@ import android.animation.Animator.AnimatorListener; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; -import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.text.TextUtils; @@ -74,11 +73,17 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi this(context, attrs, com.android.internal.R.attr.actionModeStyle); } - public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionMode, defStyle, 0); - setBackgroundDrawable(a.getDrawable( + public ActionBarContextView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ActionBarContextView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.ActionMode, defStyleAttr, defStyleRes); + setBackground(a.getDrawable( com.android.internal.R.styleable.ActionMode_background)); mTitleStyleRes = a.getResourceId( com.android.internal.R.styleable.ActionMode_titleTextStyle, 0); @@ -104,7 +109,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi } @Override - public void setSplitActionBar(boolean split) { + public void setSplitToolbar(boolean split) { if (mSplitActionBar != split) { if (mActionMenuPresenter != null) { // Mode is already active; move everything over and adjust the menu itself. @@ -132,7 +137,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi mSplitView.addView(mMenuView, layoutParams); } } - super.setSplitActionBar(split); + super.setSplitToolbar(split); } } diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index c957b67..8a9cb22 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -16,44 +16,61 @@ package com.android.internal.widget; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.view.ViewGroup; -import android.view.WindowInsets; -import com.android.internal.app.ActionBarImpl; - +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.res.TypedArray; +import android.graphics.Canvas; import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Parcelable; import android.util.AttributeSet; +import android.util.IntProperty; +import android.util.Log; +import android.util.Property; +import android.util.SparseArray; +import android.view.KeyEvent; +import android.view.Menu; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import android.view.Window; +import android.view.WindowInsets; +import android.widget.OverScroller; +import android.widget.Toolbar; +import com.android.internal.view.menu.MenuPresenter; /** * Special layout for the containing of an overlay action bar (and its * content) to correctly handle fitting system windows when the content * has request that its layout ignore them. */ -public class ActionBarOverlayLayout extends ViewGroup { +public class ActionBarOverlayLayout extends ViewGroup implements DecorContentParent { private static final String TAG = "ActionBarOverlayLayout"; private int mActionBarHeight; - private ActionBarImpl mActionBar; + //private WindowDecorActionBar mActionBar; private int mWindowVisibility = View.VISIBLE; // The main UI elements that we handle the layout of. private View mContent; - private View mActionBarBottom; + private ActionBarContainer mActionBarBottom; private ActionBarContainer mActionBarTop; // Some interior UI elements. - private ActionBarView mActionBarView; + private DecorToolbar mDecorToolbar; // Content overlay drawable - generally the action bar's shadow private Drawable mWindowContentOverlay; private boolean mIgnoreWindowContentOverlay; private boolean mOverlayMode; + private boolean mHasNonEmbeddedTabs; + private boolean mHideOnContentScroll; + private boolean mAnimatingForFling; + private int mHideOnContentScrollReference; private int mLastSystemUiVisibility; private final Rect mBaseContentInsets = new Rect(); private final Rect mLastBaseContentInsets = new Rect(); @@ -62,6 +79,84 @@ public class ActionBarOverlayLayout extends ViewGroup { private final Rect mInnerInsets = new Rect(); private final Rect mLastInnerInsets = new Rect(); + private ActionBarVisibilityCallback mActionBarVisibilityCallback; + + private final int ACTION_BAR_ANIMATE_DELAY = 600; // ms + + private OverScroller mFlingEstimator; + + private ViewPropertyAnimator mCurrentActionBarTopAnimator; + private ViewPropertyAnimator mCurrentActionBarBottomAnimator; + + private final Animator.AnimatorListener mTopAnimatorListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCurrentActionBarTopAnimator = null; + mAnimatingForFling = false; + } + + @Override + public void onAnimationCancel(Animator animation) { + mCurrentActionBarTopAnimator = null; + mAnimatingForFling = false; + } + }; + + private final Animator.AnimatorListener mBottomAnimatorListener = + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCurrentActionBarBottomAnimator = null; + mAnimatingForFling = false; + } + + @Override + public void onAnimationCancel(Animator animation) { + mCurrentActionBarBottomAnimator = null; + mAnimatingForFling = false; + } + }; + + private final Runnable mRemoveActionBarHideOffset = new Runnable() { + public void run() { + haltActionBarHideOffsetAnimations(); + mCurrentActionBarTopAnimator = mActionBarTop.animate().translationY(0) + .setListener(mTopAnimatorListener); + if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { + mCurrentActionBarBottomAnimator = mActionBarBottom.animate().translationY(0) + .setListener(mBottomAnimatorListener); + } + } + }; + + private final Runnable mAddActionBarHideOffset = new Runnable() { + public void run() { + haltActionBarHideOffsetAnimations(); + mCurrentActionBarTopAnimator = mActionBarTop.animate() + .translationY(-mActionBarTop.getHeight()) + .setListener(mTopAnimatorListener); + if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { + mCurrentActionBarBottomAnimator = mActionBarBottom.animate() + .translationY(mActionBarBottom.getHeight()) + .setListener(mBottomAnimatorListener); + } + } + }; + + public static final Property<ActionBarOverlayLayout, Integer> ACTION_BAR_HIDE_OFFSET = + new IntProperty<ActionBarOverlayLayout>("actionBarHideOffset") { + + @Override + public void setValue(ActionBarOverlayLayout object, int value) { + object.setActionBarHideOffset(value); + } + + @Override + public Integer get(ActionBarOverlayLayout object) { + return object.getActionBarHideOffset(); + } + }; + static final int[] ATTRS = new int [] { com.android.internal.R.attr.actionBarSize, com.android.internal.R.attr.windowContentOverlay @@ -86,14 +181,22 @@ public class ActionBarOverlayLayout extends ViewGroup { mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT; + + mFlingEstimator = new OverScroller(context); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + haltActionBarHideOffsetAnimations(); } - public void setActionBar(ActionBarImpl impl) { - mActionBar = impl; + public void setActionBarVisibilityCallback(ActionBarVisibilityCallback cb) { + mActionBarVisibilityCallback = cb; if (getWindowToken() != null) { // This is being initialized after being added to a window; // make sure to update all state now. - mActionBar.setWindowVisibility(mWindowVisibility); + mActionBarVisibilityCallback.onWindowVisibilityChanged(mWindowVisibility); if (mLastSystemUiVisibility != 0) { int newVis = mLastSystemUiVisibility; onWindowSystemUiVisibilityChanged(newVis); @@ -114,6 +217,14 @@ public class ActionBarOverlayLayout extends ViewGroup { Build.VERSION_CODES.KITKAT; } + public boolean isInOverlayMode() { + return mOverlayMode; + } + + public void setHasNonEmbeddedTabs(boolean hasNonEmbeddedTabs) { + mHasNonEmbeddedTabs = hasNonEmbeddedTabs; + } + public void setShowingForActionMode(boolean showing) { if (showing) { // Here's a fun hack: if the status bar is currently being hidden, @@ -140,19 +251,18 @@ public class ActionBarOverlayLayout extends ViewGroup { pullChildren(); final int diff = mLastSystemUiVisibility ^ visible; mLastSystemUiVisibility = visible; - final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0; - final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true; - final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; - if (mActionBar != null) { + final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0; + final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; + if (mActionBarVisibilityCallback != null) { // We want the bar to be visible if it is not being hidden, // or the app has not turned on a stable UI mode (meaning they // are performing explicit layout around the action bar). - mActionBar.enableContentAnimations(!stable); - if (barVisible || !stable) mActionBar.showForSystem(); - else mActionBar.hideForSystem(); + mActionBarVisibilityCallback.enableContentAnimations(!stable); + if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem(); + else mActionBarVisibilityCallback.hideForSystem(); } - if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { - if (mActionBar != null) { + if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { + if (mActionBarVisibilityCallback != null) { requestApplyInsets(); } } @@ -162,8 +272,8 @@ public class ActionBarOverlayLayout extends ViewGroup { protected void onWindowVisibilityChanged(int visibility) { super.onWindowVisibilityChanged(visibility); mWindowVisibility = visibility; - if (mActionBar != null) { - mActionBar.setWindowVisibility(visibility); + if (mActionBarVisibilityCallback != null) { + mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility); } } @@ -279,20 +389,20 @@ public class ActionBarOverlayLayout extends ViewGroup { // This is the standard space needed for the action bar. For stable measurement, // we can't depend on the size currently reported by it -- this must remain constant. topInset = mActionBarHeight; - if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) { - View tabs = mActionBarTop.getTabContainer(); + if (mHasNonEmbeddedTabs) { + final View tabs = mActionBarTop.getTabContainer(); if (tabs != null) { // If tabs are not embedded, increase space on top to account for them. topInset += mActionBarHeight; } } - } else if (mActionBarTop.getVisibility() == VISIBLE) { + } else if (mActionBarTop.getVisibility() != GONE) { // This is the space needed on top of the window for all of the action bar // and tabs. topInset = mActionBarTop.getMeasuredHeight(); } - if (mActionBarView.isSplitActionBar()) { + if (mDecorToolbar.isSplit()) { // If action bar is split, adjust bottom insets for it. if (mActionBarBottom != null) { if (stable) { @@ -395,16 +505,323 @@ public class ActionBarOverlayLayout extends ViewGroup { return false; } + @Override + public boolean onStartNestedScroll(View child, View target, int axes) { + if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) { + return false; + } + return mHideOnContentScroll; + } + + @Override + public void onNestedScrollAccepted(View child, View target, int axes) { + super.onNestedScrollAccepted(child, target, axes); + mHideOnContentScrollReference = getActionBarHideOffset(); + haltActionBarHideOffsetAnimations(); + if (mActionBarVisibilityCallback != null) { + mActionBarVisibilityCallback.onContentScrollStarted(); + } + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed) { + mHideOnContentScrollReference += dyConsumed; + setActionBarHideOffset(mHideOnContentScrollReference); + } + + @Override + public void onStopNestedScroll(View target) { + super.onStopNestedScroll(target); + if (mHideOnContentScroll && !mAnimatingForFling) { + if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) { + postRemoveActionBarHideOffset(); + } else { + postAddActionBarHideOffset(); + } + } + if (mActionBarVisibilityCallback != null) { + mActionBarVisibilityCallback.onContentScrollStopped(); + } + } + + @Override + public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { + if (!mHideOnContentScroll || !consumed) { + return false; + } + if (shouldHideActionBarOnFling(velocityX, velocityY)) { + addActionBarHideOffset(); + } else { + removeActionBarHideOffset(); + } + mAnimatingForFling = true; + return true; + } + void pullChildren() { if (mContent == null) { mContent = findViewById(com.android.internal.R.id.content); - mActionBarTop = (ActionBarContainer)findViewById( + mActionBarTop = (ActionBarContainer) findViewById( com.android.internal.R.id.action_bar_container); - mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); - mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar); + mDecorToolbar = getDecorToolbar(findViewById(com.android.internal.R.id.action_bar)); + mActionBarBottom = (ActionBarContainer) findViewById( + com.android.internal.R.id.split_action_bar); + } + } + + private DecorToolbar getDecorToolbar(View view) { + if (view instanceof DecorToolbar) { + return (DecorToolbar) view; + } else if (view instanceof Toolbar) { + return ((Toolbar) view).getWrapper(); + } else { + throw new IllegalStateException("Can't make a decor toolbar out of " + + view.getClass().getSimpleName()); } } + public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { + if (hideOnContentScroll != mHideOnContentScroll) { + mHideOnContentScroll = hideOnContentScroll; + if (!hideOnContentScroll) { + stopNestedScroll(); + haltActionBarHideOffsetAnimations(); + setActionBarHideOffset(0); + } + } + } + + public boolean isHideOnContentScrollEnabled() { + return mHideOnContentScroll; + } + + public int getActionBarHideOffset() { + return mActionBarTop != null ? -((int) mActionBarTop.getTranslationY()) : 0; + } + + public void setActionBarHideOffset(int offset) { + haltActionBarHideOffsetAnimations(); + final int topHeight = mActionBarTop.getHeight(); + offset = Math.max(0, Math.min(offset, topHeight)); + mActionBarTop.setTranslationY(-offset); + if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { + // Match the hide offset proportionally for a split bar + final float fOffset = (float) offset / topHeight; + final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset); + mActionBarBottom.setTranslationY(bOffset); + } + } + + private void haltActionBarHideOffsetAnimations() { + removeCallbacks(mRemoveActionBarHideOffset); + removeCallbacks(mAddActionBarHideOffset); + if (mCurrentActionBarTopAnimator != null) { + mCurrentActionBarTopAnimator.cancel(); + } + if (mCurrentActionBarBottomAnimator != null) { + mCurrentActionBarBottomAnimator.cancel(); + } + } + + private void postRemoveActionBarHideOffset() { + haltActionBarHideOffsetAnimations(); + postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY); + } + + private void postAddActionBarHideOffset() { + haltActionBarHideOffsetAnimations(); + postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY); + } + + private void removeActionBarHideOffset() { + haltActionBarHideOffsetAnimations(); + mRemoveActionBarHideOffset.run(); + } + + private void addActionBarHideOffset() { + haltActionBarHideOffsetAnimations(); + mAddActionBarHideOffset.run(); + } + + private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) { + mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE); + final int finalY = mFlingEstimator.getFinalY(); + return finalY > mActionBarTop.getHeight(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (super.dispatchKeyEvent(event)) { + return true; + } + + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + final int action = event.getAction(); + + // Collapse any expanded action views. + if (mDecorToolbar != null && mDecorToolbar.hasExpandedActionView()) { + if (action == KeyEvent.ACTION_UP) { + mDecorToolbar.collapseActionView(); + } + return true; + } + } + + return false; + } + + @Override + public void setWindowCallback(Window.Callback cb) { + pullChildren(); + mDecorToolbar.setWindowCallback(cb); + } + + @Override + public void setWindowTitle(CharSequence title) { + pullChildren(); + mDecorToolbar.setWindowTitle(title); + } + + @Override + public CharSequence getTitle() { + pullChildren(); + return mDecorToolbar.getTitle(); + } + + @Override + public void initFeature(int windowFeature) { + pullChildren(); + switch (windowFeature) { + case Window.FEATURE_PROGRESS: + mDecorToolbar.initProgress(); + break; + case Window.FEATURE_INDETERMINATE_PROGRESS: + mDecorToolbar.initIndeterminateProgress(); + break; + case Window.FEATURE_ACTION_BAR_OVERLAY: + setOverlayMode(true); + break; + } + } + + @Override + public void setUiOptions(int uiOptions) { + boolean splitActionBar = false; + final boolean splitWhenNarrow = + (uiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0; + if (splitWhenNarrow) { + splitActionBar = getContext().getResources().getBoolean( + com.android.internal.R.bool.split_action_bar_is_narrow); + } + if (splitActionBar) { + pullChildren(); + if (mActionBarBottom != null && mDecorToolbar.canSplit()) { + mDecorToolbar.setSplitView(mActionBarBottom); + mDecorToolbar.setSplitToolbar(splitActionBar); + mDecorToolbar.setSplitWhenNarrow(splitWhenNarrow); + + final ActionBarContextView cab = (ActionBarContextView) findViewById( + com.android.internal.R.id.action_context_bar); + cab.setSplitView(mActionBarBottom); + cab.setSplitToolbar(splitActionBar); + cab.setSplitWhenNarrow(splitWhenNarrow); + } else if (splitActionBar) { + Log.e(TAG, "Requested split action bar with " + + "incompatible window decor! Ignoring request."); + } + } + } + + @Override + public boolean hasIcon() { + pullChildren(); + return mDecorToolbar.hasIcon(); + } + + @Override + public boolean hasLogo() { + pullChildren(); + return mDecorToolbar.hasLogo(); + } + + @Override + public void setIcon(int resId) { + pullChildren(); + mDecorToolbar.setIcon(resId); + } + + @Override + public void setIcon(Drawable d) { + pullChildren(); + mDecorToolbar.setIcon(d); + } + + @Override + public void setLogo(int resId) { + pullChildren(); + mDecorToolbar.setLogo(resId); + } + + @Override + public boolean canShowOverflowMenu() { + pullChildren(); + return mDecorToolbar.canShowOverflowMenu(); + } + + @Override + public boolean isOverflowMenuShowing() { + pullChildren(); + return mDecorToolbar.isOverflowMenuShowing(); + } + + @Override + public boolean isOverflowMenuShowPending() { + pullChildren(); + return mDecorToolbar.isOverflowMenuShowPending(); + } + + @Override + public boolean showOverflowMenu() { + pullChildren(); + return mDecorToolbar.showOverflowMenu(); + } + + @Override + public boolean hideOverflowMenu() { + pullChildren(); + return mDecorToolbar.hideOverflowMenu(); + } + + @Override + public void setMenuPrepared() { + pullChildren(); + mDecorToolbar.setMenuPrepared(); + } + + @Override + public void setMenu(Menu menu, MenuPresenter.Callback cb) { + pullChildren(); + mDecorToolbar.setMenu(menu, cb); + } + + @Override + public void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) { + pullChildren(); + mDecorToolbar.saveHierarchyState(toolbarStates); + } + + @Override + public void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) { + pullChildren(); + mDecorToolbar.restoreHierarchyState(toolbarStates); + } + + @Override + public void dismissPopups() { + pullChildren(); + mDecorToolbar.dismissPopupMenus(); + } public static class LayoutParams extends MarginLayoutParams { public LayoutParams(Context c, AttributeSet attrs) { @@ -423,4 +840,13 @@ public class ActionBarOverlayLayout extends ViewGroup { super(source); } } + + public interface ActionBarVisibilityCallback { + void onWindowVisibilityChanged(int visibility); + void showForSystem(); + void hideForSystem(); + void enableContentAnimations(boolean enable); + void onContentScrollStarted(); + void onContentScrollStopped(); + } } diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 786f5cf..af82778 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -18,7 +18,6 @@ package com.android.internal.widget; import android.animation.LayoutTransition; import android.app.ActionBar; -import android.app.ActionBar.OnNavigationListener; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -41,6 +40,8 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.Window; import android.view.accessibility.AccessibilityEvent; +import android.widget.ActionMenuPresenter; +import android.widget.ActionMenuView; import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; @@ -52,8 +53,6 @@ import android.widget.TextView; import com.android.internal.R; import com.android.internal.transition.ActionBarTransition; import com.android.internal.view.menu.ActionMenuItem; -import com.android.internal.view.menu.ActionMenuPresenter; -import com.android.internal.view.menu.ActionMenuView; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuItemImpl; import com.android.internal.view.menu.MenuPresenter; @@ -63,7 +62,7 @@ import com.android.internal.view.menu.SubMenuBuilder; /** * @hide */ -public class ActionBarView extends AbsActionBarView { +public class ActionBarView extends AbsActionBarView implements DecorToolbar { private static final String TAG = "ActionBarView"; /** @@ -117,8 +116,7 @@ public class ActionBarView extends AbsActionBarView { private boolean mUserTitle; private boolean mIncludeTabs; - private boolean mIsCollapsable; - private boolean mIsCollapsed; + private boolean mIsCollapsible; private boolean mWasHomeEnabled; // Was it enabled before action view expansion? private MenuBuilder mOptionsMenu; @@ -129,7 +127,7 @@ public class ActionBarView extends AbsActionBarView { private ActionMenuItem mLogoNavItem; private SpinnerAdapter mSpinnerAdapter; - private OnNavigationListener mCallback; + private AdapterView.OnItemSelectedListener mNavItemSelectedListener; private Runnable mTabSelector; @@ -138,18 +136,6 @@ public class ActionBarView extends AbsActionBarView { Window.Callback mWindowCallback; - private final AdapterView.OnItemSelectedListener mNavItemSelectedListener = - new AdapterView.OnItemSelectedListener() { - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (mCallback != null) { - mCallback.onNavigationItemSelected(position, id); - } - } - public void onNothingSelected(AdapterView parent) { - // Do nothing - } - }; - private final OnClickListener mExpandedActionViewUpListener = new OnClickListener() { @Override public void onClick(View v) { @@ -178,8 +164,6 @@ public class ActionBarView extends AbsActionBarView { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar, com.android.internal.R.attr.actionBarStyle, 0); - ApplicationInfo appInfo = context.getApplicationInfo(); - PackageManager pm = context.getPackageManager(); mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode, ActionBar.NAVIGATION_MODE_STANDARD); mTitle = a.getText(R.styleable.ActionBar_title); @@ -260,7 +244,7 @@ public class ActionBarView extends AbsActionBarView { } if (mHomeDescriptionRes != 0) { - setHomeActionContentDescription(mHomeDescriptionRes); + setNavigationContentDescription(mHomeDescriptionRes); } if (mTabScrollView != null && mIncludeTabs) { @@ -313,7 +297,7 @@ public class ActionBarView extends AbsActionBarView { } @Override - public void setSplitActionBar(boolean splitActionBar) { + public void setSplitToolbar(boolean splitActionBar) { if (mSplitActionBar != splitActionBar) { if (mMenuView != null) { final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); @@ -349,18 +333,26 @@ public class ActionBarView extends AbsActionBarView { mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); } } - super.setSplitActionBar(splitActionBar); + super.setSplitToolbar(splitActionBar); } } - public boolean isSplitActionBar() { + public boolean isSplit() { return mSplitActionBar; } + public boolean canSplit() { + return true; + } + public boolean hasEmbeddedTabs() { return mIncludeTabs; } + public void setEmbeddedTabView(View view) { + setEmbeddedTabView((ScrollingTabContainerView) view); + } + public void setEmbeddedTabView(ScrollingTabContainerView tabs) { if (mTabScrollView != null) { removeView(mTabScrollView); @@ -376,10 +368,6 @@ public class ActionBarView extends AbsActionBarView { } } - public void setCallback(OnNavigationListener callback) { - mCallback = callback; - } - public void setMenuPrepared() { mMenuPrepared = true; } @@ -430,6 +418,7 @@ public class ActionBarView extends AbsActionBarView { mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); // Span the whole width layoutParams.width = LayoutParams.MATCH_PARENT; + layoutParams.height = LayoutParams.WRAP_CONTENT; configPresenters(builder); menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); if (mSplitView != null) { @@ -472,7 +461,7 @@ public class ActionBarView extends AbsActionBarView { } } - public void setCustomNavigationView(View view) { + public void setCustomView(View view) { final boolean showCustom = (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0; if (showCustom) { ActionBarTransition.beginDelayedTransition(this); @@ -696,7 +685,7 @@ public class ActionBarView extends AbsActionBarView { } public void setIcon(int resId) { - setIcon(resId != 0 ? mContext.getResources().getDrawable(resId) : null); + setIcon(resId != 0 ? mContext.getDrawable(resId) : null); } public boolean hasIcon() { @@ -711,7 +700,7 @@ public class ActionBarView extends AbsActionBarView { } public void setLogo(int resId) { - setLogo(resId != 0 ? mContext.getResources().getDrawable(resId) : null); + setLogo(resId != 0 ? mContext.getDrawable(resId) : null); } public boolean hasLogo() { @@ -764,15 +753,16 @@ public class ActionBarView extends AbsActionBarView { } } - public void setDropdownAdapter(SpinnerAdapter adapter) { + public void setDropdownParams(SpinnerAdapter adapter, AdapterView.OnItemSelectedListener l) { mSpinnerAdapter = adapter; + mNavItemSelectedListener = l; if (mSpinner != null) { mSpinner.setAdapter(adapter); } } - public SpinnerAdapter getDropdownAdapter() { - return mSpinnerAdapter; + public int getDropdownItemCount() { + return mSpinnerAdapter != null ? mSpinnerAdapter.getCount() : 0; } public void setDropdownSelectedPosition(int position) { @@ -783,7 +773,7 @@ public class ActionBarView extends AbsActionBarView { return mSpinner.getSelectedItemPosition(); } - public View getCustomNavigationView() { + public View getCustomView() { return mCustomNavView; } @@ -796,6 +786,11 @@ public class ActionBarView extends AbsActionBarView { } @Override + public ViewGroup getViewGroup() { + return this; + } + + @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { // Used by custom nav views if they don't supply layout params. Everything else // added to an ActionBarView should have them already. @@ -859,12 +854,8 @@ public class ActionBarView extends AbsActionBarView { mContextView = view; } - public void setCollapsable(boolean collapsable) { - mIsCollapsable = collapsable; - } - - public boolean isCollapsed() { - return mIsCollapsed; + public void setCollapsible(boolean collapsible) { + mIsCollapsible = collapsible; } /** @@ -892,7 +883,7 @@ public class ActionBarView extends AbsActionBarView { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int childCount = getChildCount(); - if (mIsCollapsable) { + if (mIsCollapsible) { int visibleChildren = 0; for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); @@ -914,11 +905,9 @@ public class ActionBarView extends AbsActionBarView { if (visibleChildren == 0) { // No size for an empty action bar when collapsable. setMeasuredDimension(0, 0); - mIsCollapsed = true; return; } } - mIsCollapsed = false; int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (widthMode != MeasureSpec.EXACTLY) { @@ -1322,20 +1311,20 @@ public class ActionBarView extends AbsActionBarView { } } - public void setHomeAsUpIndicator(Drawable indicator) { + public void setNavigationIcon(Drawable indicator) { mHomeLayout.setUpIndicator(indicator); } - public void setHomeAsUpIndicator(int resId) { + public void setNavigationIcon(int resId) { mHomeLayout.setUpIndicator(resId); } - public void setHomeActionContentDescription(CharSequence description) { + public void setNavigationContentDescription(CharSequence description) { mHomeDescription = description; updateHomeAccessibility(mUpGoerFive.isEnabled()); } - public void setHomeActionContentDescription(int resId) { + public void setNavigationContentDescription(int resId) { mHomeDescriptionRes = resId; mHomeDescription = resId != 0 ? getResources().getText(resId) : null; updateHomeAccessibility(mUpGoerFive.isEnabled()); @@ -1416,7 +1405,7 @@ public class ActionBarView extends AbsActionBarView { public void setUpIndicator(int resId) { mUpIndicatorRes = resId; - mUpView.setImageDrawable(resId != 0 ? getResources().getDrawable(resId) : null); + mUpView.setImageDrawable(resId != 0 ? getContext().getDrawable(resId) : null); } @Override diff --git a/core/java/com/android/internal/widget/AutoScrollHelper.java b/core/java/com/android/internal/widget/AutoScrollHelper.java index 7a294aa..0d468ca 100644 --- a/core/java/com/android/internal/widget/AutoScrollHelper.java +++ b/core/java/com/android/internal/widget/AutoScrollHelper.java @@ -892,6 +892,10 @@ public abstract class AutoScrollHelper implements View.OnTouchListener { public boolean canTargetScrollVertically(int direction) { final AbsListView target = mTarget; final int itemCount = target.getCount(); + if (itemCount == 0) { + return false; + } + final int childCount = target.getChildCount(); final int firstPosition = target.getFirstVisiblePosition(); final int lastPosition = firstPosition + childCount; diff --git a/core/java/com/android/internal/widget/DecorContentParent.java b/core/java/com/android/internal/widget/DecorContentParent.java new file mode 100644 index 0000000..4fa370a --- /dev/null +++ b/core/java/com/android/internal/widget/DecorContentParent.java @@ -0,0 +1,53 @@ +/* + * 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.widget; + +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.util.SparseArray; +import android.view.Menu; +import android.view.Window; +import com.android.internal.view.menu.MenuPresenter; + +/** + * Implemented by the top-level decor layout for a window. DecorContentParent offers + * entry points for a number of title/window decor features. + */ +public interface DecorContentParent { + void setWindowCallback(Window.Callback cb); + void setWindowTitle(CharSequence title); + CharSequence getTitle(); + void initFeature(int windowFeature); + void setUiOptions(int uiOptions); + boolean hasIcon(); + boolean hasLogo(); + void setIcon(int resId); + void setIcon(Drawable d); + void setLogo(int resId); + boolean canShowOverflowMenu(); + boolean isOverflowMenuShowing(); + boolean isOverflowMenuShowPending(); + boolean showOverflowMenu(); + boolean hideOverflowMenu(); + void setMenuPrepared(); + void setMenu(Menu menu, MenuPresenter.Callback cb); + void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates); + void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates); + void dismissPopups(); + +} diff --git a/core/java/com/android/internal/widget/DecorToolbar.java b/core/java/com/android/internal/widget/DecorToolbar.java new file mode 100644 index 0000000..ee6988e --- /dev/null +++ b/core/java/com/android/internal/widget/DecorToolbar.java @@ -0,0 +1,94 @@ +/* + * 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.widget; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.util.SparseArray; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.SpinnerAdapter; +import com.android.internal.view.menu.MenuPresenter; + +/** + * Common interface for a toolbar that sits as part of the window decor. + * Layouts that control window decor use this as a point of interaction with different + * bar implementations. + * + * @hide + */ +public interface DecorToolbar { + ViewGroup getViewGroup(); + Context getContext(); + boolean isSplit(); + boolean hasExpandedActionView(); + void collapseActionView(); + void setWindowCallback(Window.Callback cb); + void setWindowTitle(CharSequence title); + CharSequence getTitle(); + void setTitle(CharSequence title); + CharSequence getSubtitle(); + void setSubtitle(CharSequence subtitle); + void initProgress(); + void initIndeterminateProgress(); + boolean canSplit(); + void setSplitView(ViewGroup splitView); + void setSplitToolbar(boolean split); + void setSplitWhenNarrow(boolean splitWhenNarrow); + boolean hasIcon(); + boolean hasLogo(); + void setIcon(int resId); + void setIcon(Drawable d); + void setLogo(int resId); + void setLogo(Drawable d); + boolean canShowOverflowMenu(); + boolean isOverflowMenuShowing(); + boolean isOverflowMenuShowPending(); + boolean showOverflowMenu(); + boolean hideOverflowMenu(); + void setMenuPrepared(); + void setMenu(Menu menu, MenuPresenter.Callback cb); + void dismissPopupMenus(); + + int getDisplayOptions(); + void setDisplayOptions(int opts); + void setEmbeddedTabView(View tabView); + boolean hasEmbeddedTabs(); + boolean isTitleTruncated(); + void setCollapsible(boolean collapsible); + void setHomeButtonEnabled(boolean enable); + int getNavigationMode(); + void setNavigationMode(int mode); + void setDropdownParams(SpinnerAdapter adapter, AdapterView.OnItemSelectedListener listener); + void setDropdownSelectedPosition(int position); + int getDropdownSelectedPosition(); + int getDropdownItemCount(); + void setCustomView(View view); + View getCustomView(); + void animateToVisibility(int visibility); + void setNavigationIcon(Drawable icon); + void setNavigationIcon(int resId); + void setNavigationContentDescription(CharSequence description); + void setNavigationContentDescription(int resId); + void saveHierarchyState(SparseArray<Parcelable> toolbarStates); + void restoreHierarchyState(SparseArray<Parcelable> toolbarStates); +} diff --git a/core/java/com/android/internal/widget/DialogTitle.java b/core/java/com/android/internal/widget/DialogTitle.java index b86c438..7ea3d6b 100644 --- a/core/java/com/android/internal/widget/DialogTitle.java +++ b/core/java/com/android/internal/widget/DialogTitle.java @@ -28,10 +28,13 @@ import android.widget.TextView; * the text to the available space. */ public class DialogTitle extends TextView { - - public DialogTitle(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); + + public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); } public DialogTitle(Context context, AttributeSet attrs) { diff --git a/core/java/com/android/internal/widget/FaceUnlockView.java b/core/java/com/android/internal/widget/FaceUnlockView.java index e3c1247..121e601 100644 --- a/core/java/com/android/internal/widget/FaceUnlockView.java +++ b/core/java/com/android/internal/widget/FaceUnlockView.java @@ -18,8 +18,6 @@ package com.android.internal.widget; import android.content.Context; import android.util.AttributeSet; -import android.util.Log; -import android.view.View; import android.widget.RelativeLayout; public class FaceUnlockView extends RelativeLayout { diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 91056f1..c70841b 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -16,6 +16,8 @@ package com.android.internal.widget; +import com.android.internal.widget.ILockSettingsObserver; + /** {@hide} */ interface ILockSettings { void setBoolean(in String key, in boolean value, in int userId); @@ -28,7 +30,10 @@ interface ILockSettings { boolean checkPattern(in String pattern, int userId); void setLockPassword(in String password, int userId); boolean checkPassword(in String password, int userId); + boolean checkVoldPassword(int userId); boolean havePattern(int userId); boolean havePassword(int userId); void removeUser(int userId); + void registerObserver(in ILockSettingsObserver observer); + void unregisterObserver(in ILockSettingsObserver observer); } diff --git a/core/java/com/android/internal/widget/ILockSettingsObserver.aidl b/core/java/com/android/internal/widget/ILockSettingsObserver.aidl new file mode 100644 index 0000000..6c354d8 --- /dev/null +++ b/core/java/com/android/internal/widget/ILockSettingsObserver.aidl @@ -0,0 +1,22 @@ +/* + * 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.widget; + +/** {@hide} */ +oneway interface ILockSettingsObserver { + void onLockSettingChanged(in String key, in int userId); +} diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 8602260..d31c5cc 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -19,18 +19,21 @@ package com.android.internal.widget; import android.Manifest; import android.app.ActivityManagerNative; import android.app.admin.DevicePolicyManager; +import android.app.trust.TrustManager; import android.appwidget.AppWidgetManager; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.os.Binder; +import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.storage.IMountService; +import android.os.storage.StorageManager; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -40,12 +43,13 @@ import android.view.View; import android.widget.Button; import com.android.internal.R; -import com.android.internal.telephony.ITelephony; import com.google.android.collect.Lists; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** @@ -145,6 +149,8 @@ public class LockPatternUtils { private static final String LOCK_SCREEN_OWNER_INFO_ENABLED = Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED; + private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents"; + private final Context mContext; private final ContentResolver mContentResolver; private DevicePolicyManager mDevicePolicyManager; @@ -167,6 +173,15 @@ public class LockPatternUtils { return mDevicePolicyManager; } + private TrustManager getTrustManager() { + TrustManager trust = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE); + if (trust == null) { + Log.e(TAG, "Can't get TrustManagerService: is it running?", + new IllegalStateException("Stack trace:")); + } + return trust; + } + /** * @param contentResolver Used to look up and save settings. */ @@ -183,8 +198,8 @@ public class LockPatternUtils { private ILockSettings getLockSettings() { if (mLockSettingsService == null) { - mLockSettingsService = ILockSettings.Stub.asInterface( - (IBinder) ServiceManager.getService("lock_settings")); + mLockSettingsService = LockPatternUtilsCache.getInstance( + ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"))); } return mLockSettingsService; } @@ -242,10 +257,14 @@ public class LockPatternUtils { */ public void reportFailedPasswordAttempt() { getDevicePolicyManager().reportFailedPasswordAttempt(getCurrentOrCallingUserId()); + getTrustManager().reportUnlockAttempt(false /* authenticated */, + getCurrentOrCallingUserId()); } public void reportSuccessfulPasswordAttempt() { getDevicePolicyManager().reportSuccessfulPasswordAttempt(getCurrentOrCallingUserId()); + getTrustManager().reportUnlockAttempt(true /* authenticated */, + getCurrentOrCallingUserId()); } public void setCurrentUser(int userId) { @@ -313,6 +332,20 @@ public class LockPatternUtils { } /** + * Check to see if vold already has the password. + * Note that this also clears vold's copy of the password. + * @return Whether the vold password matches or not. + */ + public boolean checkVoldPassword() { + final int userId = getCurrentOrCallingUserId(); + try { + return getLockSettings().checkVoldPassword(userId); + } catch (RemoteException re) { + return false; + } + } + + /** * Check to see if a password matches any of the passwords stored in the * password history. * @@ -496,38 +529,71 @@ public class LockPatternUtils { */ public void saveLockPattern(List<LockPatternView.Cell> pattern, boolean isFallback) { try { - getLockSettings().setLockPattern(patternToString(pattern), getCurrentOrCallingUserId()); + int userId = getCurrentOrCallingUserId(); + getLockSettings().setLockPattern(patternToString(pattern), userId); DevicePolicyManager dpm = getDevicePolicyManager(); if (pattern != null) { + + int userHandle = userId; + if (userHandle == UserHandle.USER_OWNER) { + String stringPattern = patternToString(pattern); + updateEncryptionPassword(StorageManager.CRYPT_TYPE_PATTERN, stringPattern); + } + setBoolean(PATTERN_EVER_CHOSEN_KEY, true); if (!isFallback) { deleteGallery(); setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, - pattern.size(), 0, 0, 0, 0, 0, 0, getCurrentOrCallingUserId()); + pattern.size(), 0, 0, 0, 0, 0, 0, userId); } else { setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK); setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); finishBiometricWeak(); dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, - 0, 0, 0, 0, 0, 0, 0, getCurrentOrCallingUserId()); + 0, 0, 0, 0, 0, 0, 0, userId); } } else { dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, - 0, 0, 0, 0, 0, getCurrentOrCallingUserId()); + 0, 0, 0, 0, 0, userId); } } catch (RemoteException re) { Log.e(TAG, "Couldn't save lock pattern " + re); } } + private void updateCryptoUserInfo() { + int userId = getCurrentOrCallingUserId(); + if (userId != UserHandle.USER_OWNER) { + return; + } + + final String ownerInfo = isOwnerInfoEnabled() ? getOwnerInfo(userId) : ""; + + IBinder service = ServiceManager.getService("mount"); + if (service == null) { + Log.e(TAG, "Could not find the mount service to update the user info"); + return; + } + + IMountService mountService = IMountService.Stub.asInterface(service); + try { + Log.d(TAG, "Setting owner info"); + mountService.setField("OwnerInfo", ownerInfo); + } catch (RemoteException e) { + Log.e(TAG, "Error changing user info", e); + } + } + public void setOwnerInfo(String info, int userId) { setString(LOCK_SCREEN_OWNER_INFO, info, userId); + updateCryptoUserInfo(); } public void setOwnerInfoEnabled(boolean enabled) { setBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, enabled); + updateCryptoUserInfo(); } public String getOwnerInfo(int userId) { @@ -566,7 +632,7 @@ public class LockPatternUtils { } /** Update the encryption password if it is enabled **/ - private void updateEncryptionPassword(String password) { + private void updateEncryptionPassword(int type, String password) { DevicePolicyManager dpm = getDevicePolicyManager(); if (dpm.getStorageEncryptionStatus(getCurrentOrCallingUserId()) != DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) { @@ -581,7 +647,7 @@ public class LockPatternUtils { IMountService mountService = IMountService.Stub.asInterface(service); try { - mountService.changeEncryptionPassword(password); + mountService.changeEncryptionPassword(type, password); } catch (RemoteException e) { Log.e(TAG, "Error changing encryption password", e); } @@ -624,12 +690,15 @@ public class LockPatternUtils { getLockSettings().setLockPassword(password, userHandle); DevicePolicyManager dpm = getDevicePolicyManager(); if (password != null) { + int computedQuality = computePasswordQuality(password); + if (userHandle == UserHandle.USER_OWNER) { // Update the encryption password. - updateEncryptionPassword(password); + int type = computedQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC + ? StorageManager.CRYPT_TYPE_PIN : StorageManager.CRYPT_TYPE_PASSWORD; + updateEncryptionPassword(type, password); } - int computedQuality = computePasswordQuality(password); if (!isFallback) { deleteGallery(); setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle); @@ -676,8 +745,7 @@ public class LockPatternUtils { 0, 0, 0, 0, 0, 0, 0, userHandle); } // Add the password to the password history. We assume all - // password - // hashes have the same length for simplicity of implementation. + // password hashes have the same length for simplicity of implementation. String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle); if (passwordHistory == null) { passwordHistory = new String(); @@ -696,6 +764,11 @@ public class LockPatternUtils { } setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle); } else { + if (userHandle == UserHandle.USER_OWNER) { + // Update the encryption password. + updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, password); + } + dpm.setActivePasswordState( DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0, userHandle); @@ -714,13 +787,13 @@ public class LockPatternUtils { */ public int getKeyguardStoredPasswordQuality() { int quality = - (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); // If the user has chosen to use weak biometric sensor, then return the backup locking // method and treat biometric as a special case. if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) { quality = (int) getLong(PASSWORD_TYPE_ALTERNATE_KEY, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); } return quality; } @@ -730,7 +803,7 @@ public class LockPatternUtils { */ public boolean usingBiometricWeak() { int quality = - (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); return quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; } @@ -869,11 +942,12 @@ public class LockPatternUtils { */ public boolean isLockPatternEnabled() { final boolean backupEnabled = - getLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) - == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; + getLong(PASSWORD_TYPE_ALTERNATE_KEY, + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) + == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; return getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, false) - && (getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) + && (getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING || (usingBiometricWeak() && backupEnabled)); } @@ -1201,7 +1275,7 @@ public class LockPatternUtils { private void setLong(String secureSettingKey, long value, int userHandle) { try { - getLockSettings().setLong(secureSettingKey, value, getCurrentOrCallingUserId()); + getLockSettings().setLong(secureSettingKey, value, userHandle); } catch (RemoteException re) { // What can we do? Log.e(TAG, "Couldn't write long " + secureSettingKey + re); @@ -1285,19 +1359,11 @@ public class LockPatternUtils { /** * Resumes a call in progress. Typically launched from the EmergencyCall button * on various lockscreens. - * - * @return true if we were able to tell InCallScreen to show. */ - public boolean resumeCall() { - ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); - try { - if (phone != null && phone.showCallScreen()) { - return true; - } - } catch (RemoteException e) { - // What can we do? - } - return false; + public void resumeCall() { + TelephonyManager telephonyManager = + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + telephonyManager.showCallScreen(); } private void finishBiometricWeak() { @@ -1360,4 +1426,38 @@ public class LockPatternUtils { setBoolean(LOCKSCREEN_WIDGETS_ENABLED, enabled, userId); } + public void setEnabledTrustAgents(Collection<ComponentName> activeTrustAgents) { + setEnabledTrustAgents(activeTrustAgents, getCurrentOrCallingUserId()); + } + + public List<ComponentName> getEnabledTrustAgents() { + return getEnabledTrustAgents(getCurrentOrCallingUserId()); + } + + public void setEnabledTrustAgents(Collection<ComponentName> activeTrustAgents, int userId) { + StringBuilder sb = new StringBuilder(); + for (ComponentName cn : activeTrustAgents) { + if (sb.length() > 0) { + sb.append(','); + } + sb.append(cn.flattenToShortString()); + } + setString(ENABLED_TRUST_AGENTS, sb.toString(), userId); + getTrustManager().reportEnabledTrustAgentsChanged(getCurrentOrCallingUserId()); + } + + public List<ComponentName> getEnabledTrustAgents(int userId) { + String serialized = getString(ENABLED_TRUST_AGENTS, userId); + if (TextUtils.isEmpty(serialized)) { + return null; + } + String[] split = serialized.split(","); + ArrayList<ComponentName> activeTrustAgents = new ArrayList<ComponentName>(split.length); + for (String s : split) { + if (!TextUtils.isEmpty(s)) { + activeTrustAgents.add(ComponentName.unflattenFromString(s)); + } + } + return activeTrustAgents; + } } diff --git a/core/java/com/android/internal/widget/LockPatternUtilsCache.java b/core/java/com/android/internal/widget/LockPatternUtilsCache.java new file mode 100644 index 0000000..624f67c --- /dev/null +++ b/core/java/com/android/internal/widget/LockPatternUtilsCache.java @@ -0,0 +1,243 @@ +/* + * 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.widget; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; + +/** + * A decorator for {@link ILockSettings} that caches the key-value responses in memory. + * + * Specifically, the return values of {@link #getString(String, String, int)}, + * {@link #getLong(String, long, int)} and {@link #getBoolean(String, boolean, int)} are cached. + */ +public class LockPatternUtilsCache implements ILockSettings { + + private static final String HAS_LOCK_PATTERN_CACHE_KEY + = "LockPatternUtils.Cache.HasLockPatternCacheKey"; + private static final String HAS_LOCK_PASSWORD_CACHE_KEY + = "LockPatternUtils.Cache.HasLockPasswordCacheKey"; + + private static LockPatternUtilsCache sInstance; + + private final ILockSettings mService; + + /** Only access when holding {@code mCache} lock. */ + private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>(); + + /** Only access when holding {@link #mCache} lock. */ + private final CacheKey mCacheKey = new CacheKey(); + + + public static synchronized LockPatternUtilsCache getInstance(ILockSettings service) { + if (sInstance == null) { + sInstance = new LockPatternUtilsCache(service); + } + return sInstance; + } + + // ILockSettings + + private LockPatternUtilsCache(ILockSettings service) { + mService = service; + try { + service.registerObserver(mObserver); + } catch (RemoteException e) { + // Not safe to do caching without the observer. System process has probably died + // anyway, so crashing here is fine. + throw new RuntimeException(e); + } + } + + public void setBoolean(String key, boolean value, int userId) throws RemoteException { + invalidateCache(key, userId); + mService.setBoolean(key, value, userId); + putCache(key, userId, value); + } + + public void setLong(String key, long value, int userId) throws RemoteException { + invalidateCache(key, userId); + mService.setLong(key, value, userId); + putCache(key, userId, value); + } + + public void setString(String key, String value, int userId) throws RemoteException { + invalidateCache(key, userId); + mService.setString(key, value, userId); + putCache(key, userId, value); + } + + public long getLong(String key, long defaultValue, int userId) throws RemoteException { + Object value = peekCache(key, userId); + if (value instanceof Long) { + return (long) value; + } + long result = mService.getLong(key, defaultValue, userId); + putCache(key, userId, result); + return result; + } + + public String getString(String key, String defaultValue, int userId) throws RemoteException { + Object value = peekCache(key, userId); + if (value instanceof String) { + return (String) value; + } + String result = mService.getString(key, defaultValue, userId); + putCache(key, userId, result); + return result; + } + + public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException { + Object value = peekCache(key, userId); + if (value instanceof Boolean) { + return (boolean) value; + } + boolean result = mService.getBoolean(key, defaultValue, userId); + putCache(key, userId, result); + return result; + } + + @Override + public void setLockPattern(String pattern, int userId) throws RemoteException { + invalidateCache(HAS_LOCK_PATTERN_CACHE_KEY, userId); + mService.setLockPattern(pattern, userId); + putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, pattern != null); + } + + @Override + public boolean checkPattern(String pattern, int userId) throws RemoteException { + return mService.checkPattern(pattern, userId); + } + + @Override + public void setLockPassword(String password, int userId) throws RemoteException { + invalidateCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId); + mService.setLockPassword(password, userId); + putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, password != null); + } + + @Override + public boolean checkPassword(String password, int userId) throws RemoteException { + return mService.checkPassword(password, userId); + } + + @Override + public boolean checkVoldPassword(int userId) throws RemoteException { + return mService.checkVoldPassword(userId); + } + + @Override + public boolean havePattern(int userId) throws RemoteException { + Object value = peekCache(HAS_LOCK_PATTERN_CACHE_KEY, userId); + if (value instanceof Boolean) { + return (boolean) value; + } + boolean result = mService.havePattern(userId); + putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, result); + return result; + } + + @Override + public boolean havePassword(int userId) throws RemoteException { + Object value = peekCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId); + if (value instanceof Boolean) { + return (boolean) value; + } + boolean result = mService.havePassword(userId); + putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, result); + return result; + } + + @Override + public void removeUser(int userId) throws RemoteException { + mService.removeUser(userId); + } + + @Override + public void registerObserver(ILockSettingsObserver observer) throws RemoteException { + mService.registerObserver(observer); + } + + @Override + public void unregisterObserver(ILockSettingsObserver observer) throws RemoteException { + mService.unregisterObserver(observer); + } + + @Override + public IBinder asBinder() { + return mService.asBinder(); + } + + // Caching + + private Object peekCache(String key, int userId) { + synchronized (mCache) { + // Safe to reuse mCacheKey, because it is not stored in the map. + return mCache.get(mCacheKey.set(key, userId)); + } + } + + private void putCache(String key, int userId, Object value) { + synchronized (mCache) { + // Create a new key, because this will be stored in the map. + mCache.put(new CacheKey().set(key, userId), value); + } + } + + private void invalidateCache(String key, int userId) { + synchronized (mCache) { + // Safe to reuse mCacheKey, because it is not stored in the map. + mCache.remove(mCacheKey.set(key, userId)); + } + } + + private final ILockSettingsObserver mObserver = new ILockSettingsObserver.Stub() { + @Override + public void onLockSettingChanged(String key, int userId) throws RemoteException { + invalidateCache(key, userId); + } + }; + + private static final class CacheKey { + String key; + int userId; + + public CacheKey set(String key, int userId) { + this.key = key; + this.userId = userId; + return this; + } + + public CacheKey copy() { + return new CacheKey().set(key, userId); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CacheKey)) + return false; + CacheKey o = (CacheKey) obj; + return userId == o.userId && key.equals(o.key); + } + + @Override + public int hashCode() { + return key.hashCode() ^ userId; + } + } +} diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index b066d70..d841d53 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -32,6 +32,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; +import android.util.TypedValue; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; @@ -56,6 +57,7 @@ public class LockPatternView extends View { private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h) private static final boolean PROFILE_DRAWING = false; + private final CellState[][] mCellStates; private boolean mDrawingProfilingStarted = false; private Paint mPaint = new Paint(); @@ -186,6 +188,12 @@ public class LockPatternView extends View { } } + public static class CellState { + public float scale = 1.0f; + public float translateY = 0.0f; + public float alpha = 1.0f; + } + /** * How to display the current pattern. */ @@ -260,13 +268,23 @@ public class LockPatternView extends View { mPathPaint.setAntiAlias(true); mPathPaint.setDither(true); - mPathPaint.setColor(Color.WHITE); // TODO this should be from the style + + int defaultColor = Color.WHITE; + TypedValue outValue = new TypedValue(); + if (context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, outValue, true)) { + defaultColor = context.getResources().getColor(outValue.resourceId); + } + + final int color = a.getColor(R.styleable.LockPatternView_pathColor, defaultColor); + mPathPaint.setColor(color); + mPathPaint.setAlpha(mStrokeAlpha); mPathPaint.setStyle(Paint.Style.STROKE); mPathPaint.setStrokeJoin(Paint.Join.ROUND); mPathPaint.setStrokeCap(Paint.Cap.ROUND); // lot's of bitmaps! + // TODO: those bitmaps are hardcoded to the Holo Theme which should not be the case! mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default_holo); mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched_holo); mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default_holo); @@ -285,6 +303,18 @@ public class LockPatternView extends View { mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight()); } + mPaint.setFilterBitmap(true); + + mCellStates = new CellState[3][3]; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + mCellStates[i][j] = new CellState(); + } + } + } + + public CellState[][] getCellStates() { + return mCellStates; } private Bitmap getBitmapFor(int resId) { @@ -862,20 +892,22 @@ public class LockPatternView extends View { //float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2); for (int j = 0; j < 3; j++) { float leftX = paddingLeft + j * squareWidth; - drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]); + float scale = mCellStates[i][j].scale; + mPaint.setAlpha((int) (mCellStates[i][j].alpha * 255)); + float translationY = mCellStates[i][j].translateY; + drawCircle(canvas, (int) leftX, (int) topY + translationY, scale, drawLookup[i][j]); } } + // Reset the alpha to draw normally + mPaint.setAlpha(255); + // TODO: the path should be created and cached every time we hit-detect a cell // only the last segment of the path should be computed here - // draw the path of the pattern (unless the user is in progress, and - // we are in stealth mode) - final boolean drawPath = (!mInStealthMode || mPatternDisplayMode == DisplayMode.Wrong); - - // draw the arrows associated with the path (unless the user is in progress, and - // we are in stealth mode) - boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0; - mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms + // draw the path of the pattern (unless we are in stealth mode) + final boolean drawPath = !mInStealthMode; + + // draw the arrows associated with the path (unless we are in stealth mode) if (drawPath) { for (int i = 0; i < count - 1; i++) { Cell cell = pattern.get(i); @@ -889,7 +921,8 @@ public class LockPatternView extends View { } float leftX = paddingLeft + cell.column * squareWidth; - float topY = paddingTop + cell.row * squareHeight; + float topY = paddingTop + cell.row * squareHeight + + mCellStates[cell.row][cell.column].translateY; drawArrow(canvas, leftX, topY, cell, next); } @@ -910,6 +943,9 @@ public class LockPatternView extends View { float centerX = getCenterXForColumn(cell.column); float centerY = getCenterYForRow(cell.row); + + // Respect translation in animation + centerY += mCellStates[cell.row][cell.column].translateY; if (i == 0) { currentPath.moveTo(centerX, centerY); } else { @@ -924,8 +960,6 @@ public class LockPatternView extends View { } canvas.drawPath(currentPath, mPathPaint); } - - mPaint.setFilterBitmap(oldFlag); // restore default flag } private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) { @@ -970,11 +1004,12 @@ public class LockPatternView extends View { * @param topY * @param partOfPattern Whether this circle is part of the pattern. */ - private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) { + private void drawCircle(Canvas canvas, float leftX, float topY, float scale, + boolean partOfPattern) { Bitmap outerCircle; Bitmap innerCircle; - if (!partOfPattern || (mInStealthMode && mPatternDisplayMode != DisplayMode.Wrong)) { + if (!partOfPattern || mInStealthMode) { // unselected circle outerCircle = mBitmapCircleDefault; innerCircle = mBitmapBtnDefault; @@ -1010,7 +1045,7 @@ public class LockPatternView extends View { mCircleMatrix.setTranslate(leftX + offsetX, topY + offsetY); mCircleMatrix.preTranslate(mBitmapWidth/2, mBitmapHeight/2); - mCircleMatrix.preScale(sx, sy); + mCircleMatrix.preScale(sx * scale, sy * scale); mCircleMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2); canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint); diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java index 3c01c69..7483e75 100644 --- a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java +++ b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java @@ -16,7 +16,6 @@ package com.android.internal.widget; -import java.util.Locale; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; @@ -73,8 +72,8 @@ public class PasswordEntryKeyboard extends Keyboard { private void init(Context context) { final Resources res = context.getResources(); - mShiftIcon = res.getDrawable(R.drawable.sym_keyboard_shift); - mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked); + mShiftIcon = context.getDrawable(R.drawable.sym_keyboard_shift); + mShiftLockIcon = context.getDrawable(R.drawable.sym_keyboard_shift_locked); sSpacebarVerticalCorrection = res.getDimensionPixelOffset( R.dimen.password_keyboard_spacebar_vertical_correction); } diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java index a3df291..b2c9dc5 100644 --- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java +++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java @@ -21,9 +21,7 @@ import android.content.res.Resources; import android.inputmethodservice.Keyboard; import android.inputmethodservice.KeyboardView; import android.inputmethodservice.KeyboardView.OnKeyboardActionListener; -import android.os.Handler; import android.os.SystemClock; -import android.os.Vibrator; import android.provider.Settings; import android.util.Log; import android.view.HapticFeedbackConstants; diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java index b37adff..d27346b 100644 --- a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java +++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java @@ -29,11 +29,16 @@ public class PasswordEntryKeyboardView extends KeyboardView { static final int KEYCODE_NEXT_LANGUAGE = -104; public PasswordEntryKeyboardView(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); } - public PasswordEntryKeyboardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public PasswordEntryKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public PasswordEntryKeyboardView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } @Override diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index d82831f..e339c44 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -31,11 +31,13 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; +import android.view.WindowManagerPolicy.PointerEventListener; import android.view.MotionEvent.PointerCoords; import java.util.ArrayList; -public class PointerLocationView extends View implements InputDeviceListener { +public class PointerLocationView extends View implements InputDeviceListener, + PointerEventListener { private static final String TAG = "Pointer"; // The system property key used to specify an alternate velocity tracker strategy @@ -520,7 +522,8 @@ public class PointerLocationView extends View implements InputDeviceListener { .toString()); } - public void addPointerEvent(MotionEvent event) { + @Override + public void onPointerEvent(MotionEvent event) { final int action = event.getAction(); int NP = mPointers.size(); @@ -648,7 +651,7 @@ public class PointerLocationView extends View implements InputDeviceListener { @Override public boolean onTouchEvent(MotionEvent event) { - addPointerEvent(event); + onPointerEvent(event); if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) { requestFocus(); @@ -660,7 +663,7 @@ public class PointerLocationView extends View implements InputDeviceListener { public boolean onGenericMotionEvent(MotionEvent event) { final int source = event.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { - addPointerEvent(event); + onPointerEvent(event); } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { logMotionEvent("Joystick", event); } else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) { diff --git a/core/java/com/android/internal/widget/RotarySelector.java b/core/java/com/android/internal/widget/RotarySelector.java index 4e405f4..64ce918 100644 --- a/core/java/com/android/internal/widget/RotarySelector.java +++ b/core/java/com/android/internal/widget/RotarySelector.java @@ -24,7 +24,7 @@ import android.graphics.Paint; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; -import android.graphics.drawable.Drawable; +import android.media.AudioManager; import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; @@ -35,7 +35,9 @@ import android.view.View; import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.animation.DecelerateInterpolator; + import static android.view.animation.AnimationUtils.currentAnimationTimeMillis; + import com.android.internal.R; @@ -677,7 +679,7 @@ public class RotarySelector extends View { mVibrator = (android.os.Vibrator) getContext() .getSystemService(Context.VIBRATOR_SERVICE); } - mVibrator.vibrate(duration); + mVibrator.vibrate(duration, AudioManager.STREAM_SYSTEM); } } diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java index fa29e6e..d6bd1d6 100644 --- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java +++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java @@ -23,7 +23,6 @@ import android.animation.TimeInterpolator; import android.app.ActionBar; import android.content.Context; import android.content.res.Configuration; -import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.text.TextUtils.TruncateAt; diff --git a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java index ba113a3..5f3c5f9 100644 --- a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java +++ b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java @@ -79,17 +79,20 @@ public class SizeAdaptiveLayout extends ViewGroup { private int mModestyPanelTop; public SizeAdaptiveLayout(Context context) { - super(context); - initialize(); + this(context, null); } public SizeAdaptiveLayout(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); + this(context, attrs, 0); + } + + public SizeAdaptiveLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } - public SizeAdaptiveLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public SizeAdaptiveLayout( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); initialize(); } @@ -104,8 +107,6 @@ public class SizeAdaptiveLayout extends ViewGroup { } if (background instanceof ColorDrawable) { mModestyPanel.setBackgroundDrawable(background); - } else { - mModestyPanel.setBackgroundColor(Color.BLACK); } SizeAdaptiveLayout.LayoutParams layout = new SizeAdaptiveLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, @@ -151,6 +152,10 @@ public class SizeAdaptiveLayout extends ViewGroup { if (DEBUG) Log.d(TAG, this + " measure spec: " + MeasureSpec.toString(heightMeasureSpec)); View model = selectActiveChild(heightMeasureSpec); + if (model == null) { + setMeasuredDimension(0, 0); + return; + } SizeAdaptiveLayout.LayoutParams lp = (SizeAdaptiveLayout.LayoutParams) model.getLayoutParams(); if (DEBUG) Log.d(TAG, "active min: " + lp.minHeight + " max: " + lp.maxHeight); @@ -239,6 +244,8 @@ public class SizeAdaptiveLayout extends ViewGroup { int measureSpec = View.MeasureSpec.makeMeasureSpec(bottom - top, View.MeasureSpec.EXACTLY); mActiveChild = selectActiveChild(measureSpec); + if (mActiveChild == null) return; + mActiveChild.setVisibility(View.VISIBLE); if (mLastActive != mActiveChild && mLastActive != null) { diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java index aebc4f6..deb0fd7 100644 --- a/core/java/com/android/internal/widget/SlidingTab.java +++ b/core/java/com/android/internal/widget/SlidingTab.java @@ -21,6 +21,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.media.AudioManager; import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; @@ -821,7 +822,7 @@ public class SlidingTab extends ViewGroup { mVibrator = (android.os.Vibrator) getContext() .getSystemService(Context.VIBRATOR_SERVICE); } - mVibrator.vibrate(duration); + mVibrator.vibrate(duration, AudioManager.STREAM_SYSTEM); } } diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java index 071193c..117463a 100644 --- a/core/java/com/android/internal/widget/SubtitleView.java +++ b/core/java/com/android/internal/widget/SubtitleView.java @@ -18,7 +18,6 @@ package com.android.internal.widget; import android.content.ContentResolver; import android.content.Context; -import android.content.res.Resources.Theme; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; @@ -33,7 +32,6 @@ import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; import android.util.DisplayMetrics; -import android.util.TypedValue; import android.view.View; import android.view.accessibility.CaptioningManager.CaptionStyle; @@ -41,6 +39,12 @@ public class SubtitleView extends View { // Ratio of inner padding to font size. private static final float INNER_PADDING_RATIO = 0.125f; + /** Color used for the shadowed edge of a bevel. */ + private static final int COLOR_BEVEL_DARK = 0x80000000; + + /** Color used for the illuminated edge of a bevel. */ + private static final int COLOR_BEVEL_LIGHT = 0x80FFFFFF; + // Styled dimensions. private final float mCornerRadius; private final float mOutlineWidth; @@ -79,12 +83,15 @@ public class SubtitleView extends View { this(context, attrs, 0); } - public SubtitleView(Context context, AttributeSet attrs, int defStyle) { + public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs); - final Theme theme = context.getTheme(); - final TypedArray a = theme.obtainStyledAttributes( - attrs, android.R.styleable.TextView, defStyle, 0); + final TypedArray a = context.obtainStyledAttributes( + attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes); CharSequence text = ""; int textSize = 15; @@ -112,7 +119,6 @@ public class SubtitleView extends View { // Set up density-dependent properties. // TODO: Move these to a default style. final Resources res = getContext().getResources(); - final DisplayMetrics m = res.getDisplayMetrics(); mCornerRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_corner_radius); mOutlineWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_outline_width); mShadowRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_radius); @@ -311,7 +317,8 @@ public class SubtitleView extends View { } } - if (mEdgeType == CaptionStyle.EDGE_TYPE_OUTLINE) { + final int edgeType = mEdgeType; + if (edgeType == CaptionStyle.EDGE_TYPE_OUTLINE) { textPaint.setStrokeJoin(Join.ROUND); textPaint.setStrokeWidth(mOutlineWidth); textPaint.setColor(mEdgeColor); @@ -320,8 +327,24 @@ public class SubtitleView extends View { for (int i = 0; i < lineCount; i++) { layout.drawText(c, i, i); } - } else if (mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) { + } else if (edgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) { textPaint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor); + } else if (edgeType == CaptionStyle.EDGE_TYPE_RAISED + || edgeType == CaptionStyle.EDGE_TYPE_DEPRESSED) { + final boolean raised = edgeType == CaptionStyle.EDGE_TYPE_RAISED; + final int colorUp = raised ? Color.WHITE : mEdgeColor; + final int colorDown = raised ? mEdgeColor : Color.WHITE; + final float offset = mShadowRadius / 2f; + + textPaint.setColor(mForegroundColor); + textPaint.setStyle(Style.FILL); + textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp); + + for (int i = 0; i < lineCount; i++) { + layout.drawText(c, i, i); + } + + textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown); } textPaint.setColor(mForegroundColor); diff --git a/core/java/com/android/internal/widget/TextProgressBar.java b/core/java/com/android/internal/widget/TextProgressBar.java index e898aa4..7ca07d4 100644 --- a/core/java/com/android/internal/widget/TextProgressBar.java +++ b/core/java/com/android/internal/widget/TextProgressBar.java @@ -19,7 +19,6 @@ package com.android.internal.widget; import android.content.Context; import android.os.SystemClock; import android.util.AttributeSet; -import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -59,9 +58,13 @@ public class TextProgressBar extends RelativeLayout implements OnChronometerTick boolean mChronometerFollow = false; int mChronometerGravity = Gravity.NO_GRAVITY; + + public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } - public TextProgressBar(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); } public TextProgressBar(Context context, AttributeSet attrs) { diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java new file mode 100644 index 0000000..3e15c32 --- /dev/null +++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java @@ -0,0 +1,541 @@ +/* + * 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.widget; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.ActionBar; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.ActionMenuPresenter; +import android.widget.AdapterView; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; +import android.widget.Toolbar; +import com.android.internal.R; +import com.android.internal.view.menu.ActionMenuItem; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuPresenter; + +/** + * Internal class used to interact with the Toolbar widget without + * exposing interface methods to the public API. + * + * <p>ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView + * so that either variant acting as a + * {@link com.android.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave + * in the same way.</p> + * + * @hide + */ +public class ToolbarWidgetWrapper implements DecorToolbar { + private static final String TAG = "ToolbarWidgetWrapper"; + + private static final int AFFECTS_LOGO_MASK = + ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO; + + private Toolbar mToolbar; + + private int mDisplayOpts; + private View mTabView; + private Spinner mSpinner; + private View mCustomView; + + private Drawable mIcon; + private Drawable mLogo; + private Drawable mNavIcon; + + private boolean mTitleSet; + private CharSequence mTitle; + private CharSequence mSubtitle; + + private Window.Callback mWindowCallback; + private boolean mMenuPrepared; + private ActionMenuPresenter mActionMenuPresenter; + + public ToolbarWidgetWrapper(Toolbar toolbar) { + mToolbar = toolbar; + + mTitle = toolbar.getTitle(); + mSubtitle = toolbar.getSubtitle(); + mTitleSet = !TextUtils.isEmpty(mTitle); + + final TypedArray a = toolbar.getContext().obtainStyledAttributes(null, + R.styleable.ActionBar, R.attr.actionBarStyle, 0); + + final CharSequence title = a.getText(R.styleable.ActionBar_title); + if (!TextUtils.isEmpty(title)) { + setTitle(title); + } + + final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle); + if (!TextUtils.isEmpty(subtitle)) { + setSubtitle(subtitle); + } + + final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo); + if (logo != null) { + setLogo(logo); + } + + final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon); + if (icon != null) { + setIcon(icon); + } + + final Drawable navIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator); + if (navIcon != null) { + setNavigationIcon(navIcon); + } + + setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0)); + + final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0); + if (customNavId != 0) { + setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId, + mToolbar, false)); + setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM); + } + + final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0); + if (height > 0) { + final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams(); + lp.height = height; + mToolbar.setLayoutParams(lp); + } + + final int contentInsetStart = a.getDimensionPixelOffset( + R.styleable.ActionBar_contentInsetStart, 0); + final int contentInsetEnd = a.getDimensionPixelOffset( + R.styleable.ActionBar_contentInsetEnd, 0); + if (contentInsetStart > 0 || contentInsetEnd > 0) { + mToolbar.setContentInsetsRelative(contentInsetStart, contentInsetEnd); + } + + final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0); + if (titleTextStyle != 0) { + mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle); + } + + final int subtitleTextStyle = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0); + if (subtitleTextStyle != 0) { + mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle); + } + + a.recycle(); + + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(), + 0, android.R.id.home, 0, 0, mTitle); + @Override + public void onClick(View v) { + if (mWindowCallback != null && mMenuPrepared) { + mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem); + } + } + }); + } + + @Override + public ViewGroup getViewGroup() { + return mToolbar; + } + + @Override + public Context getContext() { + return mToolbar.getContext(); + } + + @Override + public boolean isSplit() { + return false; + } + + @Override + public boolean hasExpandedActionView() { + return mToolbar.hasExpandedActionView(); + } + + @Override + public void collapseActionView() { + mToolbar.collapseActionView(); + } + + @Override + public void setWindowCallback(Window.Callback cb) { + mWindowCallback = cb; + } + + @Override + public void setWindowTitle(CharSequence title) { + // "Real" title always trumps window title. + if (!mTitleSet) { + setTitleInt(title); + } + } + + @Override + public CharSequence getTitle() { + return mToolbar.getTitle(); + } + + @Override + public void setTitle(CharSequence title) { + mTitleSet = true; + setTitleInt(title); + } + + private void setTitleInt(CharSequence title) { + mTitle = title; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setTitle(title); + } + } + + @Override + public CharSequence getSubtitle() { + return mToolbar.getSubtitle(); + } + + @Override + public void setSubtitle(CharSequence subtitle) { + mSubtitle = subtitle; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setSubtitle(subtitle); + } + } + + @Override + public void initProgress() { + Log.i(TAG, "Progress display unsupported"); + } + + @Override + public void initIndeterminateProgress() { + Log.i(TAG, "Progress display unsupported"); + } + + @Override + public boolean canSplit() { + return false; + } + + @Override + public void setSplitView(ViewGroup splitView) { + } + + @Override + public void setSplitToolbar(boolean split) { + if (split) { + throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar"); + } + } + + @Override + public void setSplitWhenNarrow(boolean splitWhenNarrow) { + // Ignore. + } + + @Override + public boolean hasIcon() { + return mIcon != null; + } + + @Override + public boolean hasLogo() { + return mLogo != null; + } + + @Override + public void setIcon(int resId) { + setIcon(resId != 0 ? getContext().getDrawable(resId) : null); + } + + @Override + public void setIcon(Drawable d) { + mIcon = d; + updateToolbarLogo(); + } + + @Override + public void setLogo(int resId) { + setLogo(resId != 0 ? getContext().getDrawable(resId) : null); + } + + @Override + public void setLogo(Drawable d) { + mLogo = d; + updateToolbarLogo(); + } + + private void updateToolbarLogo() { + Drawable logo = null; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) { + if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) { + logo = mLogo != null ? mLogo : mIcon; + } else { + logo = mIcon; + } + } + mToolbar.setLogo(logo); + } + + @Override + public boolean canShowOverflowMenu() { + return mToolbar.canShowOverflowMenu(); + } + + @Override + public boolean isOverflowMenuShowing() { + return mToolbar.isOverflowMenuShowing(); + } + + @Override + public boolean isOverflowMenuShowPending() { + return mToolbar.isOverflowMenuShowPending(); + } + + @Override + public boolean showOverflowMenu() { + return mToolbar.showOverflowMenu(); + } + + @Override + public boolean hideOverflowMenu() { + return mToolbar.hideOverflowMenu(); + } + + @Override + public void setMenuPrepared() { + mMenuPrepared = true; + } + + @Override + public void setMenu(Menu menu, MenuPresenter.Callback cb) { + if (mActionMenuPresenter == null) { + mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext()); + mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter); + } + mActionMenuPresenter.setCallback(cb); + mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter); + } + + @Override + public void dismissPopupMenus() { + mToolbar.dismissPopupMenus(); + } + + @Override + public int getDisplayOptions() { + return mDisplayOpts; + } + + @Override + public void setDisplayOptions(int newOpts) { + final int oldOpts = mDisplayOpts; + final int changed = oldOpts ^ newOpts; + mDisplayOpts = newOpts; + if (changed != 0) { + if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + mToolbar.setNavigationIcon(mNavIcon); + } else { + mToolbar.setNavigationIcon(null); + } + } + + if ((changed & AFFECTS_LOGO_MASK) != 0) { + updateToolbarLogo(); + } + + if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setTitle(mTitle); + mToolbar.setSubtitle(mSubtitle); + } else { + mToolbar.setTitle(null); + mToolbar.setSubtitle(null); + } + } + + if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) { + if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.addView(mCustomView); + } else { + mToolbar.removeView(mCustomView); + } + } + } + } + + @Override + public void setEmbeddedTabView(View tabView) { + mTabView = tabView; + } + + @Override + public boolean hasEmbeddedTabs() { + return mTabView != null; + } + + @Override + public boolean isTitleTruncated() { + return mToolbar.isTitleTruncated(); + } + + @Override + public void setCollapsible(boolean collapsible) { + // Ignore + } + + @Override + public void setHomeButtonEnabled(boolean enable) { + // Ignore + } + + @Override + public int getNavigationMode() { + return 0; + } + + @Override + public void setNavigationMode(int mode) { + if (mode != ActionBar.NAVIGATION_MODE_STANDARD) { + throw new IllegalArgumentException( + "Navigation modes not supported in this configuration"); + } + } + + @Override + public void setDropdownParams(SpinnerAdapter adapter, + AdapterView.OnItemSelectedListener listener) { + if (mSpinner == null) { + mSpinner = new Spinner(getContext()); + } + mSpinner.setAdapter(adapter); + mSpinner.setOnItemSelectedListener(listener); + } + + @Override + public void setDropdownSelectedPosition(int position) { + if (mSpinner == null) { + throw new IllegalStateException( + "Can't set dropdown selected position without an adapter"); + } + mSpinner.setSelection(position); + } + + @Override + public int getDropdownSelectedPosition() { + return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0; + } + + @Override + public int getDropdownItemCount() { + return mSpinner != null ? mSpinner.getCount() : 0; + } + + @Override + public void setCustomView(View view) { + if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.removeView(mCustomView); + } + mCustomView = view; + if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.addView(mCustomView); + } + } + + @Override + public View getCustomView() { + return mCustomView; + } + + @Override + public void animateToVisibility(int visibility) { + if (visibility == View.GONE) { + mToolbar.animate().translationY(mToolbar.getHeight()).alpha(0) + .setListener(new AnimatorListenerAdapter() { + private boolean mCanceled = false; + @Override + public void onAnimationEnd(Animator animation) { + if (!mCanceled) { + mToolbar.setVisibility(View.GONE); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + } + }); + } else if (visibility == View.VISIBLE) { + mToolbar.animate().translationY(0).alpha(1) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mToolbar.setVisibility(View.VISIBLE); + } + }); + } + } + + @Override + public void setNavigationIcon(Drawable icon) { + mNavIcon = icon; + if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + mToolbar.setNavigationIcon(icon); + } + } + + @Override + public void setNavigationIcon(int resId) { + setNavigationIcon(mToolbar.getContext().getDrawable(resId)); + } + + @Override + public void setNavigationContentDescription(CharSequence description) { + mToolbar.setNavigationContentDescription(description); + } + + @Override + public void setNavigationContentDescription(int resId) { + mToolbar.setNavigationContentDescription(resId); + } + + @Override + public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) { + mToolbar.saveHierarchyState(toolbarStates); + } + + @Override + public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) { + mToolbar.restoreHierarchyState(toolbarStates); + } + +} diff --git a/core/java/com/android/internal/widget/WaveView.java b/core/java/com/android/internal/widget/WaveView.java index d33d50c..86f14b3 100644 --- a/core/java/com/android/internal/widget/WaveView.java +++ b/core/java/com/android/internal/widget/WaveView.java @@ -25,10 +25,10 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; +import android.media.AudioManager; import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; -import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; @@ -583,7 +583,7 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen mVibrator = (android.os.Vibrator) getContext() .getSystemService(Context.VIBRATOR_SERVICE); } - mVibrator.vibrate(duration); + mVibrator.vibrate(duration, AudioManager.STREAM_SYSTEM); } } diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java index cd1ccd3..841a02a 100644 --- a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java +++ b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java @@ -30,6 +30,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; +import android.media.AudioManager; import android.os.Bundle; import android.os.UserHandle; import android.os.Vibrator; @@ -234,9 +235,13 @@ public class GlowPadView extends View { mMagneticTargets = a.getBoolean(R.styleable.GlowPadView_magneticTargets, mMagneticTargets); int pointId = getResourceId(a, R.styleable.GlowPadView_pointDrawable); - Drawable pointDrawable = pointId != 0 ? res.getDrawable(pointId) : null; + Drawable pointDrawable = pointId != 0 ? context.getDrawable(pointId) : null; mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f); + mPointCloud = new PointCloud(pointDrawable); + mPointCloud.makePointCloud(mInnerRadius, mOuterRadius); + mPointCloud.glowManager.setRadius(mGlowRadius); + TypedValue outValue = new TypedValue(); // Read array of target drawables @@ -272,10 +277,6 @@ public class GlowPadView extends View { setVibrateEnabled(mVibrationDuration > 0); assignDefaultsIfNeeded(); - - mPointCloud = new PointCloud(pointDrawable); - mPointCloud.makePointCloud(mInnerRadius, mOuterRadius); - mPointCloud.glowManager.setRadius(mGlowRadius); } private int getResourceId(TypedArray a, int id) { @@ -564,7 +565,7 @@ public class GlowPadView extends View { mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0; if (mVibrator != null && hapticEnabled) { - mVibrator.vibrate(mVibrationDuration); + mVibrator.vibrate(mVibrationDuration, AudioManager.STREAM_SYSTEM); } } diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java deleted file mode 100644 index e22d1e8..0000000 --- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java +++ /dev/null @@ -1,1269 +0,0 @@ -/* - * Copyright (C) 2011 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.multiwaveview; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorListenerAdapter; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.UserHandle; -import android.os.Vibrator; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; - -import com.android.internal.R; - -import java.util.ArrayList; - -/** - * A special widget containing a center and outer ring. Moving the center ring to the outer ring - * causes an event that can be caught by implementing OnTriggerListener. - */ -public class MultiWaveView extends View { - private static final String TAG = "MultiWaveView"; - private static final boolean DEBUG = false; - - // Wave state machine - private static final int STATE_IDLE = 0; - private static final int STATE_START = 1; - private static final int STATE_FIRST_TOUCH = 2; - private static final int STATE_TRACKING = 3; - private static final int STATE_SNAP = 4; - private static final int STATE_FINISH = 5; - - // Animation properties. - private static final float SNAP_MARGIN_DEFAULT = 20.0f; // distance to ring before we snap to it - - public interface OnTriggerListener { - int NO_HANDLE = 0; - int CENTER_HANDLE = 1; - public void onGrabbed(View v, int handle); - public void onReleased(View v, int handle); - public void onTrigger(View v, int target); - public void onGrabbedStateChange(View v, int handle); - public void onFinishFinalAnimation(); - } - - // Tuneable parameters for animation - private static final int CHEVRON_INCREMENTAL_DELAY = 160; - private static final int CHEVRON_ANIMATION_DURATION = 850; - private static final int RETURN_TO_HOME_DELAY = 1200; - private static final int RETURN_TO_HOME_DURATION = 200; - private static final int HIDE_ANIMATION_DELAY = 200; - private static final int HIDE_ANIMATION_DURATION = 200; - private static final int SHOW_ANIMATION_DURATION = 200; - private static final int SHOW_ANIMATION_DELAY = 50; - private static final int INITIAL_SHOW_HANDLE_DURATION = 200; - - private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f; - private static final float TARGET_SCALE_EXPANDED = 1.0f; - private static final float TARGET_SCALE_COLLAPSED = 0.8f; - private static final float RING_SCALE_EXPANDED = 1.0f; - private static final float RING_SCALE_COLLAPSED = 0.5f; - - private TimeInterpolator mChevronAnimationInterpolator = Ease.Quad.easeOut; - - private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>(); - private ArrayList<TargetDrawable> mChevronDrawables = new ArrayList<TargetDrawable>(); - private AnimationBundle mChevronAnimations = new AnimationBundle(); - private AnimationBundle mTargetAnimations = new AnimationBundle(); - private AnimationBundle mHandleAnimations = new AnimationBundle(); - private ArrayList<String> mTargetDescriptions; - private ArrayList<String> mDirectionDescriptions; - private OnTriggerListener mOnTriggerListener; - private TargetDrawable mHandleDrawable; - private TargetDrawable mOuterRing; - private Vibrator mVibrator; - - private int mFeedbackCount = 3; - private int mVibrationDuration = 0; - private int mGrabbedState; - private int mActiveTarget = -1; - private float mTapRadius; - private float mWaveCenterX; - private float mWaveCenterY; - private int mMaxTargetHeight; - private int mMaxTargetWidth; - - private float mOuterRadius = 0.0f; - private float mSnapMargin = 0.0f; - private boolean mDragging; - private int mNewTargetResources; - - private class AnimationBundle extends ArrayList<Tweener> { - private static final long serialVersionUID = 0xA84D78726F127468L; - private boolean mSuspended; - - public void start() { - if (mSuspended) return; // ignore attempts to start animations - final int count = size(); - for (int i = 0; i < count; i++) { - Tweener anim = get(i); - anim.animator.start(); - } - } - - public void cancel() { - final int count = size(); - for (int i = 0; i < count; i++) { - Tweener anim = get(i); - anim.animator.cancel(); - } - clear(); - } - - public void stop() { - final int count = size(); - for (int i = 0; i < count; i++) { - Tweener anim = get(i); - anim.animator.end(); - } - clear(); - } - - public void setSuspended(boolean suspend) { - mSuspended = suspend; - } - }; - - private AnimatorListener mResetListener = new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animator) { - switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); - dispatchOnFinishFinalAnimation(); - } - }; - - private AnimatorListener mResetListenerWithPing = new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animator) { - ping(); - switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); - dispatchOnFinishFinalAnimation(); - } - }; - - private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - invalidateGlobalRegion(mHandleDrawable); - invalidate(); - } - }; - - private boolean mAnimatingTargets; - private AnimatorListener mTargetUpdateListener = new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animator) { - if (mNewTargetResources != 0) { - internalSetTargetResources(mNewTargetResources); - mNewTargetResources = 0; - hideTargets(false, false); - } - mAnimatingTargets = false; - } - }; - private int mTargetResourceId; - private int mTargetDescriptionsResourceId; - private int mDirectionDescriptionsResourceId; - private boolean mAlwaysTrackFinger; - private int mHorizontalInset; - private int mVerticalInset; - private int mGravity = Gravity.TOP; - private boolean mInitialLayout = true; - private Tweener mBackgroundAnimator; - - public MultiWaveView(Context context) { - this(context, null); - } - - public MultiWaveView(Context context, AttributeSet attrs) { - super(context, attrs); - Resources res = context.getResources(); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiWaveView); - mOuterRadius = a.getDimension(R.styleable.MultiWaveView_outerRadius, mOuterRadius); - mSnapMargin = a.getDimension(R.styleable.MultiWaveView_snapMargin, mSnapMargin); - mVibrationDuration = a.getInt(R.styleable.MultiWaveView_vibrationDuration, - mVibrationDuration); - mFeedbackCount = a.getInt(R.styleable.MultiWaveView_feedbackCount, - mFeedbackCount); - mHandleDrawable = new TargetDrawable(res, - a.peekValue(R.styleable.MultiWaveView_handleDrawable).resourceId); - mTapRadius = mHandleDrawable.getWidth()/2; - mOuterRing = new TargetDrawable(res, - a.peekValue(R.styleable.MultiWaveView_waveDrawable).resourceId); - mAlwaysTrackFinger = a.getBoolean(R.styleable.MultiWaveView_alwaysTrackFinger, false); - - // Read array of chevron drawables - TypedValue outValue = new TypedValue(); - if (a.getValue(R.styleable.MultiWaveView_chevronDrawables, outValue)) { - ArrayList<TargetDrawable> chevrons = loadDrawableArray(outValue.resourceId); - for (int i = 0; i < chevrons.size(); i++) { - final TargetDrawable chevron = chevrons.get(i); - for (int k = 0; k < mFeedbackCount; k++) { - mChevronDrawables.add(chevron == null ? null : new TargetDrawable(chevron)); - } - } - } - - // Read array of target drawables - if (a.getValue(R.styleable.MultiWaveView_targetDrawables, outValue)) { - internalSetTargetResources(outValue.resourceId); - } - if (mTargetDrawables == null || mTargetDrawables.size() == 0) { - throw new IllegalStateException("Must specify at least one target drawable"); - } - - // Read array of target descriptions - if (a.getValue(R.styleable.MultiWaveView_targetDescriptions, outValue)) { - final int resourceId = outValue.resourceId; - if (resourceId == 0) { - throw new IllegalStateException("Must specify target descriptions"); - } - setTargetDescriptionsResourceId(resourceId); - } - - // Read array of direction descriptions - if (a.getValue(R.styleable.MultiWaveView_directionDescriptions, outValue)) { - final int resourceId = outValue.resourceId; - if (resourceId == 0) { - throw new IllegalStateException("Must specify direction descriptions"); - } - setDirectionDescriptionsResourceId(resourceId); - } - - a.recycle(); - - // Use gravity attribute from LinearLayout - a = context.obtainStyledAttributes(attrs, android.R.styleable.LinearLayout); - mGravity = a.getInt(android.R.styleable.LinearLayout_gravity, Gravity.TOP); - a.recycle(); - - setVibrateEnabled(mVibrationDuration > 0); - assignDefaultsIfNeeded(); - } - - private void dump() { - Log.v(TAG, "Outer Radius = " + mOuterRadius); - Log.v(TAG, "SnapMargin = " + mSnapMargin); - Log.v(TAG, "FeedbackCount = " + mFeedbackCount); - Log.v(TAG, "VibrationDuration = " + mVibrationDuration); - Log.v(TAG, "TapRadius = " + mTapRadius); - Log.v(TAG, "WaveCenterX = " + mWaveCenterX); - Log.v(TAG, "WaveCenterY = " + mWaveCenterY); - } - - public void suspendAnimations() { - mChevronAnimations.setSuspended(true); - mTargetAnimations.setSuspended(true); - mHandleAnimations.setSuspended(true); - } - - public void resumeAnimations() { - mChevronAnimations.setSuspended(false); - mTargetAnimations.setSuspended(false); - mHandleAnimations.setSuspended(false); - mChevronAnimations.start(); - mTargetAnimations.start(); - mHandleAnimations.start(); - } - - @Override - protected int getSuggestedMinimumWidth() { - // View should be large enough to contain the background + handle and - // target drawable on either edge. - return (int) (Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) + mMaxTargetWidth); - } - - @Override - protected int getSuggestedMinimumHeight() { - // View should be large enough to contain the unlock ring + target and - // target drawable on either edge - return (int) (Math.max(mOuterRing.getHeight(), 2 * mOuterRadius) + mMaxTargetHeight); - } - - private int resolveMeasured(int measureSpec, int desired) - { - int result = 0; - int specSize = MeasureSpec.getSize(measureSpec); - switch (MeasureSpec.getMode(measureSpec)) { - case MeasureSpec.UNSPECIFIED: - result = desired; - break; - case MeasureSpec.AT_MOST: - result = Math.min(specSize, desired); - break; - case MeasureSpec.EXACTLY: - default: - result = specSize; - } - return result; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int minimumWidth = getSuggestedMinimumWidth(); - final int minimumHeight = getSuggestedMinimumHeight(); - int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth); - int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight); - computeInsets((computedWidth - minimumWidth), (computedHeight - minimumHeight)); - setMeasuredDimension(computedWidth, computedHeight); - } - - private void switchToState(int state, float x, float y) { - switch (state) { - case STATE_IDLE: - deactivateTargets(); - hideTargets(true, false); - startBackgroundAnimation(0, 0.0f); - mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); - break; - - case STATE_START: - deactivateHandle(0, 0, 1.0f, null); - startBackgroundAnimation(0, 0.0f); - break; - - case STATE_FIRST_TOUCH: - deactivateTargets(); - showTargets(true); - mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE); - startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f); - setGrabbedState(OnTriggerListener.CENTER_HANDLE); - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - announceTargets(); - } - break; - - case STATE_TRACKING: - break; - - case STATE_SNAP: - break; - - case STATE_FINISH: - doFinish(); - break; - } - } - - private void activateHandle(int duration, int delay, float finalAlpha, - AnimatorListener finishListener) { - mHandleAnimations.cancel(); - mHandleAnimations.add(Tweener.to(mHandleDrawable, duration, - "ease", Ease.Cubic.easeIn, - "delay", delay, - "alpha", finalAlpha, - "onUpdate", mUpdateListener, - "onComplete", finishListener)); - mHandleAnimations.start(); - } - - private void deactivateHandle(int duration, int delay, float finalAlpha, - AnimatorListener finishListener) { - mHandleAnimations.cancel(); - mHandleAnimations.add(Tweener.to(mHandleDrawable, duration, - "ease", Ease.Quart.easeOut, - "delay", delay, - "alpha", finalAlpha, - "x", 0, - "y", 0, - "onUpdate", mUpdateListener, - "onComplete", finishListener)); - mHandleAnimations.start(); - } - - /** - * Animation used to attract user's attention to the target button. - * Assumes mChevronDrawables is an a list with an even number of chevrons filled with - * mFeedbackCount items in the order: left, right, top, bottom. - */ - private void startChevronAnimation() { - final float chevronStartDistance = mHandleDrawable.getWidth() * 0.8f; - final float chevronStopDistance = mOuterRadius * 0.9f / 2.0f; - final float startScale = 0.5f; - final float endScale = 2.0f; - final int directionCount = mFeedbackCount > 0 ? mChevronDrawables.size()/mFeedbackCount : 0; - - mChevronAnimations.stop(); - - // Add an animation for all chevron drawables. There are mFeedbackCount drawables - // in each direction and directionCount directions. - for (int direction = 0; direction < directionCount; direction++) { - double angle = 2.0 * Math.PI * direction / directionCount; - final float sx = (float) Math.cos(angle); - final float sy = 0.0f - (float) Math.sin(angle); - final float[] xrange = new float[] - {sx * chevronStartDistance, sx * chevronStopDistance}; - final float[] yrange = new float[] - {sy * chevronStartDistance, sy * chevronStopDistance}; - for (int count = 0; count < mFeedbackCount; count++) { - int delay = count * CHEVRON_INCREMENTAL_DELAY; - final TargetDrawable icon = mChevronDrawables.get(direction*mFeedbackCount + count); - if (icon == null) { - continue; - } - mChevronAnimations.add(Tweener.to(icon, CHEVRON_ANIMATION_DURATION, - "ease", mChevronAnimationInterpolator, - "delay", delay, - "x", xrange, - "y", yrange, - "alpha", new float[] {1.0f, 0.0f}, - "scaleX", new float[] {startScale, endScale}, - "scaleY", new float[] {startScale, endScale}, - "onUpdate", mUpdateListener)); - } - } - mChevronAnimations.start(); - } - - private void deactivateTargets() { - final int count = mTargetDrawables.size(); - for (int i = 0; i < count; i++) { - TargetDrawable target = mTargetDrawables.get(i); - target.setState(TargetDrawable.STATE_INACTIVE); - } - mActiveTarget = -1; - } - - void invalidateGlobalRegion(TargetDrawable drawable) { - int width = drawable.getWidth(); - int height = drawable.getHeight(); - RectF childBounds = new RectF(0, 0, width, height); - childBounds.offset(drawable.getX() - width/2, drawable.getY() - height/2); - View view = this; - while (view.getParent() != null && view.getParent() instanceof View) { - view = (View) view.getParent(); - view.getMatrix().mapRect(childBounds); - view.invalidate((int) Math.floor(childBounds.left), - (int) Math.floor(childBounds.top), - (int) Math.ceil(childBounds.right), - (int) Math.ceil(childBounds.bottom)); - } - } - - /** - * Dispatches a trigger event to listener. Ignored if a listener is not set. - * @param whichTarget the target that was triggered. - */ - private void dispatchTriggerEvent(int whichTarget) { - vibrate(); - if (mOnTriggerListener != null) { - mOnTriggerListener.onTrigger(this, whichTarget); - } - } - - private void dispatchOnFinishFinalAnimation() { - if (mOnTriggerListener != null) { - mOnTriggerListener.onFinishFinalAnimation(); - } - } - - private void doFinish() { - final int activeTarget = mActiveTarget; - final boolean targetHit = activeTarget != -1; - - if (targetHit) { - if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit); - - highlightSelected(activeTarget); - - // Inform listener of any active targets. Typically only one will be active. - deactivateHandle(RETURN_TO_HOME_DURATION, RETURN_TO_HOME_DELAY, 0.0f, mResetListener); - dispatchTriggerEvent(activeTarget); - if (!mAlwaysTrackFinger) { - // Force ring and targets to finish animation to final expanded state - mTargetAnimations.stop(); - } - } else { - // Animate handle back to the center based on current state. - deactivateHandle(HIDE_ANIMATION_DURATION, HIDE_ANIMATION_DELAY, 1.0f, - mResetListenerWithPing); - hideTargets(true, false); - } - - setGrabbedState(OnTriggerListener.NO_HANDLE); - } - - private void highlightSelected(int activeTarget) { - // Highlight the given target and fade others - mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE); - hideUnselected(activeTarget); - } - - private void hideUnselected(int active) { - for (int i = 0; i < mTargetDrawables.size(); i++) { - if (i != active) { - mTargetDrawables.get(i).setAlpha(0.0f); - } - } - } - - private void hideTargets(boolean animate, boolean expanded) { - mTargetAnimations.cancel(); - // Note: these animations should complete at the same time so that we can swap out - // the target assets asynchronously from the setTargetResources() call. - mAnimatingTargets = animate; - final int duration = animate ? HIDE_ANIMATION_DURATION : 0; - final int delay = animate ? HIDE_ANIMATION_DELAY : 0; - - final float targetScale = expanded ? TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED; - final int length = mTargetDrawables.size(); - for (int i = 0; i < length; i++) { - TargetDrawable target = mTargetDrawables.get(i); - target.setState(TargetDrawable.STATE_INACTIVE); - mTargetAnimations.add(Tweener.to(target, duration, - "ease", Ease.Cubic.easeOut, - "alpha", 0.0f, - "scaleX", targetScale, - "scaleY", targetScale, - "delay", delay, - "onUpdate", mUpdateListener)); - } - - final float ringScaleTarget = expanded ? RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED; - mTargetAnimations.add(Tweener.to(mOuterRing, duration, - "ease", Ease.Cubic.easeOut, - "alpha", 0.0f, - "scaleX", ringScaleTarget, - "scaleY", ringScaleTarget, - "delay", delay, - "onUpdate", mUpdateListener, - "onComplete", mTargetUpdateListener)); - - mTargetAnimations.start(); - } - - private void showTargets(boolean animate) { - mTargetAnimations.stop(); - mAnimatingTargets = animate; - final int delay = animate ? SHOW_ANIMATION_DELAY : 0; - final int duration = animate ? SHOW_ANIMATION_DURATION : 0; - final int length = mTargetDrawables.size(); - for (int i = 0; i < length; i++) { - TargetDrawable target = mTargetDrawables.get(i); - target.setState(TargetDrawable.STATE_INACTIVE); - mTargetAnimations.add(Tweener.to(target, duration, - "ease", Ease.Cubic.easeOut, - "alpha", 1.0f, - "scaleX", 1.0f, - "scaleY", 1.0f, - "delay", delay, - "onUpdate", mUpdateListener)); - } - mTargetAnimations.add(Tweener.to(mOuterRing, duration, - "ease", Ease.Cubic.easeOut, - "alpha", 1.0f, - "scaleX", 1.0f, - "scaleY", 1.0f, - "delay", delay, - "onUpdate", mUpdateListener, - "onComplete", mTargetUpdateListener)); - - mTargetAnimations.start(); - } - - private void vibrate() { - final boolean hapticEnabled = Settings.System.getIntForUser( - mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, - UserHandle.USER_CURRENT) != 0; - if (mVibrator != null && hapticEnabled) { - mVibrator.vibrate(mVibrationDuration); - } - } - - private ArrayList<TargetDrawable> loadDrawableArray(int resourceId) { - Resources res = getContext().getResources(); - TypedArray array = res.obtainTypedArray(resourceId); - final int count = array.length(); - ArrayList<TargetDrawable> drawables = new ArrayList<TargetDrawable>(count); - for (int i = 0; i < count; i++) { - TypedValue value = array.peekValue(i); - TargetDrawable target = new TargetDrawable(res, value != null ? value.resourceId : 0); - drawables.add(target); - } - array.recycle(); - return drawables; - } - - private void internalSetTargetResources(int resourceId) { - mTargetDrawables = loadDrawableArray(resourceId); - mTargetResourceId = resourceId; - final int count = mTargetDrawables.size(); - int maxWidth = mHandleDrawable.getWidth(); - int maxHeight = mHandleDrawable.getHeight(); - for (int i = 0; i < count; i++) { - TargetDrawable target = mTargetDrawables.get(i); - maxWidth = Math.max(maxWidth, target.getWidth()); - maxHeight = Math.max(maxHeight, target.getHeight()); - } - if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) { - mMaxTargetWidth = maxWidth; - mMaxTargetHeight = maxHeight; - requestLayout(); // required to resize layout and call updateTargetPositions() - } else { - updateTargetPositions(mWaveCenterX, mWaveCenterY); - updateChevronPositions(mWaveCenterX, mWaveCenterY); - } - } - - /** - * Loads an array of drawables from the given resourceId. - * - * @param resourceId - */ - public void setTargetResources(int resourceId) { - if (mAnimatingTargets) { - // postpone this change until we return to the initial state - mNewTargetResources = resourceId; - } else { - internalSetTargetResources(resourceId); - } - } - - public int getTargetResourceId() { - return mTargetResourceId; - } - - /** - * Sets the resource id specifying the target descriptions for accessibility. - * - * @param resourceId The resource id. - */ - public void setTargetDescriptionsResourceId(int resourceId) { - mTargetDescriptionsResourceId = resourceId; - if (mTargetDescriptions != null) { - mTargetDescriptions.clear(); - } - } - - /** - * Gets the resource id specifying the target descriptions for accessibility. - * - * @return The resource id. - */ - public int getTargetDescriptionsResourceId() { - return mTargetDescriptionsResourceId; - } - - /** - * Sets the resource id specifying the target direction descriptions for accessibility. - * - * @param resourceId The resource id. - */ - public void setDirectionDescriptionsResourceId(int resourceId) { - mDirectionDescriptionsResourceId = resourceId; - if (mDirectionDescriptions != null) { - mDirectionDescriptions.clear(); - } - } - - /** - * Gets the resource id specifying the target direction descriptions. - * - * @return The resource id. - */ - public int getDirectionDescriptionsResourceId() { - return mDirectionDescriptionsResourceId; - } - - /** - * Enable or disable vibrate on touch. - * - * @param enabled - */ - public void setVibrateEnabled(boolean enabled) { - if (enabled && mVibrator == null) { - mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); - } else { - mVibrator = null; - } - } - - /** - * Starts chevron animation. Example use case: show chevron animation whenever the phone rings - * or the user touches the screen. - * - */ - public void ping() { - startChevronAnimation(); - } - - /** - * Resets the widget to default state and cancels all animation. If animate is 'true', will - * animate objects into place. Otherwise, objects will snap back to place. - * - * @param animate - */ - public void reset(boolean animate) { - mChevronAnimations.stop(); - mHandleAnimations.stop(); - mTargetAnimations.stop(); - startBackgroundAnimation(0, 0.0f); - hideChevrons(); - hideTargets(animate, false); - deactivateHandle(0, 0, 1.0f, null); - Tweener.reset(); - } - - private void startBackgroundAnimation(int duration, float alpha) { - Drawable background = getBackground(); - if (mAlwaysTrackFinger && background != null) { - if (mBackgroundAnimator != null) { - mBackgroundAnimator.animator.end(); - } - mBackgroundAnimator = Tweener.to(background, duration, - "ease", Ease.Cubic.easeIn, - "alpha", new int[] {0, (int)(255.0f * alpha)}, - "delay", SHOW_ANIMATION_DELAY); - mBackgroundAnimator.animator.start(); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - final int action = event.getAction(); - boolean handled = false; - switch (action) { - case MotionEvent.ACTION_DOWN: - if (DEBUG) Log.v(TAG, "*** DOWN ***"); - handleDown(event); - handled = true; - break; - - case MotionEvent.ACTION_MOVE: - if (DEBUG) Log.v(TAG, "*** MOVE ***"); - handleMove(event); - handled = true; - break; - - case MotionEvent.ACTION_UP: - if (DEBUG) Log.v(TAG, "*** UP ***"); - handleMove(event); - handleUp(event); - handled = true; - break; - - case MotionEvent.ACTION_CANCEL: - if (DEBUG) Log.v(TAG, "*** CANCEL ***"); - handleMove(event); - handleCancel(event); - handled = true; - break; - } - invalidate(); - return handled ? true : super.onTouchEvent(event); - } - - private void moveHandleTo(float x, float y, boolean animate) { - mHandleDrawable.setX(x); - mHandleDrawable.setY(y); - } - - private void handleDown(MotionEvent event) { - float eventX = event.getX(); - float eventY = event.getY(); - switchToState(STATE_START, eventX, eventY); - if (!trySwitchToFirstTouchState(eventX, eventY)) { - mDragging = false; - ping(); - } - } - - private void handleUp(MotionEvent event) { - if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE"); - switchToState(STATE_FINISH, event.getX(), event.getY()); - } - - private void handleCancel(MotionEvent event) { - if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL"); - - // We should drop the active target here but it interferes with - // moving off the screen in the direction of the navigation bar. At some point we may - // want to revisit how we handle this. For now we'll allow a canceled event to - // activate the current target. - - // mActiveTarget = -1; // Drop the active target if canceled. - - switchToState(STATE_FINISH, event.getX(), event.getY()); - } - - private void handleMove(MotionEvent event) { - int activeTarget = -1; - final int historySize = event.getHistorySize(); - ArrayList<TargetDrawable> targets = mTargetDrawables; - int ntargets = targets.size(); - float x = 0.0f; - float y = 0.0f; - for (int k = 0; k < historySize + 1; k++) { - float eventX = k < historySize ? event.getHistoricalX(k) : event.getX(); - float eventY = k < historySize ? event.getHistoricalY(k) : event.getY(); - // tx and ty are relative to wave center - float tx = eventX - mWaveCenterX; - float ty = eventY - mWaveCenterY; - float touchRadius = (float) Math.sqrt(dist2(tx, ty)); - final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f; - float limitX = tx * scale; - float limitY = ty * scale; - double angleRad = Math.atan2(-ty, tx); - - if (!mDragging) { - trySwitchToFirstTouchState(eventX, eventY); - } - - if (mDragging) { - // For multiple targets, snap to the one that matches - final float snapRadius = mOuterRadius - mSnapMargin; - final float snapDistance2 = snapRadius * snapRadius; - // Find first target in range - for (int i = 0; i < ntargets; i++) { - TargetDrawable target = targets.get(i); - - double targetMinRad = (i - 0.5) * 2 * Math.PI / ntargets; - double targetMaxRad = (i + 0.5) * 2 * Math.PI / ntargets; - if (target.isEnabled()) { - boolean angleMatches = - (angleRad > targetMinRad && angleRad <= targetMaxRad) || - (angleRad + 2 * Math.PI > targetMinRad && - angleRad + 2 * Math.PI <= targetMaxRad); - if (angleMatches && (dist2(tx, ty) > snapDistance2)) { - activeTarget = i; - } - } - } - } - x = limitX; - y = limitY; - } - - if (!mDragging) { - return; - } - - if (activeTarget != -1) { - switchToState(STATE_SNAP, x,y); - moveHandleTo(x, y, false); - } else { - switchToState(STATE_TRACKING, x, y); - moveHandleTo(x, y, false); - } - - // Draw handle outside parent's bounds - invalidateGlobalRegion(mHandleDrawable); - - if (mActiveTarget != activeTarget) { - // Defocus the old target - if (mActiveTarget != -1) { - TargetDrawable target = targets.get(mActiveTarget); - if (target.hasState(TargetDrawable.STATE_FOCUSED)) { - target.setState(TargetDrawable.STATE_INACTIVE); - } - } - // Focus the new target - if (activeTarget != -1) { - TargetDrawable target = targets.get(activeTarget); - if (target.hasState(TargetDrawable.STATE_FOCUSED)) { - target.setState(TargetDrawable.STATE_FOCUSED); - } - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - String targetContentDescription = getTargetDescription(activeTarget); - announceText(targetContentDescription); - } - activateHandle(0, 0, 0.0f, null); - } else { - activateHandle(0, 0, 1.0f, null); - } - } - mActiveTarget = activeTarget; - } - - @Override - public boolean onHoverEvent(MotionEvent event) { - if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) { - final int action = event.getAction(); - switch (action) { - case MotionEvent.ACTION_HOVER_ENTER: - event.setAction(MotionEvent.ACTION_DOWN); - break; - case MotionEvent.ACTION_HOVER_MOVE: - event.setAction(MotionEvent.ACTION_MOVE); - break; - case MotionEvent.ACTION_HOVER_EXIT: - event.setAction(MotionEvent.ACTION_UP); - break; - } - onTouchEvent(event); - event.setAction(action); - } - return super.onHoverEvent(event); - } - - /** - * Sets the current grabbed state, and dispatches a grabbed state change - * event to our listener. - */ - private void setGrabbedState(int newState) { - if (newState != mGrabbedState) { - if (newState != OnTriggerListener.NO_HANDLE) { - vibrate(); - } - mGrabbedState = newState; - if (mOnTriggerListener != null) { - if (newState == OnTriggerListener.NO_HANDLE) { - mOnTriggerListener.onReleased(this, OnTriggerListener.CENTER_HANDLE); - } else { - mOnTriggerListener.onGrabbed(this, OnTriggerListener.CENTER_HANDLE); - } - mOnTriggerListener.onGrabbedStateChange(this, newState); - } - } - } - - private boolean trySwitchToFirstTouchState(float x, float y) { - final float tx = x - mWaveCenterX; - final float ty = y - mWaveCenterY; - if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledTapRadiusSquared()) { - if (DEBUG) Log.v(TAG, "** Handle HIT"); - switchToState(STATE_FIRST_TOUCH, x, y); - moveHandleTo(tx, ty, false); - mDragging = true; - return true; - } - return false; - } - - private void assignDefaultsIfNeeded() { - if (mOuterRadius == 0.0f) { - mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f; - } - if (mSnapMargin == 0.0f) { - mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics()); - } - } - - private void computeInsets(int dx, int dy) { - final int layoutDirection = getLayoutDirection(); - final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); - - switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { - case Gravity.LEFT: - mHorizontalInset = 0; - break; - case Gravity.RIGHT: - mHorizontalInset = dx; - break; - case Gravity.CENTER_HORIZONTAL: - default: - mHorizontalInset = dx / 2; - break; - } - switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) { - case Gravity.TOP: - mVerticalInset = 0; - break; - case Gravity.BOTTOM: - mVerticalInset = dy; - break; - case Gravity.CENTER_VERTICAL: - default: - mVerticalInset = dy / 2; - break; - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - final int width = right - left; - final int height = bottom - top; - - // Target placement width/height. This puts the targets on the greater of the ring - // width or the specified outer radius. - final float placementWidth = Math.max(mOuterRing.getWidth(), 2 * mOuterRadius); - final float placementHeight = Math.max(mOuterRing.getHeight(), 2 * mOuterRadius); - float newWaveCenterX = mHorizontalInset - + Math.max(width, mMaxTargetWidth + placementWidth) / 2; - float newWaveCenterY = mVerticalInset - + Math.max(height, + mMaxTargetHeight + placementHeight) / 2; - - if (mInitialLayout) { - hideChevrons(); - hideTargets(false, false); - moveHandleTo(0, 0, false); - mInitialLayout = false; - } - - mOuterRing.setPositionX(newWaveCenterX); - mOuterRing.setPositionY(newWaveCenterY); - - mHandleDrawable.setPositionX(newWaveCenterX); - mHandleDrawable.setPositionY(newWaveCenterY); - - updateTargetPositions(newWaveCenterX, newWaveCenterY); - updateChevronPositions(newWaveCenterX, newWaveCenterY); - - mWaveCenterX = newWaveCenterX; - mWaveCenterY = newWaveCenterY; - - if (DEBUG) dump(); - } - - private void updateTargetPositions(float centerX, float centerY) { - // Reposition the target drawables if the view changed. - ArrayList<TargetDrawable> targets = mTargetDrawables; - final int size = targets.size(); - final float alpha = (float) (-2.0f * Math.PI / size); - for (int i = 0; i < size; i++) { - final TargetDrawable targetIcon = targets.get(i); - final float angle = alpha * i; - targetIcon.setPositionX(centerX); - targetIcon.setPositionY(centerY); - targetIcon.setX(mOuterRadius * (float) Math.cos(angle)); - targetIcon.setY(mOuterRadius * (float) Math.sin(angle)); - } - } - - private void updateChevronPositions(float centerX, float centerY) { - ArrayList<TargetDrawable> chevrons = mChevronDrawables; - final int size = chevrons.size(); - for (int i = 0; i < size; i++) { - TargetDrawable target = chevrons.get(i); - if (target != null) { - target.setPositionX(centerX); - target.setPositionY(centerY); - } - } - } - - private void hideChevrons() { - ArrayList<TargetDrawable> chevrons = mChevronDrawables; - final int size = chevrons.size(); - for (int i = 0; i < size; i++) { - TargetDrawable chevron = chevrons.get(i); - if (chevron != null) { - chevron.setAlpha(0.0f); - } - } - } - - @Override - protected void onDraw(Canvas canvas) { - mOuterRing.draw(canvas); - final int ntargets = mTargetDrawables.size(); - for (int i = 0; i < ntargets; i++) { - TargetDrawable target = mTargetDrawables.get(i); - if (target != null) { - target.draw(canvas); - } - } - final int nchevrons = mChevronDrawables.size(); - for (int i = 0; i < nchevrons; i++) { - TargetDrawable chevron = mChevronDrawables.get(i); - if (chevron != null) { - chevron.draw(canvas); - } - } - mHandleDrawable.draw(canvas); - } - - public void setOnTriggerListener(OnTriggerListener listener) { - mOnTriggerListener = listener; - } - - private float square(float d) { - return d * d; - } - - private float dist2(float dx, float dy) { - return dx*dx + dy*dy; - } - - private float getScaledTapRadiusSquared() { - final float scaledTapRadius; - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - scaledTapRadius = TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mTapRadius; - } else { - scaledTapRadius = mTapRadius; - } - return square(scaledTapRadius); - } - - private void announceTargets() { - StringBuilder utterance = new StringBuilder(); - final int targetCount = mTargetDrawables.size(); - for (int i = 0; i < targetCount; i++) { - String targetDescription = getTargetDescription(i); - String directionDescription = getDirectionDescription(i); - if (!TextUtils.isEmpty(targetDescription) - && !TextUtils.isEmpty(directionDescription)) { - String text = String.format(directionDescription, targetDescription); - utterance.append(text); - } - if (utterance.length() > 0) { - announceText(utterance.toString()); - } - } - } - - private void announceText(String text) { - setContentDescription(text); - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - setContentDescription(null); - } - - private String getTargetDescription(int index) { - if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) { - mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId); - if (mTargetDrawables.size() != mTargetDescriptions.size()) { - Log.w(TAG, "The number of target drawables must be" - + " euqal to the number of target descriptions."); - return null; - } - } - return mTargetDescriptions.get(index); - } - - private String getDirectionDescription(int index) { - if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) { - mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId); - if (mTargetDrawables.size() != mDirectionDescriptions.size()) { - Log.w(TAG, "The number of target drawables must be" - + " euqal to the number of direction descriptions."); - return null; - } - } - return mDirectionDescriptions.get(index); - } - - private ArrayList<String> loadDescriptions(int resourceId) { - TypedArray array = getContext().getResources().obtainTypedArray(resourceId); - final int count = array.length(); - ArrayList<String> targetContentDescriptions = new ArrayList<String>(count); - for (int i = 0; i < count; i++) { - String contentDescription = array.getString(i); - targetContentDescriptions.add(contentDescription); - } - array.recycle(); - return targetContentDescriptions; - } - - public int getResourceIdForTarget(int index) { - final TargetDrawable drawable = mTargetDrawables.get(index); - return drawable == null ? 0 : drawable.getResourceId(); - } - - public void setEnableTarget(int resourceId, boolean enabled) { - for (int i = 0; i < mTargetDrawables.size(); i++) { - final TargetDrawable target = mTargetDrawables.get(i); - if (target.getResourceId() == resourceId) { - target.setEnabled(enabled); - break; // should never be more than one match - } - } - } - - /** - * Gets the position of a target in the array that matches the given resource. - * @param resourceId - * @return the index or -1 if not found - */ - public int getTargetPosition(int resourceId) { - for (int i = 0; i < mTargetDrawables.size(); i++) { - final TargetDrawable target = mTargetDrawables.get(i); - if (target.getResourceId() == resourceId) { - return i; // should never be more than one match - } - } - return -1; - } - - private boolean replaceTargetDrawables(Resources res, int existingResourceId, - int newResourceId) { - if (existingResourceId == 0 || newResourceId == 0) { - return false; - } - - boolean result = false; - final ArrayList<TargetDrawable> drawables = mTargetDrawables; - final int size = drawables.size(); - for (int i = 0; i < size; i++) { - final TargetDrawable target = drawables.get(i); - if (target != null && target.getResourceId() == existingResourceId) { - target.setDrawable(res, newResourceId); - result = true; - } - } - - if (result) { - requestLayout(); // in case any given drawable's size changes - } - - return result; - } - - /** - * Searches the given package for a resource to use to replace the Drawable on the - * target with the given resource id - * @param component of the .apk that contains the resource - * @param name of the metadata in the .apk - * @param existingResId the resource id of the target to search for - * @return true if found in the given package and replaced at least one target Drawables - */ - public boolean replaceTargetDrawablesIfPresent(ComponentName component, String name, - int existingResId) { - if (existingResId == 0) return false; - - try { - PackageManager packageManager = mContext.getPackageManager(); - // Look for the search icon specified in the activity meta-data - Bundle metaData = packageManager.getActivityInfo( - component, PackageManager.GET_META_DATA).metaData; - if (metaData != null) { - int iconResId = metaData.getInt(name); - if (iconResId != 0) { - Resources res = packageManager.getResourcesForActivity(component); - return replaceTargetDrawables(res, existingResId, iconResId); - } - } - } catch (NameNotFoundException e) { - Log.w(TAG, "Failed to swap drawable; " - + component.flattenToShortString() + " not found", e); - } catch (Resources.NotFoundException nfe) { - Log.w(TAG, "Failed to swap drawable from " - + component.flattenToShortString(), nfe); - } - return false; - } -} diff --git a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java index 16bec16..5a4c441 100644 --- a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java +++ b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java @@ -18,7 +18,6 @@ package com.android.internal.widget.multiwaveview; import android.content.res.Resources; import android.graphics.Canvas; -import android.graphics.ColorFilter; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.util.Log; diff --git a/core/java/com/android/server/AppWidgetBackupBridge.java b/core/java/com/android/server/AppWidgetBackupBridge.java new file mode 100644 index 0000000..2ea2f79 --- /dev/null +++ b/core/java/com/android/server/AppWidgetBackupBridge.java @@ -0,0 +1,63 @@ +/* + * 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.server; + +import java.util.List; + +/** + * Runtime bridge between the Backup Manager Service and the App Widget Service, + * since those two modules are intentionally decoupled for modularity. + * + * @hide + */ +public class AppWidgetBackupBridge { + private static WidgetBackupProvider sAppWidgetService; + + public static void register(WidgetBackupProvider instance) { + sAppWidgetService = instance; + } + + public static List<String> getWidgetParticipants(int userId) { + return (sAppWidgetService != null) + ? sAppWidgetService.getWidgetParticipants(userId) + : null; + } + + public static byte[] getWidgetState(String packageName, int userId) { + return (sAppWidgetService != null) + ? sAppWidgetService.getWidgetState(packageName, userId) + : null; + } + + public static void restoreStarting(int userId) { + if (sAppWidgetService != null) { + sAppWidgetService.restoreStarting(userId); + } + } + + public static void restoreWidgetState(String packageName, byte[] restoredState, int userId) { + if (sAppWidgetService != null) { + sAppWidgetService.restoreWidgetState(packageName, restoredState, userId); + } + } + + public static void restoreFinished(int userId) { + if (sAppWidgetService != null) { + sAppWidgetService.restoreFinished(userId); + } + } +} diff --git a/core/java/com/android/server/SystemService.java b/core/java/com/android/server/SystemService.java index e374563..bf36bb1 100644 --- a/core/java/com/android/server/SystemService.java +++ b/core/java/com/android/server/SystemService.java @@ -123,6 +123,38 @@ public abstract class SystemService { public void onBootPhase(int phase) {} /** + * Called when a new user is starting, for system services to initialize any per-user + * state they maintain for running users. + * @param userHandle The identifier of the user. + */ + public void onStartUser(int userHandle) {} + + /** + * Called when switching to a different foreground user, for system services that have + * special behavior for whichever user is currently in the foreground. This is called + * before any application processes are aware of the new user. + * @param userHandle The identifier of the user. + */ + public void onSwitchUser(int userHandle) {} + + /** + * Called when an existing user is stopping, for system services to finalize any per-user + * state they maintain for running users. This is called prior to sending the SHUTDOWN + * broadcast to the user; it is a good place to stop making use of any resources of that + * user (such as binding to a service running in the user). + * @param userHandle The identifier of the user. + */ + public void onStopUser(int userHandle) {} + + /** + * Called when an existing user is stopping, for system services to finalize any per-user + * state they maintain for running users. This is called after all application process + * teardown of the user is complete. + * @param userHandle The identifier of the user. + */ + public void onCleanupUser(int userHandle) {} + + /** * Publish the service so it is accessible to other services and apps. */ protected final void publishBinderService(String name, IBinder service) { diff --git a/core/java/com/android/server/SystemServiceManager.java b/core/java/com/android/server/SystemServiceManager.java index eb8df0e..87a50a9 100644 --- a/core/java/com/android/server/SystemServiceManager.java +++ b/core/java/com/android/server/SystemServiceManager.java @@ -131,6 +131,58 @@ public class SystemServiceManager { } } + public void startUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onStartUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting start of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + + public void switchUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onSwitchUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting switch of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + + public void stopUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onStopUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting stop of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + + public void cleanupUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onCleanupUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + /** Sets the safe mode flag for services to query. */ public void setSafeMode(boolean safeMode) { mSafeMode = safeMode; diff --git a/core/java/com/android/server/WidgetBackupProvider.java b/core/java/com/android/server/WidgetBackupProvider.java new file mode 100644 index 0000000..a2efbdd --- /dev/null +++ b/core/java/com/android/server/WidgetBackupProvider.java @@ -0,0 +1,34 @@ +/* + * 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.server; + +import java.util.List; + +/** + * Shim to allow core/backup to communicate with the app widget service + * about various important events without needing to be able to see the + * implementation of the service. + * + * @hide + */ +public interface WidgetBackupProvider { + public List<String> getWidgetParticipants(int userId); + public byte[] getWidgetState(String packageName, int userId); + public void restoreStarting(int userId); + public void restoreWidgetState(String packageName, byte[] restoredState, int userId); + public void restoreFinished(int userId); +} diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java index 5502a17..430dd63 100644 --- a/core/java/com/android/server/net/BaseNetworkObserver.java +++ b/core/java/com/android/server/net/BaseNetworkObserver.java @@ -57,7 +57,7 @@ public class BaseNetworkObserver extends INetworkManagementEventObserver.Stub { } @Override - public void interfaceClassDataActivityChanged(String label, boolean active) { + public void interfaceClassDataActivityChanged(String label, boolean active, long tsNanos) { // default no-op } |