Merge "Add MtpDatabase class."
diff --git a/api/current.txt b/api/current.txt
index d70317e..f75ef0b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1395,6 +1395,7 @@
field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b
field public static final int windowAnimationStyle = 16842926; // 0x10100ae
field public static final int windowBackground = 16842836; // 0x1010054
+ field public static final int windowBackgroundFallback = 16844035; // 0x1010503
field public static final int windowClipToOutline = 16843947; // 0x10104ab
field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
field public static final int windowContentOverlay = 16842841; // 0x1010059
@@ -1420,7 +1421,6 @@
field public static final int windowNoTitle = 16842838; // 0x1010056
field public static final int windowOverscan = 16843727; // 0x10103cf
field public static final int windowReenterTransition = 16843951; // 0x10104af
- field public static final int windowResizingBackground = 16844035; // 0x1010503
field public static final int windowReturnTransition = 16843950; // 0x10104ae
field public static final int windowSharedElementEnterTransition = 16843833; // 0x1010439
field public static final int windowSharedElementExitTransition = 16843834; // 0x101043a
@@ -25035,6 +25035,7 @@
field public static final int OUTGOING_TYPE = 2; // 0x2
field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+ field public static final java.lang.String POST_DIAL_DIGITS = "post_dial_digits";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
field public static final int PRESENTATION_PAYPHONE = 4; // 0x4
field public static final int PRESENTATION_RESTRICTED = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index f44065e..c4c6f1c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1491,6 +1491,7 @@
field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b
field public static final int windowAnimationStyle = 16842926; // 0x10100ae
field public static final int windowBackground = 16842836; // 0x1010054
+ field public static final int windowBackgroundFallback = 16844035; // 0x1010503
field public static final int windowClipToOutline = 16843947; // 0x10104ab
field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
field public static final int windowContentOverlay = 16842841; // 0x1010059
@@ -1516,7 +1517,6 @@
field public static final int windowNoTitle = 16842838; // 0x1010056
field public static final int windowOverscan = 16843727; // 0x10103cf
field public static final int windowReenterTransition = 16843951; // 0x10104af
- field public static final int windowResizingBackground = 16844035; // 0x1010503
field public static final int windowReturnTransition = 16843950; // 0x10104ae
field public static final int windowSharedElementEnterTransition = 16843833; // 0x1010439
field public static final int windowSharedElementExitTransition = 16843834; // 0x101043a
@@ -26993,6 +26993,7 @@
field public static final int OUTGOING_TYPE = 2; // 0x2
field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+ field public static final java.lang.String POST_DIAL_DIGITS = "post_dial_digits";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
field public static final int PRESENTATION_PAYPHONE = 4; // 0x4
field public static final int PRESENTATION_RESTRICTED = 2; // 0x2
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index dc2aa3a..d1000ad 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -1410,9 +1410,11 @@
}
private void removeMessages() {
- mHandler.removeMessages(MSG_REPEAT);
- mHandler.removeMessages(MSG_LONGPRESS);
- mHandler.removeMessages(MSG_SHOW_PREVIEW);
+ if (mHandler != null) {
+ mHandler.removeMessages(MSG_REPEAT);
+ mHandler.removeMessages(MSG_LONGPRESS);
+ mHandler.removeMessages(MSG_SHOW_PREVIEW);
+ }
}
@Override
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 1273772b..5852f5f 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.IntegerRes;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -42,6 +43,8 @@
import java.util.Map;
import java.util.Set;
+import dalvik.system.VMRuntime;
+
/**
* Container for a message (data and object references) that can
* be sent through an IBinder. A Parcel can contain both flattened data
@@ -193,6 +196,7 @@
* indicating that we're responsible for its lifecycle.
*/
private boolean mOwnsNativeParcelObject;
+ private long mNativeSize;
private RuntimeException mStack;
@@ -244,7 +248,7 @@
private static native int nativeDataAvail(long nativePtr);
private static native int nativeDataPosition(long nativePtr);
private static native int nativeDataCapacity(long nativePtr);
- private static native void nativeSetDataSize(long nativePtr, int size);
+ private static native long nativeSetDataSize(long nativePtr, int size);
private static native void nativeSetDataPosition(long nativePtr, int pos);
private static native void nativeSetDataCapacity(long nativePtr, int size);
@@ -259,7 +263,7 @@
private static native void nativeWriteDouble(long nativePtr, double val);
private static native void nativeWriteString(long nativePtr, String val);
private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
- private static native void nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
+ private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
private static native byte[] nativeCreateByteArray(long nativePtr);
private static native byte[] nativeReadBlob(long nativePtr);
@@ -272,13 +276,13 @@
private static native FileDescriptor nativeReadFileDescriptor(long nativePtr);
private static native long nativeCreate();
- private static native void nativeFreeBuffer(long nativePtr);
+ private static native long nativeFreeBuffer(long nativePtr);
private static native void nativeDestroy(long nativePtr);
private static native byte[] nativeMarshall(long nativePtr);
- private static native void nativeUnmarshall(
+ private static native long nativeUnmarshall(
long nativePtr, byte[] data, int offset, int length);
- private static native void nativeAppendFrom(
+ private static native long nativeAppendFrom(
long thisNativePtr, long otherNativePtr, int offset, int length);
private static native boolean nativeHasFileDescriptors(long nativePtr);
private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
@@ -390,7 +394,7 @@
* @param size The new number of bytes in the Parcel.
*/
public final void setDataSize(int size) {
- nativeSetDataSize(mNativePtr, size);
+ updateNativeSize(nativeSetDataSize(mNativePtr, size));
}
/**
@@ -442,11 +446,11 @@
* Set the bytes in data to be the raw bytes of this Parcel.
*/
public final void unmarshall(byte[] data, int offset, int length) {
- nativeUnmarshall(mNativePtr, data, offset, length);
+ updateNativeSize(nativeUnmarshall(mNativePtr, data, offset, length));
}
public final void appendFrom(Parcel parcel, int offset, int length) {
- nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length);
+ updateNativeSize(nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length));
}
/**
@@ -599,7 +603,24 @@
* if {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE} is set.</p>
*/
public final void writeFileDescriptor(FileDescriptor val) {
- nativeWriteFileDescriptor(mNativePtr, val);
+ updateNativeSize(nativeWriteFileDescriptor(mNativePtr, val));
+ }
+
+ private void updateNativeSize(long newNativeSize) {
+ if (mOwnsNativeParcelObject) {
+ if (newNativeSize > Integer.MAX_VALUE) {
+ newNativeSize = Integer.MAX_VALUE;
+ }
+ if (newNativeSize != mNativeSize) {
+ int delta = (int) (newNativeSize - mNativeSize);
+ if (delta > 0) {
+ VMRuntime.getRuntime().registerNativeAllocation(delta);
+ } else {
+ VMRuntime.getRuntime().registerNativeFree(-delta);
+ }
+ mNativeSize = newNativeSize;
+ }
+ }
}
/**
@@ -2545,7 +2566,7 @@
private void freeBuffer() {
if (mOwnsNativeParcelObject) {
- nativeFreeBuffer(mNativePtr);
+ updateNativeSize(nativeFreeBuffer(mNativePtr));
}
}
@@ -2553,6 +2574,7 @@
if (mNativePtr != 0) {
if (mOwnsNativeParcelObject) {
nativeDestroy(mNativePtr);
+ updateNativeSize(0);
}
mNativePtr = 0;
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 342f8c7..4b63c36 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -38,7 +38,6 @@
import android.telecom.TelecomManager;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
-import android.util.Log;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.PhoneConstants;
@@ -406,6 +405,14 @@
public static final String SUB_ID = "sub_id";
/**
+ * The post-dial portion of a dialed number, including any digits dialed after a
+ * {@link TelecomManager#DTMF_CHARACTER_PAUSE} or a {@link
+ * TelecomManager#DTMF_CHARACTER_WAIT} and these characters themselves.
+ * <P>Type: TEXT</P>
+ */
+ public static final String POST_DIAL_DIGITS = "post_dial_digits";
+
+ /**
* If a successful call is made that is longer than this duration, update the phone number
* in the ContactsProvider with the normalized version of the number, based on the user's
* current country code.
@@ -436,7 +443,7 @@
public static Uri addCall(CallerInfo ci, Context context, String number,
int presentation, int callType, int features, PhoneAccountHandle accountHandle,
long start, int duration, Long dataUsage) {
- return addCall(ci, context, number, presentation, callType, features, accountHandle,
+ return addCall(ci, context, number, "", presentation, callType, features, accountHandle,
start, duration, dataUsage, false, false);
}
@@ -466,10 +473,11 @@
* {@hide}
*/
public static Uri addCall(CallerInfo ci, Context context, String number,
- int presentation, int callType, int features, PhoneAccountHandle accountHandle,
- long start, int duration, Long dataUsage, boolean addForAllUsers) {
- return addCall(ci, context, number, presentation, callType, features, accountHandle,
- start, duration, dataUsage, addForAllUsers, false);
+ String postDialDigits, int presentation, int callType, int features,
+ PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
+ boolean addForAllUsers) {
+ return addCall(ci, context, number, postDialDigits, presentation, callType, features,
+ accountHandle, start, duration, dataUsage, addForAllUsers, false);
}
/**
@@ -479,6 +487,8 @@
* if the contact is unknown.
* @param context the context used to get the ContentResolver
* @param number the phone number to be added to the calls db
+ * @param postDialDigits the post-dial digits that were dialed after the number,
+ * if it was outgoing. Otherwise it is ''.
* @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which
* is set by the network and denotes the number presenting rules for
* "allowed", "payphone", "restricted" or "unknown"
@@ -499,8 +509,9 @@
* {@hide}
*/
public static Uri addCall(CallerInfo ci, Context context, String number,
- int presentation, int callType, int features, PhoneAccountHandle accountHandle,
- long start, int duration, Long dataUsage, boolean addForAllUsers, boolean is_read) {
+ String postDialDigits, int presentation, int callType, int features,
+ PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
+ boolean addForAllUsers, boolean is_read) {
final ContentResolver resolver = context.getContentResolver();
int numberPresentation = PRESENTATION_ALLOWED;
@@ -551,6 +562,7 @@
ContentValues values = new ContentValues(6);
values.put(NUMBER, number);
+ values.put(POST_DIAL_DIGITS, postDialDigits);
values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));
values.put(TYPE, Integer.valueOf(callType));
values.put(FEATURES, features);
diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java
index 379651e..0d5c135 100644
--- a/core/java/android/util/LocaleList.java
+++ b/core/java/android/util/LocaleList.java
@@ -37,6 +37,11 @@
*/
public final class LocaleList {
private final Locale[] mList;
+ // This is a comma-separated list of the locales in the LocaleList created at construction time,
+ // basically the result of running each locale's toLanguageTag() method and concatenating them
+ // with commas in between.
+ private final String mStringRepresentation;
+
private static final Locale[] sEmptyList = new Locale[0];
private static final LocaleList sEmptyLocaleList = new LocaleList();
@@ -95,15 +100,9 @@
return sb.toString();
}
+ @NonNull
public String toLanguageTags() {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mList.length; ++i) {
- sb.append(mList[i].toLanguageTag());
- if (i < mList.length - 1) {
- sb.append(',');
- }
- }
- return sb.toString();
+ return mStringRepresentation;
}
/**
@@ -112,6 +111,7 @@
*/
public LocaleList() {
mList = sEmptyList;
+ mStringRepresentation = "";
}
/**
@@ -121,9 +121,11 @@
public LocaleList(@Nullable Locale locale) {
if (locale == null) {
mList = sEmptyList;
+ mStringRepresentation = "";
} else {
mList = new Locale[1];
mList[0] = (Locale) locale.clone();
+ mStringRepresentation = locale.toLanguageTag();
}
}
@@ -134,9 +136,11 @@
public LocaleList(@Nullable Locale[] list) {
if (list == null || list.length == 0) {
mList = sEmptyList;
+ mStringRepresentation = "";
} else {
final Locale[] localeList = new Locale[list.length];
final HashSet<Locale> seenLocales = new HashSet<Locale>();
+ final StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.length; ++i) {
final Locale l = list[i];
if (l == null) {
@@ -144,11 +148,17 @@
} else if (seenLocales.contains(l)) {
throw new IllegalArgumentException();
} else {
- seenLocales.add(l);
- localeList[i] = (Locale) l.clone();
+ final Locale localeClone = (Locale) l.clone();
+ localeList[i] = localeClone;
+ sb.append(localeClone.toLanguageTag());
+ if (i < list.length - 1) {
+ sb.append(',');
+ }
+ seenLocales.add(localeClone);
}
}
mList = localeList;
+ mStringRepresentation = sb.toString();
}
}
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 986c0f8..8e5af79 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -29,6 +29,8 @@
import java.util.Locale;
+import libcore.icu.LocaleData;
+
/**
* A widget for selecting the time of day, in either 24-hour or AM/PM mode.
* <p>
@@ -303,6 +305,16 @@
void onValidationChanged(boolean valid);
}
+ static String[] getAmPmStrings(Context context) {
+ final Locale locale = context.getResources().getConfiguration().locale;
+ final LocaleData d = LocaleData.get(locale);
+
+ final String[] result = new String[2];
+ result[0] = d.amPm[0].length() > 4 ? d.narrowAm : d.amPm[0];
+ result[1] = d.amPm[1].length() > 4 ? d.narrowPm : d.amPm[1];
+ return result;
+ }
+
/**
* An abstract class which can be used as a start for TimePicker implementations
*/
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 6d41c1d..4dc5fd3e 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -29,21 +29,22 @@
import android.text.format.DateUtils;
import android.text.style.TtsSpan;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.StateSet;
import android.view.HapticFeedbackConstants;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.internal.R;
+import com.android.internal.widget.NumericTextView;
+import com.android.internal.widget.NumericTextView.OnValueChangedListener;
-import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;
@@ -52,7 +53,12 @@
*/
class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate implements
RadialTimePickerView.OnValueSelectedListener {
- private static final String TAG = "TimePickerClockDelegate";
+ /**
+ * Delay in milliseconds before valid but potentially incomplete, for
+ * example "1" but not "12", keyboard edits are propagated from the
+ * hour / minute fields to the radial picker.
+ */
+ private static final long DELAY_COMMIT_MILLIS = 2000;
// Index used by RadialPickerLayout
private static final int HOUR_INDEX = 0;
@@ -61,9 +67,6 @@
// NOT a real index for the purpose of what's showing.
private static final int AMPM_INDEX = 2;
- // Also NOT a real index, just used for keyboard mode.
- private static final int ENABLE_PICKER_INDEX = 3;
-
private static final int[] ATTRS_TEXT_COLOR = new int[] {R.attr.textColor};
private static final int[] ATTRS_DISABLED_ALPHA = new int[] {R.attr.disabledAlpha};
@@ -74,18 +77,14 @@
private static final int HOURS_IN_HALF_DAY = 12;
- private final View mHeaderView;
- private final TextView mHourView;
- private final TextView mMinuteView;
+ private final NumericTextView mHourView;
+ private final NumericTextView mMinuteView;
private final View mAmPmLayout;
- private final CheckedTextView mAmLabel;
- private final CheckedTextView mPmLabel;
+ private final RadioButton mAmLabel;
+ private final RadioButton mPmLabel;
private final RadialTimePickerView mRadialTimePickerView;
private final TextView mSeparatorView;
- private final String mAmText;
- private final String mPmText;
-
private boolean mIsEnabled = true;
private boolean mAllowAutoAdvance;
private int mInitialHourOfDay;
@@ -93,20 +92,14 @@
private boolean mIs24HourView;
private boolean mIsAmPmAtStart;
- // For hardware IME input.
- private char mPlaceholderText;
- private String mDoublePlaceholderText;
- private String mDeletedKeyFormat;
- private boolean mInKbMode;
- private ArrayList<Integer> mTypedTimes = new ArrayList<Integer>();
- private Node mLegalTimesTree;
- private int mAmKeyCode;
- private int mPmKeyCode;
-
// Accessibility strings.
private String mSelectHours;
private String mSelectMinutes;
+ // Localization data.
+ private boolean mHourFormatShowLeadingZero;
+ private boolean mHourFormatStartsAtZero;
+
// Most recent time announcement values for accessibility.
private CharSequence mLastAnnouncedText;
private boolean mLastAnnouncedIsHour;
@@ -127,43 +120,42 @@
mSelectHours = res.getString(R.string.select_hours);
mSelectMinutes = res.getString(R.string.select_minutes);
- String[] amPmStrings = TimePickerSpinnerDelegate.getAmPmStrings(context);
- mAmText = amPmStrings[0];
- mPmText = amPmStrings[1];
-
final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout,
R.layout.time_picker_material);
final View mainView = inflater.inflate(layoutResourceId, delegator);
-
- mHeaderView = mainView.findViewById(R.id.time_header);
+ final View headerView = mainView.findViewById(R.id.time_header);
+ headerView.setOnTouchListener(new NearestTouchDelegate());
// Set up hour/minute labels.
- mHourView = (TextView) mainView.findViewById(R.id.hours);
+ mHourView = (NumericTextView) mainView.findViewById(R.id.hours);
mHourView.setOnClickListener(mClickListener);
+ mHourView.setOnFocusChangeListener(mFocusListener);
+ mHourView.setOnDigitEnteredListener(mDigitEnteredListener);
mHourView.setAccessibilityDelegate(
new ClickActionDelegate(context, R.string.select_hours));
mSeparatorView = (TextView) mainView.findViewById(R.id.separator);
- mMinuteView = (TextView) mainView.findViewById(R.id.minutes);
+ mMinuteView = (NumericTextView) mainView.findViewById(R.id.minutes);
mMinuteView.setOnClickListener(mClickListener);
+ mMinuteView.setOnFocusChangeListener(mFocusListener);
+ mMinuteView.setOnDigitEnteredListener(mDigitEnteredListener);
mMinuteView.setAccessibilityDelegate(
new ClickActionDelegate(context, R.string.select_minutes));
-
- // Now that we have text appearances out of the way, make sure the hour
- // and minute views are correctly sized.
- mHourView.setMinWidth(computeStableWidth(mHourView, 24));
- mMinuteView.setMinWidth(computeStableWidth(mMinuteView, 60));
-
- final SpannableStringBuilder amLabel = new SpannableStringBuilder()
- .append(amPmStrings[0], new TtsSpan.VerbatimBuilder(amPmStrings[0]).build(), 0);
+ mMinuteView.setRange(0, 59);
// Set up AM/PM labels.
mAmPmLayout = mainView.findViewById(R.id.ampm_layout);
- mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label);
+ mAmPmLayout.setOnTouchListener(new NearestTouchDelegate());
+
+ final String[] amPmStrings = TimePicker.getAmPmStrings(context);
+ mAmLabel = (RadioButton) mAmPmLayout.findViewById(R.id.am_label);
mAmLabel.setText(obtainVerbatim(amPmStrings[0]));
mAmLabel.setOnClickListener(mClickListener);
- mPmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.pm_label);
+ ensureMinimumTextWidth(mAmLabel);
+
+ mPmLabel = (RadioButton) mAmPmLayout.findViewById(R.id.pm_label);
mPmLabel.setText(obtainVerbatim(amPmStrings[1]));
mPmLabel.setOnClickListener(mClickListener);
+ ensureMinimumTextWidth(mPmLabel);
// For the sake of backwards compatibility, attempt to extract the text
// color from the header time text appearance. If it's set, we'll let
@@ -195,7 +187,7 @@
// Set up header background, if available.
if (a.hasValueOrEmpty(R.styleable.TimePicker_headerBackground)) {
- mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
+ headerView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
}
a.recycle();
@@ -207,18 +199,66 @@
mAllowAutoAdvance = true;
- // Set up for keyboard mode.
- mDoublePlaceholderText = res.getString(R.string.time_placeholder);
- mDeletedKeyFormat = res.getString(R.string.deleted_key);
- mPlaceholderText = mDoublePlaceholderText.charAt(0);
- mAmKeyCode = mPmKeyCode = -1;
- generateLegalTimesTree();
+ // Updates mHourFormat variables used below.
+ updateHourFormat(mCurrentLocale, mIs24HourView);
- // Initialize with current time
- final Calendar calendar = Calendar.getInstance(mCurrentLocale);
- final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
- final int currentMinute = calendar.get(Calendar.MINUTE);
- initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX);
+ // Update hour text field.
+ final int minHour = mHourFormatStartsAtZero ? 0 : 1;
+ final int maxHour = (mIs24HourView ? 23 : 11) + minHour;
+ mHourView.setRange(minHour, maxHour);
+ mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
+
+ // Initialize with current time.
+ mTempCalendar = Calendar.getInstance(mCurrentLocale);
+ final int currentHour = mTempCalendar.get(Calendar.HOUR_OF_DAY);
+ final int currentMinute = mTempCalendar.get(Calendar.MINUTE);
+ initialize(currentHour, currentMinute, mIs24HourView, HOUR_INDEX);
+ }
+
+ /**
+ * Ensures that a TextView is wide enough to contain its text without
+ * wrapping or clipping. Measures the specified view and sets the minimum
+ * width to the view's desired width.
+ *
+ * @param v the text view to measure
+ */
+ private static void ensureMinimumTextWidth(TextView v) {
+ v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ // Set both the TextView and the View version of minimum
+ // width because they are subtly different.
+ final int minWidth = v.getMeasuredWidth();
+ v.setMinWidth(minWidth);
+ v.setMinimumWidth(minWidth);
+ }
+
+ /**
+ * Determines how the hour should be formatted and updates member variables
+ * related to hour formatting.
+ *
+ * @param locale the locale in which the view is displayed
+ * @param is24Hour whether the view is in 24-hour (hour-of-day) mode
+ */
+ private void updateHourFormat(Locale locale, boolean is24Hour) {
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(
+ locale, is24Hour ? "Hm" : "hm");
+ final int lengthPattern = bestDateTimePattern.length();
+ boolean showLeadingZero = false;
+ char hourFormat = '\0';
+
+ for (int i = 0; i < lengthPattern; i++) {
+ final char c = bestDateTimePattern.charAt(i);
+ if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
+ hourFormat = c;
+ if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
+ showLeadingZero = true;
+ }
+ break;
+ }
+ }
+
+ mHourFormatShowLeadingZero = showLeadingZero;
+ mHourFormatStartsAtZero = hourFormat == 'K' || hourFormat == 'H';
}
private static final CharSequence obtainVerbatim(String text) {
@@ -290,51 +330,24 @@
}
}
- private int computeStableWidth(TextView v, int maxNumber) {
- int maxWidth = 0;
-
- for (int i = 0; i < maxNumber; i++) {
- final String text = String.format("%02d", i);
- v.setText(text);
- v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-
- final int width = v.getMeasuredWidth();
- if (width > maxWidth) {
- maxWidth = width;
- }
- }
-
- return maxWidth;
- }
-
private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) {
mInitialHourOfDay = hourOfDay;
mInitialMinute = minute;
mIs24HourView = is24HourView;
- mInKbMode = false;
updateUI(index);
}
private void setupListeners() {
- mHeaderView.setOnKeyListener(mKeyListener);
- mHeaderView.setOnFocusChangeListener(mFocusListener);
- mHeaderView.setFocusable(true);
-
mRadialTimePickerView.setOnValueSelectedListener(this);
}
private void updateUI(int index) {
- // Update RadialPicker values
- updateRadialPicker(index);
- // Enable or disable the AM/PM view.
updateHeaderAmPm();
- // Update Hour and Minutes
updateHeaderHour(mInitialHourOfDay, false);
- // Update time separator
updateHeaderSeparator();
- // Update Minutes
updateHeaderMinute(mInitialMinute, false);
- // Invalidate everything
+ updateRadialPicker(index);
+
mDelegator.invalidate();
}
@@ -447,14 +460,11 @@
if (is24HourView == mIs24HourView) {
return;
}
+
mIs24HourView = is24HourView;
- generateLegalTimesTree();
- int hour = mRadialTimePickerView.getCurrentHour();
- mInitialHourOfDay = hour;
- updateHeaderHour(hour, false);
- updateHeaderAmPm();
- updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing());
- mDelegator.invalidate();
+ mInitialHourOfDay = getCurrentHour();
+
+ updateUI(mRadialTimePickerView.getCurrentItemShowing());
}
/**
@@ -499,20 +509,14 @@
@Override
public Parcelable onSaveInstanceState(Parcelable superState) {
return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
- is24HourView(), inKbMode(), getTypedTimes(), getCurrentItemShowing());
+ is24HourView(), getCurrentItemShowing());
}
@Override
public void onRestoreInstanceState(Parcelable state) {
- SavedState ss = (SavedState) state;
- setInKbMode(ss.inKbMode());
- setTypedTimes(ss.getTypesTimes());
+ final SavedState ss = (SavedState) state;
initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing());
mRadialTimePickerView.invalidate();
- if (mInKbMode) {
- tryStartingKbMode(-1);
- mHourView.invalidate();
- }
}
@Override
@@ -543,33 +547,6 @@
}
/**
- * Set whether in keyboard mode or not.
- *
- * @param inKbMode True means in keyboard mode.
- */
- private void setInKbMode(boolean inKbMode) {
- mInKbMode = inKbMode;
- }
-
- /**
- * @return true if in keyboard mode
- */
- private boolean inKbMode() {
- return mInKbMode;
- }
-
- private void setTypedTimes(ArrayList<Integer> typeTimes) {
- mTypedTimes = typeTimes;
- }
-
- /**
- * @return an array of typed times
- */
- private ArrayList<Integer> getTypedTimes() {
- return mTypedTimes;
- }
-
- /**
* @return the index of the current item showing
*/
private int getCurrentItemShowing() {
@@ -595,19 +572,14 @@
private final int mHour;
private final int mMinute;
private final boolean mIs24HourMode;
- private final boolean mInKbMode;
- private final ArrayList<Integer> mTypedTimes;
private final int mCurrentItemShowing;
private SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode,
- boolean isKbMode, ArrayList<Integer> typedTimes,
- int currentItemShowing) {
+ int currentItemShowing) {
super(superState);
mHour = hour;
mMinute = minute;
mIs24HourMode = is24HourMode;
- mInKbMode = isKbMode;
- mTypedTimes = typedTimes;
mCurrentItemShowing = currentItemShowing;
}
@@ -616,8 +588,6 @@
mHour = in.readInt();
mMinute = in.readInt();
mIs24HourMode = (in.readInt() == 1);
- mInKbMode = (in.readInt() == 1);
- mTypedTimes = in.readArrayList(getClass().getClassLoader());
mCurrentItemShowing = in.readInt();
}
@@ -633,14 +603,6 @@
return mIs24HourMode;
}
- public boolean inKbMode() {
- return mInKbMode;
- }
-
- public ArrayList<Integer> getTypesTimes() {
- return mTypedTimes;
- }
-
public int getCurrentItemShowing() {
return mCurrentItemShowing;
}
@@ -651,13 +613,11 @@
dest.writeInt(mHour);
dest.writeInt(mMinute);
dest.writeInt(mIs24HourMode ? 1 : 0);
- dest.writeInt(mInKbMode ? 1 : 0);
- dest.writeList(mTypedTimes);
dest.writeInt(mCurrentItemShowing);
}
@SuppressWarnings({"unused", "hiding"})
- public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
+ public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@@ -703,12 +663,6 @@
case AMPM_INDEX:
updateAmPmLabelStates(newValue);
break;
- case ENABLE_PICKER_INDEX:
- if (!isTypedTimeFullyLegal()) {
- mTypedTimes.clear();
- }
- finishKbMode();
- break;
}
if (mOnTimeChangedListener != null) {
@@ -716,61 +670,41 @@
}
}
- private void updateHeaderHour(int value, boolean announce) {
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
- (mIs24HourView) ? "Hm" : "hm");
- final int lengthPattern = bestDateTimePattern.length();
- boolean hourWithTwoDigit = false;
- char hourFormat = '\0';
- // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
- // the hour format that we found.
- for (int i = 0; i < lengthPattern; i++) {
- final char c = bestDateTimePattern.charAt(i);
- if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
- hourFormat = c;
- if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
- hourWithTwoDigit = true;
- }
- break;
- }
+ /**
+ * Converts hour-of-day (0-23) time into a localized hour number.
+ *
+ * @param hourOfDay the hour-of-day (0-23)
+ * @return a localized hour number
+ */
+ private int getLocalizedHour(int hourOfDay) {
+ if (!mIs24HourView) {
+ // Convert to hour-of-am-pm.
+ hourOfDay %= 12;
}
- final String format;
- if (hourWithTwoDigit) {
- format = "%02d";
- } else {
- format = "%d";
+
+ if (!mHourFormatStartsAtZero && hourOfDay == 0) {
+ // Convert to clock-hour (either of-day or of-am-pm).
+ hourOfDay = mIs24HourView ? 24 : 12;
}
- if (mIs24HourView) {
- // 'k' means 1-24 hour
- if (hourFormat == 'k' && value == 0) {
- value = 24;
- }
- } else {
- // 'K' means 0-11 hour
- value = modulo12(value, hourFormat == 'K');
- }
- CharSequence text = String.format(format, value);
- mHourView.setText(text);
+
+ return hourOfDay;
+ }
+
+ private void updateHeaderHour(int hourOfDay, boolean announce) {
+ final int localizedHour = getLocalizedHour(hourOfDay);
+ mHourView.setValue(localizedHour);
+
if (announce) {
- tryAnnounceForAccessibility(text, true);
+ tryAnnounceForAccessibility(mHourView.getText(), true);
}
}
- private void tryAnnounceForAccessibility(CharSequence text, boolean isHour) {
- if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) {
- // TODO: Find a better solution, potentially live regions?
- mDelegator.announceForAccessibility(text);
- mLastAnnouncedText = text;
- mLastAnnouncedIsHour = isHour;
- }
- }
+ private void updateHeaderMinute(int minuteOfHour, boolean announce) {
+ mMinuteView.setValue(minuteOfHour);
- private static int modulo12(int n, boolean startWithZero) {
- int value = n % 12;
- if (value == 0 && !startWithZero) {
- value = 12;
+ if (announce) {
+ tryAnnounceForAccessibility(mMinuteView.getText(), false);
}
- return value;
}
/**
@@ -812,14 +746,12 @@
return -1;
}
- private void updateHeaderMinute(int value, boolean announceForAccessibility) {
- if (value == 60) {
- value = 0;
- }
- final CharSequence text = String.format(mCurrentLocale, "%02d", value);
- mMinuteView.setText(text);
- if (announceForAccessibility) {
- tryAnnounceForAccessibility(text, false);
+ private void tryAnnounceForAccessibility(CharSequence text, boolean isHour) {
+ if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) {
+ // TODO: Find a better solution, potentially live regions?
+ mDelegator.announceForAccessibility(text);
+ mLastAnnouncedText = text;
+ mLastAnnouncedIsHour = isHour;
}
}
@@ -848,477 +780,82 @@
mRadialTimePickerView.setAmOrPm(amOrPm);
}
- /**
- * For keyboard mode, processes key events.
- *
- * @param keyCode the pressed key.
- *
- * @return true if the key was successfully processed, false otherwise.
- */
- private boolean processKeyUp(int keyCode) {
- if (keyCode == KeyEvent.KEYCODE_DEL) {
- if (mInKbMode) {
- if (!mTypedTimes.isEmpty()) {
- int deleted = deleteLastTypedKey();
- String deletedKeyStr;
- if (deleted == getAmOrPmKeyCode(AM)) {
- deletedKeyStr = mAmText;
- } else if (deleted == getAmOrPmKeyCode(PM)) {
- deletedKeyStr = mPmText;
- } else {
- deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
+ private final OnValueChangedListener mDigitEnteredListener = new OnValueChangedListener() {
+ @Override
+ public void onValueChanged(NumericTextView view, int value,
+ boolean isValid, boolean isFinished) {
+ final Runnable commitCallback;
+ final View nextFocusTarget;
+ if (view == mHourView) {
+ commitCallback = mCommitHour;
+ nextFocusTarget = view.isFocused() ? mMinuteView : null;
+ } else if (view == mMinuteView) {
+ commitCallback = mCommitMinute;
+ nextFocusTarget = null;
+ } else {
+ return;
+ }
+
+ view.removeCallbacks(commitCallback);
+
+ if (isValid) {
+ if (isFinished) {
+ // Done with hours entry, make visual updates
+ // immediately and move to next focus if needed.
+ commitCallback.run();
+
+ if (nextFocusTarget != null) {
+ nextFocusTarget.requestFocus();
}
- mDelegator.announceForAccessibility(
- String.format(mDeletedKeyFormat, deletedKeyStr));
- updateDisplay(true);
- }
- }
- } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
- || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
- || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
- || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
- || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
- || (!mIs24HourView &&
- (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
- if (!mInKbMode) {
- if (mRadialTimePickerView == null) {
- // Something's wrong, because time picker should definitely not be null.
- Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null.");
- return true;
- }
- mTypedTimes.clear();
- tryStartingKbMode(keyCode);
- return true;
- }
- // We're already in keyboard mode.
- if (addKeyIfLegal(keyCode)) {
- updateDisplay(false);
- }
- return true;
- }
- return false;
- }
-
- /**
- * Try to start keyboard mode with the specified key.
- *
- * @param keyCode The key to use as the first press. Keyboard mode will not be started if the
- * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
- * key.
- */
- private void tryStartingKbMode(int keyCode) {
- if (keyCode == -1 || addKeyIfLegal(keyCode)) {
- mInKbMode = true;
- onValidationChanged(false);
- updateDisplay(false);
- mRadialTimePickerView.setInputEnabled(false);
- }
- }
-
- private boolean addKeyIfLegal(int keyCode) {
- // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
- // we'll need to see if AM/PM have been typed.
- if ((mIs24HourView && mTypedTimes.size() == 4) ||
- (!mIs24HourView && isTypedTimeFullyLegal())) {
- return false;
- }
-
- mTypedTimes.add(keyCode);
- if (!isTypedTimeLegalSoFar()) {
- deleteLastTypedKey();
- return false;
- }
-
- int val = getValFromKeyCode(keyCode);
- mDelegator.announceForAccessibility(String.format("%d", val));
- // Automatically fill in 0's if AM or PM was legally entered.
- if (isTypedTimeFullyLegal()) {
- if (!mIs24HourView && mTypedTimes.size() <= 3) {
- mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
- mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
- }
- onValidationChanged(true);
- }
-
- return true;
- }
-
- /**
- * Traverse the tree to see if the keys that have been typed so far are legal as is,
- * or may become legal as more keys are typed (excluding backspace).
- */
- private boolean isTypedTimeLegalSoFar() {
- Node node = mLegalTimesTree;
- for (int keyCode : mTypedTimes) {
- node = node.canReach(keyCode);
- if (node == null) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Check if the time that has been typed so far is completely legal, as is.
- */
- private boolean isTypedTimeFullyLegal() {
- if (mIs24HourView) {
- // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note:
- // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
- int[] values = getEnteredTime(null);
- return (values[0] >= 0 && values[1] >= 0 && values[1] < 60);
- } else {
- // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be
- // legally added at specific times based on the tree's algorithm.
- return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) ||
- mTypedTimes.contains(getAmOrPmKeyCode(PM)));
- }
- }
-
- private int deleteLastTypedKey() {
- int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
- if (!isTypedTimeFullyLegal()) {
- onValidationChanged(false);
- }
- return deleted;
- }
-
- /**
- * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time.
- */
- private void finishKbMode() {
- mInKbMode = false;
- if (!mTypedTimes.isEmpty()) {
- int values[] = getEnteredTime(null);
- mRadialTimePickerView.setCurrentHour(values[0]);
- mRadialTimePickerView.setCurrentMinute(values[1]);
- if (!mIs24HourView) {
- mRadialTimePickerView.setAmOrPm(values[2]);
- }
- mTypedTimes.clear();
- }
- updateDisplay(false);
- mRadialTimePickerView.setInputEnabled(true);
- }
-
- /**
- * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is
- * empty, either show an empty display (filled with the placeholder text), or update from the
- * timepicker's values.
- *
- * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text.
- * Otherwise, revert to the timepicker's values.
- */
- private void updateDisplay(boolean allowEmptyDisplay) {
- if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
- int hour = mRadialTimePickerView.getCurrentHour();
- int minute = mRadialTimePickerView.getCurrentMinute();
- updateHeaderHour(hour, false);
- updateHeaderMinute(minute, false);
- if (!mIs24HourView) {
- updateAmPmLabelStates(hour < 12 ? AM : PM);
- }
- setCurrentItemShowing(mRadialTimePickerView.getCurrentItemShowing(), true, true);
- onValidationChanged(true);
- } else {
- boolean[] enteredZeros = {false, false};
- int[] values = getEnteredTime(enteredZeros);
- String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
- String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
- String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
- String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
- String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
- String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
- mHourView.setText(hourStr);
- mHourView.setActivated(false);
- mMinuteView.setText(minuteStr);
- mMinuteView.setActivated(false);
- if (!mIs24HourView) {
- updateAmPmLabelStates(values[2]);
- }
- }
- }
-
- private int getValFromKeyCode(int keyCode) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_0:
- return 0;
- case KeyEvent.KEYCODE_1:
- return 1;
- case KeyEvent.KEYCODE_2:
- return 2;
- case KeyEvent.KEYCODE_3:
- return 3;
- case KeyEvent.KEYCODE_4:
- return 4;
- case KeyEvent.KEYCODE_5:
- return 5;
- case KeyEvent.KEYCODE_6:
- return 6;
- case KeyEvent.KEYCODE_7:
- return 7;
- case KeyEvent.KEYCODE_8:
- return 8;
- case KeyEvent.KEYCODE_9:
- return 9;
- default:
- return -1;
- }
- }
-
- /**
- * Get the currently-entered time, as integer values of the hours and minutes typed.
- *
- * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which
- * may then be used for the caller to know whether zeros had been explicitly entered as either
- * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
- *
- * @return A size-3 int array. The first value will be the hours, the second value will be the
- * minutes, and the third will be either AM or PM.
- */
- private int[] getEnteredTime(boolean[] enteredZeros) {
- int amOrPm = -1;
- int startIndex = 1;
- if (!mIs24HourView && isTypedTimeFullyLegal()) {
- int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
- if (keyCode == getAmOrPmKeyCode(AM)) {
- amOrPm = AM;
- } else if (keyCode == getAmOrPmKeyCode(PM)){
- amOrPm = PM;
- }
- startIndex = 2;
- }
- int minute = -1;
- int hour = -1;
- for (int i = startIndex; i <= mTypedTimes.size(); i++) {
- int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
- if (i == startIndex) {
- minute = val;
- } else if (i == startIndex+1) {
- minute += 10 * val;
- if (enteredZeros != null && val == 0) {
- enteredZeros[1] = true;
- }
- } else if (i == startIndex+2) {
- hour = val;
- } else if (i == startIndex+3) {
- hour += 10 * val;
- if (enteredZeros != null && val == 0) {
- enteredZeros[0] = true;
+ } else {
+ // May still be making changes. Postpone visual
+ // updates to prevent distracting the user.
+ view.postDelayed(commitCallback, DELAY_COMMIT_MILLIS);
}
}
}
+ };
- return new int[] { hour, minute, amOrPm };
- }
+ private final Runnable mCommitHour = new Runnable() {
+ @Override
+ public void run() {
+ setCurrentHour(mHourView.getValue());
+ }
+ };
- /**
- * Get the keycode value for AM and PM in the current language.
- */
- private int getAmOrPmKeyCode(int amOrPm) {
- // Cache the codes.
- if (mAmKeyCode == -1 || mPmKeyCode == -1) {
- // Find the first character in the AM/PM text that is unique.
- final KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
- final CharSequence amText = mAmText.toLowerCase(mCurrentLocale);
- final CharSequence pmText = mPmText.toLowerCase(mCurrentLocale);
- final int N = Math.min(amText.length(), pmText.length());
- for (int i = 0; i < N; i++) {
- final char amChar = amText.charAt(i);
- final char pmChar = pmText.charAt(i);
- if (amChar != pmChar) {
- // There should be 4 events: a down and up for both AM and PM.
- final KeyEvent[] events = kcm.getEvents(new char[] { amChar, pmChar });
- if (events != null && events.length == 4) {
- mAmKeyCode = events[0].getKeyCode();
- mPmKeyCode = events[2].getKeyCode();
- } else {
- Log.e(TAG, "Unable to find keycodes for AM and PM.");
- }
- break;
+ private final Runnable mCommitMinute = new Runnable() {
+ @Override
+ public void run() {
+ setCurrentMinute(mMinuteView.getValue());
+ }
+ };
+
+ private final View.OnFocusChangeListener mFocusListener = new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean focused) {
+ if (focused) {
+ switch (v.getId()) {
+ case R.id.am_label:
+ setAmOrPm(AM);
+ break;
+ case R.id.pm_label:
+ setAmOrPm(PM);
+ break;
+ case R.id.hours:
+ setCurrentItemShowing(HOUR_INDEX, true, true);
+ break;
+ case R.id.minutes:
+ setCurrentItemShowing(MINUTE_INDEX, true, true);
+ break;
+ default:
+ // Failed to handle this click, don't vibrate.
+ return;
}
+
+ tryVibrate();
}
}
-
- if (amOrPm == AM) {
- return mAmKeyCode;
- } else if (amOrPm == PM) {
- return mPmKeyCode;
- }
-
- return -1;
- }
-
- /**
- * Create a tree for deciding what keys can legally be typed.
- */
- private void generateLegalTimesTree() {
- // Create a quick cache of numbers to their keycodes.
- final int k0 = KeyEvent.KEYCODE_0;
- final int k1 = KeyEvent.KEYCODE_1;
- final int k2 = KeyEvent.KEYCODE_2;
- final int k3 = KeyEvent.KEYCODE_3;
- final int k4 = KeyEvent.KEYCODE_4;
- final int k5 = KeyEvent.KEYCODE_5;
- final int k6 = KeyEvent.KEYCODE_6;
- final int k7 = KeyEvent.KEYCODE_7;
- final int k8 = KeyEvent.KEYCODE_8;
- final int k9 = KeyEvent.KEYCODE_9;
-
- // The root of the tree doesn't contain any numbers.
- mLegalTimesTree = new Node();
- if (mIs24HourView) {
- // We'll be re-using these nodes, so we'll save them.
- Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
- Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
- // The first digit must be followed by the second digit.
- minuteFirstDigit.addChild(minuteSecondDigit);
-
- // The first digit may be 0-1.
- Node firstDigit = new Node(k0, k1);
- mLegalTimesTree.addChild(firstDigit);
-
- // When the first digit is 0-1, the second digit may be 0-5.
- Node secondDigit = new Node(k0, k1, k2, k3, k4, k5);
- firstDigit.addChild(secondDigit);
- // We may now be followed by the first minute digit. E.g. 00:09, 15:58.
- secondDigit.addChild(minuteFirstDigit);
-
- // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9.
- Node thirdDigit = new Node(k6, k7, k8, k9);
- // The time must now be finished. E.g. 0:55, 1:08.
- secondDigit.addChild(thirdDigit);
-
- // When the first digit is 0-1, the second digit may be 6-9.
- secondDigit = new Node(k6, k7, k8, k9);
- firstDigit.addChild(secondDigit);
- // We must now be followed by the first minute digit. E.g. 06:50, 18:20.
- secondDigit.addChild(minuteFirstDigit);
-
- // The first digit may be 2.
- firstDigit = new Node(k2);
- mLegalTimesTree.addChild(firstDigit);
-
- // When the first digit is 2, the second digit may be 0-3.
- secondDigit = new Node(k0, k1, k2, k3);
- firstDigit.addChild(secondDigit);
- // We must now be followed by the first minute digit. E.g. 20:50, 23:09.
- secondDigit.addChild(minuteFirstDigit);
-
- // When the first digit is 2, the second digit may be 4-5.
- secondDigit = new Node(k4, k5);
- firstDigit.addChild(secondDigit);
- // We must now be followd by the last minute digit. E.g. 2:40, 2:53.
- secondDigit.addChild(minuteSecondDigit);
-
- // The first digit may be 3-9.
- firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9);
- mLegalTimesTree.addChild(firstDigit);
- // We must now be followed by the first minute digit. E.g. 3:57, 8:12.
- firstDigit.addChild(minuteFirstDigit);
- } else {
- // We'll need to use the AM/PM node a lot.
- // Set up AM and PM to respond to "a" and "p".
- Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM));
-
- // The first hour digit may be 1.
- Node firstDigit = new Node(k1);
- mLegalTimesTree.addChild(firstDigit);
- // We'll allow quick input of on-the-hour times. E.g. 1pm.
- firstDigit.addChild(ampm);
-
- // When the first digit is 1, the second digit may be 0-2.
- Node secondDigit = new Node(k0, k1, k2);
- firstDigit.addChild(secondDigit);
- // Also for quick input of on-the-hour times. E.g. 10pm, 12am.
- secondDigit.addChild(ampm);
-
- // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5.
- Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5);
- secondDigit.addChild(thirdDigit);
- // The time may be finished now. E.g. 1:02pm, 1:25am.
- thirdDigit.addChild(ampm);
-
- // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5,
- // the fourth digit may be 0-9.
- Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
- thirdDigit.addChild(fourthDigit);
- // The time must be finished now. E.g. 10:49am, 12:40pm.
- fourthDigit.addChild(ampm);
-
- // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9.
- thirdDigit = new Node(k6, k7, k8, k9);
- secondDigit.addChild(thirdDigit);
- // The time must be finished now. E.g. 1:08am, 1:26pm.
- thirdDigit.addChild(ampm);
-
- // When the first digit is 1, the second digit may be 3-5.
- secondDigit = new Node(k3, k4, k5);
- firstDigit.addChild(secondDigit);
-
- // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9.
- thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
- secondDigit.addChild(thirdDigit);
- // The time must be finished now. E.g. 1:39am, 1:50pm.
- thirdDigit.addChild(ampm);
-
- // The hour digit may be 2-9.
- firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9);
- mLegalTimesTree.addChild(firstDigit);
- // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm.
- firstDigit.addChild(ampm);
-
- // When the first digit is 2-9, the second digit may be 0-5.
- secondDigit = new Node(k0, k1, k2, k3, k4, k5);
- firstDigit.addChild(secondDigit);
-
- // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9.
- thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
- secondDigit.addChild(thirdDigit);
- // The time must be finished now. E.g. 2:57am, 9:30pm.
- thirdDigit.addChild(ampm);
- }
- }
-
- /**
- * Simple node class to be used for traversal to check for legal times.
- * mLegalKeys represents the keys that can be typed to get to the node.
- * mChildren are the children that can be reached from this node.
- */
- private class Node {
- private int[] mLegalKeys;
- private ArrayList<Node> mChildren;
-
- public Node(int... legalKeys) {
- mLegalKeys = legalKeys;
- mChildren = new ArrayList<Node>();
- }
-
- public void addChild(Node child) {
- mChildren.add(child);
- }
-
- public boolean containsKey(int key) {
- for (int i = 0; i < mLegalKeys.length; i++) {
- if (mLegalKeys[i] == key) {
- return true;
- }
- }
- return false;
- }
-
- public Node canReach(int key) {
- if (mChildren == null) {
- return null;
- }
- for (Node child : mChildren) {
- if (child.containsKey(key)) {
- return child;
- }
- }
- return null;
- }
- }
+ };
private final View.OnClickListener mClickListener = new View.OnClickListener() {
@Override
@@ -1347,28 +884,55 @@
}
};
- private final View.OnKeyListener mKeyListener = new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_UP) {
- return processKeyUp(keyCode);
+ /**
+ * Delegates unhandled touches in a view group to the nearest child view.
+ */
+ private static class NearestTouchDelegate implements View.OnTouchListener {
+ private View mInitialTouchTarget;
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ final int actionMasked = motionEvent.getActionMasked();
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ mInitialTouchTarget = findNearestChild((ViewGroup) view,
+ (int) motionEvent.getX(), (int) motionEvent.getY());
+ }
+
+ final View child = mInitialTouchTarget;
+ if (child == null) {
+ return false;
+ }
+
+ final float offsetX = view.getScrollX() - child.getLeft();
+ final float offsetY = view.getScrollY() - child.getTop();
+ motionEvent.offsetLocation(offsetX, offsetY);
+ final boolean handled = child.dispatchTouchEvent(motionEvent);
+ motionEvent.offsetLocation(-offsetX, -offsetY);
+
+ if (actionMasked == MotionEvent.ACTION_UP
+ || actionMasked == MotionEvent.ACTION_CANCEL) {
+ mInitialTouchTarget = null;
+ }
+
+ return handled;
}
- return false;
- }
- };
- private final View.OnFocusChangeListener mFocusListener = new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (!hasFocus && mInKbMode && isTypedTimeFullyLegal()) {
- finishKbMode();
+ private View findNearestChild(ViewGroup v, int x, int y) {
+ View bestChild = null;
+ int bestDist = Integer.MAX_VALUE;
- if (mOnTimeChangedListener != null) {
- mOnTimeChangedListener.onTimeChanged(mDelegator,
- mRadialTimePickerView.getCurrentHour(),
- mRadialTimePickerView.getCurrentMinute());
+ for (int i = 0, count = v.getChildCount(); i < count; i++) {
+ final View child = v.getChildAt(i);
+ final int dX = x - (child.getLeft() + child.getWidth() / 2);
+ final int dY = y - (child.getTop() + child.getHeight() / 2);
+ final int dist = dX * dX + dY * dY;
+ if (bestDist > dist) {
+ bestChild = child;
+ bestDist = dist;
}
}
+
+ return bestChild;
}
- };
+ }
}
diff --git a/core/java/com/android/internal/policy/DecorContext.java b/core/java/com/android/internal/policy/DecorContext.java
index aa603c1..4f17c39 100644
--- a/core/java/com/android/internal/policy/DecorContext.java
+++ b/core/java/com/android/internal/policy/DecorContext.java
@@ -17,7 +17,6 @@
package com.android.internal.policy;
import android.content.Context;
-import android.content.res.TypedArray;
import android.view.ContextThemeWrapper;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -31,7 +30,6 @@
class DecorContext extends ContextThemeWrapper {
private PhoneWindow mPhoneWindow;
private WindowManager mWindowManager;
- private TypedArray mWindowStyle;
public DecorContext(Context context) {
super(context, null);
@@ -54,13 +52,4 @@
}
return super.getSystemService(name);
}
-
- public TypedArray getWindowStyle() {
- synchronized (this) {
- if (mWindowStyle == null) {
- mWindowStyle = obtainStyledAttributes(com.android.internal.R.styleable.Window);
- }
- return mWindowStyle;
- }
- }
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 8b81812..4644211 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -31,7 +31,6 @@
import android.animation.ObjectAnimator;
import android.app.ActivityManagerNative;
import android.app.SearchManager;
-import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.UserHandle;
@@ -5438,20 +5437,7 @@
* */
private Drawable getResizingBackgroundDrawable() {
final Context context = mDecor.getContext();
- final TypedArray windowStyle;
- if (context instanceof DecorContext) {
- windowStyle = ((DecorContext) context).getWindowStyle();
- } else {
- windowStyle = getWindowStyle();
- }
- final int resourceId =
- windowStyle.getResourceId(R.styleable.Window_windowResizingBackground, 0);
- if (resourceId != 0) {
- return context.getDrawable(resourceId);
- }
- // The app didn't set a resizing background color. In this case we try to use the app's
- // background drawable for the resizing background.
if (mBackgroundResource != 0) {
final Drawable drawable = context.getDrawable(mBackgroundResource);
if (drawable != null) {
@@ -5459,8 +5445,6 @@
}
}
- // The app background drawable isn't currently set. This might be because the app cleared
- // it. In this case we try to use the app's background fallback drawable.
if (mBackgroundFallbackResource != 0) {
final Drawable fallbackDrawable = context.getDrawable(mBackgroundFallbackResource);
if (fallbackDrawable != null) {
@@ -5468,7 +5452,9 @@
}
}
- return new ColorDrawable(context.getResources().getInteger(
- com.android.internal.R.integer.config_windowResizingBackgroundColorARGB));
+ // We shouldn't really get here as the background fallback should be always available since
+ // it is defaulted by the system.
+ Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + this);
+ return null;
}
}
diff --git a/core/java/com/android/internal/widget/NumericTextView.java b/core/java/com/android/internal/widget/NumericTextView.java
new file mode 100644
index 0000000..27c5834
--- /dev/null
+++ b/core/java/com/android/internal/widget/NumericTextView.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.StateSet;
+import android.view.KeyEvent;
+import android.widget.TextView;
+
+/**
+ * Extension of TextView that can handle displaying and inputting a range of
+ * numbers.
+ * <p>
+ * Clients of this view should never call {@link #setText(CharSequence)} or
+ * {@link #setHint(CharSequence)} directly. Instead, they should call
+ * {@link #setValue(int)} to modify the currently displayed value.
+ */
+public class NumericTextView extends TextView {
+ private static final int RADIX = 10;
+ private static final double LOG_RADIX = Math.log(RADIX);
+
+ private int mMinValue = 0;
+ private int mMaxValue = 99;
+
+ /** Number of digits in the maximum value. */
+ private int mMaxCount = 2;
+
+ private boolean mShowLeadingZeroes = true;
+
+ private int mValue;
+
+ /** Number of digits entered during editing mode. */
+ private int mCount;
+
+ /** Used to restore the value after an aborted edit. */
+ private int mPreviousValue;
+
+ private OnValueChangedListener mListener;
+
+ public NumericTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ // Generate the hint text color based on disabled state.
+ final int textColorDisabled = getTextColors().getColorForState(StateSet.get(0), 0);
+ setHintTextColor(textColorDisabled);
+
+ setFocusable(true);
+ }
+
+ @Override
+ protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+
+ if (focused) {
+ mPreviousValue = mValue;
+ mValue = 0;
+ mCount = 0;
+
+ // Transfer current text to hint.
+ setHint(getText());
+ setText("");
+ } else {
+ if (mCount == 0) {
+ // No digits were entered, revert to previous value.
+ mValue = mPreviousValue;
+
+ setText(getHint());
+ setHint("");
+ }
+
+ // Ensure the committed value is within range.
+ if (mValue < mMinValue) {
+ mValue = mMinValue;
+ }
+
+ setValue(mValue);
+
+ if (mListener != null) {
+ mListener.onValueChanged(this, mValue, true, true);
+ }
+ }
+ }
+
+ /**
+ * Sets the currently displayed value.
+ * <p>
+ * The specified {@code value} must be within the range specified by
+ * {@link #setRange(int, int)} (e.g. between {@link #getRangeMinimum()}
+ * and {@link #getRangeMaximum()}).
+ *
+ * @param value the value to display
+ */
+ public final void setValue(int value) {
+ if (mValue != value) {
+ mValue = value;
+
+ updateDisplayedValue();
+ }
+ }
+
+ /**
+ * Returns the currently displayed value.
+ * <p>
+ * If the value is currently being edited, returns the live value which may
+ * not be within the range specified by {@link #setRange(int, int)}.
+ *
+ * @return the currently displayed value
+ */
+ public final int getValue() {
+ return mValue;
+ }
+
+ /**
+ * Sets the valid range (inclusive).
+ *
+ * @param minValue the minimum valid value (inclusive)
+ * @param maxValue the maximum valid value (inclusive)
+ */
+ public final void setRange(int minValue, int maxValue) {
+ if (mMinValue != minValue) {
+ mMinValue = minValue;
+ }
+
+ if (mMaxValue != maxValue) {
+ mMaxValue = maxValue;
+ mMaxCount = 1 + (int) (Math.log(maxValue) / LOG_RADIX);
+
+ updateMinimumWidth();
+ updateDisplayedValue();
+ }
+ }
+
+ /**
+ * @return the minimum value value (inclusive)
+ */
+ public final int getRangeMinimum() {
+ return mMinValue;
+ }
+
+ /**
+ * @return the maximum value value (inclusive)
+ */
+ public final int getRangeMaximum() {
+ return mMaxValue;
+ }
+
+ /**
+ * Sets whether this view shows leading zeroes.
+ * <p>
+ * When leading zeroes are shown, the displayed value will be padded
+ * with zeroes to the width of the maximum value as specified by
+ * {@link #setRange(int, int)} (see also {@link #getRangeMaximum()}.
+ * <p>
+ * For example, with leading zeroes shown, a maximum of 99 and value of
+ * 9 would display "09". A maximum of 100 and a value of 9 would display
+ * "009". With leading zeroes hidden, both cases would show "9".
+ *
+ * @param showLeadingZeroes {@code true} to show leading zeroes,
+ * {@code false} to hide them
+ */
+ public final void setShowLeadingZeroes(boolean showLeadingZeroes) {
+ if (mShowLeadingZeroes != showLeadingZeroes) {
+ mShowLeadingZeroes = showLeadingZeroes;
+
+ updateDisplayedValue();
+ }
+ }
+
+ public final boolean getShowLeadingZeroes() {
+ return mShowLeadingZeroes;
+ }
+
+ /**
+ * Computes the display value and updates the text of the view.
+ * <p>
+ * This method should be called whenever the current value or display
+ * properties (leading zeroes, max digits) change.
+ */
+ private void updateDisplayedValue() {
+ final String format;
+ if (mShowLeadingZeroes) {
+ format = "%0" + mMaxCount + "d";
+ } else {
+ format = "%d";
+ }
+
+ // Always use String.format() rather than Integer.toString()
+ // to obtain correctly localized values.
+ setText(String.format(format, mValue));
+ }
+
+ /**
+ * Computes the minimum width in pixels required to display all possible
+ * values and updates the minimum width of the view.
+ * <p>
+ * This method should be called whenever the maximum value changes.
+ */
+ private void updateMinimumWidth() {
+ final CharSequence previousText = getText();
+ int maxWidth = 0;
+
+ for (int i = 0; i < mMaxValue; i++) {
+ setText(String.format("%0" + mMaxCount + "d", i));
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ final int width = getMeasuredWidth();
+ if (width > maxWidth) {
+ maxWidth = width;
+ }
+ }
+
+ setText(previousText);
+ setMinWidth(maxWidth);
+ setMinimumWidth(maxWidth);
+ }
+
+ public final void setOnDigitEnteredListener(OnValueChangedListener listener) {
+ mListener = listener;
+ }
+
+ public final OnValueChangedListener getOnDigitEnteredListener() {
+ return mListener;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return isKeyCodeNumeric(keyCode)
+ || (keyCode == KeyEvent.KEYCODE_DEL)
+ || super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ return isKeyCodeNumeric(keyCode)
+ || (keyCode == KeyEvent.KEYCODE_DEL)
+ || super.onKeyMultiple(keyCode, repeatCount, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return handleKeyUp(keyCode)
+ || super.onKeyUp(keyCode, event);
+ }
+
+ private boolean handleKeyUp(int keyCode) {
+ if (keyCode == KeyEvent.KEYCODE_DEL) {
+ // Backspace removes the least-significant digit, if available.
+ if (mCount > 0) {
+ mValue /= RADIX;
+ mCount--;
+ }
+ } else if (isKeyCodeNumeric(keyCode)) {
+ if (mCount < mMaxCount) {
+ final int keyValue = numericKeyCodeToInt(keyCode);
+ final int newValue = mValue * RADIX + keyValue;
+ if (newValue <= mMaxValue) {
+ mValue = newValue;
+ mCount++;
+ }
+ }
+ } else {
+ return false;
+ }
+
+ final String formattedValue;
+ if (mCount > 0) {
+ // If the user types 01, we should always show the leading 0 even if
+ // getShowLeadingZeroes() is false. Preserve typed leading zeroes by
+ // using the number of digits entered as the format width.
+ formattedValue = String.format("%0" + mCount + "d", mValue);
+ } else {
+ formattedValue = "";
+ }
+
+ setText(formattedValue);
+
+ if (mListener != null) {
+ final boolean isValid = mValue >= mMinValue;
+ final boolean isFinished = mCount >= mMaxCount || mValue * RADIX > mMaxValue;
+ mListener.onValueChanged(this, mValue, isValid, isFinished);
+ }
+
+ return true;
+ }
+
+ private static boolean isKeyCodeNumeric(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
+ || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
+ || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
+ || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
+ || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9;
+ }
+
+ private static int numericKeyCodeToInt(int keyCode) {
+ return keyCode - KeyEvent.KEYCODE_0;
+ }
+
+ public interface OnValueChangedListener {
+ /**
+ * Called when the value displayed by {@code view} changes.
+ *
+ * @param view the view whose value changed
+ * @param value the new value
+ * @param isValid {@code true} if the value is valid (e.g. within the
+ * range specified by {@link #setRange(int, int)}),
+ * {@code false} otherwise
+ * @param isFinished {@code true} if the no more digits may be entered,
+ * {@code false} if more digits may be entered
+ */
+ void onValueChanged(NumericTextView view, int value, boolean isValid, boolean isFinished);
+ }
+}
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index d1780d6..7fd288a 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -33,11 +33,11 @@
FontStyle resolved = resolvedFace->fStyle;
/* Prepare minikin FontStyle */
- const std::string& lang = paint->getTextLocale();
- FontLanguage minikinLang(lang.c_str(), lang.size());
+ const std::string& langs = paint->getTextLocales();
+ FontLanguages minikinLangs(langs.c_str(), langs.size());
FontVariant minikinVariant = (paint->getFontVariant() == VARIANT_ELEGANT) ? VARIANT_ELEGANT
: VARIANT_COMPACT;
- FontStyle minikinStyle(minikinLang, minikinVariant, resolved.getWeight(), resolved.getItalic());
+ FontStyle minikinStyle(minikinLangs, minikinVariant, resolved.getWeight(), resolved.getItalic());
/* Prepare minikin Paint */
// Note: it would be nice to handle fractional size values (it would improve smooth zoom
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 314e4b6..6b09450 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -43,12 +43,10 @@
#include "Paint.h"
#include "TypefaceImpl.h"
-#include <vector>
+#include <cassert>
+#include <cstring>
#include <memory>
-
-// temporary for debugging
-#include <Caches.h>
-#include <utils/Log.h>
+#include <vector>
namespace android {
@@ -71,12 +69,12 @@
paint->setTextEncoding(Paint::kGlyphID_TextEncoding);
}
-struct LocaleCacheEntry {
- std::string javaLocale;
- std::string languageTag;
+struct LocalesCacheEntry {
+ std::string javaLocales;
+ std::string languageTags;
};
-static thread_local LocaleCacheEntry sSingleEntryLocaleCache;
+static thread_local LocalesCacheEntry sSingleEntryLocalesCache;
namespace PaintGlue {
enum MoveOpt {
@@ -360,17 +358,57 @@
output[0] = '\0';
}
- static void setTextLocale(JNIEnv* env, jobject clazz, jlong objHandle, jstring locale) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- ScopedUtfChars localeChars(env, locale);
- if (sSingleEntryLocaleCache.javaLocale != localeChars.c_str()) {
- sSingleEntryLocaleCache.javaLocale = localeChars.c_str();
- char langTag[ULOC_FULLNAME_CAPACITY];
- toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, localeChars.c_str());
- sSingleEntryLocaleCache.languageTag = langTag;
+ static void toLanguageTags(std::string* output, const char* locales) {
+ if (output == NULL) {
+ return;
+ }
+ if (locales == NULL) {
+ output->clear();
+ return;
}
- obj->setTextLocale(sSingleEntryLocaleCache.languageTag);
+ char langTag[ULOC_FULLNAME_CAPACITY];
+ const char* commaLoc = strchr(locales, ',');
+ if (commaLoc == NULL) {
+ assert(locales[0] != '\0'); // the string should not be empty
+ toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locales);
+ *output = langTag;
+ return;
+ }
+
+ size_t len = strlen(locales);
+ char locale[len];
+ output->clear();
+ output->reserve(len);
+ const char* lastStart = locales;
+ do {
+ assert(lastStart > commaLoc); // the substring should not be empty
+ strncpy(locale, lastStart, commaLoc - lastStart);
+ locale[commaLoc - lastStart] = '\0';
+ toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
+ if (langTag[0] != '\0') {
+ output->append(langTag);
+ output->push_back(',');
+ }
+ lastStart = commaLoc + 1;
+ commaLoc = strchr(lastStart, ',');
+ } while (commaLoc != NULL);
+ assert(lastStart[0] != '\0'); // the final substring should not be empty
+ toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, lastStart);
+ if (langTag[0] != '\0') {
+ output->append(langTag);
+ }
+ }
+
+ static void setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ ScopedUtfChars localesChars(env, locales);
+ if (sSingleEntryLocalesCache.javaLocales != localesChars.c_str()) {
+ sSingleEntryLocalesCache.javaLocales = localesChars.c_str();
+ toLanguageTags(&sSingleEntryLocalesCache.languageTags, localesChars.c_str());
+ }
+
+ obj->setTextLocales(sSingleEntryLocalesCache.languageTags);
}
static jboolean isElegantTextHeight(JNIEnv* env, jobject, jlong paintHandle) {
@@ -952,7 +990,7 @@
{"nSetRasterizer","!(JJ)J", (void*) PaintGlue::setRasterizer},
{"nGetTextAlign","!(J)I", (void*) PaintGlue::getTextAlign},
{"nSetTextAlign","!(JI)V", (void*) PaintGlue::setTextAlign},
- {"nSetTextLocale","!(JLjava/lang/String;)V", (void*) PaintGlue::setTextLocale},
+ {"nSetTextLocales","!(JLjava/lang/String;)V", (void*) PaintGlue::setTextLocales},
{"nIsElegantTextHeight","!(J)Z", (void*) PaintGlue::isElegantTextHeight},
{"nSetElegantTextHeight","!(JZ)V", (void*) PaintGlue::setElegantTextHeight},
{"nGetTextSize","!(J)F", (void*) PaintGlue::getTextSize},
diff --git a/core/jni/android/graphics/Paint.h b/core/jni/android/graphics/Paint.h
index 6df22ff..7a34bc2 100644
--- a/core/jni/android/graphics/Paint.h
+++ b/core/jni/android/graphics/Paint.h
@@ -53,12 +53,12 @@
return mFontFeatureSettings;
}
- void setTextLocale(const std::string &textLocale) {
- mTextLocale = textLocale;
+ void setTextLocales(const std::string &textLocales) {
+ mTextLocales = textLocales;
}
- const std::string& getTextLocale() const {
- return mTextLocale;
+ const std::string& getTextLocales() const {
+ return mTextLocales;
}
void setFontVariant(FontVariant variant) {
@@ -80,7 +80,7 @@
private:
float mLetterSpacing = 0;
std::string mFontFeatureSettings;
- std::string mTextLocale;
+ std::string mTextLocales;
FontVariant mFontVariant;
uint32_t mHyphenEdit = 0;
};
diff --git a/core/jni/android/graphics/PaintImpl.cpp b/core/jni/android/graphics/PaintImpl.cpp
index da85018..d5a0972 100644
--- a/core/jni/android/graphics/PaintImpl.cpp
+++ b/core/jni/android/graphics/PaintImpl.cpp
@@ -23,12 +23,12 @@
namespace android {
Paint::Paint() : SkPaint(),
- mLetterSpacing(0), mFontFeatureSettings(), mTextLocale(), mFontVariant(VARIANT_DEFAULT) {
+ mLetterSpacing(0), mFontFeatureSettings(), mTextLocales(), mFontVariant(VARIANT_DEFAULT) {
}
Paint::Paint(const Paint& paint) : SkPaint(paint),
mLetterSpacing(paint.mLetterSpacing), mFontFeatureSettings(paint.mFontFeatureSettings),
- mTextLocale(paint.mTextLocale), mFontVariant(paint.mFontVariant),
+ mTextLocales(paint.mTextLocales), mFontVariant(paint.mFontVariant),
mHyphenEdit(paint.mHyphenEdit) {
}
@@ -39,7 +39,7 @@
SkPaint::operator=(other);
mLetterSpacing = other.mLetterSpacing;
mFontFeatureSettings = other.mFontFeatureSettings;
- mTextLocale = other.mTextLocale;
+ mTextLocales = other.mTextLocales;
mFontVariant = other.mFontVariant;
mHyphenEdit = other.mHyphenEdit;
return *this;
@@ -49,7 +49,7 @@
return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b)
&& a.mLetterSpacing == b.mLetterSpacing
&& a.mFontFeatureSettings == b.mFontFeatureSettings
- && a.mTextLocale == b.mTextLocale
+ && a.mTextLocales == b.mTextLocales
&& a.mFontVariant == b.mFontVariant
&& a.mHyphenEdit == b.mHyphenEdit;
}
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index a14afa0..41aa9ca 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -114,7 +114,7 @@
return parcel ? parcel->dataCapacity() : 0;
}
-static void android_os_Parcel_setDataSize(JNIEnv* env, jclass clazz, jlong nativePtr, jint size)
+static jlong android_os_Parcel_setDataSize(JNIEnv* env, jclass clazz, jlong nativePtr, jint size)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -122,7 +122,9 @@
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
+ return parcel->getOpenAshmemSize();
}
+ return 0;
}
static void android_os_Parcel_setDataPosition(JNIEnv* env, jclass clazz, jlong nativePtr, jint pos)
@@ -304,7 +306,7 @@
}
}
-static void android_os_Parcel_writeFileDescriptor(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
+static jlong android_os_Parcel_writeFileDescriptor(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -313,7 +315,9 @@
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
+ return parcel->getOpenAshmemSize();
}
+ return 0;
}
static jbyteArray android_os_Parcel_createByteArray(JNIEnv* env, jclass clazz, jlong nativePtr)
@@ -546,12 +550,14 @@
return reinterpret_cast<jlong>(parcel);
}
-static void android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jlong android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
parcel->freeData();
+ return parcel->getOpenAshmemSize();
}
+ return 0;
}
static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr)
@@ -589,12 +595,12 @@
return ret;
}
-static void android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong nativePtr,
- jbyteArray data, jint offset, jint length)
+static jlong android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong nativePtr,
+ jbyteArray data, jint offset, jint length)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel == NULL || length < 0) {
- return;
+ return 0;
}
jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(data, 0);
@@ -608,24 +614,26 @@
env->ReleasePrimitiveArrayCritical(data, array, 0);
}
+ return parcel->getOpenAshmemSize();
}
-static void android_os_Parcel_appendFrom(JNIEnv* env, jclass clazz, jlong thisNativePtr,
- jlong otherNativePtr, jint offset, jint length)
+static jlong android_os_Parcel_appendFrom(JNIEnv* env, jclass clazz, jlong thisNativePtr,
+ jlong otherNativePtr, jint offset, jint length)
{
Parcel* thisParcel = reinterpret_cast<Parcel*>(thisNativePtr);
if (thisParcel == NULL) {
- return;
+ return 0;
}
Parcel* otherParcel = reinterpret_cast<Parcel*>(otherNativePtr);
if (otherParcel == NULL) {
- return;
+ return thisParcel->getOpenAshmemSize();
}
status_t err = thisParcel->appendFrom(otherParcel, offset, length);
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
+ return thisParcel->getOpenAshmemSize();
}
static jboolean android_os_Parcel_hasFileDescriptors(JNIEnv* env, jclass clazz, jlong nativePtr)
@@ -718,7 +726,7 @@
{"nativeDataAvail", "(J)I", (void*)android_os_Parcel_dataAvail},
{"nativeDataPosition", "(J)I", (void*)android_os_Parcel_dataPosition},
{"nativeDataCapacity", "(J)I", (void*)android_os_Parcel_dataCapacity},
- {"nativeSetDataSize", "(JI)V", (void*)android_os_Parcel_setDataSize},
+ {"nativeSetDataSize", "(JI)J", (void*)android_os_Parcel_setDataSize},
{"nativeSetDataPosition", "(JI)V", (void*)android_os_Parcel_setDataPosition},
{"nativeSetDataCapacity", "(JI)V", (void*)android_os_Parcel_setDataCapacity},
@@ -733,7 +741,7 @@
{"nativeWriteDouble", "(JD)V", (void*)android_os_Parcel_writeDouble},
{"nativeWriteString", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString},
{"nativeWriteStrongBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
- {"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)V", (void*)android_os_Parcel_writeFileDescriptor},
+ {"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)J", (void*)android_os_Parcel_writeFileDescriptor},
{"nativeCreateByteArray", "(J)[B", (void*)android_os_Parcel_createByteArray},
{"nativeReadBlob", "(J)[B", (void*)android_os_Parcel_readBlob},
@@ -751,12 +759,12 @@
{"clearFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_clearFileDescriptor},
{"nativeCreate", "()J", (void*)android_os_Parcel_create},
- {"nativeFreeBuffer", "(J)V", (void*)android_os_Parcel_freeBuffer},
+ {"nativeFreeBuffer", "(J)J", (void*)android_os_Parcel_freeBuffer},
{"nativeDestroy", "(J)V", (void*)android_os_Parcel_destroy},
{"nativeMarshall", "(J)[B", (void*)android_os_Parcel_marshall},
- {"nativeUnmarshall", "(J[BII)V", (void*)android_os_Parcel_unmarshall},
- {"nativeAppendFrom", "(JJII)V", (void*)android_os_Parcel_appendFrom},
+ {"nativeUnmarshall", "(J[BII)J", (void*)android_os_Parcel_unmarshall},
+ {"nativeAppendFrom", "(JJII)J", (void*)android_os_Parcel_appendFrom},
{"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
{"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
{"nativeEnforceInterface", "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
diff --git a/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml b/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml
index baa8958..905fd4d 100644
--- a/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml
+++ b/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml
@@ -1,8 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
+ android:state_enabled="false"
+ android:color="?attr/textColorSecondaryInverse"
+ android:alpha="?attr/disabledAlpha" />
+ <item
android:state_activated="true"
android:color="?attr/textColorPrimaryInverse" />
<item
android:color="?attr/textColorSecondaryInverse" />
-</selector>
\ No newline at end of file
+</selector>
diff --git a/core/res/res/drawable/time_picker_editable_background.xml b/core/res/res/drawable/time_picker_editable_background.xml
new file mode 100644
index 0000000..72e863b
--- /dev/null
+++ b/core/res/res/drawable/time_picker_editable_background.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true">
+ <layer-list>
+ <item android:gravity="bottom"
+ android:height="10dp">
+ <shape android:shape="line"
+ android:tint="?attr/textColorPrimaryInverse">
+ <stroke android:color="@color/white"
+ android:width="2dp" />
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+ <item android:drawable="@color/transparent" />
+</selector>
diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml
index bb347cb..7a0c38f 100644
--- a/core/res/res/layout-land/time_picker_material.xml
+++ b/core/res/res/layout-land/time_picker_material.xml
@@ -51,15 +51,17 @@
<!-- The hour should always be to the left of the separator,
regardless of the current locale's layout direction. -->
- <TextView
+ <com.android.internal.widget.NumericTextView
android:id="@+id/hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+ android:background="@drawable/time_picker_editable_background"
android:singleLine="true"
android:ellipsize="none"
android:gravity="right"
- android:includeFontPadding="false" />
+ android:focusable="true"
+ android:nextFocusForward="@+id/minutes" />
<TextView
android:id="@+id/separator"
@@ -71,57 +73,56 @@
<!-- The minutes should always be to the right of the separator,
regardless of the current locale's layout direction. -->
- <TextView
+ <com.android.internal.widget.NumericTextView
android:id="@+id/minutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+ android:background="@drawable/time_picker_editable_background"
android:singleLine="true"
android:ellipsize="none"
android:gravity="left"
- android:includeFontPadding="false" />
+ android:focusable="true"
+ android:nextFocusForward="@+id/am_label" />
</LinearLayout>
- <!-- The layout alignment of this view will switch between toRightOf
- @id/minutes and toLeftOf @id/hours depending on the locale. -->
- <LinearLayout
+ <RadioGroup
android:id="@+id/ampm_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/time_layout"
android:layout_centerHorizontal="true"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
android:orientation="vertical"
android:layoutDirection="locale">
-
- <CheckedTextView
+ <RadioButton
android:id="@+id/am_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minHeight="48dp"
- android:minWidth="48dp"
- android:gravity="bottom"
+ android:padding="8dp"
+ android:layout_marginBottom="-8dp"
android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
- android:paddingStart="@dimen/timepicker_ampm_horizontal_padding"
- android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding"
- android:paddingTop="4dp"
- android:paddingBottom="6dp"
android:lines="1"
- android:ellipsize="none" />
-
- <CheckedTextView
+ android:ellipsize="none"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground"
+ android:includeFontPadding="false"
+ android:nextFocusForward="@+id/pm_label"
+ android:button="@null" />
+ <RadioButton
android:id="@+id/pm_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minHeight="48dp"
- android:minWidth="48dp"
- android:gravity="top"
+ android:padding="8dp"
android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
- android:paddingStart="@dimen/timepicker_ampm_horizontal_padding"
- android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding"
android:lines="1"
android:ellipsize="none"
- android:includeFontPadding="false" />
- </LinearLayout>
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground"
+ android:includeFontPadding="false"
+ android:button="@null" />
+ </RadioGroup>
</RelativeLayout>
<ViewStub
diff --git a/core/res/res/layout/time_picker_header_material.xml b/core/res/res/layout/time_picker_header_material.xml
index acdc509..7019ced 100644
--- a/core/res/res/layout/time_picker_header_material.xml
+++ b/core/res/res/layout/time_picker_header_material.xml
@@ -21,23 +21,24 @@
android:id="@+id/time_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="horizontal"
- android:padding="@dimen/timepicker_separator_padding"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<!-- The hour should always be to the left of the separator,
regardless of the current locale's layout direction. -->
- <TextView
+ <com.android.internal.widget.NumericTextView
android:id="@+id/hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/separator"
android:layout_alignBaseline="@+id/separator"
android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+ android:background="@drawable/time_picker_editable_background"
android:singleLine="true"
android:ellipsize="none"
- android:gravity="right" />
+ android:gravity="right"
+ android:focusable="true"
+ android:nextFocusForward="@+id/minutes" />
<TextView
android:id="@+id/separator"
@@ -51,50 +52,57 @@
<!-- The minutes should always be to the left of the separator,
regardless of the current locale's layout direction. -->
- <TextView
+ <com.android.internal.widget.NumericTextView
android:id="@+id/minutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/separator"
android:layout_alignBaseline="@+id/separator"
android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+ android:background="@drawable/time_picker_editable_background"
android:singleLine="true"
android:ellipsize="none"
- android:gravity="left" />
+ android:gravity="left"
+ android:focusable="true"
+ android:nextFocusForward="@+id/am_label" />
<!-- The layout alignment of this view will switch between toRightOf
@id/minutes and toLeftOf @id/hours depending on the locale. -->
- <LinearLayout
+ <RadioGroup
android:id="@+id/ampm_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/minutes"
android:layout_alignBaseline="@+id/minutes"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
android:orientation="vertical"
android:baselineAlignedChildIndex="1">
- <CheckedTextView
+ <RadioButton
android:id="@+id/am_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="48dp"
- android:minHeight="48dp"
- android:gravity="bottom"
- android:paddingTop="@dimen/timepicker_am_top_padding"
+ android:padding="8dp"
+ android:layout_marginBottom="-8dp"
android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
android:lines="1"
- android:ellipsize="none" />
- <CheckedTextView
+ android:ellipsize="none"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground"
+ android:includeFontPadding="false"
+ android:nextFocusForward="@+id/pm_label"
+ android:button="@null" />
+ <RadioButton
android:id="@+id/pm_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="48dp"
- android:minHeight="48dp"
- android:gravity="top"
- android:paddingTop="@dimen/timepicker_pm_top_padding"
+ android:padding="8dp"
android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
android:lines="1"
- android:ellipsize="none" />
- </LinearLayout>
+ android:ellipsize="none"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground"
+ android:includeFontPadding="false"
+ android:button="@null" />
+ </RadioGroup>
</RelativeLayout>
diff --git a/core/res/res/values-mcc204-mnc04/config.xml b/core/res/res/values-mcc204-mnc04/config.xml
index fb639ca..0f39e42 100755
--- a/core/res/res/values-mcc204-mnc04/config.xml
+++ b/core/res/res/values-mcc204-mnc04/config.xml
@@ -44,14 +44,5 @@
<item>false</item>
</string-array>
- <!-- String containing the apn value for tethering. May be overriden by secure settings
- TETHER_DUN_APN. Value is a comma separated series of strings:
- "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
- Or string format of ApnSettingV3.
- note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
- <string-array translatable="false" name="config_tether_apndata">
- <item>[ApnSettingV3]SaskTel Tethering,inet.stm.sk.ca,,,,,,,,,204,04,,DUN,,,true,0,,,,,,,gid,5A</item>
- </string-array>
-
<string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true;BAE0000000000000</string>
</resources>
diff --git a/core/res/res/values-mcc302-mnc780/config.xml b/core/res/res/values-mcc302-mnc780/config.xml
index 51abd36..a48f695 100644
--- a/core/res/res/values-mcc302-mnc780/config.xml
+++ b/core/res/res/values-mcc302-mnc780/config.xml
@@ -21,25 +21,6 @@
for different hardware and product builds. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
- <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
- <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
- <integer-array translatable="false" name="config_tether_upstream_types">
- <item>1</item>
- <item>4</item>
- <item>7</item>
- <item>9</item>
- </integer-array>
-
- <!-- String containing the apn value for tethering. May be overriden by secure settings
- TETHER_DUN_APN. Value is a comma separated series of strings:
- "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
- Or string format of ApnSettingV3.
- note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
- <string-array translatable="false" name="config_tether_apndata">
- <item>SaskTel Tethering,inet.stm.sk.ca,,,,,,,,,302,780,,DUN</item>
- </string-array>
-
<!-- Don't use roaming icon for considered operators -->
<string-array translatable="false" name="config_operatorConsideredNonRoaming">
<item>302</item>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8e36eb0..093ea80 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -323,14 +323,10 @@
<attr name="windowBackground" format="reference" />
<!-- Drawable to draw selectively within the inset areas when the windowBackground
has been set to null. This protects against seeing visual garbage in the
- surface when the app has not drawn any content into this area. -->
+ surface when the app has not drawn any content into this area. One example is
+ when the user is resizing a window of an activity that has
+ {@link android.R.attr#resizeableActivity} set for multi-window mode. -->
<attr name="windowBackgroundFallback" format="reference" />
- <!-- Drawable used to fill in areas the app has not rendered content to yet when the user is
- resizing the window of an activity that has {@link android.R.attr#resizeableActivity}
- set for multi-window mode. If unset, the system will try to use windowBackground if
- set, then windowBackgroundFallback if set. Otherwise, the system default resizing
- background color -->
- <attr name="windowResizingBackground" format="reference" />
<!-- Drawable to use as a frame around the window. -->
<attr name="windowFrame" format="reference" />
<!-- Flag indicating whether there should be no title on this window. -->
@@ -1850,7 +1846,6 @@
<declare-styleable name="Window">
<attr name="windowBackground" />
<attr name="windowBackgroundFallback" />
- <attr name="windowResizingBackground" />
<attr name="windowContentOverlay" />
<attr name="windowFrame" />
<attr name="windowNoTitle" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d20b09f..74c745b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1249,6 +1249,9 @@
<!-- Operating volatage for bluetooth controller. 0 by default-->
<integer translatable="false" name="config_bluetooth_operating_voltage_mv">4</integer>
+ <!-- Whether supported profiles should be reloaded upon enabling bluetooth -->
+ <bool name="config_bluetooth_reload_supported_profiles_when_enabled">false</bool>
+
<!-- The default data-use polling period. -->
<integer name="config_datause_polling_period_sec">600</integer>
@@ -2349,9 +2352,6 @@
is non-interactive. -->
<bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool>
- <!-- Default background color to use when resizing a window if the app didn't specify one. -->
- <integer name="config_windowResizingBackgroundColorARGB">0xFF808080</integer>
-
<!-- Name of the component to handle network policy notifications. If present,
disables NetworkPolicyManagerService's presentation of data-usage notifications. -->
<string translatable="false" name="config_networkPolicyNotificationComponent"></string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 839e81b..c05b585 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2677,7 +2677,7 @@
<public type="attr" name="level" />
<public type="attr" name="contextPopupMenuStyle" />
<public type="attr" name="textAppearancePopupMenuHeader" />
- <public type="attr" name="windowResizingBackground" />
+ <public type="attr" name="windowBackgroundFallback" />
<public type="style" name="Theme.Material.DayNight" />
<public type="style" name="Theme.Material.DayNight.DarkActionBar" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6f239e6..3baddbb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -370,6 +370,7 @@
<java-symbol type="integer" name="config_bluetooth_rx_cur_ma" />
<java-symbol type="integer" name="config_bluetooth_tx_cur_ma" />
<java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" />
+ <java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" />
<java-symbol type="integer" name="config_cursorWindowSize" />
<java-symbol type="integer" name="config_drawLockTimeoutMillis" />
<java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
@@ -2332,7 +2333,5 @@
<java-symbol type="string" name="config_iccHotswapPromptForRestartDialogComponent" />
- <java-symbol type="integer" name="config_windowResizingBackgroundColorARGB" />
-
<java-symbol type="string" name="config_packagedKeyboardName" />
</resources>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 11b4a9e..35182f9 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1335,7 +1335,7 @@
return;
}
mLocales = new LocaleList(locale);
- nSetTextLocale(mNativePaint, locale.toString());
+ nSetTextLocales(mNativePaint, locale.toString());
}
/**
@@ -1372,8 +1372,7 @@
}
if (locales.equals(mLocales)) return;
mLocales = locales;
- // TODO: Pass the whole LocaleList to native code
- nSetTextLocale(mNativePaint, locales.getPrimary().toString());
+ nSetTextLocales(mNativePaint, locales.toLanguageTags());
}
/**
@@ -2715,8 +2714,8 @@
private static native void nSetTextAlign(long paintPtr,
int align);
- private static native void nSetTextLocale(long paintPtr,
- String locale);
+ private static native void nSetTextLocales(long paintPtr,
+ String locales);
private static native float nGetTextAdvances(long paintPtr, long typefacePtr,
char[] text, int index, int count, int contextIndex, int contextCount,
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 842f575..4385e70 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -27,6 +27,7 @@
utils/LinearAllocator.cpp \
utils/NinePatchImpl.cpp \
utils/StringUtils.cpp \
+ utils/TestWindowContext.cpp \
AmbientShadow.cpp \
AnimationContext.cpp \
Animator.cpp \
@@ -84,8 +85,7 @@
hwui_cflags := \
-DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES \
-DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\" \
- -Wall -Wno-unused-parameter -Wunreachable-code \
- -ffast-math -O3 -Werror
+ -Wall -Wno-unused-parameter -Wunreachable-code -Werror
ifeq (true, $(HWUI_NEW_OPS))
hwui_src_files += \
@@ -261,7 +261,8 @@
LOCAL_SRC_FILES += \
microbench/DisplayListCanvasBench.cpp \
- microbench/LinearAllocatorBench.cpp
+ microbench/LinearAllocatorBench.cpp \
+ microbench/ShadowBench.cpp
ifeq (true, $(HWUI_NEW_OPS))
LOCAL_SRC_FILES += \
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
index 12a3e76..0835c29 100644
--- a/libs/hwui/TessellationCache.cpp
+++ b/libs/hwui/TessellationCache.cpp
@@ -217,7 +217,7 @@
}
}
-static void tessellateShadows(
+void tessellateShadows(
const Matrix4* drawTransform, const Rect* localClip,
bool isCasterOpaque, const SkPath* casterPerimeter,
const Matrix4* casterTransformXY, const Matrix4* casterTransformZ,
diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h
index b54666b..06e567e 100644
--- a/libs/hwui/TessellationCache.h
+++ b/libs/hwui/TessellationCache.h
@@ -17,16 +17,22 @@
#ifndef ANDROID_HWUI_TESSELLATION_CACHE_H
#define ANDROID_HWUI_TESSELLATION_CACHE_H
-#include <utils/LruCache.h>
-#include <utils/Mutex.h>
-
#include "Debug.h"
+#include "Matrix.h"
+#include "Rect.h"
+#include "Vector.h"
+#include "thread/TaskProcessor.h"
#include "utils/Macros.h"
#include "utils/Pair.h"
+#include <SkPaint.h>
+
+#include <utils/LruCache.h>
+#include <utils/Mutex.h>
+#include <utils/StrongPointer.h>
+
class SkBitmap;
class SkCanvas;
-class SkPaint;
class SkPath;
struct SkRect;
@@ -185,6 +191,13 @@
}; // class TessellationCache
+void tessellateShadows(
+ const Matrix4* drawTransform, const Rect* localClip,
+ bool isCasterOpaque, const SkPath* casterPerimeter,
+ const Matrix4* casterTransformXY, const Matrix4* casterTransformZ,
+ const Vector3& lightCenter, float lightRadius,
+ VertexBuffer& ambientBuffer, VertexBuffer& spotBuffer);
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Vector.h b/libs/hwui/Vector.h
index 7c3f2fd..6367dbd 100644
--- a/libs/hwui/Vector.h
+++ b/libs/hwui/Vector.h
@@ -135,8 +135,8 @@
}
- void dump() {
- ALOGD("Vector3[%.2f, %.2f, %.2f]", x, y, z);
+ void dump(const char* label = "Vector3") const {
+ ALOGD("%s[%.2f, %.2f, %.2f]", label, x, y, z);
}
};
diff --git a/libs/hwui/microbench/ShadowBench.cpp b/libs/hwui/microbench/ShadowBench.cpp
new file mode 100644
index 0000000..bd51693
--- /dev/null
+++ b/libs/hwui/microbench/ShadowBench.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <benchmark/Benchmark.h>
+
+#include "Matrix.h"
+#include "Rect.h"
+#include "Vector.h"
+#include "VertexBuffer.h"
+#include "TessellationCache.h"
+#include "microbench/MicroBench.h"
+
+#include <SkPath.h>
+
+#include <memory>
+
+using namespace android;
+using namespace android::uirenderer;
+
+struct ShadowTestData {
+ Matrix4 drawTransform;
+ Rect localClip;
+ Matrix4 casterTransformXY;
+ Matrix4 casterTransformZ;
+ Vector3 lightCenter;
+ float lightRadius;
+};
+
+void createShadowTestData(ShadowTestData* out) {
+ static float SAMPLE_DRAW_TRANSFORM[] = {
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1,
+ };
+ static float SAMPLE_CASTERXY[] = {
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 32, 32, 0, 1,
+ };
+ static float SAMPLE_CASTERZ[] = {
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 32, 32, 32, 1,
+ };
+ static Rect SAMPLE_CLIP(0, 0, 1536, 2048);
+ static Vector3 SAMPLE_LIGHT_CENTER{768, -400, 1600};
+ static float SAMPLE_LIGHT_RADIUS = 1600;
+
+ out->drawTransform.load(SAMPLE_DRAW_TRANSFORM);
+ out->localClip = SAMPLE_CLIP;
+ out->casterTransformXY.load(SAMPLE_CASTERXY);
+ out->casterTransformZ.load(SAMPLE_CASTERZ);
+ out->lightCenter = SAMPLE_LIGHT_CENTER;
+ out->lightRadius = SAMPLE_LIGHT_RADIUS;
+}
+
+static inline void tessellateShadows(ShadowTestData& testData, bool opaque,
+ const SkPath& shape, VertexBuffer* ambient, VertexBuffer* spot) {
+ tessellateShadows(&testData.drawTransform, &testData.localClip,
+ opaque, &shape, &testData.casterTransformXY,
+ &testData.casterTransformZ, testData.lightCenter,
+ testData.lightRadius, *ambient, *spot);
+}
+
+BENCHMARK_NO_ARG(BM_TessellateShadows_roundrect_opaque);
+void BM_TessellateShadows_roundrect_opaque::Run(int iters) {
+ ShadowTestData shadowData;
+ createShadowTestData(&shadowData);
+ SkPath path;
+ path.reset();
+ path.addRoundRect(SkRect::MakeLTRB(0, 0, 100, 100), 5, 5);
+
+ StartBenchmarkTiming();
+ for (int i = 0; i < iters; i++) {
+ std::unique_ptr<VertexBuffer> ambient(new VertexBuffer);
+ std::unique_ptr<VertexBuffer> spot(new VertexBuffer);
+ tessellateShadows(shadowData, true, path, ambient.get(), spot.get());
+ MicroBench::DoNotOptimize(ambient.get());
+ MicroBench::DoNotOptimize(spot.get());
+ }
+ StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_TessellateShadows_roundrect_translucent);
+void BM_TessellateShadows_roundrect_translucent::Run(int iters) {
+ ShadowTestData shadowData;
+ createShadowTestData(&shadowData);
+ SkPath path;
+ path.reset();
+ path.addRoundRect(SkRect::MakeLTRB(0, 0, 100, 100), 5, 5);
+
+ StartBenchmarkTiming();
+ for (int i = 0; i < iters; i++) {
+ std::unique_ptr<VertexBuffer> ambient(new VertexBuffer);
+ std::unique_ptr<VertexBuffer> spot(new VertexBuffer);
+ tessellateShadows(shadowData, false, path, ambient.get(), spot.get());
+ MicroBench::DoNotOptimize(ambient.get());
+ MicroBench::DoNotOptimize(spot.get());
+ }
+ StopBenchmarkTiming();
+}
diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp
new file mode 100644
index 0000000..84aae75
--- /dev/null
+++ b/libs/hwui/utils/TestWindowContext.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "TestWindowContext.h"
+
+#include "AnimationContext.h"
+#include "DisplayListCanvas.h"
+#include "IContextFactory.h"
+#include "RenderNode.h"
+#include "SkTypes.h"
+#include "gui/BufferQueue.h"
+#include "gui/CpuConsumer.h"
+#include "gui/IGraphicBufferConsumer.h"
+#include "gui/IGraphicBufferProducer.h"
+#include "gui/Surface.h"
+#include "renderthread/RenderProxy.h"
+
+
+namespace {
+
+/**
+ * Helper class for setting up android::uirenderer::renderthread::RenderProxy.
+ */
+class ContextFactory : public android::uirenderer::IContextFactory {
+public:
+ android::uirenderer::AnimationContext* createAnimationContext
+ (android::uirenderer::renderthread::TimeLord& clock) override {
+ return new android::uirenderer::AnimationContext(clock);
+ }
+};
+
+} // anonymous namespace
+
+namespace android {
+namespace uirenderer {
+
+/**
+ Android strong pointers (android::sp) can't hold forward-declared classes,
+ so we have to use pointer-to-implementation here if we want to hide the
+ details from our non-framework users.
+*/
+
+class TestWindowContext::TestWindowData {
+
+public:
+
+ TestWindowData(SkISize size) : mSize(size) {
+ android::BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+ mCpuConsumer = new android::CpuConsumer(mConsumer, 1);
+ mCpuConsumer->setName(android::String8("TestWindowContext"));
+ mCpuConsumer->setDefaultBufferSize(mSize.width(), mSize.height());
+ mAndroidSurface = new android::Surface(mProducer);
+ native_window_set_buffers_dimensions(mAndroidSurface.get(),
+ mSize.width(), mSize.height());
+ native_window_set_buffers_format(mAndroidSurface.get(),
+ android::PIXEL_FORMAT_RGBA_8888);
+ native_window_set_usage(mAndroidSurface.get(),
+ GRALLOC_USAGE_SW_READ_OFTEN |
+ GRALLOC_USAGE_SW_WRITE_NEVER |
+ GRALLOC_USAGE_HW_RENDER);
+ mRootNode.reset(new android::uirenderer::RenderNode());
+ mRootNode->incStrong(nullptr);
+ mRootNode->mutateStagingProperties().setLeftTopRightBottom
+ (0, 0, mSize.width(), mSize.height());
+ mRootNode->mutateStagingProperties().setClipToBounds(false);
+ mRootNode->setPropertyFieldsDirty(android::uirenderer::RenderNode::GENERIC);
+ ContextFactory factory;
+ mProxy.reset
+ (new android::uirenderer::renderthread::RenderProxy(false,
+ mRootNode.get(),
+ &factory));
+ mProxy->loadSystemProperties();
+ mProxy->initialize(mAndroidSurface.get());
+ float lightX = mSize.width() / 2.0f;
+ android::uirenderer::Vector3 lightVector { lightX, -200.0f, 800.0f };
+ mProxy->setup(mSize.width(), mSize.height(), 800.0f,
+ 255 * 0.075f, 255 * 0.15f);
+ mProxy->setLightCenter(lightVector);
+ mCanvas.reset(new
+ android::uirenderer::DisplayListCanvas(mSize.width(),
+ mSize.height()));
+ }
+
+ SkCanvas* prepareToDraw() {
+ //mCanvas->reset(mSize.width(), mSize.height());
+ mCanvas->clipRect(0, 0, mSize.width(), mSize.height(),
+ SkRegion::Op::kReplace_Op);
+ return mCanvas->asSkCanvas();
+ }
+
+ void finishDrawing() {
+ mRootNode->setStagingDisplayList(mCanvas->finishRecording());
+ mProxy->syncAndDrawFrame();
+ // Surprisingly, calling mProxy->fence() here appears to make no difference to
+ // the timings we record.
+ }
+
+ void fence() {
+ mProxy->fence();
+ }
+
+ bool capturePixels(SkBitmap* bmp) {
+ SkImageInfo destinationConfig =
+ SkImageInfo::Make(mSize.width(), mSize.height(),
+ kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+ bmp->allocPixels(destinationConfig);
+ sk_memset32((uint32_t*) bmp->getPixels(), SK_ColorRED,
+ mSize.width() * mSize.height());
+
+ android::CpuConsumer::LockedBuffer nativeBuffer;
+ android::status_t retval = mCpuConsumer->lockNextBuffer(&nativeBuffer);
+ if (retval == android::BAD_VALUE) {
+ SkDebugf("write_canvas_png() got no buffer; returning transparent");
+ // No buffer ready to read - commonly triggered by dm sending us
+ // a no-op source, or calling code that doesn't do anything on this
+ // backend.
+ bmp->eraseColor(SK_ColorTRANSPARENT);
+ return false;
+ } else if (retval) {
+ SkDebugf("Failed to lock buffer to read pixels: %d.", retval);
+ return false;
+ }
+
+ // Move the pixels into the destination SkBitmap
+
+ SK_ALWAYSBREAK(nativeBuffer.format == android::PIXEL_FORMAT_RGBA_8888 &&
+ "Native buffer not RGBA!");
+ SkImageInfo nativeConfig =
+ SkImageInfo::Make(nativeBuffer.width, nativeBuffer.height,
+ kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+
+ // Android stride is in pixels, Skia stride is in bytes
+ SkBitmap nativeWrapper;
+ bool success =
+ nativeWrapper.installPixels(nativeConfig, nativeBuffer.data, nativeBuffer.stride * 4);
+ if (!success) {
+ SkDebugf("Failed to wrap HWUI buffer in a SkBitmap");
+ return false;
+ }
+
+ SK_ALWAYSBREAK(bmp->colorType() == kRGBA_8888_SkColorType &&
+ "Destination buffer not RGBA!");
+ success =
+ nativeWrapper.readPixels(destinationConfig, bmp->getPixels(), bmp->rowBytes(), 0, 0);
+ if (!success) {
+ SkDebugf("Failed to extract pixels from HWUI buffer");
+ return false;
+ }
+
+ mCpuConsumer->unlockBuffer(nativeBuffer);
+
+ return true;
+ }
+
+private:
+
+ std::unique_ptr<android::uirenderer::RenderNode> mRootNode;
+ std::unique_ptr<android::uirenderer::renderthread::RenderProxy> mProxy;
+ std::unique_ptr<android::uirenderer::DisplayListCanvas> mCanvas;
+ android::sp<android::IGraphicBufferProducer> mProducer;
+ android::sp<android::IGraphicBufferConsumer> mConsumer;
+ android::sp<android::CpuConsumer> mCpuConsumer;
+ android::sp<android::Surface> mAndroidSurface;
+ SkISize mSize;
+};
+
+
+TestWindowContext::TestWindowContext() :
+ mData (nullptr) { }
+
+void TestWindowContext::initialize(int width, int height) {
+ mData = new TestWindowData(SkISize::Make(width, height));
+}
+
+SkCanvas* TestWindowContext::prepareToDraw() {
+ return mData ? mData->prepareToDraw() : nullptr;
+}
+
+void TestWindowContext::finishDrawing() {
+ if (mData) {
+ mData->finishDrawing();
+ }
+}
+
+void TestWindowContext::fence() {
+ if (mData) {
+ mData->fence();
+ }
+}
+
+bool TestWindowContext::capturePixels(SkBitmap* bmp) {
+ return mData ? mData->capturePixels(bmp) : false;
+}
+
+} // namespace uirenderer
+} // namespace android
+
diff --git a/libs/hwui/utils/TestWindowContext.h b/libs/hwui/utils/TestWindowContext.h
new file mode 100644
index 0000000..445a11b
--- /dev/null
+++ b/libs/hwui/utils/TestWindowContext.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TESTWINDOWCONTEXT_H_
+#define TESTWINDOWCONTEXT_H_
+
+#include <cutils/compiler.h>
+
+class SkBitmap;
+class SkCanvas;
+
+namespace android {
+
+namespace uirenderer {
+
+/**
+ Wraps all libui/libgui classes and types that external tests depend on,
+ exposing only primitive Skia types.
+*/
+
+class ANDROID_API TestWindowContext {
+
+public:
+
+ TestWindowContext();
+
+ /// We need to know the size of the window.
+ void initialize(int width, int height);
+
+ /// Returns a canvas to draw into; NULL if not yet initialize()d.
+ SkCanvas* prepareToDraw();
+
+ /// Flushes all drawing commands to HWUI; no-op if not yet initialize()d.
+ void finishDrawing();
+
+ /// Blocks until HWUI has processed all pending drawing commands;
+ /// no-op if not yet initialize()d.
+ void fence();
+
+ /// Returns false if not yet initialize()d.
+ bool capturePixels(SkBitmap* bmp);
+
+private:
+ /// Hidden implementation.
+ class TestWindowData;
+
+ TestWindowData* mData;
+
+};
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // TESTWINDOWCONTEXT_H_
+
diff --git a/libs/packagelistparser/Android.mk b/libs/packagelistparser/Android.mk
deleted file mode 100644
index 802a3cb..0000000
--- a/libs/packagelistparser/Android.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-#########################
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libpackagelistparser
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := packagelistparser.c
-LOCAL_COPY_HEADERS_TO := packagelistparser
-LOCAL_COPY_HEADERS := packagelistparser.h
-LOCAL_SHARED_LIBRARIES := liblog
-
-LOCAL_CLANG := true
-LOCAL_SANITIZE := integer
-
-include $(BUILD_SHARED_LIBRARY)
-
-#########################
-include $(CLEAR_VARS)
-
-
-LOCAL_MODULE := libpackagelistparser
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := packagelistparser.c
-LOCAL_COPY_HEADERS_TO := packagelistparser
-LOCAL_COPY_HEADERS := packagelistparser.h
-LOCAL_STATIC_LIBRARIES := liblog
-
-LOCAL_CLANG := true
-LOCAL_SANITIZE := integer
-
-include $(BUILD_STATIC_LIBRARY)
diff --git a/libs/packagelistparser/packagelistparser.c b/libs/packagelistparser/packagelistparser.c
deleted file mode 100644
index 3e2539c..0000000
--- a/libs/packagelistparser/packagelistparser.c
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright 2015, Intel Corporation
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Written by William Roberts <william.c.roberts@intel.com>
- *
- */
-
-#include <errno.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <sys/limits.h>
-
-#define LOG_TAG "packagelistparser"
-#include <utils/Log.h>
-
-#include "packagelistparser.h"
-
-#define CLOGE(fmt, ...) \
- do {\
- IF_ALOGE() {\
- ALOGE(fmt, ##__VA_ARGS__);\
- }\
- } while(0)
-
-static size_t get_gid_cnt(const char *gids)
-{
- size_t cnt;
-
- if (*gids == '\0') {
- return 0;
- }
-
- if (!strcmp(gids, "none")) {
- return 0;
- }
-
- for (cnt = 1; gids[cnt]; gids[cnt] == ',' ? cnt++ : *gids++)
- ;
-
- return cnt;
-}
-
-static bool parse_gids(char *gids, gid_t *gid_list, size_t *cnt)
-{
- gid_t gid;
- char* token;
- char *endptr;
- size_t cmp = 0;
-
- while ((token = strsep(&gids, ",\r\n"))) {
-
- if (cmp > *cnt) {
- return false;
- }
-
- gid = strtoul(token, &endptr, 10);
- if (*endptr != '\0') {
- return false;
- }
-
- /*
- * if unsigned long is greater than size of gid_t,
- * prevent a truncation based roll-over
- */
- if (gid > GID_MAX) {
- CLOGE("A gid in field \"gid list\" greater than GID_MAX");
- return false;
- }
-
- gid_list[cmp++] = gid;
- }
- return true;
-}
-
-extern bool packagelist_parse(pfn_on_package callback, void *userdata)
-{
-
- FILE *fp;
- char *cur;
- char *next;
- char *endptr;
- unsigned long tmp;
- ssize_t bytesread;
-
- bool rc = false;
- char *buf = NULL;
- size_t buflen = 0;
- unsigned long lineno = 1;
- const char *errmsg = NULL;
- struct pkg_info *pkg_info = NULL;
-
- fp = fopen(PACKAGES_LIST_FILE, "re");
- if (!fp) {
- CLOGE("Could not open: \"%s\", error: \"%s\"\n", PACKAGES_LIST_FILE,
- strerror(errno));
- return false;
- }
-
- while ((bytesread = getline(&buf, &buflen, fp)) > 0) {
-
- pkg_info = calloc(1, sizeof(*pkg_info));
- if (!pkg_info) {
- goto err;
- }
-
- next = buf;
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for \"package name\"";
- goto err;
- }
-
- pkg_info->name = strdup(cur);
- if (!pkg_info->name) {
- goto err;
- }
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for field \"uid\"";
- goto err;
- }
-
- tmp = strtoul(cur, &endptr, 10);
- if (*endptr != '\0') {
- errmsg = "Could not convert field \"uid\" to integer value";
- goto err;
- }
-
- /*
- * if unsigned long is greater than size of uid_t,
- * prevent a truncation based roll-over
- */
- if (tmp > UID_MAX) {
- errmsg = "Field \"uid\" greater than UID_MAX";
- goto err;
- }
-
- pkg_info->uid = (uid_t) tmp;
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for field \"debuggable\"";
- goto err;
- }
-
- tmp = strtoul(cur, &endptr, 10);
- if (*endptr != '\0') {
- errmsg = "Could not convert field \"debuggable\" to integer value";
- goto err;
- }
-
- /* should be a valid boolean of 1 or 0 */
- if (!(tmp == 0 || tmp == 1)) {
- errmsg = "Field \"debuggable\" is not 0 or 1 boolean value";
- goto err;
- }
-
- pkg_info->debuggable = (bool) tmp;
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for field \"data dir\"";
- goto err;
- }
-
- pkg_info->data_dir = strdup(cur);
- if (!pkg_info->data_dir) {
- goto err;
- }
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for field \"seinfo\"";
- goto err;
- }
-
- pkg_info->seinfo = strdup(cur);
- if (!pkg_info->seinfo) {
- goto err;
- }
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for field \"gid(s)\"";
- goto err;
- }
-
- /*
- * Parse the gid list, could be in the form of none, single gid or list:
- * none
- * gid
- * gid, gid ...
- */
- pkg_info->gids.cnt = get_gid_cnt(cur);
- if (pkg_info->gids.cnt > 0) {
-
- pkg_info->gids.gids = calloc(pkg_info->gids.cnt, sizeof(gid_t));
- if (!pkg_info->gids.gids) {
- goto err;
- }
-
- rc = parse_gids(cur, pkg_info->gids.gids, &pkg_info->gids.cnt);
- if (!rc) {
- errmsg = "Could not parse field \"gid list\"";
- goto err;
- }
- }
-
- rc = callback(pkg_info, userdata);
- if (rc == false) {
- /*
- * We do not log this as this can be intentional from
- * callback to abort processing. We go to out to not
- * free the pkg_info
- */
- rc = true;
- goto out;
- }
- lineno++;
- }
-
- rc = true;
-
-out:
- free(buf);
- fclose(fp);
- return rc;
-
-err:
- if (errmsg) {
- CLOGE("Error Parsing \"%s\" on line: %lu for reason: %s",
- PACKAGES_LIST_FILE, lineno, errmsg);
- }
- rc = false;
- packagelist_free(pkg_info);
- goto out;
-}
-
-void packagelist_free(pkg_info *info)
-{
- if (info) {
- free(info->name);
- free(info->data_dir);
- free(info->seinfo);
- free(info->gids.gids);
- free(info);
- }
-}
diff --git a/libs/packagelistparser/packagelistparser.h b/libs/packagelistparser/packagelistparser.h
deleted file mode 100644
index d602c26..0000000
--- a/libs/packagelistparser/packagelistparser.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2015, Intel Corporation
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Written by William Roberts <william.c.roberts@intel.com>
- *
- * This is a parser library for parsing the packages.list file generated
- * by PackageManager service.
- *
- * This simple parser is sensitive to format changes in
- * frameworks/base/services/core/java/com/android/server/pm/Settings.java
- * A dependency note has been added to that file to correct
- * this parser.
- */
-
-#ifndef PACKAGELISTPARSER_H_
-#define PACKAGELISTPARSER_H_
-
-#include <stdbool.h>
-#include <sys/cdefs.h>
-#include <sys/types.h>
-
-__BEGIN_DECLS
-
-/** The file containing the list of installed packages on the system */
-#define PACKAGES_LIST_FILE "/data/system/packages.list"
-
-typedef struct pkg_info pkg_info;
-typedef struct gid_list gid_list;
-
-struct gid_list {
- size_t cnt;
- gid_t *gids;
-};
-
-struct pkg_info {
- char *name;
- uid_t uid;
- bool debuggable;
- char *data_dir;
- char *seinfo;
- gid_list gids;
- void *private_data;
-};
-
-/**
- * Callback function to be used by packagelist_parse() routine.
- * @param info
- * The parsed package information
- * @param userdata
- * The supplied userdata pointer to packagelist_parse()
- * @return
- * true to keep processing, false to stop.
- */
-typedef bool (*pfn_on_package)(pkg_info *info, void *userdata);
-
-/**
- * Parses the file specified by PACKAGES_LIST_FILE and invokes the callback on
- * each entry found. Once the callback is invoked, ownership of the pkg_info pointer
- * is passed to the callback routine, thus they are required to perform any cleanup
- * desired.
- * @param callback
- * The callback function called on each parsed line of the packages list.
- * @param userdata
- * An optional userdata supplied pointer to pass to the callback function.
- * @return
- * true on success false on failure.
- */
-extern bool packagelist_parse(pfn_on_package callback, void *userdata);
-
-/**
- * Frees a pkg_info structure.
- * @param info
- * The struct to free
- */
-extern void packagelist_free(pkg_info *info);
-
-__END_DECLS
-
-#endif /* PACKAGELISTPARSER_H_ */
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index c06ea0a..d4c3ba3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -38,9 +38,7 @@
* Returns true if event was triggered by a finger or stylus touch.
*/
static boolean isTouchEvent(MotionEvent e) {
- return isTouchType(e.getToolType(0))
- // Temporarily work around uiautomator's missing tool type support.
- || isUnknownType(e.getToolType(0));
+ return isTouchType(e.getToolType(0));
}
/**
@@ -51,7 +49,7 @@
}
/**
- * Returns true if type is finger or stylus.
+ * Returns true if event was triggered by a finger or stylus touch.
*/
static boolean isTouchType(int toolType) {
return toolType == MotionEvent.TOOL_TYPE_FINGER
@@ -59,13 +57,6 @@
}
/**
- * Returns true if type is unknown.
- */
- static boolean isUnknownType(int toolType) {
- return toolType == MotionEvent.TOOL_TYPE_UNKNOWN;
- }
-
- /**
* Returns true if event was triggered by a finger or stylus touch.
*/
static boolean isActionDown(MotionEvent e) {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
new file mode 100644
index 0000000..fe82c8d
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
@@ -0,0 +1,334 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# English (US), Workman keyboard layout.
+# Unlike the default (generic) keyboard layout, English (US) does not contain any
+# special ALT characters.
+#
+
+type OVERLAY
+
+map key 17 D
+map key 18 R
+map key 19 W
+map key 20 B
+map key 21 J
+map key 22 F
+map key 23 U
+map key 24 P
+map key 25 SEMICOLON
+map key 32 H
+map key 33 T
+map key 34 G
+map key 35 Y
+map key 36 N
+map key 37 E
+map key 38 O
+map key 39 I
+map key 46 M
+map key 47 C
+map key 48 V
+map key 49 K
+map key 50 L
+
+### ROW 1
+
+key GRAVE {
+ label: '`'
+ base: '`'
+ shift: '~'
+}
+
+key 1 {
+ label: '1'
+ base: '1'
+ shift: '!'
+}
+
+key 2 {
+ label: '2'
+ base: '2'
+ shift: '@'
+}
+
+key 3 {
+ label: '3'
+ base: '3'
+ shift: '#'
+}
+
+key 4 {
+ label: '4'
+ base: '4'
+ shift: '$'
+}
+
+key 5 {
+ label: '5'
+ base: '5'
+ shift: '%'
+}
+
+key 6 {
+ label: '6'
+ base: '6'
+ shift: '^'
+}
+
+key 7 {
+ label: '7'
+ base: '7'
+ shift: '&'
+}
+
+key 8 {
+ label: '8'
+ base: '8'
+ shift: '*'
+}
+
+key 9 {
+ label: '9'
+ base: '9'
+ shift: '('
+}
+
+key 0 {
+ label: '0'
+ base: '0'
+ shift: ')'
+}
+
+key MINUS {
+ label: '-'
+ base: '-'
+ shift: '_'
+}
+
+key EQUALS {
+ label: '='
+ base: '='
+ shift: '+'
+}
+
+### ROW 2
+
+key Q {
+ label: 'Q'
+ base: 'q'
+ shift, capslock: 'Q'
+}
+
+key D {
+ label: 'D'
+ base: 'd'
+ shift, capslock: 'D'
+}
+
+key R {
+ label: 'R'
+ base: 'r'
+ shift, capslock: 'R'
+}
+
+key W {
+ label: 'W'
+ base: 'w'
+ shift, capslock: 'W'
+}
+
+key B {
+ label: 'B'
+ base: 'b'
+ shift, capslock: 'B'
+}
+
+key J {
+ label: 'J'
+ base: 'j'
+ shift, capslock: 'J'
+}
+
+key F {
+ label: 'F'
+ base: 'f'
+ shift, capslock: 'F'
+}
+
+key U {
+ label: 'U'
+ base: 'u'
+ shift, capslock: 'U'
+}
+
+key P {
+ label: 'P'
+ base: 'p'
+ shift, capslock: 'P'
+}
+
+key SEMICOLON {
+ label: ';'
+ base: ';'
+ shift, capslock: ':'
+}
+
+key LEFT_BRACKET {
+ label: '['
+ base: '['
+ shift: '{'
+}
+
+key RIGHT_BRACKET {
+ label: ']'
+ base: ']'
+ shift: '}'
+}
+
+key BACKSLASH {
+ label: '\\'
+ base: '\\'
+ shift: '|'
+}
+
+### ROW 3
+
+key A {
+ label: 'A'
+ base: 'a'
+ shift, capslock: 'A'
+}
+
+key S {
+ label: 'S'
+ base: 's'
+ shift, capslock: 'S'
+}
+
+key H {
+ label: 'H'
+ base: 'h'
+ shift, capslock: 'H'
+}
+
+key T {
+ label: 'T'
+ base: 't'
+ shift, capslock: 'T'
+}
+
+key G {
+ label: 'G'
+ base: 'g'
+ shift, capslock: 'G'
+}
+
+key Y {
+ label: 'Y'
+ base: 'y'
+ shift, capslock: 'Y'
+}
+
+key N {
+ label: 'N'
+ base: 'n'
+ shift, capslock: 'N'
+}
+
+key E {
+ label: 'E'
+ base: 'e'
+ shift, capslock: 'E'
+}
+
+key O {
+ label: 'O'
+ base: 'o'
+ shift: 'O'
+}
+
+key I {
+ label: 'I'
+ base: 'i'
+ shift, capslock: 'I'
+}
+
+key APOSTROPHE {
+ label: '\''
+ base: '\''
+ shift: '"'
+}
+
+### ROW 4
+
+key Z {
+ label: 'Z'
+ base: 'z'
+ shift, capslock: 'Z'
+}
+
+key X {
+ label: 'X'
+ base: 'x'
+ shift, capslock: 'X'
+}
+
+key M {
+ label: 'M'
+ base: 'm'
+ shift, capslock: 'M'
+}
+
+key C {
+ label: 'C'
+ base: 'c'
+ shift, capslock: 'C'
+}
+
+key V {
+ label: 'V'
+ base: 'v'
+ shift, capslock: 'V'
+}
+
+key K {
+ label: 'K'
+ base: 'k'
+ shift, capslock: 'K'
+}
+
+key L {
+ label: 'L'
+ base: 'l'
+ shift, capslock: 'L'
+}
+
+key COMMA {
+ label: ','
+ base: ','
+ shift: '<'
+}
+
+key PERIOD {
+ label: '.'
+ base: '.'
+ shift: '>'
+}
+
+key SLASH {
+ label: '/'
+ base: '/'
+ shift: '?'
+}
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index 968961a..5644c9a 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -21,6 +21,9 @@
<!-- US English (Dvorak style) keyboard layout label. [CHAR LIMIT=35] -->
<string name="keyboard_layout_english_us_dvorak_label">English (US), Dvorak style</string>
+ <!-- US English (Workman style) keyboard layout label. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_english_us_workman_label">English (US), Workman style</string>
+
<!-- German keyboard layout label. [CHAR LIMIT=35] -->
<string name="keyboard_layout_german_label">German</string>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 6f7253c..a302162 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -20,6 +20,10 @@
android:label="@string/keyboard_layout_english_us_dvorak_label"
android:keyboardLayout="@raw/keyboard_layout_english_us_dvorak" />
+ <keyboard-layout android:name="keyboard_layout_english_us_workman"
+ android:label="@string/keyboard_layout_english_us_workman_label"
+ android:keyboardLayout="@raw/keyboard_layout_english_us_workman" />
+
<keyboard-layout android:name="keyboard_layout_german"
android:label="@string/keyboard_layout_german_label"
android:keyboardLayout="@raw/keyboard_layout_german" />
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 8f634e1..8241ddf 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -36,7 +36,6 @@
<style name="RecentsTheme.Wallpaper">
<!-- Wallpaper -->
<item name="android:windowBackground">@color/transparent</item>
- <item name="android:windowResizingBackground">@color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowShowWallpaper">true</item>
</style>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index ae79fe2..c216f97 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -34,6 +34,7 @@
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoader;
import java.util.ArrayList;
@@ -51,7 +52,9 @@
public final static int EVENT_BUS_PRIORITY = 1;
public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
- private SystemServicesProxy mSystemServicesProxy;
+ private static SystemServicesProxy sSystemServicesProxy;
+ private static RecentsTaskLoader sTaskLoader;
+
private Handler mHandler;
private RecentsImpl mImpl;
@@ -118,20 +121,30 @@
return mSystemUserCallbacks;
}
+ public static RecentsTaskLoader getTaskLoader() {
+ return sTaskLoader;
+ }
+
+ public static SystemServicesProxy getSystemServices() {
+ return sSystemServicesProxy;
+ }
+
@Override
public void start() {
- mSystemServicesProxy = new SystemServicesProxy(mContext);
+ sSystemServicesProxy = new SystemServicesProxy(mContext);
+ sTaskLoader = new RecentsTaskLoader(mContext);
mHandler = new Handler();
mImpl = new RecentsImpl(mContext);
// Register with the event bus
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
+ EventBus.getDefault().register(sTaskLoader, EVENT_BUS_PRIORITY);
// Due to the fact that RecentsActivity is per-user, we need to establish and interface for
// the system user's Recents component to pass events (like show/hide/toggleRecents) to the
// secondary user, and vice versa (like visibility change, screen pinning).
- final int processUser = mSystemServicesProxy.getProcessUser();
- if (mSystemServicesProxy.isSystemUser(processUser)) {
+ final int processUser = sSystemServicesProxy.getProcessUser();
+ if (sSystemServicesProxy.isSystemUser(processUser)) {
// For the system user, initialize an instance of the interface that we can pass to the
// secondary user
mSystemUserCallbacks = new RecentsSystemUser(mContext, mImpl);
@@ -153,8 +166,8 @@
*/
@Override
public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
- int currentUser = mSystemServicesProxy.getCurrentUser();
- if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ int currentUser = sSystemServicesProxy.getCurrentUser();
+ if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.showRecents(triggeredFromAltTab);
} else {
if (mSystemUserCallbacks != null) {
@@ -178,8 +191,8 @@
*/
@Override
public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- int currentUser = mSystemServicesProxy.getCurrentUser();
- if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ int currentUser = sSystemServicesProxy.getCurrentUser();
+ if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
} else {
if (mSystemUserCallbacks != null) {
@@ -203,8 +216,8 @@
*/
@Override
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
- int currentUser = mSystemServicesProxy.getCurrentUser();
- if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ int currentUser = sSystemServicesProxy.getCurrentUser();
+ if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.toggleRecents();
} else {
if (mSystemUserCallbacks != null) {
@@ -228,8 +241,8 @@
*/
@Override
public void preloadRecents() {
- int currentUser = mSystemServicesProxy.getCurrentUser();
- if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ int currentUser = sSystemServicesProxy.getCurrentUser();
+ if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.preloadRecents();
} else {
if (mSystemUserCallbacks != null) {
@@ -250,8 +263,8 @@
@Override
public void cancelPreloadingRecents() {
- int currentUser = mSystemServicesProxy.getCurrentUser();
- if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ int currentUser = sSystemServicesProxy.getCurrentUser();
+ if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.cancelPreloadingRecents();
} else {
if (mSystemUserCallbacks != null) {
@@ -284,8 +297,8 @@
* Updates on configuration change.
*/
public void onConfigurationChanged(Configuration newConfig) {
- int currentUser = mSystemServicesProxy.getCurrentUser();
- if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ int currentUser = sSystemServicesProxy.getCurrentUser();
+ if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.onConfigurationChanged();
} else {
if (mSystemUserCallbacks != null) {
@@ -350,7 +363,7 @@
* Attempts to register with the system user.
*/
private void registerWithSystemUser() {
- final int processUser = mSystemServicesProxy.getProcessUser();
+ final int processUser = sSystemServicesProxy.getProcessUser();
postToSystemUser(new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 199d985..331a124 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -47,6 +47,7 @@
import com.android.systemui.recents.events.ui.DismissTaskEvent;
import com.android.systemui.recents.events.ui.ResizeTaskEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
+import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.misc.Console;
@@ -73,6 +74,7 @@
RecentsConfiguration mConfig;
RecentsPackageMonitor mPackageMonitor;
long mLastTabKeyEventTime;
+ boolean mFinishedOnStartup;
// Top level views
RecentsView mRecentsView;
@@ -137,7 +139,7 @@
dismissRecentsToHomeIfVisible(false);
} else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
// When the search activity changes, update the search widget view
- SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ SystemServicesProxy ssp = Recents.getSystemServices();
mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(context, mAppWidgetHost);
refreshSearchWidgetView();
}
@@ -148,7 +150,7 @@
void updateRecentsTasks() {
// If AlternateRecentsComponent has preloaded a load plan, then use that to prevent
// reconstructing the task stack
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan();
if (plan == null) {
plan = loader.createLoadPlan(this);
@@ -241,7 +243,7 @@
/** Dismisses recents if we are already visible and the intent is to toggle the recents view */
boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
RecentsActivityLaunchState launchState = mConfig.getLaunchState();
- SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
// If we currently have filtered stacks, then unfilter those first
if (checkFilteredStackState &&
@@ -284,7 +286,7 @@
/** Dismisses Recents directly to Home if we currently aren't transitioning. */
boolean dismissRecentsToHomeIfVisible(boolean animated) {
- SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
// Return to Home
dismissRecentsToHome(animated);
@@ -297,20 +299,25 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mFinishedOnStartup = false;
+
+ // In the case that the activity starts up before the Recents component has initialized
+ // (usually when debugging/pushing the SysUI apk), just finish this activity.
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp == null) {
+ mFinishedOnStartup = true;
+ finish();
+ return;
+ }
// Register this activity with the event bus
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
- // For the non-primary user, ensure that the SystemServicesProxy and configuration is
- // initialized
- RecentsTaskLoader.initialize(this);
- SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
- mConfig = RecentsConfiguration.initialize(this, ssp);
- mConfig.update(this, ssp, ssp.getWindowRect());
- mPackageMonitor = new RecentsPackageMonitor();
-
// Initialize the widget host (the host id is static and does not change)
+ mConfig = RecentsConfiguration.getInstance();
mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
+ mPackageMonitor = new RecentsPackageMonitor();
+ mPackageMonitor.register(this);
// Set the Recents layout
setContentView(R.layout.recents);
@@ -341,16 +348,6 @@
@Override
protected void onStart() {
super.onStart();
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
- MetricsLogger.visible(this, MetricsLogger.OVERVIEW_ACTIVITY);
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- SystemServicesProxy ssp = loader.getSystemServicesProxy();
-
- // Notify that recents is now visible
- EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, true));
-
- // Register any broadcast receivers for the task loader
- mPackageMonitor.register(this);
// Update the recent tasks
updateRecentsTasks();
@@ -358,6 +355,7 @@
// If this is a new instance from a configuration change, then we have to manually trigger
// the enter animation state, or if recents was relaunched by AM, without going through
// the normal mechanisms
+ RecentsActivityLaunchState launchState = mConfig.getLaunchState();
boolean wasLaunchedByAm = !launchState.launchedFromHome &&
!launchState.launchedFromAppWithThumbnail;
if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
@@ -367,6 +365,12 @@
if (!launchState.launchedHasConfigurationChanged) {
mRecentsView.disableLayersForOneFrame();
}
+
+ // Notify that recents is now visible
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, true));
+
+ MetricsLogger.visible(this, MetricsLogger.OVERVIEW_ACTIVITY);
}
@Override
@@ -381,19 +385,11 @@
@Override
protected void onStop() {
super.onStop();
- MetricsLogger.hidden(this, MetricsLogger.OVERVIEW_ACTIVITY);
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- SystemServicesProxy ssp = loader.getSystemServicesProxy();
// Notify that recents is now hidden
+ SystemServicesProxy ssp = Recents.getSystemServices();
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, false));
- // Notify the views that we are no longer visible
- mRecentsView.onRecentsHidden();
-
- // Unregister any broadcast receivers for the task loader
- mPackageMonitor.unregister();
-
// Workaround for b/22542869, if the RecentsActivity is started again, but without going
// through SystemUI, we need to reset the config launch flags to ensure that we do not
// wait on the system to send a signal that was never queued.
@@ -404,15 +400,25 @@
launchState.launchedToTaskId = -1;
launchState.launchedWithAltTab = false;
launchState.launchedHasConfigurationChanged = false;
+
+ MetricsLogger.hidden(this, MetricsLogger.OVERVIEW_ACTIVITY);
}
@Override
protected void onDestroy() {
super.onDestroy();
+ // In the case that the activity finished on startup, just skip the unregistration below
+ if (mFinishedOnStartup) {
+ return;
+ }
+
// Unregister the system broadcast receivers
unregisterReceiver(mSystemBroadcastReceiver);
+ // Unregister any broadcast receivers for the task loader
+ mPackageMonitor.unregister();
+
// Stop listening for widget package changes if there was one bound
mAppWidgetHost.stopListening();
EventBus.getDefault().unregister(this);
@@ -432,7 +438,7 @@
@Override
public void onTrimMemory(int level) {
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ RecentsTaskLoader loader = Recents.getTaskLoader();
if (loader != null) {
loader.onTrimMemory(level);
}
@@ -477,7 +483,7 @@
@Override
public void onUserInteraction() {
- mRecentsView.onUserInteraction();
+ EventBus.getDefault().send(new UserInteractionEvent());
}
@Override
@@ -559,9 +565,8 @@
public final void onBusEvent(ShowApplicationInfoEvent event) {
// Create a new task stack with the application info details activity
- Intent baseIntent = event.task.key.baseIntent;
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
- Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null));
+ Uri.fromParts("package", event.task.key.getComponent().getPackageName(), null));
intent.setComponent(intent.resolveActivity(getPackageManager()));
TaskStackBuilder.create(this)
.addNextIntentWithParentStack(intent).startActivities(null,
@@ -573,11 +578,12 @@
public final void onBusEvent(DismissTaskEvent event) {
// Remove any stored data from the loader
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ RecentsTaskLoader loader = Recents.getTaskLoader();
loader.deleteTaskData(event.task, false);
// Remove the task from activity manager
- loader.getSystemServicesProxy().removeTask(event.task.key.id);
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.removeTask(event.task.key.id);
}
public final void onBusEvent(ResizeTaskEvent event) {
@@ -602,7 +608,7 @@
private void refreshSearchWidgetView() {
if (mSearchWidgetInfo != null) {
- SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ SystemServicesProxy ssp = Recents.getSystemServices();
int searchWidgetId = ssp.getSearchAppWidgetId(this);
mSearchWidgetHostView = (RecentsAppWidgetHostView) mAppWidgetHost.createView(
this, searchWidgetId, mSearchWidgetInfo);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index f60dcb2..563956b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -111,8 +111,7 @@
void update(Context context, SystemServicesProxy ssp, Rect windowRect) {
// Only update resources that can change after the first load, either through developer
// settings or via multi window
- lockToAppEnabled = ssp.getSystemSetting(context,
- Settings.System.LOCK_TO_APP_ENABLED) != 0;
+ lockToAppEnabled = ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0;
hasDockedTasks = ssp.hasDockedTask();
// Recompute some values based on the given state, since we can not rely on the resource
@@ -143,8 +142,10 @@
return mLaunchState;
}
- /** Called when the configuration has changed, and we want to reset any configuration specific
- * members. */
+ /**
+ * Called when the configuration has changed, and we want to reset any configuration specific
+ * members.
+ */
public void updateOnConfigurationChange() {
mLaunchState.updateOnConfigurationChange();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 0d1a54e..aaeb10c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -26,7 +26,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
-import android.os.AsyncTask;
+import android.graphics.RectF;
import android.os.Handler;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -44,6 +44,7 @@
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.ForegroundThread;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.recents.model.RecentsTaskLoader;
@@ -66,6 +67,7 @@
implements ActivityOptions.OnAnimationStartedListener {
private final static String TAG = "RecentsImpl";
+ private final static boolean DEBUG = false;
private final static int sMinToggleDelay = 350;
@@ -97,8 +99,8 @@
/*
RecentsConfiguration config = RecentsConfiguration.getInstance();
if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- SystemServicesProxy ssp = loader.getSystemServicesProxy();
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+ SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
// Load the next task only if we aren't svelte
@@ -121,10 +123,9 @@
}
}
- static RecentsTaskLoadPlan sInstanceLoadPlan;
+ private static RecentsTaskLoadPlan sInstanceLoadPlan;
Context mContext;
- SystemServicesProxy mSystemServicesProxy;
Handler mHandler;
TaskStackListenerImpl mTaskStackListener;
RecentsAppWidgetHost mAppWidgetHost;
@@ -158,19 +159,21 @@
public RecentsImpl(Context context) {
mContext = context;
- mSystemServicesProxy = new SystemServicesProxy(mContext);
mHandler = new Handler();
mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
Resources res = mContext.getResources();
- RecentsTaskLoader.initialize(mContext);
LayoutInflater inflater = LayoutInflater.from(mContext);
+ // Initialize the static foreground thread
+ ForegroundThread.get();
+
// Register the task stack listener
mTaskStackListener = new TaskStackListenerImpl(mHandler);
- mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.registerTaskStackListener(mTaskStackListener);
// Initialize the static configuration resources
- mConfig = RecentsConfiguration.initialize(mContext, mSystemServicesProxy);
+ mConfig = RecentsConfiguration.initialize(mContext, ssp);
mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
@@ -182,7 +185,7 @@
// When we start, preload the data associated with the previous recent tasks.
// We can use a new plan since the caches will be the same.
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true /* isTopTaskHome */);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
@@ -236,9 +239,10 @@
try {
// Check if the top task is in the home stack, and start the recents activity
- ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
MutableBoolean isTopTaskHome = new MutableBoolean(true);
- if (topTask == null || !mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
+ if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
startRecentsActivity(topTask, isTopTaskHome.value);
}
} catch (ActivityNotFoundException e) {
@@ -251,7 +255,7 @@
if (mBootCompleted) {
// Defer to the activity to handle hiding recents, if it handles it, then it must still
// be visible
- EventBus.getDefault().send(new HideRecentsEvent(triggeredFromAltTab,
+ EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
triggeredFromHomeKey));
}
}
@@ -270,11 +274,12 @@
// If Recents is the front most activity, then we should just communicate with it
// directly to launch the first task or dismiss itself
- ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
MutableBoolean isTopTaskHome = new MutableBoolean(true);
- if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
+ if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
// Notify recents to toggle itself
- EventBus.getDefault().send(new ToggleRecentsEvent());
+ EventBus.getDefault().post(new ToggleRecentsEvent());
mLastToggleTime = SystemClock.elapsedRealtime();
return;
} else {
@@ -290,15 +295,18 @@
public void preloadRecents() {
// Preload only the raw task list into a new load plan (which will be consumed by the
// RecentsActivity) only if there is a task to animate to.
- ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
MutableBoolean topTaskHome = new MutableBoolean(true);
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ RecentsTaskLoader loader = Recents.getTaskLoader();
sInstanceLoadPlan = loader.createLoadPlan(mContext);
- if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
+ if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
if (stack.getTaskCount() > 0) {
+ // We try and draw the thumbnail transition bitmap in parallel before
+ // toggle/show recents is called
preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
}
}
@@ -311,8 +319,9 @@
public void showRelativeAffiliatedTask(boolean showNextTask) {
// Return early if there is no focused stack
- int focusedStackId = mSystemServicesProxy.getFocusedStack();
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ int focusedStackId = ssp.getFocusedStack();
+ RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true /* isTopTaskHome */);
TaskStack focusedStack = plan.getTaskStack();
@@ -320,11 +329,11 @@
// Return early if there are no tasks in the focused stack
if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
- ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask();
+ ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
// Return early if there is no running task (can't determine affiliated tasks in this case)
if (runningTask == null) return;
// Return early if the running task is in the home stack (optimization)
- if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;
+ if (ssp.isInHomeStack(runningTask.id)) return;
// Find the task in the recents list
ArrayList<Task> tasks = focusedStack.getTasks();
@@ -360,11 +369,11 @@
if (toTask == null) {
if (numAffiliatedTasks > 1) {
if (showNextTask) {
- mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
+ ssp.startInPlaceAnimationOnFrontMostApplication(
ActivityOptions.makeCustomInPlaceAnimation(mContext,
R.anim.recents_launch_next_affiliated_task_bounce));
} else {
- mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
+ ssp.startInPlaceAnimationOnFrontMostApplication(
ActivityOptions.makeCustomInPlaceAnimation(mContext,
R.anim.recents_launch_prev_affiliated_task_bounce));
}
@@ -378,10 +387,9 @@
// Launch the task
if (toTask.isActive) {
// Bring an active task to the foreground
- mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts);
+ ssp.moveTaskToFront(toTask.key.id, launchOpts);
} else {
- mSystemServicesProxy.startActivityFromRecents(mContext, toTask.key.id,
- toTask.activityLabel, launchOpts);
+ ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts);
}
}
@@ -414,18 +422,18 @@
* is not already bound (can be expensive)
*/
private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
- Rect windowRect = mSystemServicesProxy.getWindowRect();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ Rect windowRect = ssp.getWindowRect();
// Update the configuration for the current state
- mConfig.update(mContext, mSystemServicesProxy, mSystemServicesProxy.getWindowRect());
+ mConfig.update(mContext, ssp, ssp.getWindowRect());
if (tryAndBindSearchWidget) {
// Try and pre-emptively bind the search widget on startup to ensure that we
// have the right thumbnail bounds to animate to.
// Note: We have to reload the widget id before we get the task stack bounds below
- if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
- mConfig.getSearchBarBounds(windowRect,
- mStatusBarHeight, mSearchBarBounds);
+ if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
+ mConfig.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
}
}
Rect systemInsets = new Rect(0, mStatusBarHeight,
@@ -457,13 +465,12 @@
* Preloads the icon of a task.
*/
private void preloadIcon(ActivityManager.RunningTaskInfo task) {
-
// Ensure that we load the running task's icon
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
launchOpts.runningTaskId = task.id;
launchOpts.loadThumbnails = false;
launchOpts.onlyLoadForCache = true;
- RecentsTaskLoader.getInstance().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
+ Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
}
/**
@@ -482,25 +489,25 @@
final Task toTask = new Task();
final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
topTask.id, toTask);
- new AsyncTask<Void, Void, Bitmap>() {
+ ForegroundThread.getHandler().post(new Runnable() {
@Override
- protected Bitmap doInBackground(Void... params) {
- return drawThumbnailTransitionBitmap(toTask, toTransform);
+ public void run() {
+ final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mThumbnailTransitionBitmapCache = transitionBitmap;
+ mThumbnailTransitionBitmapCacheKey = toTask;
+ }
+ });
}
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- mThumbnailTransitionBitmapCache = bitmap;
- mThumbnailTransitionBitmapCacheKey = toTask;
- }
- }.execute();
+ });
}
/**
* Creates the activity options for a unknown state->recents transition.
*/
private ActivityOptions getUnknownTransitionActivityOptions() {
- mStartAnimationTriggered = false;
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_unknown_enter,
R.anim.recents_from_unknown_exit,
@@ -511,7 +518,6 @@
* Creates the activity options for a home->recents transition.
*/
private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
- mStartAnimationTriggered = false;
if (fromSearchHome) {
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_search_launcher_enter,
@@ -534,7 +540,7 @@
Task toTask = new Task();
TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
topTask.id, toTask);
- Rect toTaskRect = toTransform.rect;
+ RectF toTaskRect = toTransform.rect;
Bitmap thumbnail;
if (mThumbnailTransitionBitmapCacheKey != null
&& mThumbnailTransitionBitmapCacheKey.key != null
@@ -547,10 +553,9 @@
thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
}
if (thumbnail != null) {
- mStartAnimationTriggered = false;
return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
- thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
- toTaskRect.height(), mHandler, this);
+ thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
+ (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
}
// If both the screenshot and thumbnail fails, then just fall back to the default transition
@@ -621,7 +626,8 @@
*/
private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
boolean isTopTaskHome) {
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ RecentsTaskLoader loader = Recents.getTaskLoader();
// Update the header bar if necessary
reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
@@ -660,9 +666,9 @@
// If there is no thumbnail transition, but is launching from home into recents, then
// use a quick home transition and do the animation from home
if (hasRecentTasks) {
- String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName();
- String searchWidgetPackage =
- Prefs.getString(mContext, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
+ String homeActivityPackage = ssp.getHomeActivityPackageName();
+ String searchWidgetPackage = Prefs.getString(mContext,
+ Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
// Determine whether we are coming from a search owned home activity
boolean fromSearchHome = (homeActivityPackage != null) &&
@@ -686,6 +692,8 @@
private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
+ mStartAnimationTriggered = false;
+
// Update the configuration based on the launch options
RecentsActivityLaunchState launchState = mConfig.getLaunchState();
launchState.launchedFromHome = fromSearchHome || fromHome;
@@ -718,7 +726,7 @@
// Notify recents to start the enter animation
if (!mStartAnimationTriggered) {
mStartAnimationTriggered = true;
- EventBus.getDefault().send(new EnterRecentsWindowAnimationStartedEvent());
+ EventBus.getDefault().post(new EnterRecentsWindowAnimationStartedEvent());
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
index 7735ba9..31ee8ad 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -30,7 +30,6 @@
import android.widget.Toast;
import com.android.systemui.R;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.views.RecentsView;
@@ -80,7 +79,6 @@
private View mResizeTaskDialogContent;
private RecentsActivity mRecentsActivity;
private RecentsView mRecentsView;
- private SystemServicesProxy mSsp;
private Rect[] mBounds = {new Rect(), new Rect(), new Rect(), new Rect()};
private Task[] mTasks = {null, null, null, null};
@@ -93,7 +91,6 @@
public RecentsResizeTaskDialog(FragmentManager mgr, RecentsActivity activity) {
mFragmentManager = mgr;
mRecentsActivity = activity;
- mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
}
/** Shows the resize-task dialog. */
@@ -144,7 +141,8 @@
/** Helper function to place window(s) on the display according to an arrangement request. */
private void placeTasks(int arrangement) {
- Rect rect = mSsp.getDisplayRect();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ Rect rect = ssp.getDisplayRect();
for (int i = 0; i < mBounds.length; ++i) {
mBounds[i].set(rect);
if (i != 0) {
@@ -240,7 +238,7 @@
// current app configuration.
for (int i = additionalTasks; i >= 0; --i) {
if (mTasks[i] != null) {
- mSsp.setTaskResizeable(mTasks[i].key.id);
+ ssp.setTaskResizeable(mTasks[i].key.id);
}
}
@@ -278,8 +276,9 @@
if (mTasks[0].key.stackId != DOCKED_STACK_ID) {
int taskId = mTasks[0].key.id;
- mSsp.setTaskResizeable(taskId);
- mSsp.dockTask(taskId, createMode);
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.setTaskResizeable(taskId);
+ ssp.dockTask(taskId, createMode);
mRecentsView.launchTask(mTasks[0], null, DOCKED_STACK_ID);
} else {
Toast.makeText(getContext(), "Already docked", Toast.LENGTH_SHORT);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
index ef543d0..fec0fc5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -208,7 +208,7 @@
*
* Events should not be edited by subscribers.
*/
- public static class Event {
+ public static class Event implements Cloneable {
// Indicates that this event's dispatch should be traced and logged to logcat
boolean trace;
// Indicates that this event must be posted on the EventBus's looper thread before invocation
@@ -218,6 +218,14 @@
// Only accessible from derived events
protected Event() {}
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ Event evt = (Event) super.clone();
+ // When cloning an event, reset the cancelled-dispatch state
+ evt.cancelled = false;
+ return evt;
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
index 6769716..6e6cd84 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.recents.model;
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
/**
- * The String LRU cache.
+ * This is sent whenever the user interacts with the activity.
*/
-class StringLruCache extends KeyStoreLruCache<String> {
- public StringLruCache(int cacheSize) {
- super(cacheSize);
- }
-}
\ No newline at end of file
+public class UserInteractionEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
new file mode 100644
index 0000000..8dc2983
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * Similar to {@link com.android.internal.os.BackgroundThread}, this is a shared singleton
+ * foreground thread for each process.
+ */
+public final class ForegroundThread extends HandlerThread {
+ private static ForegroundThread sInstance;
+ private static Handler sHandler;
+
+ private ForegroundThread() {
+ super("recents.fg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ }
+
+ private static void ensureThreadLocked() {
+ if (sInstance == null) {
+ sInstance = new ForegroundThread();
+ sInstance.start();
+ sHandler = new Handler(sInstance.getLooper());
+ }
+ }
+
+ public static ForegroundThread get() {
+ synchronized (ForegroundThread.class) {
+ ensureThreadLocked();
+ return sInstance;
+ }
+ }
+
+ public static Handler getHandler() {
+ synchronized (ForegroundThread.class) {
+ ensureThreadLocked();
+ return sHandler;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index dab2c65..a51e475 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -46,8 +46,6 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -61,6 +59,7 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.app.AssistUtils;
+import com.android.internal.os.BackgroundThread;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
@@ -83,12 +82,8 @@
final static String TAG = "SystemServicesProxy";
final static BitmapFactory.Options sBitmapOptions;
- final static HandlerThread sBgThread;
static {
- sBgThread = new HandlerThread("Recents-SystemServicesProxy",
- android.os.Process.THREAD_PRIORITY_BACKGROUND);
- sBgThread.start();
sBitmapOptions = new BitmapFactory.Options();
sBitmapOptions.inMutable = true;
}
@@ -106,8 +101,6 @@
String mRecentsPackage;
ComponentName mAssistComponent;
- Handler mBgThreadHandler;
-
Bitmap mDummyIcon;
int mDummyThumbnailWidth;
int mDummyThumbnailHeight;
@@ -127,7 +120,6 @@
mUm = UserManager.get(context);
mDisplay = mWm.getDefaultDisplay();
mRecentsPackage = context.getPackageName();
- mBgThreadHandler = new Handler(sBgThread.getLooper());
// Get the dummy thumbnail width/heights
Resources res = context.getResources();
@@ -418,7 +410,7 @@
if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
// Remove the task.
- mBgThreadHandler.post(new Runnable() {
+ BackgroundThread.getHandler().post(new Runnable() {
@Override
public void run() {
mAm.removeTask(taskId);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 93c5ee7..2bf2ccb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -18,13 +18,10 @@
import android.animation.Animator;
import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Rect;
+import android.graphics.RectF;
import android.view.View;
import android.view.ViewParent;
-import java.util.ArrayList;
-
/* Common code */
public class Utilities {
@@ -45,93 +42,19 @@
}
/** Scales a rect about its centroid */
- public static void scaleRectAboutCenter(Rect r, float scale) {
+ public static void scaleRectAboutCenter(RectF r, float scale) {
if (scale != 1.0f) {
- int cx = r.centerX();
- int cy = r.centerY();
+ float cx = r.centerX();
+ float cy = r.centerY();
r.offset(-cx, -cy);
- r.left = (int) (r.left * scale + 0.5f);
- r.top = (int) (r.top * scale + 0.5f);
- r.right = (int) (r.right * scale + 0.5f);
- r.bottom = (int) (r.bottom * scale + 0.5f);
+ r.left *= scale;
+ r.top *= scale;
+ r.right *= scale;
+ r.bottom *= scale;
r.offset(cx, cy);
}
}
- /** Maps a coorindate in a descendant view into the parent. */
- public static float mapCoordInDescendentToSelf(View descendant, View root,
- float[] coord, boolean includeRootScroll) {
- ArrayList<View> ancestorChain = new ArrayList<View>();
-
- float[] pt = {coord[0], coord[1]};
-
- View v = descendant;
- while(v != root && v != null) {
- ancestorChain.add(v);
- v = (View) v.getParent();
- }
- ancestorChain.add(root);
-
- float scale = 1.0f;
- int count = ancestorChain.size();
- for (int i = 0; i < count; i++) {
- View v0 = ancestorChain.get(i);
- // For TextViews, scroll has a meaning which relates to the text position
- // which is very strange... ignore the scroll.
- if (v0 != descendant || includeRootScroll) {
- pt[0] -= v0.getScrollX();
- pt[1] -= v0.getScrollY();
- }
-
- v0.getMatrix().mapPoints(pt);
- pt[0] += v0.getLeft();
- pt[1] += v0.getTop();
- scale *= v0.getScaleX();
- }
-
- coord[0] = pt[0];
- coord[1] = pt[1];
- return scale;
- }
-
- /** Maps a coordinate in the root to a descendent. */
- public static float mapCoordInSelfToDescendent(View descendant, View root,
- float[] coord, Matrix tmpInverseMatrix) {
- ArrayList<View> ancestorChain = new ArrayList<View>();
-
- float[] pt = {coord[0], coord[1]};
-
- View v = descendant;
- while(v != root) {
- ancestorChain.add(v);
- v = (View) v.getParent();
- }
- ancestorChain.add(root);
-
- float scale = 1.0f;
- int count = ancestorChain.size();
- tmpInverseMatrix.set(Matrix.IDENTITY_MATRIX);
- for (int i = count - 1; i >= 0; i--) {
- View ancestor = ancestorChain.get(i);
- View next = i > 0 ? ancestorChain.get(i-1) : null;
-
- pt[0] += ancestor.getScrollX();
- pt[1] += ancestor.getScrollY();
-
- if (next != null) {
- pt[0] -= next.getLeft();
- pt[1] -= next.getTop();
- next.getMatrix().invert(tmpInverseMatrix);
- tmpInverseMatrix.mapPoints(pt);
- scale *= next.getScaleX();
- }
- }
-
- coord[0] = pt[0];
- coord[1] = pt[1];
- return scale;
- }
-
/** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
public static float computeContrastBetweenColors(int bg, int fg) {
float bgR = Color.red(bg) / 255f;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
deleted file mode 100644
index 624a8ff..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.systemui.recents.model;
-
-import android.graphics.Bitmap;
-
-/**
- * The Bitmap LRU cache.
- */
-class BitmapLruCache extends KeyStoreLruCache<Bitmap> {
- public BitmapLruCache(int cacheSize) {
- super(cacheSize);
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
deleted file mode 100644
index 01a515b..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.systemui.recents.model;
-
-import android.graphics.drawable.Drawable;
-
-/**
- * The Drawable LRU cache.
- */
-class DrawableLruCache extends KeyStoreLruCache<Drawable> {
- public DrawableLruCache(int cacheSize) {
- super(cacheSize);
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
deleted file mode 100644
index 97e0916..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.systemui.recents.model;
-
-import android.util.LruCache;
-
-import java.util.HashMap;
-
-/**
- * An LRU cache that internally support querying the keys as well as values. We use this to keep
- * track of the task metadata to determine when to invalidate the cache when tasks have been
- * updated. Generally, this cache will return the last known cache value for the requested task
- * key.
- */
-public class KeyStoreLruCache<V> {
- // We keep a set of keys that are associated with the LRU cache, so that we can find out
- // information about the Task that was previously in the cache.
- HashMap<Integer, Task.TaskKey> mTaskKeys = new HashMap<Integer, Task.TaskKey>();
- // The cache implementation, mapping task id -> value
- LruCache<Integer, V> mCache;
-
- public KeyStoreLruCache(int cacheSize) {
- mCache = new LruCache<Integer, V>(cacheSize) {
-
- @Override
- protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) {
- mTaskKeys.remove(taskId);
- }
- };
- }
-
- /** Gets a specific entry in the cache. */
- final V get(Task.TaskKey key) {
- return mCache.get(key.id);
- }
-
- /**
- * Returns the value only if the Task has not updated since the last time it was in the cache.
- */
- final V getAndInvalidateIfModified(Task.TaskKey key) {
- Task.TaskKey lastKey = mTaskKeys.get(key.id);
- if (lastKey != null && (lastKey.lastActiveTime < key.lastActiveTime)) {
- // The task has updated (been made active since the last time it was put into the
- // LRU cache) so invalidate that item in the cache
- remove(key);
- return null;
- }
- // Either the task does not exist in the cache, or the last active time is the same as
- // the key specified, so return what is in the cache
- return mCache.get(key.id);
- }
-
- /** Puts an entry in the cache for a specific key. */
- final void put(Task.TaskKey key, V value) {
- mCache.put(key.id, value);
- mTaskKeys.put(key.id, key);
- }
-
- /** Removes a cache entry for a specific key. */
- final void remove(Task.TaskKey key) {
- mCache.remove(key.id);
- mTaskKeys.remove(key.id);
- }
-
- /** Removes all the entries in the cache. */
- final void evictAll() {
- mCache.evictAll();
- mTaskKeys.clear();
- }
-
- /** Returns the size of the cache. */
- final int size() {
- return mCache.size();
- }
-
- /** Trims the cache to a specific size */
- final void trimToSize(int cacheSize) {
- mCache.resize(cacheSize);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index 8f9a293..d9057b8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -16,17 +16,12 @@
package com.android.systemui.recents.model;
-import android.content.ComponentName;
import android.content.Context;
-import android.os.Looper;
import android.os.UserHandle;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.BackgroundThread;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-
-import java.util.HashSet;
-import java.util.List;
/**
* The package monitor listens for changes from PackageManager to update the contents of the
@@ -38,8 +33,9 @@
public void register(Context context) {
try {
// We register for events from all users, but will cross-reference them with
- // packages for the current user and any profiles they have
- register(context, Looper.getMainLooper(), UserHandle.ALL, true);
+ // packages for the current user and any profiles they have. Ensure that events are
+ // handled in a background thread.
+ register(context, BackgroundThread.get().getLooper(), UserHandle.ALL, true);
} catch (IllegalStateException e) {
e.printStackTrace();
}
@@ -57,9 +53,9 @@
@Override
public void onPackageRemoved(String packageName, int uid) {
- // Notify callbacks that a package has changed
+ // Notify callbacks on the main thread that a package has changed
final int eventUserId = getChangingUserId();
- EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId));
+ EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
}
@Override
@@ -70,39 +66,8 @@
@Override
public void onPackageModified(String packageName) {
- // Notify callbacks that a package has changed
+ // Notify callbacks on the main thread that a package has changed
final int eventUserId = getChangingUserId();
- EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId));
- }
-
- /**
- * Computes the components that have been removed as a result of a change in the specified
- * package.
- */
- public HashSet<ComponentName> computeComponentsRemoved(List<Task.TaskKey> taskKeys,
- String packageName, int userId) {
- // Identify all the tasks that should be removed as a result of the package being removed.
- // Using a set to ensure that we callback once per unique component.
- HashSet<ComponentName> existingComponents = new HashSet<ComponentName>();
- HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
- for (Task.TaskKey t : taskKeys) {
- // Skip if this doesn't apply to the current user
- if (t.userId != userId) continue;
-
- ComponentName cn = t.baseIntent.getComponent();
- if (cn.getPackageName().equals(packageName)) {
- if (existingComponents.contains(cn)) {
- // If we know that the component still exists in the package, then skip
- continue;
- }
- SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
- if (ssp.getActivityInfo(cn, userId) != null) {
- existingComponents.add(cn);
- } else {
- removedComponents.add(cn);
- }
- }
- }
- return removedComponents;
+ EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 6ef7253..8de8e15 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -23,12 +23,12 @@
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.Log;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
@@ -41,8 +41,10 @@
* options specified, such that we can transition into the Recents activity seamlessly
*/
public class RecentsTaskLoadPlan {
- static String TAG = "RecentsTaskLoadPlan";
- static boolean DEBUG = false;
+ private static String TAG = "RecentsTaskLoadPlan";
+ private static boolean DEBUG = false;
+
+ private static int INVALID_TASK_ID = -1;
/** The set of conditions to load tasks. */
public static class Options {
@@ -57,25 +59,22 @@
Context mContext;
RecentsConfiguration mConfig;
- SystemServicesProxy mSystemServicesProxy;
List<ActivityManager.RecentTaskInfo> mRawTasks;
TaskStack mStack;
- HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache =
- new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
/** Package level ctor */
- RecentsTaskLoadPlan(Context context, RecentsConfiguration config, SystemServicesProxy ssp) {
+ RecentsTaskLoadPlan(Context context, RecentsConfiguration config) {
mContext = context;
mConfig = config;
- mSystemServicesProxy = ssp;
}
/**
* An optimization to preload the raw list of tasks.
*/
public synchronized void preloadRawTasks(boolean isTopTaskHome) {
- mRawTasks = mSystemServicesProxy.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
Collections.reverse(mRawTasks);
@@ -87,12 +86,10 @@
* have a list of all the recent tasks with their metadata, not including icons or
* thumbnails which were not cached and have to be loaded.
*/
- synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
+ public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
if (DEBUG) Log.d(TAG, "preloadPlan");
- // This activity info cache will be used for both preloadPlan() and executePlan()
- mActivityInfoCache.clear();
-
+ SystemServicesProxy ssp = Recents.getSystemServices();
Resources res = mContext.getResources();
ArrayList<Task> stackTasks = new ArrayList<>();
if (mRawTasks == null) {
@@ -106,30 +103,14 @@
Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
t.userId, t.firstActiveTime, t.lastActiveTime);
- // Get an existing activity info handle if possible
- Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
- ActivityInfoHandle infoHandle;
- boolean hadCachedActivityInfo = false;
- if (mActivityInfoCache.containsKey(cnKey)) {
- infoHandle = mActivityInfoCache.get(cnKey);
- hadCachedActivityInfo = true;
- } else {
- infoHandle = new ActivityInfoHandle();
- }
-
// Load the label, icon, and color
String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription,
- mSystemServicesProxy, infoHandle);
+ ssp);
String contentDescription = loader.getAndUpdateContentDescription(taskKey,
- activityLabel, mSystemServicesProxy, res);
- Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
- mSystemServicesProxy, res, infoHandle, false);
- int activityColor = loader.getActivityPrimaryColor(t.taskDescription, res);
-
- // Update the activity info cache
- if (!hadCachedActivityInfo && infoHandle.info != null) {
- mActivityInfoCache.put(cnKey, infoHandle);
- }
+ activityLabel, ssp, res);
+ Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, ssp,
+ res, false);
+ int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
Bitmap icon = t.taskDescription != null
? t.taskDescription.getInMemoryIcon()
@@ -139,11 +120,11 @@
: null;
// Add the task to the stack
- Task task = new Task(taskKey, (t.id != RecentsTaskLoader.INVALID_TASK_ID),
- t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, contentDescription,
- activityIcon, activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled,
- icon, iconFilename);
- task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
+ Task task = new Task(taskKey, (t.id != INVALID_TASK_ID), t.affiliatedTaskId,
+ t.affiliatedTaskColor, activityLabel, contentDescription, activityIcon,
+ activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, icon,
+ iconFilename);
+ task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
stackTasks.add(task);
@@ -158,12 +139,13 @@
/**
* Called to apply the actual loading based on the specified conditions.
*/
- synchronized void executePlan(Options opts, RecentsTaskLoader loader,
+ public synchronized void executePlan(Options opts, RecentsTaskLoader loader,
TaskResourceLoadQueue loadQueue) {
if (DEBUG) Log.d(TAG, "executePlan, # tasks: " + opts.numVisibleTasks +
", # thumbnails: " + opts.numVisibleTaskThumbnails +
", running task id: " + opts.runningTaskId);
+ SystemServicesProxy ssp = Recents.getSystemServices();
Resources res = mContext.getResources();
// Iterate through each of the tasks and load them according to the load conditions.
@@ -174,17 +156,6 @@
Task task = tasks.get(i);
Task.TaskKey taskKey = task.key;
- // Get an existing activity info handle if possible
- Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
- ActivityInfoHandle infoHandle;
- boolean hadCachedActivityInfo = false;
- if (mActivityInfoCache.containsKey(cnKey)) {
- infoHandle = mActivityInfoCache.get(cnKey);
- hadCachedActivityInfo = true;
- } else {
- infoHandle = new ActivityInfoHandle();
- }
-
boolean isRunningTask = (task.key.id == opts.runningTaskId);
boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
@@ -197,26 +168,20 @@
if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
if (task.activityIcon == null) {
if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
- task.activityIcon = loader.getAndUpdateActivityIcon(taskKey,
- t.taskDescription, mSystemServicesProxy, res, infoHandle, true);
+ task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
+ ssp, res, true);
}
}
if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
if (task.thumbnail == null || isRunningTask) {
if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
- task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
- mSystemServicesProxy, true);
+ task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, true);
} else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
loadQueue.addTask(task);
}
}
}
-
- // Update the activity info cache
- if (!hadCachedActivityInfo && infoHandle.info != null) {
- mActivityInfoCache.put(cnKey, infoHandle);
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 39bef81..ea97b71 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -18,6 +18,7 @@
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
@@ -27,20 +28,21 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
+import android.util.LruCache;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
-/** Handle to an ActivityInfo */
-class ActivityInfoHandle {
- ActivityInfo info;
-}
-
-/** A bitmap load queue */
+/**
+ * A Task load queue
+ */
class TaskResourceLoadQueue {
ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
@@ -78,8 +80,10 @@
}
}
-/* Task resource loader */
-class TaskResourceLoader implements Runnable {
+/**
+ * Task resource loader
+ */
+class BackgroundTaskLoader implements Runnable {
static String TAG = "TaskResourceLoader";
static boolean DEBUG = false;
@@ -88,10 +92,9 @@
Handler mLoadThreadHandler;
Handler mMainThreadHandler;
- SystemServicesProxy mSystemServicesProxy;
TaskResourceLoadQueue mLoadQueue;
- DrawableLruCache mApplicationIconCache;
- BitmapLruCache mThumbnailCache;
+ TaskKeyLruCache<Drawable> mApplicationIconCache;
+ TaskKeyLruCache<Bitmap> mThumbnailCache;
Bitmap mDefaultThumbnail;
BitmapDrawable mDefaultApplicationIcon;
@@ -99,9 +102,9 @@
boolean mWaitingOnLoadQueue;
/** Constructor, creates a new loading thread that loads task resources in the background */
- public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache applicationIconCache,
- BitmapLruCache thumbnailCache, Bitmap defaultThumbnail,
- BitmapDrawable defaultApplicationIcon) {
+ public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
+ TaskKeyLruCache<Drawable> applicationIconCache, TaskKeyLruCache<Bitmap> thumbnailCache,
+ Bitmap defaultThumbnail, BitmapDrawable defaultApplicationIcon) {
mLoadQueue = loadQueue;
mApplicationIconCache = applicationIconCache;
mThumbnailCache = thumbnailCache;
@@ -119,7 +122,6 @@
void start(Context context) {
mContext = context;
mCancelled = false;
- mSystemServicesProxy = new SystemServicesProxy(context);
// Notify the load thread to start loading
synchronized(mLoadThread) {
mLoadThread.notifyAll();
@@ -130,7 +132,6 @@
void stop() {
// Mark as cancelled for the thread to pick up
mCancelled = true;
- mSystemServicesProxy = null;
// If we are waiting for the load queue for more tasks, then we can just reset the
// Context now, since nothing is using it
if (mWaitingOnLoadQueue) {
@@ -155,7 +156,7 @@
}
} else {
RecentsConfiguration config = RecentsConfiguration.getInstance();
- SystemServicesProxy ssp = mSystemServicesProxy;
+ SystemServicesProxy ssp = Recents.getSystemServices();
// If we've stopped the loader, then fall through to the above logic to wait on
// the load thread
if (ssp != null) {
@@ -172,7 +173,7 @@
if (cachedIcon == null) {
ActivityInfo info = ssp.getActivityInfo(
- t.key.baseIntent.getComponent(), t.key.userId);
+ t.key.getComponent(), t.key.userId);
if (info != null) {
if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
cachedIcon = ssp.getActivityIcon(info, t.key.userId);
@@ -246,201 +247,67 @@
}
}
-/* Recents task loader
- * NOTE: We should not hold any references to non-application Context from a static instance */
+/**
+ * Recents task loader
+ */
public class RecentsTaskLoader {
+
private static final String TAG = "RecentsTaskLoader";
+ private static final boolean DEBUG = false;
- static RecentsTaskLoader sInstance;
- static int INVALID_TASK_ID = -1;
+ // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
+ // for many tasks, which we use to get the activity labels and icons. Unlike the other caches
+ // below, this is per-package so we can't invalidate the items in the cache based on the last
+ // active time. Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a
+ // package in the cache has been updated, so that we may remove it.
+ private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
+ private final TaskKeyLruCache<Drawable> mApplicationIconCache;
+ private final TaskKeyLruCache<Bitmap> mThumbnailCache;
+ private final TaskKeyLruCache<String> mActivityLabelCache;
+ private final TaskKeyLruCache<String> mContentDescriptionCache;
+ private final TaskResourceLoadQueue mLoadQueue;
+ private final BackgroundTaskLoader mLoader;
- SystemServicesProxy mSystemServicesProxy;
- DrawableLruCache mApplicationIconCache;
- BitmapLruCache mThumbnailCache;
- StringLruCache mActivityLabelCache;
- StringLruCache mContentDescriptionCache;
- TaskResourceLoadQueue mLoadQueue;
- TaskResourceLoader mLoader;
+ private final int mMaxThumbnailCacheSize;
+ private final int mMaxIconCacheSize;
+ private int mNumVisibleTasksLoaded;
+ private int mNumVisibleThumbnailsLoaded;
- int mMaxThumbnailCacheSize;
- int mMaxIconCacheSize;
- int mNumVisibleTasksLoaded;
- int mNumVisibleThumbnailsLoaded;
-
+ int mDefaultTaskBarBackgroundColor;
BitmapDrawable mDefaultApplicationIcon;
Bitmap mDefaultThumbnail;
- /** Private Constructor */
- private RecentsTaskLoader(Context context) {
- mMaxThumbnailCacheSize = context.getResources().getInteger(
- R.integer.config_recents_max_thumbnail_count);
- mMaxIconCacheSize = context.getResources().getInteger(
- R.integer.config_recents_max_icon_count);
+ public RecentsTaskLoader(Context context) {
+ Resources res = context.getResources();
+ mDefaultTaskBarBackgroundColor =
+ res.getColor(R.color.recents_task_bar_default_background_color);
+ mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
+ mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
mMaxIconCacheSize;
int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
mMaxThumbnailCacheSize;
// Create the default assets
- Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
- icon.eraseColor(0x00000000);
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+ icon.eraseColor(0);
mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
mDefaultThumbnail.setHasAlpha(false);
mDefaultThumbnail.eraseColor(0xFFffffff);
mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon);
// Initialize the proxy, cache and loaders
- mSystemServicesProxy = new SystemServicesProxy(context);
+ int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
mLoadQueue = new TaskResourceLoadQueue();
- mApplicationIconCache = new DrawableLruCache(iconCacheSize);
- mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
- mActivityLabelCache = new StringLruCache(100);
- mContentDescriptionCache = new StringLruCache(100);
- mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
+ mApplicationIconCache = new TaskKeyLruCache<>(iconCacheSize);
+ mThumbnailCache = new TaskKeyLruCache<>(thumbnailCacheSize);
+ mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks);
+ mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks);
+ mActivityInfoCache = new LruCache(numRecentTasks);
+ mLoader = new BackgroundTaskLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
mDefaultThumbnail, mDefaultApplicationIcon);
}
- /** Initializes the recents task loader */
- public static RecentsTaskLoader initialize(Context context) {
- if (sInstance == null) {
- sInstance = new RecentsTaskLoader(context);
- }
- return sInstance;
- }
-
- /** Returns the current recents task loader */
- public static RecentsTaskLoader getInstance() {
- return sInstance;
- }
-
- /** Returns the system services proxy */
- public SystemServicesProxy getSystemServicesProxy() {
- return mSystemServicesProxy;
- }
-
- /** Returns the activity label using as many cached values as we can. */
- public String getAndUpdateActivityLabel(Task.TaskKey taskKey,
- ActivityManager.TaskDescription td, SystemServicesProxy ssp,
- ActivityInfoHandle infoHandle) {
- // Return the task description label if it exists
- if (td != null && td.getLabel() != null) {
- return td.getLabel();
- }
- // Return the cached activity label if it exists
- String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
- if (label != null) {
- return label;
- }
- // All short paths failed, load the label from the activity info and cache it
- if (infoHandle.info == null) {
- infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
- taskKey.userId);
- }
- if (infoHandle.info != null) {
- label = ssp.getActivityLabel(infoHandle.info);
- mActivityLabelCache.put(taskKey, label);
- return label;
- } else {
- Log.w(TAG, "Missing ActivityInfo for " + taskKey.baseIntent.getComponent()
- + " u=" + taskKey.userId);
- }
- // If the activity info does not exist or fails to load, return an empty label for now,
- // but do not cache it
- return "";
- }
-
- /** Returns the content description using as many cached values as we can. */
- public String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel,
- SystemServicesProxy ssp, Resources res) {
- // Return the cached content description if it exists
- String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
- if (label != null) {
- return label;
- }
- // If the given activity label is empty, don't compute or cache the content description
- if (activityLabel.isEmpty()) {
- return "";
- }
-
- label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res);
- if (label != null) {
- mContentDescriptionCache.put(taskKey, label);
- return label;
- } else {
- Log.w(TAG, "Missing content description for " + taskKey.baseIntent.getComponent()
- + " u=" + taskKey.userId);
- }
- // If the content description does not exist, return an empty label for now, but do not
- // cache it
- return "";
- }
-
- /** Returns the activity icon using as many cached values as we can. */
- public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey,
- ActivityManager.TaskDescription td, SystemServicesProxy ssp,
- Resources res, ActivityInfoHandle infoHandle, boolean loadIfNotCached) {
- // Return the cached activity icon if it exists
- Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey);
- if (icon != null) {
- return icon;
- }
-
- if (loadIfNotCached) {
- // Return and cache the task description icon if it exists
- Drawable tdDrawable = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(),
- td.getIconFilename(), ssp, res);
- if (tdDrawable != null) {
- mApplicationIconCache.put(taskKey, tdDrawable);
- return tdDrawable;
- }
-
- // Load the icon from the activity info and cache it
- if (infoHandle.info == null) {
- infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
- taskKey.userId);
- }
- if (infoHandle.info != null) {
- icon = ssp.getActivityIcon(infoHandle.info, taskKey.userId);
- if (icon != null) {
- mApplicationIconCache.put(taskKey, icon);
- return icon;
- }
- }
- }
- // We couldn't load any icon
- return null;
- }
-
- /** Returns the bitmap using as many cached values as we can. */
- public Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, SystemServicesProxy ssp,
- boolean loadIfNotCached) {
- // Return the cached thumbnail if it exists
- Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
- if (thumbnail != null) {
- return thumbnail;
- }
-
- RecentsConfiguration config = RecentsConfiguration.getInstance();
- if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING && loadIfNotCached) {
- // Load the thumbnail from the system
- thumbnail = ssp.getTaskThumbnail(taskKey.id);
- if (thumbnail != null) {
- mThumbnailCache.put(taskKey, thumbnail);
- return thumbnail;
- }
- }
- // We couldn't load any thumbnail
- return null;
- }
-
- /** Returns the activity's primary color. */
- public int getActivityPrimaryColor(ActivityManager.TaskDescription td, Resources res) {
- if (td != null && td.getPrimaryColor() != 0) {
- return td.getPrimaryColor();
- }
- return res.getColor(R.color.recents_task_bar_default_background_color);
- }
-
/** Returns the size of the app icon cache. */
public int getApplicationIconCacheSize() {
return mMaxIconCacheSize;
@@ -454,7 +321,7 @@
/** Creates a new plan for loading the recent tasks. */
public RecentsTaskLoadPlan createLoadPlan(Context context) {
RecentsConfiguration config = RecentsConfiguration.getInstance();
- RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config, mSystemServicesProxy);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config);
return plan;
}
@@ -505,17 +372,12 @@
mLoadQueue.removeTask(t);
mThumbnailCache.remove(t.key);
mApplicationIconCache.remove(t.key);
+ mActivityInfoCache.remove(t.key.getComponent());
if (notifyTaskDataUnloaded) {
t.notifyTaskDataUnloaded(null, mDefaultApplicationIcon);
}
}
- /** Stops the task loader and clears all pending tasks */
- void stopLoader() {
- mLoader.stop();
- mLoadQueue.clearTasks();
- }
-
/**
* Handles signals from the system, trimming memory when requested to prevent us from running
* out of memory.
@@ -542,18 +404,23 @@
// We are leaving recents, so trim the data a bit
mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
+ mActivityInfoCache.trimToSize(Math.max(1,
+ ActivityManager.getMaxRecentTasksStatic() / 2));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
// We are going to be low on memory
mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
+ mActivityInfoCache.trimToSize(Math.max(1,
+ ActivityManager.getMaxRecentTasksStatic() / 4));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
// We are low on memory, so release everything
mThumbnailCache.evictAll();
mApplicationIconCache.evictAll();
+ mActivityInfoCache.evictAll();
// The cache is small, only clear the label cache when we are critical
mActivityLabelCache.evictAll();
mContentDescriptionCache.evictAll();
@@ -562,4 +429,165 @@
break;
}
}
+
+ /**
+ * Returns the cached task label if the task key is not expired, updating the cache if it is.
+ */
+ String getAndUpdateActivityLabel(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
+ SystemServicesProxy ssp) {
+ // Return the task description label if it exists
+ if (td != null && td.getLabel() != null) {
+ return td.getLabel();
+ }
+ // Return the cached activity label if it exists
+ String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
+ if (label != null) {
+ return label;
+ }
+ // All short paths failed, load the label from the activity info and cache it
+ ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey, ssp);
+ if (activityInfo != null) {
+ label = ssp.getActivityLabel(activityInfo);
+ mActivityLabelCache.put(taskKey, label);
+ return label;
+ }
+ // If the activity info does not exist or fails to load, return an empty label for now,
+ // but do not cache it
+ return "";
+ }
+
+ /**
+ * Returns the cached task content description if the task key is not expired, updating the
+ * cache if it is.
+ */
+ String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel,
+ SystemServicesProxy ssp, Resources res) {
+ // Return the cached content description if it exists
+ String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
+ if (label != null) {
+ return label;
+ }
+ // If the given activity label is empty, don't compute or cache the content description
+ if (activityLabel.isEmpty()) {
+ return "";
+ }
+
+ label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res);
+ if (label != null) {
+ mContentDescriptionCache.put(taskKey, label);
+ return label;
+ }
+ // If the content description does not exist, return an empty label for now, but do not
+ // cache it
+ return "";
+ }
+
+ /**
+ * Returns the cached task icon if the task key is not expired, updating the cache if it is.
+ */
+ Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
+ SystemServicesProxy ssp, Resources res, boolean loadIfNotCached) {
+ // Return the cached activity icon if it exists
+ Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey);
+ if (icon != null) {
+ return icon;
+ }
+
+ if (loadIfNotCached) {
+ // Return and cache the task description icon if it exists
+ icon = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(),
+ td.getIconFilename(), ssp, res);
+ if (icon != null) {
+ mApplicationIconCache.put(taskKey, icon);
+ return icon;
+ }
+
+ // Load the icon from the activity info and cache it
+ ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey, ssp);
+ if (activityInfo != null) {
+ icon = ssp.getActivityIcon(activityInfo, taskKey.userId);
+ if (icon != null) {
+ mApplicationIconCache.put(taskKey, icon);
+ return icon;
+ }
+ }
+ }
+ // We couldn't load any icon
+ return null;
+ }
+
+ /**
+ * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
+ */
+ Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, SystemServicesProxy ssp,
+ boolean loadIfNotCached) {
+ // Return the cached thumbnail if it exists
+ Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
+ if (thumbnail != null) {
+ return thumbnail;
+ }
+
+ if (loadIfNotCached) {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
+ // Load the thumbnail from the system
+ thumbnail = ssp.getTaskThumbnail(taskKey.id);
+ if (thumbnail != null) {
+ mThumbnailCache.put(taskKey, thumbnail);
+ return thumbnail;
+ }
+ }
+ }
+ // We couldn't load any thumbnail
+ return null;
+ }
+
+ /**
+ * Returns the task's primary color.
+ */
+ int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
+ if (td != null && td.getPrimaryColor() != 0) {
+ return td.getPrimaryColor();
+ }
+ return mDefaultTaskBarBackgroundColor;
+ }
+
+ /**
+ * Returns the activity info for the given task key, retrieving one from the system if the
+ * task key is expired.
+ */
+ private ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey, SystemServicesProxy ssp) {
+ ComponentName cn = taskKey.getComponent();
+ ActivityInfo activityInfo = mActivityInfoCache.get(cn);
+ if (activityInfo == null) {
+ activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
+ mActivityInfoCache.put(cn, activityInfo);
+ }
+ return activityInfo;
+ }
+
+ /**
+ * Stops the task loader and clears all queued, pending task loads.
+ */
+ private void stopLoader() {
+ mLoader.stop();
+ mLoadQueue.clearTasks();
+ }
+
+ /**** Event Bus Events ****/
+
+ public final void onBusEvent(PackagesChangedEvent event) {
+ // Remove all the cached activity infos for this package. The other caches do not need to
+ // be pruned at this time, as the TaskKey expiration checks will flush them next time their
+ // cached contents are requested
+ Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
+ for (ComponentName cn : activityInfoCache.keySet()) {
+ if (cn.getPackageName().equals(event.packageName)) {
+ if (DEBUG) {
+ Log.d(TAG, "Removing activity info from cache: " + cn);
+ }
+ mActivityInfoCache.remove(cn);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index c14adf4..0793180 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -38,38 +38,11 @@
public void onTaskDataUnloaded();
/* Notifies when a task's stack id has changed. */
- public void onMultiStackDebugTaskStackIdChanged();
- }
-
- /** The ComponentNameKey represents the unique primary key for a component
- * belonging to a specified user. */
- public static class ComponentNameKey {
- final ComponentName component;
- final int userId;
-
- public ComponentNameKey(ComponentName cn, int user) {
- component = cn;
- userId = user;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(component, userId);
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof ComponentNameKey)) {
- return false;
- }
- return component.equals(((ComponentNameKey) o).component) &&
- userId == ((ComponentNameKey) o).userId;
- }
+ public void onTaskStackIdChanged();
}
/* The Task Key represents the unique primary key for the task */
public static class TaskKey {
- final ComponentNameKey mComponentNameKey;
public final int id;
public int stackId;
public final Intent baseIntent;
@@ -79,7 +52,6 @@
public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
long lastActiveTime) {
- mComponentNameKey = new ComponentNameKey(intent.getComponent(), userId);
this.id = id;
this.stackId = stackId;
this.baseIntent = intent;
@@ -88,9 +60,8 @@
this.lastActiveTime = lastActiveTime;
}
- /** Returns the component name key for this task. */
- public ComponentNameKey getComponentNameKey() {
- return mComponentNameKey;
+ public ComponentName getComponent() {
+ return this.baseIntent.getComponent();
}
@Override
@@ -113,7 +84,7 @@
+ "s: " + stackId + ", "
+ "u: " + userId + ", "
+ "lat: " + lastActiveTime + ", "
- + baseIntent.getComponent().getPackageName();
+ + getComponent().getPackageName();
}
}
@@ -134,7 +105,8 @@
public boolean lockToTaskEnabled;
public Bitmap icon;
public String iconFilename;
- TaskCallbacks mCb;
+
+ private TaskCallbacks mCb;
public Task() {
// Do nothing
@@ -194,7 +166,7 @@
public void setStackId(int stackId) {
key.stackId = stackId;
if (mCb != null) {
- mCb.onMultiStackDebugTaskStackIdChanged();
+ mCb.onTaskStackIdChanged();
}
}
@@ -229,7 +201,7 @@
if (group != null) {
groupAffiliation = Integer.toString(group.affiliation);
}
- return "Task (" + groupAffiliation + "): " + key.baseIntent.getComponent().getPackageName() +
+ return "Task (" + groupAffiliation + "): " + key.getComponent().getPackageName() +
" [" + super.toString() + "]";
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
new file mode 100644
index 0000000..6d11f14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.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.systemui.recents.model;
+
+import android.util.LruCache;
+import android.util.SparseArray;
+
+/**
+ * A mapping of {@link Task.TaskKey} to value, with additional LRU functionality where the least
+ * recently referenced key/values will be evicted as more values than the given cache size are
+ * inserted.
+ *
+ * In addition, this also allows the caller to invalidate cached values for keys that have since
+ * changed.
+ */
+public class TaskKeyLruCache<V> {
+
+ private final SparseArray<Task.TaskKey> mKeys = new SparseArray<>();
+ private final LruCache<Integer, V> mCache;
+
+ public TaskKeyLruCache(int cacheSize) {
+ mCache = new LruCache<Integer, V>(cacheSize) {
+
+ @Override
+ protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) {
+ mKeys.remove(taskId);
+ }
+ };
+ }
+
+ /**
+ * Gets a specific entry in the cache with the specified key, regardless of whether the cached
+ * value is valid or not.
+ */
+ final V get(Task.TaskKey key) {
+ return mCache.get(key.id);
+ }
+
+ /**
+ * Returns the value only if the key is valid (has not been updated since the last time it was
+ * in the cache)
+ */
+ final V getAndInvalidateIfModified(Task.TaskKey key) {
+ Task.TaskKey lastKey = mKeys.get(key.id);
+ if (lastKey != null) {
+ if ((lastKey.stackId != key.stackId) || (lastKey.lastActiveTime < key.lastActiveTime)) {
+ // The task has updated (been made active since the last time it was put into the
+ // LRU cache) or the stack id for the task has changed, invalidate that cache item
+ remove(key);
+ return null;
+ }
+ }
+ // Either the task does not exist in the cache, or the last active time is the same as
+ // the key specified, so return what is in the cache
+ return mCache.get(key.id);
+ }
+
+ /** Puts an entry in the cache for a specific key. */
+ final void put(Task.TaskKey key, V value) {
+ mKeys.put(key.id, key);
+ mCache.put(key.id, value);
+ }
+
+ /** Removes a cache entry for a specific key. */
+ final void remove(Task.TaskKey key) {
+ mKeys.remove(key.id);
+ mCache.remove(key.id);
+ }
+
+ /** Removes all the entries in the cache. */
+ final void evictAll() {
+ mCache.evictAll();
+ mKeys.clear();
+ }
+
+ /** Trims the cache to a specific size */
+ final void trimToSize(int cacheSize) {
+ mCache.resize(cacheSize);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 94c2653..b3937c3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.model;
import android.animation.ObjectAnimator;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
@@ -24,13 +25,16 @@
import android.graphics.drawable.ColorDrawable;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.NamedCounter;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
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.Random;
@@ -423,8 +427,8 @@
boolean filtered = mTaskList.setFilter(new TaskFilter() {
@Override
public boolean acceptTask(Task at, int i) {
- return t.key.baseIntent.getComponent().getPackageName().equals(
- at.key.baseIntent.getComponent().getPackageName());
+ return t.key.getComponent().getPackageName().equals(
+ at.key.getComponent().getPackageName());
}
});
if (filtered && mCb != null) {
@@ -489,7 +493,7 @@
int groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
for (int i = 0; i < taskCount; i++) {
Task t = tasks.get(i);
- String packageName = t.key.baseIntent.getComponent().getPackageName();
+ String packageName = t.key.getComponent().getPackageName();
packageName = "pkg";
TaskGrouping group;
if (packageName.equals(prevPackage) && groupCountDown > 0) {
@@ -576,6 +580,37 @@
}
}
+ /**
+ * Computes the components of tasks in this stack that have been removed as a result of a change
+ * in the specified package.
+ */
+ public HashSet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
+ // Identify all the tasks that should be removed as a result of the package being removed.
+ // Using a set to ensure that we callback once per unique component.
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ HashSet<ComponentName> existingComponents = new HashSet<>();
+ HashSet<ComponentName> removedComponents = new HashSet<>();
+ ArrayList<Task.TaskKey> taskKeys = getTaskKeys();
+ for (Task.TaskKey t : taskKeys) {
+ // Skip if this doesn't apply to the current user
+ if (t.userId != userId) continue;
+
+ ComponentName cn = t.getComponent();
+ if (cn.getPackageName().equals(packageName)) {
+ if (existingComponents.contains(cn)) {
+ // If we know that the component still exists in the package, then skip
+ continue;
+ }
+ if (ssp.getActivityInfo(cn, userId) != null) {
+ existingComponents.add(cn);
+ } else {
+ removedComponents.add(cn);
+ }
+ }
+ }
+ return removedComponents;
+ }
+
@Override
public String toString() {
String str = "Tasks:\n";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index c2400df..7f618e3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -42,6 +42,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsAppWidgetHostView;
import com.android.systemui.recents.RecentsConfiguration;
@@ -53,7 +54,6 @@
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
@@ -404,14 +404,6 @@
return super.verifyDrawable(who);
}
- /** Notifies each task view of the user interaction. */
- public void onUserInteraction() {
- // Get the first stack view
- if (mTaskStackView != null) {
- mTaskStackView.onUserInteraction();
- }
- }
-
/** Focuses the next task in the first stack view */
public void focusNextTask(boolean forward) {
// Get the first stack view
@@ -602,8 +594,8 @@
final int left = pts[0] + offsetX;
final int top = pts[1] + offsetY;
- final Rect rect = new Rect(left, top, left + transform.rect.width(),
- top + transform.rect.height());
+ final Rect rect = new Rect(left, top, left + (int) transform.rect.width(),
+ top + (int) transform.rect.height());
return new AppTransitionAnimationSpec(taskId, b, rect);
}
@@ -630,15 +622,14 @@
// and then offset to the expected transform rect, but bound this to just
// outside the display rect (to ensure we don't animate from too far away)
sourceView = stackView;
- offsetX = transform.rect.left;
+ offsetX = (int) transform.rect.left;
offsetY = getMeasuredHeight();
} else {
sourceView = tv.mThumbnailView;
}
// Compute the thumbnail to scale up from
- final SystemServicesProxy ssp =
- RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ final SystemServicesProxy ssp = Recents.getSystemServices();
ActivityOptions opts = null;
ActivityOptions.OnAnimationStartedListener animStartedListener = null;
if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
@@ -652,8 +643,6 @@
postDelayed(new Runnable() {
@Override
public void run() {
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- SystemServicesProxy ssp = loader.getSystemServicesProxy();
EventBus.getDefault().send(new ScreenPinningRequestEvent(
getContext(), ssp));
}
@@ -667,7 +656,7 @@
animStartedListener, destinationStack);
opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).createAshmemBitmap(),
- offsetX, offsetY, transform.rect.width(), transform.rect.height(),
+ offsetX, offsetY, (int) transform.rect.width(), (int) transform.rect.height(),
sourceView.getHandler(), animStartedListener);
} else {
opts = ActivityOptions.makeBasic();
@@ -687,8 +676,6 @@
if (ssp.startActivityFromRecents(getContext(), task.key.id,
task.activityLabel, launchOpts)) {
if (screenPinningRequested) {
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- SystemServicesProxy ssp = loader.getSystemServicesProxy();
EventBus.getDefault().send(new ScreenPinningRequestEvent(
getContext(), ssp));
}
@@ -748,14 +735,6 @@
MetricsLogger.count(getContext(), "overview_task_all_dismissed", 1);
}
- /** Final callback after Recents is finally hidden. */
- public void onRecentsHidden() {
- // Notify each task stack view
- if (mTaskStackView != null) {
- mTaskStackView.onRecentsHidden();
- }
- }
-
@Override
public void onTaskStackFilterTriggered() {
// Hide the search bar
@@ -820,7 +799,7 @@
// Dock the new task if we are hovering over a valid dock state
if (event.dockState != TaskStack.DockState.NONE) {
- SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ SystemServicesProxy ssp = Recents.getSystemServices();
ssp.setTaskResizeable(event.task.key.id);
ssp.dockTask(event.task.key.id, event.dockState.createMode);
launchTask(event.task, null, INVALID_STACK_ID);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 6f12c52..5928854 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -20,8 +20,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -30,16 +30,18 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
@@ -87,11 +89,11 @@
boolean mStartEnterAnimationRequestedAfterLayout;
boolean mStartEnterAnimationCompleted;
ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
+
Rect mTaskStackBounds = new Rect();
int[] mTmpVisibleRange = new int[2];
- float[] mTmpCoord = new float[2];
- Matrix mTmpMatrix = new Matrix();
Rect mTmpRect = new Rect();
+ RectF mTmpTaskRect = new RectF();
TaskViewTransform mTmpTransform = new TaskViewTransform();
HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
ArrayList<TaskView> mTaskViews = new ArrayList<>();
@@ -330,8 +332,7 @@
/** Synchronizes the views with the model */
boolean synchronizeStackViewsWithModel() {
if (mStackViewsDirty) {
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- SystemServicesProxy ssp = loader.getSystemServicesProxy();
+ SystemServicesProxy ssp = Recents.getSystemServices();
// Get all the task transforms
ArrayList<Task> tasks = mStack.getTasks();
@@ -411,23 +412,24 @@
return false;
}
- /** Updates the clip for each of the task views. */
+ /**
+ * Updates the clip for each of the task views from back to front.
+ */
void clipTaskViews() {
// Update the clip on each task child
List<TaskView> taskViews = getTaskViews();
+ TaskView tmpTv = null;
int taskViewCount = taskViews.size();
- for (int i = 0; i < taskViewCount - 1; i++) {
+ for (int i = 0; i < taskViewCount; i++) {
TaskView tv = taskViews.get(i);
- TaskView nextTv = null;
- TaskView tmpTv = null;
+ TaskView frontTv = null;
int clipBottom = 0;
- if (tv.shouldClipViewInStack()) {
+ if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) {
// Find the next view to clip against
- int nextIndex = i;
- while (nextIndex < (taskViewCount - 1)) {
- tmpTv = taskViews.get(++nextIndex);
- if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
- nextTv = tmpTv;
+ for (int j = i + 1; j < taskViewCount; j++) {
+ tmpTv = taskViews.get(j);
+ if (tmpTv.shouldClipViewInStack()) {
+ frontTv = tmpTv;
break;
}
}
@@ -435,23 +437,23 @@
// Clip against the next view, this is just an approximation since we are
// stacked and we can make assumptions about the visibility of the this
// task relative to the ones in front of it.
- if (nextTv != null) {
- // Map the top edge of next task view into the local space of the current
- // task view to find the clip amount in local space
- mTmpCoord[0] = mTmpCoord[1] = 0;
- Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false);
- Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix);
- clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1]
- - nextTv.getPaddingTop() - 1);
+ if (frontTv != null) {
+ mTmpTaskRect.set(mLayoutAlgorithm.mTaskRect);
+ mTmpTaskRect.offset(0, tv.getTranslationY());
+ Utilities.scaleRectAboutCenter(mTmpTaskRect, tv.getScaleX());
+ float taskBottom = mTmpTaskRect.bottom;
+ mTmpTaskRect.set(mLayoutAlgorithm.mTaskRect);
+ mTmpTaskRect.offset(0, frontTv.getTranslationY());
+ Utilities.scaleRectAboutCenter(mTmpTaskRect, frontTv.getScaleX());
+ float frontTaskTop = mTmpTaskRect.top;
+ if (frontTaskTop < taskBottom) {
+ // Map the stack view space coordinate (the rects) to view space
+ clipBottom = (int) ((taskBottom - frontTaskTop) / tv.getScaleX()) - 1;
+ }
}
}
tv.getViewBounds().setClipBottom(clipBottom);
}
- if (taskViewCount > 0) {
- // The front most task should never be clipped
- TaskView tv = taskViews.get(taskViewCount - 1);
- tv.getViewBounds().setClipBottom(0);
- }
mStackViewsClipDirty = false;
}
@@ -876,8 +878,7 @@
// Poke the dozer to restart the trigger after the animation completes
mUIDozeTrigger.poke();
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- SystemServicesProxy ssp = loader.getSystemServicesProxy();
+ SystemServicesProxy ssp = Recents.getSystemServices();
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
if (taskViewCount > 0) {
@@ -953,21 +954,10 @@
}
}
- /** Final callback after Recents is finally hidden. */
- void onRecentsHidden() {
- reset();
- }
-
public boolean isTransformedTouchPointInView(float x, float y, View child) {
return isTransformedTouchPointInView(x, y, child, null);
}
- /** Pokes the dozer on user interaction. */
- void onUserInteraction() {
- // Poke the doze trigger if it is dozing
- mUIDozeTrigger.poke();
- }
-
@Override
protected void dispatchDraw(Canvas canvas) {
mLayersDisabled = false;
@@ -1148,7 +1138,7 @@
}
// Report that this tasks's data is no longer being used
- RecentsTaskLoader.getInstance().unloadTaskData(task);
+ Recents.getTaskLoader().unloadTaskData(task);
// Detach the view from the hierarchy
detachViewFromParent(tv);
@@ -1172,7 +1162,7 @@
tv.onTaskBound(task);
// Load the task data
- RecentsTaskLoader.getInstance().loadTaskData(task);
+ Recents.getTaskLoader().loadTaskData(task);
// If the doze trigger has already fired, then update the state for this task view
tv.setNoUserInteractionState();
@@ -1261,14 +1251,14 @@
public final void onBusEvent(PackagesChangedEvent event) {
// Compute which components need to be removed
- HashSet<ComponentName> removedComponents = event.monitor.computeComponentsRemoved(
- mStack.getTaskKeys(), event.packageName, event.userId);
+ HashSet<ComponentName> removedComponents = mStack.computeComponentsRemoved(
+ event.packageName, event.userId);
// For other tasks, just remove them directly if they no longer exist
ArrayList<Task> tasks = mStack.getTasks();
for (int i = tasks.size() - 1; i >= 0; i--) {
final Task t = tasks.get(i);
- if (removedComponents.contains(t.key.baseIntent.getComponent())) {
+ if (removedComponents.contains(t.key.getComponent())) {
TaskView tv = getChildViewForTask(t);
if (tv != null) {
// For visible children, defer removing the task until after the animation
@@ -1315,4 +1305,15 @@
}
}
}
+
+ public final void onBusEvent(UserInteractionEvent event) {
+ // Poke the doze trigger on user interaction
+ mUIDozeTrigger.poke();
+ }
+
+ public final void onBusEvent(RecentsVisibilityChangedEvent event) {
+ if (!event.visible) {
+ reset();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 802a057..bab4da7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -759,7 +759,7 @@
}
@Override
- public void onMultiStackDebugTaskStackIdChanged() {
+ public void onTaskStackIdChanged() {
mHeaderView.rebindToTask(mTask);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 949d515..e1e07ef 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -48,13 +48,12 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
-import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.ui.ResizeTaskEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
@@ -62,8 +61,6 @@
public class TaskViewHeader extends FrameLayout
implements View.OnClickListener, View.OnLongClickListener {
- RecentsConfiguration mConfig;
- private SystemServicesProxy mSsp;
Task mTask;
// Header views
@@ -111,8 +108,6 @@
public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mConfig = RecentsConfiguration.getInstance();
- mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
setWillNotDraw(false);
setClipToOutline(true);
setOutlineProvider(new ViewOutlineProvider() {
@@ -266,8 +261,9 @@
/** Updates the resize task bar button. */
void updateResizeTaskBarIcon(Task t) {
- Rect display = mSsp.getWindowRect();
- Rect taskRect = mSsp.getTaskBounds(t.key.id);
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ Rect display = ssp.getWindowRect();
+ Rect taskRect = ssp.getTaskBounds(t.key.id);
int resId = R.drawable.star;
if (display.equals(taskRect) || taskRect.isEmpty()) {
resId = R.drawable.vector_drawable_place_fullscreen;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index f8f7052..ec50eb6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.views;
import android.animation.ValueAnimator;
-import android.graphics.Rect;
+import android.graphics.RectF;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
@@ -35,7 +35,7 @@
// This is a window-space rect that is purely used for coordinating the animation of an app
// window into Recents.
- public Rect rect = new Rect();
+ public RectF rect = new RectF();
public TaskViewTransform() {
// Do nothing
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 3759c91..be7071e 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -64,6 +64,10 @@
case RS_TYPE_FLOAT_32: \
len = _env->GetArrayLength((jfloatArray)data); \
ptr = _env->GetFloatArrayElements((jfloatArray)data, flag); \
+ if (ptr == nullptr) { \
+ ALOGE("Failed to get Java array elements."); \
+ return; \
+ } \
typeBytes = 4; \
if (usePadding) { \
srcPtr = ptr; \
@@ -89,6 +93,10 @@
case RS_TYPE_FLOAT_64: \
len = _env->GetArrayLength((jdoubleArray)data); \
ptr = _env->GetDoubleArrayElements((jdoubleArray)data, flag); \
+ if (ptr == nullptr) { \
+ ALOGE("Failed to get Java array elements."); \
+ return; \
+ } \
typeBytes = 8; \
if (usePadding) { \
srcPtr = ptr; \
@@ -115,6 +123,10 @@
case RS_TYPE_UNSIGNED_8: \
len = _env->GetArrayLength((jbyteArray)data); \
ptr = _env->GetByteArrayElements((jbyteArray)data, flag); \
+ if (ptr == nullptr) { \
+ ALOGE("Failed to get Java array elements."); \
+ return; \
+ } \
typeBytes = 1; \
if (usePadding) { \
srcPtr = ptr; \
@@ -141,6 +153,10 @@
case RS_TYPE_UNSIGNED_16: \
len = _env->GetArrayLength((jshortArray)data); \
ptr = _env->GetShortArrayElements((jshortArray)data, flag); \
+ if (ptr == nullptr) { \
+ ALOGE("Failed to get Java array elements."); \
+ return; \
+ } \
typeBytes = 2; \
if (usePadding) { \
srcPtr = ptr; \
@@ -167,6 +183,10 @@
case RS_TYPE_UNSIGNED_32: \
len = _env->GetArrayLength((jintArray)data); \
ptr = _env->GetIntArrayElements((jintArray)data, flag); \
+ if (ptr == nullptr) { \
+ ALOGE("Failed to get Java array elements."); \
+ return; \
+ } \
typeBytes = 4; \
if (usePadding) { \
srcPtr = ptr; \
@@ -193,6 +213,10 @@
case RS_TYPE_UNSIGNED_64: \
len = _env->GetArrayLength((jlongArray)data); \
ptr = _env->GetLongArrayElements((jlongArray)data, flag); \
+ if (ptr == nullptr) { \
+ ALOGE("Failed to get Java array elements."); \
+ return; \
+ } \
typeBytes = 8; \
if (usePadding) { \
srcPtr = ptr; \
@@ -332,16 +356,40 @@
jlong* jFieldIDs = _env->GetLongArrayElements(fieldIDArray, nullptr);
jsize fieldIDs_length = _env->GetArrayLength(fieldIDArray);
+ if (jFieldIDs == nullptr) {
+ ALOGE("Failed to get Java array elements: fieldIDs.");
+ return ret;
+ }
+
jlong* jValues = _env->GetLongArrayElements(valueArray, nullptr);
jsize values_length = _env->GetArrayLength(valueArray);
+ if (jValues == nullptr) {
+ ALOGE("Failed to get Java array elements: values.");
+ return ret;
+ }
+
jint* jSizes = _env->GetIntArrayElements(sizeArray, nullptr);
jsize sizes_length = _env->GetArrayLength(sizeArray);
+ if (jSizes == nullptr) {
+ ALOGE("Failed to get Java array elements: sizes.");
+ return ret;
+ }
+
jlong* jDepClosures =
_env->GetLongArrayElements(depClosureArray, nullptr);
jsize depClosures_length = _env->GetArrayLength(depClosureArray);
+ if (jDepClosures == nullptr) {
+ ALOGE("Failed to get Java array elements: depClosures.");
+ return ret;
+ }
+
jlong* jDepFieldIDs =
_env->GetLongArrayElements(depFieldIDArray, nullptr);
jsize depFieldIDs_length = _env->GetArrayLength(depFieldIDArray);
+ if (jDepFieldIDs == nullptr) {
+ ALOGE("Failed to get Java array elements: depFieldIDs.");
+ return ret;
+ }
size_t numValues, numDependencies;
RsScriptFieldID* fieldIDs;
@@ -435,12 +483,31 @@
jbyte* jParams = _env->GetByteArrayElements(paramArray, nullptr);
jsize jParamLength = _env->GetArrayLength(paramArray);
+ if (jParams == nullptr) {
+ ALOGE("Failed to get Java array elements: params.");
+ return ret;
+ }
+
jlong* jFieldIDs = _env->GetLongArrayElements(fieldIDArray, nullptr);
jsize fieldIDs_length = _env->GetArrayLength(fieldIDArray);
+ if (jFieldIDs == nullptr) {
+ ALOGE("Failed to get Java array elements: fieldIDs.");
+ return ret;
+ }
+
jlong* jValues = _env->GetLongArrayElements(valueArray, nullptr);
jsize values_length = _env->GetArrayLength(valueArray);
+ if (jValues == nullptr) {
+ ALOGE("Failed to get Java array elements: values.");
+ return ret;
+ }
+
jint* jSizes = _env->GetIntArrayElements(sizeArray, nullptr);
jsize sizes_length = _env->GetArrayLength(sizeArray);
+ if (jSizes == nullptr) {
+ ALOGE("Failed to get Java array elements: sizes.");
+ return ret;
+ }
size_t numValues;
RsScriptFieldID* fieldIDs;
@@ -515,6 +582,10 @@
jlong* jClosures = _env->GetLongArrayElements(closureArray, nullptr);
jsize numClosures = _env->GetArrayLength(closureArray);
+ if (jClosures == nullptr) {
+ ALOGE("Failed to get Java array elements: closures.");
+ return ret;
+ }
RsClosure* closures;
@@ -720,6 +791,11 @@
}
jint len = _env->GetArrayLength(str);
jbyte * cptr = (jbyte *) _env->GetPrimitiveArrayCritical(str, 0);
+ if (cptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
+
rsAssignName((RsContext)con, (void *)obj, (const char *)cptr, len);
_env->ReleasePrimitiveArrayCritical(str, cptr, JNI_ABORT);
}
@@ -916,6 +992,10 @@
ALOGD("nContextGetMessage, con(%p), len(%i)", (RsContext)con, len);
}
jint *ptr = _env->GetIntArrayElements(data, nullptr);
+ if (ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return 0;
+ }
size_t receiveLen;
uint32_t subID;
int id = rsContextGetMessage((RsContext)con,
@@ -936,6 +1016,10 @@
ALOGD("nContextPeekMessage, con(%p)", (RsContext)con);
}
jint *auxDataPtr = _env->GetIntArrayElements(auxData, nullptr);
+ if (auxDataPtr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return 0;
+ }
size_t receiveLen;
uint32_t subID;
int id = rsContextPeekMessage((RsContext)con, &receiveLen, sizeof(receiveLen),
@@ -970,6 +1054,10 @@
if (data) {
len = _env->GetArrayLength(data);
ptr = _env->GetIntArrayElements(data, nullptr);
+ if (ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
}
if (kLogApi) {
ALOGD("nContextSendMessage, con(%p), id(%i), len(%i)", (RsContext)con, id, len);
@@ -1004,7 +1092,15 @@
}
jlong *jIds = _env->GetLongArrayElements(_ids, nullptr);
+ if (jIds == nullptr) {
+ ALOGE("Failed to get Java array elements: ids");
+ return 0;
+ }
jint *jArraySizes = _env->GetIntArrayElements(_arraySizes, nullptr);
+ if (jArraySizes == nullptr) {
+ ALOGE("Failed to get Java array elements: arraySizes");
+ return 0;
+ }
RsElement *ids = (RsElement*)malloc(fieldCount * sizeof(RsElement));
uint32_t *arraySizes = (uint32_t *)malloc(fieldCount * sizeof(uint32_t));
@@ -1311,6 +1407,10 @@
sizeBytes);
}
jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+ if (ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
rsAllocationElementData((RsContext)con, (RsAllocation)alloc,
xoff, yoff, zoff,
lod, ptr, sizeBytes, compIdx);
@@ -1449,6 +1549,10 @@
sizeBytes);
}
jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+ if (ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
rsAllocationElementRead((RsContext)con, (RsAllocation)alloc,
xoff, yoff, zoff,
lod, ptr, sizeBytes, compIdx);
@@ -1775,6 +1879,10 @@
}
jint len = _env->GetArrayLength(data);
jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+ if (ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
rsScriptSetVarV((RsContext)con, (RsScript)script, slot, ptr, len);
_env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
}
@@ -1787,6 +1895,10 @@
}
jint len = _env->GetArrayLength(data);
jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+ if (ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
rsScriptGetVarV((RsContext)con, (RsScript)script, slot, ptr, len);
_env->ReleaseByteArrayElements(data, ptr, 0);
}
@@ -1800,8 +1912,16 @@
}
jint len = _env->GetArrayLength(data);
jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+ if (ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
jint dimsLen = _env->GetArrayLength(dims) * sizeof(int);
jint *dimsPtr = _env->GetIntArrayElements(dims, nullptr);
+ if (dimsPtr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
rsScriptSetVarVE((RsContext)con, (RsScript)script, slot, ptr, len, (RsElement)elem,
(const uint32_t*) dimsPtr, dimsLen);
_env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
@@ -1819,6 +1939,10 @@
jint length = _env->GetArrayLength(timeZone);
jbyte* timeZone_ptr;
timeZone_ptr = (jbyte *) _env->GetPrimitiveArrayCritical(timeZone, (jboolean *)0);
+ if (timeZone_ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
rsScriptSetTimeZone((RsContext)con, (RsScript)script, (const char *)timeZone_ptr, length);
@@ -1844,6 +1968,10 @@
}
jint len = _env->GetArrayLength(data);
jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+ if (ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
rsScriptInvokeV((RsContext)con, (RsScript)script, slot, ptr, len);
_env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
}
@@ -1870,8 +1998,12 @@
return;
}
- // TODO (b/20760800): Check in_ptr is not null
in_ptr = _env->GetLongArrayElements(ains, nullptr);
+ if (in_ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
+
if (sizeof(RsAllocation) == sizeof(jlong)) {
in_allocs = (RsAllocation*)in_ptr;
@@ -1897,6 +2029,10 @@
if (params != nullptr) {
param_len = _env->GetArrayLength(params);
param_ptr = _env->GetByteArrayElements(params, nullptr);
+ if (param_ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
}
RsScriptCall sc, *sca = nullptr;
@@ -1908,6 +2044,10 @@
if (limits != nullptr) {
limit_len = _env->GetArrayLength(limits);
limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+ if (limit_ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
assert(limit_len == 6);
UNUSED(limit_len); // As the assert might not be compiled.
@@ -1966,6 +2106,10 @@
if (limits != nullptr) {
limit_len = _env->GetArrayLength(limits);
limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+ if (limit_ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return;
+ }
// We expect to be passed an array [x1, x2] which specifies
// the sub-range for a 1-dimensional reduction.
@@ -2037,6 +2181,10 @@
}
script_ptr = (jbyte *)
_env->GetPrimitiveArrayCritical(scriptRef, (jboolean *)0);
+ if (script_ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return ret;
+ }
//rsScriptCSetText((RsContext)con, (const char *)script_ptr, length);
@@ -2104,6 +2252,10 @@
jint kernelsLen = _env->GetArrayLength(_kernels);
jlong *jKernelsPtr = _env->GetLongArrayElements(_kernels, nullptr);
+ if (jKernelsPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: kernels");
+ return 0;
+ }
RsScriptKernelID* kernelsPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * kernelsLen);
for(int i = 0; i < kernelsLen; ++i) {
kernelsPtr[i] = (RsScriptKernelID)jKernelsPtr[i];
@@ -2111,6 +2263,10 @@
jint srcLen = _env->GetArrayLength(_src);
jlong *jSrcPtr = _env->GetLongArrayElements(_src, nullptr);
+ if (jSrcPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: src");
+ return 0;
+ }
RsScriptKernelID* srcPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * srcLen);
for(int i = 0; i < srcLen; ++i) {
srcPtr[i] = (RsScriptKernelID)jSrcPtr[i];
@@ -2118,6 +2274,10 @@
jint dstkLen = _env->GetArrayLength(_dstk);
jlong *jDstkPtr = _env->GetLongArrayElements(_dstk, nullptr);
+ if (jDstkPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: dstk");
+ return 0;
+ }
RsScriptKernelID* dstkPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstkLen);
for(int i = 0; i < dstkLen; ++i) {
dstkPtr[i] = (RsScriptKernelID)jDstkPtr[i];
@@ -2125,6 +2285,10 @@
jint dstfLen = _env->GetArrayLength(_dstf);
jlong *jDstfPtr = _env->GetLongArrayElements(_dstf, nullptr);
+ if (jDstfPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: dstf");
+ return 0;
+ }
RsScriptKernelID* dstfPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstfLen);
for(int i = 0; i < dstfLen; ++i) {
dstfPtr[i] = (RsScriptKernelID)jDstfPtr[i];
@@ -2132,6 +2296,10 @@
jint typesLen = _env->GetArrayLength(_types);
jlong *jTypesPtr = _env->GetLongArrayElements(_types, nullptr);
+ if (jTypesPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: types");
+ return 0;
+ }
RsType* typesPtr = (RsType*) malloc(sizeof(RsType) * typesLen);
for(int i = 0; i < typesLen; ++i) {
typesPtr[i] = (RsType)jTypesPtr[i];
@@ -2244,6 +2412,10 @@
AutoJavaStringToUTF8 shaderUTF(_env, shader);
jlong *jParamPtr = _env->GetLongArrayElements(params, nullptr);
jint paramLen = _env->GetArrayLength(params);
+ if (jParamPtr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return 0;
+ }
int texCount = _env->GetArrayLength(texNames);
AutoJavaStringArrayToUTF8 names(_env, texNames, texCount);
@@ -2277,6 +2449,10 @@
AutoJavaStringToUTF8 shaderUTF(_env, shader);
jlong *jParamPtr = _env->GetLongArrayElements(params, nullptr);
jint paramLen = _env->GetArrayLength(params);
+ if (jParamPtr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ return 0;
+ }
if (kLogApi) {
ALOGD("nProgramVertexCreate, con(%p), paramLen(%i)", (RsContext)con, paramLen);
@@ -2392,6 +2568,10 @@
jint vtxLen = _env->GetArrayLength(_vtx);
jlong *jVtxPtr = _env->GetLongArrayElements(_vtx, nullptr);
+ if (jVtxPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: vtx");
+ return 0;
+ }
RsAllocation* vtxPtr = (RsAllocation*) malloc(sizeof(RsAllocation) * vtxLen);
for(int i = 0; i < vtxLen; ++i) {
vtxPtr[i] = (RsAllocation)(uintptr_t)jVtxPtr[i];
@@ -2399,6 +2579,10 @@
jint idxLen = _env->GetArrayLength(_idx);
jlong *jIdxPtr = _env->GetLongArrayElements(_idx, nullptr);
+ if (jIdxPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: idx");
+ return 0;
+ }
RsAllocation* idxPtr = (RsAllocation*) malloc(sizeof(RsAllocation) * idxLen);
for(int i = 0; i < idxLen; ++i) {
idxPtr[i] = (RsAllocation)(uintptr_t)jIdxPtr[i];
@@ -2406,6 +2590,10 @@
jint primLen = _env->GetArrayLength(_prim);
jint *primPtr = _env->GetIntArrayElements(_prim, nullptr);
+ if (primPtr == nullptr) {
+ ALOGE("Failed to get Java array elements: prim");
+ return 0;
+ }
jlong id = (jlong)(uintptr_t)rsMeshCreate((RsContext)con,
(RsAllocation *)vtxPtr, vtxLen,
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 465118c2..df6b1d6 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -355,7 +355,8 @@
final ServiceMap smap = getServiceMap(r.userId);
boolean addToStarting = false;
- if (!callerFg && r.app == null && mAm.mStartedUsers.get(r.userId) != null) {
+ if (!callerFg && r.app == null
+ && mAm.mUserController.hasStartedUserState(r.userId)) {
ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
if (proc == null || proc.curProcState > ActivityManager.PROCESS_STATE_RECEIVER) {
// If this is not coming from a foreground caller, then we may want
@@ -1401,7 +1402,7 @@
// Make sure that the user who owns this service is started. If not,
// we don't want to allow it to run.
- if (mAm.mStartedUsers.get(r.userId) == null) {
+ if (!mAm.mUserController.hasStartedUserState(r.userId)) {
String msg = "Unable to launch app "
+ r.appInfo.packageName + "/"
+ r.appInfo.uid + " for service "
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bb1b6b8..c728b39 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -79,7 +79,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
-import android.util.SparseIntArray;
import android.view.Display;
import com.android.internal.R;
@@ -205,7 +204,6 @@
import android.os.IBinder;
import android.os.IPermissionController;
import android.os.IProcessInfoService;
-import android.os.IRemoteCallback;
import android.os.IUserManager;
import android.os.Looper;
import android.os.Message;
@@ -370,17 +368,10 @@
// How long we wait until we timeout on key dispatching during instrumentation.
static final int INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT = 60*1000;
- // Amount of time we wait for observers to handle a user switch before
- // giving up on them and unfreezing the screen.
- static final int USER_SWITCH_TIMEOUT = 2*1000;
-
// This is the amount of time an app needs to be running a foreground service before
// we will consider it to be doing interaction for usage stats.
static final int SERVICE_USAGE_INTERACTION_TIME = 30*60*1000;
- // Maximum number of users we allow to be running at a time.
- static final int MAX_RUNNING_USERS = 3;
-
// How long to wait in getAssistContextExtras for the activity and foreground services
// to respond with the result.
static final int PENDING_ASSIST_EXTRAS_TIMEOUT = 500;
@@ -505,6 +496,8 @@
*/
String mDeviceOwnerName;
+ final UserController mUserController;
+
public class PendingAssistExtras extends Binder implements Runnable {
public final ActivityRecord activity;
public final Bundle extras;
@@ -707,32 +700,6 @@
final SparseArray<UidRecord> mActiveUids = new SparseArray<>();
/**
- * Which users have been started, so are allowed to run code.
- */
- final SparseArray<UserState> mStartedUsers = new SparseArray<>();
-
- /**
- * LRU list of history of current users. Most recently current is at the end.
- */
- final ArrayList<Integer> mUserLru = new ArrayList<Integer>();
-
- /**
- * Constant array of the users that are currently started.
- */
- int[] mStartedUserArray = new int[] { 0 };
-
- /**
- * Registered observers of the user switching mechanics.
- */
- final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
- = new RemoteCallbackList<IUserSwitchObserver>();
-
- /**
- * Currently active user switch.
- */
- Object mCurUserSwitchCallback;
-
- /**
* Packages that the user has asked to have run in screen size
* compatibility mode instead of filling the screen.
*/
@@ -1300,19 +1267,6 @@
final ActivityThread mSystemThread;
- // Holds the current foreground user's id
- int mCurrentUserId = 0;
- // Holds the target user's id during a user switch
- int mTargetUserId = UserHandle.USER_NULL;
- // If there are multiple profiles for the current user, their ids are here
- // Currently only the primary user can have managed profiles
- int[] mCurrentProfileIds = new int[] {}; // Accessed by ActivityStack
-
- /**
- * Mapping from each known user ID to the profile group ID it is associated with.
- */
- SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
-
private UserManagerService mUserManager;
private final class AppDeathRecipient implements IBinder.DeathRecipient {
@@ -1442,7 +1396,7 @@
boolean isBackground = (UserHandle.getAppId(proc.uid)
>= Process.FIRST_APPLICATION_UID
&& proc.pid != MY_PID);
- for (int userId : mCurrentProfileIds) {
+ for (int userId : mUserController.mCurrentProfileIds) {
isBackground &= (proc.userId != userId);
}
if (isBackground && !showBackground) {
@@ -1829,15 +1783,15 @@
break;
}
case REPORT_USER_SWITCH_MSG: {
- dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+ mUserController.dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
break;
}
case CONTINUE_USER_SWITCH_MSG: {
- continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+ mUserController.continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
break;
}
case USER_SWITCH_TIMEOUT_MSG: {
- timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+ mUserController.timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
break;
}
case IMMERSIVE_MODE_LOCK_MSG: {
@@ -1866,7 +1820,7 @@
}
case START_PROFILES_MSG: {
synchronized (ActivityManagerService.this) {
- startProfilesLocked();
+ mUserController.startProfilesLocked();
}
break;
}
@@ -2057,14 +2011,14 @@
}
} break;
case FOREGROUND_PROFILE_CHANGED_MSG: {
- dispatchForegroundProfileChanged(msg.arg1);
+ mUserController.dispatchForegroundProfileChanged(msg.arg1);
} break;
case REPORT_TIME_TRACKER_MSG: {
AppTimeTracker tracker = (AppTimeTracker)msg.obj;
tracker.deliverResult(mContext);
} break;
case REPORT_USER_SWITCH_COMPLETE_MSG: {
- dispatchUserSwitchComplete(msg.arg1);
+ mUserController.dispatchUserSwitchComplete(msg.arg1);
} break;
case SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG: {
IUiAutomationConnection connection = (IUiAutomationConnection) msg.obj;
@@ -2386,10 +2340,7 @@
mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));
- // User 0 is the first and only user that runs at boot.
- mStartedUsers.put(UserHandle.USER_SYSTEM, new UserState(UserHandle.SYSTEM, true));
- mUserLru.add(UserHandle.USER_SYSTEM);
- updateStartedUserArrayLocked();
+ mUserController = new UserController(this);
GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
@@ -5566,16 +5517,6 @@
null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid));
}
- private void forceStopUserLocked(int userId, String reason) {
- forceStopPackageLocked(null, -1, false, false, true, false, false, userId, reason);
- Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
- }
private final boolean killPackageProcessesLocked(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -5739,7 +5680,7 @@
}
- private final boolean forceStopPackageLocked(String packageName, int appId,
+ final boolean forceStopPackageLocked(String packageName, int appId,
boolean callerWillRestart, boolean purgeCache, boolean doit,
boolean evenPersistent, boolean uninstalling, int userId, String reason) {
int i;
@@ -6460,32 +6401,18 @@
|| "".equals(SystemProperties.get("vold.encrypt_progress"))) {
SystemProperties.set("dev.bootcomplete", "1");
}
- for (int i=0; i<mStartedUsers.size(); i++) {
- UserState uss = mStartedUsers.valueAt(i);
- if (uss.mState == UserState.STATE_BOOTING) {
- uss.mState = UserState.STATE_RUNNING;
- final int userId = mStartedUsers.keyAt(i);
- Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
- broadcastIntentLocked(null, null, intent, null,
- new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered,
- boolean sticky, int sendingUser) {
- synchronized (ActivityManagerService.this) {
- requestPssAllProcsLocked(SystemClock.uptimeMillis(),
- true, false);
- }
- }
- },
- 0, null, null,
- new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
- AppOpsManager.OP_NONE, null, true, false,
- MY_PID, Process.SYSTEM_UID, userId);
- }
- }
+ mUserController.sendBootCompletedLocked(
+ new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered,
+ boolean sticky, int sendingUser) {
+ synchronized (ActivityManagerService.this) {
+ requestPssAllProcsLocked(SystemClock.uptimeMillis(),
+ true, false);
+ }
+ }
+ });
scheduleStartProfilesLocked();
}
}
@@ -9492,7 +9419,7 @@
boolean checkedGrants = false;
if (checkUser) {
// Looking for cross-user grants before enforcing the typical cross-users permissions
- int tmpTargetUserId = unsafeConvertIncomingUser(userId);
+ int tmpTargetUserId = unsafeConvertIncomingUserLocked(userId);
if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
return null;
@@ -10338,7 +10265,9 @@
int callingPid = Binder.getCallingPid();
long ident = 0;
boolean clearedIdentity = false;
- userId = unsafeConvertIncomingUser(userId);
+ synchronized (this) {
+ userId = unsafeConvertIncomingUserLocked(userId);
+ }
if (canClearIdentity(callingPid, callingUid, userId)) {
clearedIdentity = true;
ident = Binder.clearCallingIdentity();
@@ -11005,8 +10934,9 @@
@Override
public boolean isAssistDataAllowedOnCurrentActivity() {
- int userId = mCurrentUserId;
+ int userId;
synchronized (this) {
+ userId = mUserController.mCurrentUserId;
ActivityRecord activity = getFocusedStack().topActivity();
if (activity == null) {
return false;
@@ -11929,7 +11859,7 @@
// Make sure we have the current profile info, since it is needed for
// security checks.
- updateCurrentProfileIdsLocked();
+ mUserController.updateCurrentProfileIdsLocked();
mRecentTasks.clear();
mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(
@@ -12036,18 +11966,20 @@
retrieveSettings();
loadResourcesOnSystemReady();
-
+ final int currentUserId;
synchronized (this) {
+ currentUserId = mUserController.mCurrentUserId;
readGrantedUriPermissionsLocked();
}
if (goingCallback != null) goingCallback.run();
+
mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
- Integer.toString(mCurrentUserId), mCurrentUserId);
+ Integer.toString(currentUserId), currentUserId);
mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
- Integer.toString(mCurrentUserId), mCurrentUserId);
- mSystemServiceManager.startUser(mCurrentUserId);
+ Integer.toString(currentUserId), currentUserId);
+ mSystemServiceManager.startUser(currentUserId);
synchronized (this) {
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
@@ -12075,7 +12007,7 @@
// Start up initial activity.
mBooting = true;
- startHomeActivityLocked(mCurrentUserId, "systemReady");
+ startHomeActivityLocked(currentUserId, "systemReady");
try {
if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
@@ -12096,13 +12028,14 @@
Intent intent = new Intent(Intent.ACTION_USER_STARTED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, mCurrentUserId);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
broadcastIntentLocked(null, null, intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, Process.SYSTEM_UID, mCurrentUserId);
+ null, false, false, MY_PID, Process.SYSTEM_UID,
+ currentUserId);
intent = new Intent(Intent.ACTION_USER_STARTING);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, mCurrentUserId);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
broadcastIntentLocked(null, null, intent,
null, new IIntentReceiver.Stub() {
@Override
@@ -12119,7 +12052,7 @@
Binder.restoreCallingIdentity(ident);
}
mStackSupervisor.resumeTopActivitiesLocked();
- sendUserSwitchBroadcastsLocked(-1, mCurrentUserId);
+ sendUserSwitchBroadcastsLocked(-1, currentUserId);
}
}
@@ -12318,7 +12251,7 @@
// launching the report UI under a different user.
app.errorReportReceiver = null;
- for (int userId : mCurrentProfileIds) {
+ for (int userId : mUserController.mCurrentProfileIds) {
if (app.userId == userId) {
app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
mContext, app.info.packageName, app.info.flags);
@@ -13775,38 +13708,7 @@
if (dumpPackage == null) {
pw.println();
needSep = false;
- pw.println(" mStartedUsers:");
- for (int i=0; i<mStartedUsers.size(); i++) {
- UserState uss = mStartedUsers.valueAt(i);
- pw.print(" User #"); pw.print(uss.mHandle.getIdentifier());
- pw.print(": "); uss.dump("", pw);
- }
- pw.print(" mStartedUserArray: [");
- for (int i=0; i<mStartedUserArray.length; i++) {
- if (i > 0) pw.print(", ");
- pw.print(mStartedUserArray[i]);
- }
- pw.println("]");
- pw.print(" mUserLru: [");
- for (int i=0; i<mUserLru.size(); i++) {
- if (i > 0) pw.print(", ");
- pw.print(mUserLru.get(i));
- }
- pw.println("]");
- if (dumpAll) {
- pw.print(" mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
- }
- synchronized (mUserProfileGroupIdsSelfLocked) {
- if (mUserProfileGroupIdsSelfLocked.size() > 0) {
- pw.println(" mUserProfileGroupIds:");
- for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
- pw.print(" User #");
- pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
- pw.print(" -> profile #");
- pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
- }
- }
- }
+ mUserController.dump(pw, dumpAll);
}
if (mHomeProcess != null && (dumpPackage == null
|| mHomeProcess.pkgList.containsKey(dumpPackage))) {
@@ -16098,9 +16000,9 @@
requireFull ? ALLOW_FULL_ONLY : ALLOW_NON_FULL, name, callerPackage);
}
- int unsafeConvertIncomingUser(int userId) {
+ int unsafeConvertIncomingUserLocked(int userId) {
return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
- ? mCurrentUserId : userId;
+ ? mUserController.mCurrentUserId : userId;
}
int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
@@ -16116,7 +16018,7 @@
// the value the caller will receive and someone else changing it.
// We assume that USER_CURRENT_OR_SELF will use the current user; later
// we will switch to the calling user if access to the current user fails.
- int targetUserId = unsafeConvertIncomingUser(userId);
+ int targetUserId = unsafeConvertIncomingUserLocked(userId);
if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
final boolean allow;
@@ -16137,14 +16039,7 @@
} else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
// We may or may not allow this depending on whether the two users are
// in the same profile.
- synchronized (mUserProfileGroupIdsSelfLocked) {
- int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
- UserInfo.NO_PROFILE_GROUP_ID);
- int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
- UserInfo.NO_PROFILE_GROUP_ID);
- allow = callingProfile != UserInfo.NO_PROFILE_GROUP_ID
- && callingProfile == targetProfile;
- }
+ allow = mUserController.isSameProfileGroup(callingUserId, targetUserId);
} else {
throw new IllegalArgumentException("Unknown mode: " + allowMode);
}
@@ -16763,7 +16658,7 @@
return receivers;
}
- private final int broadcastIntentLocked(ProcessRecord callerApp,
+ final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle options,
@@ -17080,7 +16975,7 @@
int[] users;
if (userId == UserHandle.USER_ALL) {
// Caller wants broadcast to go to all started users.
- users = mStartedUserArray;
+ users = mUserController.getStartedUserArrayLocked();
} else {
// Caller wants broadcast to go to one specific user.
users = new int[] {userId};
@@ -17667,6 +17562,12 @@
Binder.restoreCallingIdentity(origId);
}
}
+ void updateUserConfigurationLocked() {
+ Configuration configuration = new Configuration(mConfiguration);
+ Settings.System.getConfigurationForUser(mContext.getContentResolver(), configuration,
+ mUserController.mCurrentUserId);
+ updateConfigurationLocked(configuration, null, false);
+ }
boolean updateConfigurationLocked(Configuration values,
ActivityRecord starting, boolean initLocale) {
@@ -17712,7 +17613,8 @@
newConfig.seq = mConfigurationSeq;
mConfiguration = newConfig;
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
- mUsageStatsService.reportConfigurationChange(newConfig, mCurrentUserId);
+ mUsageStatsService.reportConfigurationChange(newConfig,
+ mUserController.mCurrentUserId);
//mUsageStatsService.noteStartConfig(newConfig);
final Configuration configCopy = new Configuration(mConfiguration);
@@ -19196,7 +19098,7 @@
String authority) {
if (app == null) return;
if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- UserState userState = mStartedUsers.get(app.userId);
+ UserState userState = mUserController.getStartedUserState(app.userId);
if (userState == null) return;
final long now = SystemClock.elapsedRealtime();
Long lastReported = userState.mProviderLastReportedFg.get(authority);
@@ -20076,44 +19978,18 @@
*/
@Override
public boolean startUserInBackground(final int userId) {
- return startUser(userId, /* foreground */ false);
+ return mUserController.startUser(userId, /* foreground */ false);
}
/**
* Start user, if its not already running, and bring it to foreground.
*/
boolean startUserInForeground(final int userId, Dialog dlg) {
- boolean result = startUser(userId, /* foreground */ true);
+ boolean result = mUserController.startUser(userId, /* foreground */ true);
dlg.dismiss();
return result;
}
- /**
- * Refreshes the list of users related to the current user when either a
- * user switch happens or when a new related user is started in the
- * background.
- */
- private void updateCurrentProfileIdsLocked() {
- final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
- mCurrentUserId, false /* enabledOnly */);
- int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
- for (int i = 0; i < currentProfileIds.length; i++) {
- currentProfileIds[i] = profiles.get(i).id;
- }
- mCurrentProfileIds = currentProfileIds;
-
- synchronized (mUserProfileGroupIdsSelfLocked) {
- mUserProfileGroupIdsSelfLocked.clear();
- final List<UserInfo> users = getUserManagerLocked().getUsers(false);
- for (int i = 0; i < users.size(); i++) {
- UserInfo user = users.get(i);
- if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
- mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
- }
- }
- }
- }
-
private Set<Integer> getProfileIdsLocked(int userId) {
Set<Integer> userIds = new HashSet<Integer>();
final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
@@ -20139,7 +20015,7 @@
return false;
}
userName = userInfo.name;
- mTargetUserId = userId;
+ mUserController.mTargetUserId = userId;
}
mUiHandler.removeMessages(START_USER_SWITCH_MSG);
mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
@@ -20153,186 +20029,6 @@
d.show();
}
- private boolean startUser(final int userId, final boolean foreground) {
- if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: switchUser() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + INTERACT_ACROSS_USERS_FULL;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
-
- if (DEBUG_MU) Slog.i(TAG_MU, "starting userid:" + userId + " fore:" + foreground);
-
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (this) {
- final int oldUserId = mCurrentUserId;
- if (oldUserId == userId) {
- return true;
- }
-
- mStackSupervisor.setLockTaskModeLocked(null, ActivityManager.LOCK_TASK_MODE_NONE,
- "startUser", false);
-
- final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
- if (userInfo == null) {
- Slog.w(TAG, "No user info for user #" + userId);
- return false;
- }
- if (foreground && userInfo.isManagedProfile()) {
- Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
- return false;
- }
-
- if (foreground) {
- mWindowManager.startFreezingScreen(R.anim.screen_user_exit,
- R.anim.screen_user_enter);
- }
-
- boolean needStart = false;
-
- // If the user we are switching to is not currently started, then
- // we need to start it now.
- if (mStartedUsers.get(userId) == null) {
- mStartedUsers.put(userId, new UserState(new UserHandle(userId), false));
- updateStartedUserArrayLocked();
- needStart = true;
- }
-
- final Integer userIdInt = Integer.valueOf(userId);
- mUserLru.remove(userIdInt);
- mUserLru.add(userIdInt);
-
- if (foreground) {
- mCurrentUserId = userId;
- updateUserConfigurationLocked();
- mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
- updateCurrentProfileIdsLocked();
- mWindowManager.setCurrentUser(userId, mCurrentProfileIds);
- // Once the internal notion of the active user has switched, we lock the device
- // with the option to show the user switcher on the keyguard.
- mWindowManager.lockNow(null);
- } else {
- final Integer currentUserIdInt = Integer.valueOf(mCurrentUserId);
- updateCurrentProfileIdsLocked();
- mWindowManager.setCurrentProfileIds(mCurrentProfileIds);
- mUserLru.remove(currentUserIdInt);
- mUserLru.add(currentUserIdInt);
- }
-
- final UserState uss = mStartedUsers.get(userId);
-
- // Make sure user is in the started state. If it is currently
- // stopping, we need to knock that off.
- if (uss.mState == UserState.STATE_STOPPING) {
- // If we are stopping, we haven't sent ACTION_SHUTDOWN,
- // so we can just fairly silently bring the user back from
- // the almost-dead.
- uss.mState = UserState.STATE_RUNNING;
- updateStartedUserArrayLocked();
- needStart = true;
- } else if (uss.mState == UserState.STATE_SHUTDOWN) {
- // This means ACTION_SHUTDOWN has been sent, so we will
- // need to treat this as a new boot of the user.
- uss.mState = UserState.STATE_BOOTING;
- updateStartedUserArrayLocked();
- needStart = true;
- }
-
- if (uss.mState == UserState.STATE_BOOTING) {
- // Booting up a new user, need to tell system services about it.
- // Note that this is on the same handler as scheduling of broadcasts,
- // which is important because it needs to go first.
- mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
- }
-
- if (foreground) {
- mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
- oldUserId));
- mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
- mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
- oldUserId, userId, uss));
- mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
- oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
- }
-
- if (needStart) {
- // Send USER_STARTED broadcast
- Intent intent = new Intent(Intent.ACTION_USER_STARTED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, Process.SYSTEM_UID, userId);
- }
-
- if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
- if (userId != UserHandle.USER_SYSTEM) {
- Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- broadcastIntentLocked(null, null, intent, null,
- new IIntentReceiver.Stub() {
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered,
- boolean sticky, int sendingUser) {
- onUserInitialized(uss, foreground, oldUserId, userId);
- }
- }, 0, null, null, null, AppOpsManager.OP_NONE,
- null, true, false, MY_PID, Process.SYSTEM_UID, userId);
- uss.initializing = true;
- } else {
- getUserManagerLocked().makeInitialized(userInfo.id);
- }
- }
-
- if (foreground) {
- if (!uss.initializing) {
- moveUserToForegroundLocked(uss, oldUserId, userId);
- }
- } else {
- mStackSupervisor.startBackgroundUserLocked(userId, uss);
- }
-
- if (needStart) {
- Intent intent = new Intent(Intent.ACTION_USER_STARTING);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- broadcastIntentLocked(null, null, intent,
- null, new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered, boolean sticky,
- int sendingUser) throws RemoteException {
- }
- }, 0, null, null,
- new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
- null, true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
-
- return true;
- }
-
- void dispatchForegroundProfileChanged(int userId) {
- final int N = mUserSwitchObservers.beginBroadcast();
- for (int i = 0; i < N; i++) {
- try {
- mUserSwitchObservers.getBroadcastItem(i).onForegroundProfileSwitch(userId);
- } catch (RemoteException e) {
- // Ignore
- }
- }
- mUserSwitchObservers.finishBroadcast();
- }
-
void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
long ident = Binder.clearCallingIdentity();
try {
@@ -20381,150 +20077,6 @@
}
}
- void dispatchUserSwitch(final UserState uss, final int oldUserId,
- final int newUserId) {
- final int N = mUserSwitchObservers.beginBroadcast();
- if (N > 0) {
- final IRemoteCallback callback = new IRemoteCallback.Stub() {
- int mCount = 0;
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- synchronized (ActivityManagerService.this) {
- if (mCurUserSwitchCallback == this) {
- mCount++;
- if (mCount == N) {
- sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
- }
- }
- }
- }
- };
- synchronized (this) {
- uss.switching = true;
- mCurUserSwitchCallback = callback;
- }
- for (int i=0; i<N; i++) {
- try {
- mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(
- newUserId, callback);
- } catch (RemoteException e) {
- }
- }
- } else {
- synchronized (this) {
- sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
- }
- }
- mUserSwitchObservers.finishBroadcast();
- }
-
- void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
- synchronized (this) {
- Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
- sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
- }
- }
-
- void sendContinueUserSwitchLocked(UserState uss, int oldUserId, int newUserId) {
- mCurUserSwitchCallback = null;
- mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
- oldUserId, newUserId, uss));
- }
-
- void onUserInitialized(UserState uss, boolean foreground, int oldUserId, int newUserId) {
- synchronized (this) {
- if (foreground) {
- moveUserToForegroundLocked(uss, oldUserId, newUserId);
- }
- }
-
- completeSwitchAndInitialize(uss, newUserId, true, false);
- }
-
- void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
- boolean homeInFront = mStackSupervisor.switchUserLocked(newUserId, uss);
- if (homeInFront) {
- startHomeActivityLocked(newUserId, "moveUserToFroreground");
- } else {
- mStackSupervisor.resumeTopActivitiesLocked();
- }
- EventLogTags.writeAmSwitchUser(newUserId);
- getUserManagerLocked().onUserForeground(newUserId);
- sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
- }
-
- private void updateUserConfigurationLocked() {
- Configuration configuration = new Configuration(mConfiguration);
- Settings.System.getConfigurationForUser(mContext.getContentResolver(), configuration,
- mCurrentUserId);
- updateConfigurationLocked(configuration, null, false);
- }
-
- void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
- completeSwitchAndInitialize(uss, newUserId, false, true);
- }
-
- void completeSwitchAndInitialize(UserState uss, int newUserId,
- boolean clearInitializing, boolean clearSwitching) {
- boolean unfrozen = false;
- synchronized (this) {
- if (clearInitializing) {
- uss.initializing = false;
- getUserManagerLocked().makeInitialized(uss.mHandle.getIdentifier());
- }
- if (clearSwitching) {
- uss.switching = false;
- }
- if (!uss.switching && !uss.initializing) {
- mWindowManager.stopFreezingScreen();
- unfrozen = true;
- }
- }
- if (unfrozen) {
- mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG,
- newUserId, 0));
- }
- stopGuestUserIfBackground();
- }
-
- /** Called on handler thread */
- void dispatchUserSwitchComplete(int userId) {
- final int observerCount = mUserSwitchObservers.beginBroadcast();
- for (int i = 0; i < observerCount; i++) {
- try {
- mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(userId);
- } catch (RemoteException e) {
- }
- }
- mUserSwitchObservers.finishBroadcast();
- }
-
- /**
- * Stops the guest user if it has gone to the background.
- */
- private void stopGuestUserIfBackground() {
- synchronized (this) {
- final int num = mUserLru.size();
- for (int i = 0; i < num; i++) {
- Integer oldUserId = mUserLru.get(i);
- UserState oldUss = mStartedUsers.get(oldUserId);
- if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId
- || oldUss.mState == UserState.STATE_STOPPING
- || oldUss.mState == UserState.STATE_SHUTDOWN) {
- continue;
- }
- UserInfo userInfo = mUserManager.getUserInfo(oldUserId);
- if (userInfo.isGuest()) {
- // This is a user to be stopped.
- stopUserLocked(oldUserId, null);
- break;
- }
- }
- }
- }
-
void scheduleStartProfilesLocked() {
if (!mHandler.hasMessages(START_PROFILES_MSG)) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
@@ -20532,229 +20084,9 @@
}
}
- void startProfilesLocked() {
- if (DEBUG_MU) Slog.i(TAG_MU, "startProfilesLocked");
- List<UserInfo> profiles = getUserManagerLocked().getProfiles(
- mCurrentUserId, false /* enabledOnly */);
- List<UserInfo> toStart = new ArrayList<UserInfo>(profiles.size());
- for (UserInfo user : profiles) {
- if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
- && user.id != mCurrentUserId) {
- toStart.add(user);
- }
- }
- final int n = toStart.size();
- int i = 0;
- for (; i < n && i < (MAX_RUNNING_USERS - 1); ++i) {
- startUserInBackground(toStart.get(i).id);
- }
- if (i < n) {
- Slog.w(TAG_MU, "More profiles than MAX_RUNNING_USERS");
- }
- }
-
- void finishUserBoot(UserState uss) {
- synchronized (this) {
- if (uss.mState == UserState.STATE_BOOTING
- && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) {
- uss.mState = UserState.STATE_RUNNING;
- final int userId = uss.mHandle.getIdentifier();
- Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null,
- new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
- AppOpsManager.OP_NONE, null, true, false, MY_PID, Process.SYSTEM_UID,
- userId);
- }
- }
- }
-
- void finishUserSwitch(UserState uss) {
- synchronized (this) {
- finishUserBoot(uss);
-
- startProfilesLocked();
-
- int num = mUserLru.size();
- int i = 0;
- while (num > MAX_RUNNING_USERS && i < mUserLru.size()) {
- Integer oldUserId = mUserLru.get(i);
- UserState oldUss = mStartedUsers.get(oldUserId);
- if (oldUss == null) {
- // Shouldn't happen, but be sane if it does.
- mUserLru.remove(i);
- num--;
- continue;
- }
- if (oldUss.mState == UserState.STATE_STOPPING
- || oldUss.mState == UserState.STATE_SHUTDOWN) {
- // This user is already stopping, doesn't count.
- num--;
- i++;
- continue;
- }
- if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId) {
- // Owner/System user and current user can't be stopped. We count it as running
- // when it is not a pure system user.
- if (UserInfo.isSystemOnly(oldUserId)) {
- num--;
- }
- i++;
- continue;
- }
- // This is a user to be stopped.
- stopUserLocked(oldUserId, null);
- num--;
- i++;
- }
- }
- }
-
@Override
public int stopUser(final int userId, final IStopUserCallback callback) {
- if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: switchUser() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + INTERACT_ACROSS_USERS_FULL;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
- throw new IllegalArgumentException("Can't stop system user " + userId);
- }
- enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
- synchronized (this) {
- return stopUserLocked(userId, callback);
- }
- }
-
- private int stopUserLocked(final int userId, final IStopUserCallback callback) {
- if (DEBUG_MU) Slog.i(TAG_MU, "stopUserLocked userId=" + userId);
- if (mCurrentUserId == userId && mTargetUserId == UserHandle.USER_NULL) {
- return ActivityManager.USER_OP_IS_CURRENT;
- }
-
- final UserState uss = mStartedUsers.get(userId);
- if (uss == null) {
- // User is not started, nothing to do... but we do need to
- // callback if requested.
- if (callback != null) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- callback.userStopped(userId);
- } catch (RemoteException e) {
- }
- }
- });
- }
- return ActivityManager.USER_OP_SUCCESS;
- }
-
- if (callback != null) {
- uss.mStopCallbacks.add(callback);
- }
-
- if (uss.mState != UserState.STATE_STOPPING
- && uss.mState != UserState.STATE_SHUTDOWN) {
- uss.mState = UserState.STATE_STOPPING;
- updateStartedUserArrayLocked();
-
- long ident = Binder.clearCallingIdentity();
- try {
- // We are going to broadcast ACTION_USER_STOPPING and then
- // once that is done send a final ACTION_SHUTDOWN and then
- // stop the user.
- final Intent stoppingIntent = new Intent(Intent.ACTION_USER_STOPPING);
- stoppingIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- stoppingIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- stoppingIntent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true);
- final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
- // This is the result receiver for the final shutdown broadcast.
- final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode, String data,
- Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
- finishUserStop(uss);
- }
- };
- // This is the result receiver for the initial stopping broadcast.
- final IIntentReceiver stoppingReceiver = new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode, String data,
- Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
- // On to the next.
- synchronized (ActivityManagerService.this) {
- if (uss.mState != UserState.STATE_STOPPING) {
- // Whoops, we are being started back up. Abort, abort!
- return;
- }
- uss.mState = UserState.STATE_SHUTDOWN;
- }
- mBatteryStatsService.noteEvent(
- BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
- Integer.toString(userId), userId);
- mSystemServiceManager.stopUser(userId);
- broadcastIntentLocked(null, null, shutdownIntent,
- null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
- null, true, false, MY_PID, Process.SYSTEM_UID, userId);
- }
- };
- // Kick things off.
- broadcastIntentLocked(null, null, stoppingIntent,
- null, stoppingReceiver, 0, null, null,
- new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
- null, true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- return ActivityManager.USER_OP_SUCCESS;
- }
-
- void finishUserStop(UserState uss) {
- final int userId = uss.mHandle.getIdentifier();
- boolean stopped;
- ArrayList<IStopUserCallback> callbacks;
- synchronized (this) {
- callbacks = new ArrayList<IStopUserCallback>(uss.mStopCallbacks);
- if (mStartedUsers.get(userId) != uss) {
- stopped = false;
- } else if (uss.mState != UserState.STATE_SHUTDOWN) {
- stopped = false;
- } else {
- stopped = true;
- // User can no longer run.
- mStartedUsers.remove(userId);
- mUserLru.remove(Integer.valueOf(userId));
- updateStartedUserArrayLocked();
-
- // Clean up all state and processes associated with the user.
- // Kill all the processes for the user.
- forceStopUserLocked(userId, "finish user");
- }
- }
-
- for (int i=0; i<callbacks.size(); i++) {
- try {
- if (stopped) callbacks.get(i).userStopped(userId);
- else callbacks.get(i).userStopAborted(userId);
- } catch (RemoteException e) {
- }
- }
-
- if (stopped) {
- mSystemServiceManager.cleanupUser(userId);
- synchronized (this) {
- mStackSupervisor.removeUserLocked(userId);
- }
- }
+ return mUserController.stopUser(userId, callback);
}
void onUserRemovedLocked(int userId) {
@@ -20763,25 +20095,7 @@
@Override
public UserInfo getCurrentUser() {
- if ((checkCallingPermission(INTERACT_ACROSS_USERS)
- != PackageManager.PERMISSION_GRANTED) && (
- checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
- != PackageManager.PERMISSION_GRANTED)) {
- String msg = "Permission Denial: getCurrentUser() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + INTERACT_ACROSS_USERS;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- synchronized (this) {
- int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
- return getUserManagerLocked().getUserInfo(userId);
- }
- }
-
- int getCurrentUserIdLocked() {
- return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+ return mUserController.getCurrentUser();
}
@Override
@@ -20801,7 +20115,7 @@
}
boolean isUserRunningLocked(int userId, boolean orStopped) {
- UserState state = mStartedUsers.get(userId);
+ UserState state = mUserController.getStartedUserState(userId);
if (state == null) {
return false;
}
@@ -20824,50 +20138,18 @@
throw new SecurityException(msg);
}
synchronized (this) {
- return mStartedUserArray;
- }
- }
-
- private void updateStartedUserArrayLocked() {
- int num = 0;
- for (int i=0; i<mStartedUsers.size(); i++) {
- UserState uss = mStartedUsers.valueAt(i);
- // This list does not include stopping users.
- if (uss.mState != UserState.STATE_STOPPING
- && uss.mState != UserState.STATE_SHUTDOWN) {
- num++;
- }
- }
- mStartedUserArray = new int[num];
- num = 0;
- for (int i=0; i<mStartedUsers.size(); i++) {
- UserState uss = mStartedUsers.valueAt(i);
- if (uss.mState != UserState.STATE_STOPPING
- && uss.mState != UserState.STATE_SHUTDOWN) {
- mStartedUserArray[num] = mStartedUsers.keyAt(i);
- num++;
- }
+ return mUserController.getStartedUserArrayLocked();
}
}
@Override
public void registerUserSwitchObserver(IUserSwitchObserver observer) {
- if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: registerUserSwitchObserver() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + INTERACT_ACROSS_USERS_FULL;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
-
- mUserSwitchObservers.register(observer);
+ mUserController.registerUserSwitchObserver(observer);
}
@Override
public void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
- mUserSwitchObservers.unregister(observer);
+ mUserController.unregisterUserSwitchObserver(observer);
}
int[] getUsersLocked() {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 1721470..8bf1d22 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -366,7 +366,7 @@
mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
mWindowManager = mService.mWindowManager;
mStackId = activityContainer.mStackId;
- mCurrentUser = mService.mCurrentUserId;
+ mCurrentUser = mService.mUserController.mCurrentUserId;
mRecentTasks = recentTasks;
mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
? new LaunchingTaskPositioner() : null;
@@ -1819,7 +1819,7 @@
// Make sure that the user who owns this activity is started. If not,
// we will just leave it as is because someone should be bringing
// another user's activities to the top of the stack.
- if (mService.mStartedUsers.get(next.userId) == null) {
+ if (!mService.mUserController.hasStartedUserState(next.userId)) {
Slog.w(TAG, "Skipping resume of top activity " + next
+ ": user " + next.userId + " is stopped");
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 54ac58a..99f7ec6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -97,7 +97,6 @@
import android.service.voice.IVoiceInteractionSession;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
@@ -2693,7 +2692,7 @@
// Complete user switch
if (startingUsers != null) {
for (int i = 0; i < startingUsers.size(); i++) {
- mService.finishUserSwitch(startingUsers.get(i));
+ mService.mUserController.finishUserSwitch(startingUsers.get(i));
}
}
// Complete starting up of background users
@@ -2701,7 +2700,7 @@
startingUsers = new ArrayList<UserState>(mStartingBackgroundUsers);
mStartingBackgroundUsers.clear();
for (int i = 0; i < startingUsers.size(); i++) {
- mService.finishUserBoot(startingUsers.get(i));
+ mService.mUserController.finishUserBoot(startingUsers.get(i));
}
}
}
@@ -3756,10 +3755,7 @@
/** Checks whether the userid is a profile of the current user. */
boolean isCurrentProfileLocked(int userId) {
if (userId == mCurrentUser) return true;
- for (int i = 0; i < mService.mCurrentProfileIds.length; i++) {
- if (mService.mCurrentProfileIds[i] == userId) return true;
- }
- return false;
+ return mService.mUserController.isCurrentProfileLocked(userId);
}
/** Checks whether the activity should be shown for current user. */
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 5b46799..6ed880e 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -248,7 +248,7 @@
boolean sendFinish = finishedReceiver != null;
int userId = key.userId;
if (userId == UserHandle.USER_CURRENT) {
- userId = owner.getCurrentUserIdLocked();
+ userId = owner.mUserController.getCurrentUserIdLocked();
}
switch (key.type) {
case ActivityManager.INTENT_SENDER_ACTIVITY:
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
new file mode 100644
index 0000000..ff74d83
--- /dev/null
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -0,0 +1,862 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.app.ActivityManager.USER_OP_IS_CURRENT;
+import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
+import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
+import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
+import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
+import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.IStopUserCallback;
+import android.app.IUserSwitchObserver;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.UserManagerService;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
+ */
+final class UserController {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
+ // Maximum number of users we allow to be running at a time.
+ static final int MAX_RUNNING_USERS = 3;
+
+ // Amount of time we wait for observers to handle a user switch before
+ // giving up on them and unfreezing the screen.
+ static final int USER_SWITCH_TIMEOUT = 2 * 1000;
+
+ private final ActivityManagerService mService;
+ private final Handler mHandler;
+
+ // Holds the current foreground user's id
+ int mCurrentUserId = 0;
+ // Holds the target user's id during a user switch
+ int mTargetUserId = UserHandle.USER_NULL;
+
+ /**
+ * Which users have been started, so are allowed to run code.
+ */
+ private final SparseArray<UserState> mStartedUsers = new SparseArray<>();
+ /**
+ * LRU list of history of current users. Most recently current is at the end.
+ */
+ private final ArrayList<Integer> mUserLru = new ArrayList<>();
+
+ /**
+ * Constant array of the users that are currently started.
+ */
+ private int[] mStartedUserArray = new int[] { 0 };
+
+ // If there are multiple profiles for the current user, their ids are here
+ // Currently only the primary user can have managed profiles
+ int[] mCurrentProfileIds = new int[] {}; // Accessed by ActivityStack
+
+ /**
+ * Mapping from each known user ID to the profile group ID it is associated with.
+ */
+ private final SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
+
+ /**
+ * Registered observers of the user switching mechanics.
+ */
+ final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
+ = new RemoteCallbackList<>();
+
+ /**
+ * Currently active user switch.
+ */
+ Object mCurUserSwitchCallback;
+
+ UserController(ActivityManagerService service) {
+ mService = service;
+ mHandler = mService.mHandler;
+ // User 0 is the first and only user that runs at boot.
+ mStartedUsers.put(UserHandle.USER_SYSTEM, new UserState(UserHandle.SYSTEM, true));
+ mUserLru.add(UserHandle.USER_SYSTEM);
+ updateStartedUserArrayLocked();
+ }
+
+ void finishUserSwitch(UserState uss) {
+ synchronized (mService) {
+ finishUserBoot(uss);
+
+ startProfilesLocked();
+
+ int num = mUserLru.size();
+ int i = 0;
+ while (num > MAX_RUNNING_USERS && i < mUserLru.size()) {
+ Integer oldUserId = mUserLru.get(i);
+ UserState oldUss = mStartedUsers.get(oldUserId);
+ if (oldUss == null) {
+ // Shouldn't happen, but be sane if it does.
+ mUserLru.remove(i);
+ num--;
+ continue;
+ }
+ if (oldUss.mState == UserState.STATE_STOPPING
+ || oldUss.mState == UserState.STATE_SHUTDOWN) {
+ // This user is already stopping, doesn't count.
+ num--;
+ i++;
+ continue;
+ }
+ if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId) {
+ // Owner/System user and current user can't be stopped. We count it as running
+ // when it is not a pure system user.
+ if (UserInfo.isSystemOnly(oldUserId)) {
+ num--;
+ }
+ i++;
+ continue;
+ }
+ // This is a user to be stopped.
+ stopUserLocked(oldUserId, null);
+ num--;
+ i++;
+ }
+ }
+ }
+
+ void finishUserBoot(UserState uss) {
+ synchronized (mService) {
+ if (uss.mState == UserState.STATE_BOOTING
+ && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) {
+ uss.mState = UserState.STATE_RUNNING;
+ final int userId = uss.mHandle.getIdentifier();
+ Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+ mService.broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null,
+ new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+ AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID,
+ Process.SYSTEM_UID, userId);
+ }
+ }
+ }
+
+ int stopUser(final int userId, final IStopUserCallback callback) {
+ if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: switchUser() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + INTERACT_ACROSS_USERS_FULL;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
+ throw new IllegalArgumentException("Can't stop system user " + userId);
+ }
+ mService.enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES,
+ userId);
+ synchronized (mService) {
+ return stopUserLocked(userId, callback);
+ }
+ }
+
+ private int stopUserLocked(final int userId, final IStopUserCallback callback) {
+ if (DEBUG_MU) Slog.i(TAG, "stopUserLocked userId=" + userId);
+ if (mCurrentUserId == userId && mTargetUserId == UserHandle.USER_NULL) {
+ return USER_OP_IS_CURRENT;
+ }
+
+ final UserState uss = mStartedUsers.get(userId);
+ if (uss == null) {
+ // User is not started, nothing to do... but we do need to
+ // callback if requested.
+ if (callback != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ callback.userStopped(userId);
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+ return USER_OP_SUCCESS;
+ }
+
+ if (callback != null) {
+ uss.mStopCallbacks.add(callback);
+ }
+
+ if (uss.mState != UserState.STATE_STOPPING
+ && uss.mState != UserState.STATE_SHUTDOWN) {
+ uss.mState = UserState.STATE_STOPPING;
+ updateStartedUserArrayLocked();
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ // We are going to broadcast ACTION_USER_STOPPING and then
+ // once that is done send a final ACTION_SHUTDOWN and then
+ // stop the user.
+ final Intent stoppingIntent = new Intent(Intent.ACTION_USER_STOPPING);
+ stoppingIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ stoppingIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ stoppingIntent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true);
+ final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
+ // This is the result receiver for the final shutdown broadcast.
+ final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+ finishUserStop(uss);
+ }
+ };
+ // This is the result receiver for the initial stopping broadcast.
+ final IIntentReceiver stoppingReceiver = new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+ // On to the next.
+ synchronized (mService) {
+ if (uss.mState != UserState.STATE_STOPPING) {
+ // Whoops, we are being started back up. Abort, abort!
+ return;
+ }
+ uss.mState = UserState.STATE_SHUTDOWN;
+ }
+ mService.mBatteryStatsService.noteEvent(
+ BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
+ Integer.toString(userId), userId);
+ mService.mSystemServiceManager.stopUser(userId);
+ mService.broadcastIntentLocked(null, null, shutdownIntent,
+ null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, true, false, ActivityManagerService.MY_PID,
+ android.os.Process.SYSTEM_UID, userId);
+ }
+ };
+ // Kick things off.
+ mService.broadcastIntentLocked(null, null, stoppingIntent,
+ null, stoppingReceiver, 0, null, null,
+ new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+ null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+ UserHandle.USER_ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ return USER_OP_SUCCESS;
+ }
+
+ void finishUserStop(UserState uss) {
+ final int userId = uss.mHandle.getIdentifier();
+ boolean stopped;
+ ArrayList<IStopUserCallback> callbacks;
+ synchronized (mService) {
+ callbacks = new ArrayList<>(uss.mStopCallbacks);
+ if (mStartedUsers.get(userId) != uss) {
+ stopped = false;
+ } else if (uss.mState != UserState.STATE_SHUTDOWN) {
+ stopped = false;
+ } else {
+ stopped = true;
+ // User can no longer run.
+ mStartedUsers.remove(userId);
+ mUserLru.remove(Integer.valueOf(userId));
+ updateStartedUserArrayLocked();
+
+ // Clean up all state and processes associated with the user.
+ // Kill all the processes for the user.
+ forceStopUserLocked(userId, "finish user");
+ }
+ }
+
+ for (int i = 0; i < callbacks.size(); i++) {
+ try {
+ if (stopped) callbacks.get(i).userStopped(userId);
+ else callbacks.get(i).userStopAborted(userId);
+ } catch (RemoteException e) {
+ }
+ }
+
+ if (stopped) {
+ mService.mSystemServiceManager.cleanupUser(userId);
+ synchronized (mService) {
+ mService.mStackSupervisor.removeUserLocked(userId);
+ }
+ }
+ }
+
+ private void forceStopUserLocked(int userId, String reason) {
+ mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
+ userId, reason);
+ Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ mService.broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+ UserHandle.USER_ALL);
+ }
+
+
+ /**
+ * Stops the guest user if it has gone to the background.
+ */
+ private void stopGuestUserIfBackground() {
+ synchronized (mService) {
+ final int num = mUserLru.size();
+ for (int i = 0; i < num; i++) {
+ Integer oldUserId = mUserLru.get(i);
+ UserState oldUss = mStartedUsers.get(oldUserId);
+ if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId
+ || oldUss.mState == UserState.STATE_STOPPING
+ || oldUss.mState == UserState.STATE_SHUTDOWN) {
+ continue;
+ }
+ UserInfo userInfo = getUserManagerLocked().getUserInfo(oldUserId);
+ if (userInfo.isGuest()) {
+ // This is a user to be stopped.
+ stopUserLocked(oldUserId, null);
+ break;
+ }
+ }
+ }
+ }
+
+ void startProfilesLocked() {
+ if (DEBUG_MU) Slog.i(TAG, "startProfilesLocked");
+ List<UserInfo> profiles = getUserManagerLocked().getProfiles(
+ mCurrentUserId, false /* enabledOnly */);
+ List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
+ for (UserInfo user : profiles) {
+ if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
+ && user.id != mCurrentUserId) {
+ profilesToStart.add(user);
+ }
+ }
+ final int profilesToStartSize = profilesToStart.size();
+ int i = 0;
+ for (; i < profilesToStartSize && i < (MAX_RUNNING_USERS - 1); ++i) {
+ startUser(profilesToStart.get(i).id, /* foreground= */ false);
+ }
+ if (i < profilesToStartSize) {
+ Slog.w(TAG, "More profiles than MAX_RUNNING_USERS");
+ }
+ }
+
+ private UserManagerService getUserManagerLocked() {
+ return mService.getUserManagerLocked();
+ }
+
+ boolean startUser(final int userId, final boolean foreground) {
+ if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: switchUser() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + INTERACT_ACROSS_USERS_FULL;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ if (DEBUG_MU) Slog.i(TAG, "starting userid:" + userId + " fore:" + foreground);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mService) {
+ final int oldUserId = mCurrentUserId;
+ if (oldUserId == userId) {
+ return true;
+ }
+
+ mService.mStackSupervisor.setLockTaskModeLocked(null,
+ ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
+
+ final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+ if (userInfo == null) {
+ Slog.w(TAG, "No user info for user #" + userId);
+ return false;
+ }
+ if (foreground && userInfo.isManagedProfile()) {
+ Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
+ return false;
+ }
+
+ if (foreground) {
+ mService.mWindowManager.startFreezingScreen(
+ R.anim.screen_user_exit, R.anim.screen_user_enter);
+ }
+
+ boolean needStart = false;
+
+ // If the user we are switching to is not currently started, then
+ // we need to start it now.
+ if (mStartedUsers.get(userId) == null) {
+ mStartedUsers.put(userId, new UserState(new UserHandle(userId), false));
+ updateStartedUserArrayLocked();
+ needStart = true;
+ }
+
+ final Integer userIdInt = userId;
+ mUserLru.remove(userIdInt);
+ mUserLru.add(userIdInt);
+
+ if (foreground) {
+ mCurrentUserId = userId;
+ mService.updateUserConfigurationLocked();
+ mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
+ updateCurrentProfileIdsLocked();
+ mService.mWindowManager.setCurrentUser(userId, mCurrentProfileIds);
+ // Once the internal notion of the active user has switched, we lock the device
+ // with the option to show the user switcher on the keyguard.
+ mService.mWindowManager.lockNow(null);
+ } else {
+ final Integer currentUserIdInt = mCurrentUserId;
+ updateCurrentProfileIdsLocked();
+ mService.mWindowManager.setCurrentProfileIds(mCurrentProfileIds);
+ mUserLru.remove(currentUserIdInt);
+ mUserLru.add(currentUserIdInt);
+ }
+
+ final UserState uss = mStartedUsers.get(userId);
+
+ // Make sure user is in the started state. If it is currently
+ // stopping, we need to knock that off.
+ if (uss.mState == UserState.STATE_STOPPING) {
+ // If we are stopping, we haven't sent ACTION_SHUTDOWN,
+ // so we can just fairly silently bring the user back from
+ // the almost-dead.
+ uss.mState = UserState.STATE_RUNNING;
+ updateStartedUserArrayLocked();
+ needStart = true;
+ } else if (uss.mState == UserState.STATE_SHUTDOWN) {
+ // This means ACTION_SHUTDOWN has been sent, so we will
+ // need to treat this as a new boot of the user.
+ uss.mState = UserState.STATE_BOOTING;
+ updateStartedUserArrayLocked();
+ needStart = true;
+ }
+
+ if (uss.mState == UserState.STATE_BOOTING) {
+ // Booting up a new user, need to tell system services about it.
+ // Note that this is on the same handler as scheduling of broadcasts,
+ // which is important because it needs to go first.
+ mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
+ }
+
+ if (foreground) {
+ mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
+ oldUserId));
+ mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
+ mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
+ oldUserId, userId, uss));
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
+ oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
+ }
+
+ if (needStart) {
+ // Send USER_STARTED broadcast
+ Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ mService.broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+ userId);
+ }
+
+ if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+ if (userId != UserHandle.USER_SYSTEM) {
+ Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mService.broadcastIntentLocked(null, null, intent, null,
+ new IIntentReceiver.Stub() {
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered,
+ boolean sticky, int sendingUser) {
+ onUserInitialized(uss, foreground, oldUserId, userId);
+ }
+ }, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, true, false, ActivityManagerService.MY_PID,
+ Process.SYSTEM_UID, userId);
+ uss.initializing = true;
+ } else {
+ getUserManagerLocked().makeInitialized(userInfo.id);
+ }
+ }
+
+ if (foreground) {
+ if (!uss.initializing) {
+ moveUserToForegroundLocked(uss, oldUserId, userId);
+ }
+ } else {
+ mService.mStackSupervisor.startBackgroundUserLocked(userId, uss);
+ }
+
+ if (needStart) {
+ Intent intent = new Intent(Intent.ACTION_USER_STARTING);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ mService.broadcastIntentLocked(null, null, intent,
+ null, new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered, boolean sticky,
+ int sendingUser) throws RemoteException {
+ }
+ }, 0, null, null,
+ new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+ null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+ UserHandle.USER_ALL);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ return true;
+ }
+
+ void dispatchForegroundProfileChanged(int userId) {
+ final int observerCount = mUserSwitchObservers.beginBroadcast();
+ for (int i = 0; i < observerCount; i++) {
+ try {
+ mUserSwitchObservers.getBroadcastItem(i).onForegroundProfileSwitch(userId);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+ mUserSwitchObservers.finishBroadcast();
+ }
+
+ /** Called on handler thread */
+ void dispatchUserSwitchComplete(int userId) {
+ final int observerCount = mUserSwitchObservers.beginBroadcast();
+ for (int i = 0; i < observerCount; i++) {
+ try {
+ mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(userId);
+ } catch (RemoteException e) {
+ }
+ }
+ mUserSwitchObservers.finishBroadcast();
+ }
+
+ void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
+ synchronized (mService) {
+ Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
+ sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+ }
+ }
+
+ void dispatchUserSwitch(final UserState uss, final int oldUserId,
+ final int newUserId) {
+ final int observerCount = mUserSwitchObservers.beginBroadcast();
+ if (observerCount > 0) {
+ final IRemoteCallback callback = new IRemoteCallback.Stub() {
+ int mCount = 0;
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ synchronized (mService) {
+ if (mCurUserSwitchCallback == this) {
+ mCount++;
+ if (mCount == observerCount) {
+ sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+ }
+ }
+ }
+ }
+ };
+ synchronized (mService) {
+ uss.switching = true;
+ mCurUserSwitchCallback = callback;
+ }
+ for (int i = 0; i < observerCount; i++) {
+ try {
+ mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(
+ newUserId, callback);
+ } catch (RemoteException e) {
+ }
+ }
+ } else {
+ synchronized (mService) {
+ sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+ }
+ }
+ mUserSwitchObservers.finishBroadcast();
+ }
+
+ void sendContinueUserSwitchLocked(UserState uss, int oldUserId, int newUserId) {
+ mCurUserSwitchCallback = null;
+ mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(ActivityManagerService.CONTINUE_USER_SWITCH_MSG,
+ oldUserId, newUserId, uss));
+ }
+
+ void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
+ completeSwitchAndInitialize(uss, newUserId, false, true);
+ }
+
+ void onUserInitialized(UserState uss, boolean foreground, int oldUserId, int newUserId) {
+ synchronized (mService) {
+ if (foreground) {
+ moveUserToForegroundLocked(uss, oldUserId, newUserId);
+ }
+ }
+ completeSwitchAndInitialize(uss, newUserId, true, false);
+ }
+
+ void completeSwitchAndInitialize(UserState uss, int newUserId,
+ boolean clearInitializing, boolean clearSwitching) {
+ boolean unfrozen = false;
+ synchronized (mService) {
+ if (clearInitializing) {
+ uss.initializing = false;
+ getUserManagerLocked().makeInitialized(uss.mHandle.getIdentifier());
+ }
+ if (clearSwitching) {
+ uss.switching = false;
+ }
+ if (!uss.switching && !uss.initializing) {
+ mService.mWindowManager.stopFreezingScreen();
+ unfrozen = true;
+ }
+ }
+ if (unfrozen) {
+ mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG,
+ newUserId, 0));
+ }
+ stopGuestUserIfBackground();
+ }
+
+ void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
+ boolean homeInFront = mService.mStackSupervisor.switchUserLocked(newUserId, uss);
+ if (homeInFront) {
+ mService.startHomeActivityLocked(newUserId, "moveUserToForeground");
+ } else {
+ mService.mStackSupervisor.resumeTopActivitiesLocked();
+ }
+ EventLogTags.writeAmSwitchUser(newUserId);
+ getUserManagerLocked().onUserForeground(newUserId);
+ mService.sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+ }
+
+ void registerUserSwitchObserver(IUserSwitchObserver observer) {
+ if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
+ final String msg = "Permission Denial: registerUserSwitchObserver() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + INTERACT_ACROSS_USERS_FULL;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ mUserSwitchObservers.register(observer);
+ }
+
+ void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
+ mUserSwitchObservers.unregister(observer);
+ }
+
+ UserState getStartedUserState(int userId) {
+ return mStartedUsers.get(userId);
+ }
+
+ boolean hasStartedUserState(int userId) {
+ return mStartedUsers.get(userId) != null;
+ }
+
+ private void updateStartedUserArrayLocked() {
+ int num = 0;
+ for (int i = 0; i < mStartedUsers.size(); i++) {
+ UserState uss = mStartedUsers.valueAt(i);
+ // This list does not include stopping users.
+ if (uss.mState != UserState.STATE_STOPPING
+ && uss.mState != UserState.STATE_SHUTDOWN) {
+ num++;
+ }
+ }
+ mStartedUserArray = new int[num];
+ num = 0;
+ for (int i = 0; i < mStartedUsers.size(); i++) {
+ UserState uss = mStartedUsers.valueAt(i);
+ if (uss.mState != UserState.STATE_STOPPING
+ && uss.mState != UserState.STATE_SHUTDOWN) {
+ mStartedUserArray[num] = mStartedUsers.keyAt(i);
+ num++;
+ }
+ }
+ }
+
+ void sendBootCompletedLocked(IIntentReceiver resultTo) {
+ for (int i = 0; i < mStartedUsers.size(); i++) {
+ UserState uss = mStartedUsers.valueAt(i);
+ if (uss.mState == UserState.STATE_BOOTING) {
+ uss.mState = UserState.STATE_RUNNING;
+ final int userId = mStartedUsers.keyAt(i);
+ Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+ mService.broadcastIntentLocked(null, null, intent, null,
+ resultTo, 0, null, null,
+ new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+ AppOpsManager.OP_NONE, null, true, false,
+ ActivityManagerService.MY_PID, Process.SYSTEM_UID, userId);
+ }
+ }
+ }
+
+ /**
+ * Refreshes the list of users related to the current user when either a
+ * user switch happens or when a new related user is started in the
+ * background.
+ */
+ void updateCurrentProfileIdsLocked() {
+ final List<UserInfo> profiles = getUserManagerLocked().getProfiles(mCurrentUserId,
+ false /* enabledOnly */);
+ int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
+ for (int i = 0; i < currentProfileIds.length; i++) {
+ currentProfileIds[i] = profiles.get(i).id;
+ }
+ mCurrentProfileIds = currentProfileIds;
+
+ synchronized (mUserProfileGroupIdsSelfLocked) {
+ mUserProfileGroupIdsSelfLocked.clear();
+ final List<UserInfo> users = getUserManagerLocked().getUsers(false);
+ for (int i = 0; i < users.size(); i++) {
+ UserInfo user = users.get(i);
+ if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+ mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
+ }
+ }
+ }
+ }
+
+ int[] getStartedUserArrayLocked() {
+ return mStartedUserArray;
+ }
+
+ UserInfo getCurrentUser() {
+ if ((mService.checkCallingPermission(INTERACT_ACROSS_USERS)
+ != PackageManager.PERMISSION_GRANTED) && (
+ mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED)) {
+ String msg = "Permission Denial: getCurrentUser() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + INTERACT_ACROSS_USERS;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ synchronized (mService) {
+ return getCurrentUserLocked();
+ }
+ }
+
+ UserInfo getCurrentUserLocked() {
+ int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+ return getUserManagerLocked().getUserInfo(userId);
+ }
+
+ int getCurrentUserIdLocked() {
+ return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+ }
+
+ boolean isSameProfileGroup(int callingUserId, int targetUserId) {
+ synchronized (mUserProfileGroupIdsSelfLocked) {
+ int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
+ UserInfo.NO_PROFILE_GROUP_ID);
+ int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
+ UserInfo.NO_PROFILE_GROUP_ID);
+ return callingProfile != UserInfo.NO_PROFILE_GROUP_ID
+ && callingProfile == targetProfile;
+ }
+ }
+
+ boolean isCurrentProfileLocked(int userId) {
+ return ArrayUtils.contains(mCurrentProfileIds, userId);
+ }
+
+ void dump(PrintWriter pw, boolean dumpAll) {
+ pw.println(" mStartedUsers:");
+ for (int i = 0; i < mStartedUsers.size(); i++) {
+ UserState uss = mStartedUsers.valueAt(i);
+ pw.print(" User #"); pw.print(uss.mHandle.getIdentifier());
+ pw.print(": "); uss.dump("", pw);
+ }
+ pw.print(" mStartedUserArray: [");
+ for (int i = 0; i < mStartedUserArray.length; i++) {
+ if (i > 0) pw.print(", ");
+ pw.print(mStartedUserArray[i]);
+ }
+ pw.println("]");
+ pw.print(" mUserLru: [");
+ for (int i = 0; i < mUserLru.size(); i++) {
+ if (i > 0) pw.print(", ");
+ pw.print(mUserLru.get(i));
+ }
+ pw.println("]");
+ if (dumpAll) {
+ pw.print(" mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
+ }
+ synchronized (mUserProfileGroupIdsSelfLocked) {
+ if (mUserProfileGroupIdsSelfLocked.size() > 0) {
+ pw.println(" mUserProfileGroupIds:");
+ for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
+ pw.print(" User #");
+ pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
+ pw.print(" -> profile #");
+ pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
+ }
+ }
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e354029..b38b9ce 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3016,7 +3016,7 @@
// Display task switcher for ALT-TAB.
if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_TAB) {
- if (mRecentAppsHeldModifiers == 0 && !keyguardOn) {
+ if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
if (KeyEvent.metaStateHasModifiers(shiftlessModifiers, KeyEvent.META_ALT_ON)) {
mRecentAppsHeldModifiers = shiftlessModifiers;
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 2828cd0..bf7063f 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -367,6 +367,7 @@
return false;
}
+ // This must be called while inside a transaction.
boolean showAllWindowsLocked() {
boolean isAnimating = false;
final int NW = mAllAppWinAnimators.size();
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index 7c2da2d..be3e922 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -56,7 +56,7 @@
int screenOffset, int maskThickness) {
mScreenSize = new Point();
display.getSize(mScreenSize);
- if (mScreenSize.x != mScreenSize.y) {
+ if (mScreenSize.x != mScreenSize.y + screenOffset) {
Slog.w(TAG, "Screen dimensions of displayId = " + display.getDisplayId() +
"are not equal, circularMask will not be drawn.");
mDimensionsUnequal = true;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 3521682..f5e97e5 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -265,21 +265,27 @@
}
void broadcastDragEndedLw() {
+ final int myPid = Process.myPid();
+
if (WindowManagerService.DEBUG_DRAG) {
Slog.d(WindowManagerService.TAG, "broadcasting DRAG_ENDED");
}
- DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
- 0, 0, null, null, null, null, mDragResult);
- for (WindowState ws: mNotifiedWindows) {
+ for (WindowState ws : mNotifiedWindows) {
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+ 0, 0, null, null, null, null, mDragResult);
try {
ws.mClient.dispatchDragEvent(evt);
} catch (RemoteException e) {
Slog.w(WindowManagerService.TAG, "Unable to drag-end window " + ws);
}
+ // if the current window is in the same process,
+ // the dispatch has already recycled the event
+ if (myPid != ws.mSession.mPid) {
+ evt.recycle();
+ }
}
mNotifiedWindows.clear();
mDragInProgress = false;
- evt.recycle();
}
void endDragLw() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ecc1f2c..1754123 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1808,8 +1808,6 @@
+ Debug.getCallers(3));
}
if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) {
- if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
- WindowManagerService.logSurface(mWin, "SHOW (performShowLocked)", null);
if (DEBUG_VISIBILITY || (DEBUG_STARTING_WINDOW &&
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING)) {
Slog.v(TAG, "Showing " + this
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 247562f..0c004b2 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1147,7 +1147,16 @@
for (int j = 0; j < windowsCount; j++) {
appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
}
- mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+ ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
+ SurfaceControl.openTransaction();
+ try {
+ mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+ } finally {
+ SurfaceControl.closeTransaction();
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+ "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
+ }
mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
int topOpeningLayer = 0;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index ff8d6d4d..1970542 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1086,17 +1086,8 @@
public WifiActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) {
if (mService == null) return null;
try {
- WifiActivityEnergyInfo record;
- if (!isEnhancedPowerReportingSupported()) {
- return null;
- }
synchronized(this) {
- record = mService.reportActivityInfo();
- if (record != null && record.isValid()) {
- return record;
- } else {
- return null;
- }
+ return mService.reportActivityInfo();
}
} catch (RemoteException e) {
Log.e(TAG, "getControllerActivityEnergyInfo: " + e);
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index a65f250..c26ca6e 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -709,6 +709,7 @@
validateChannel();
HotlistSettings settings = new HotlistSettings();
settings.bssidInfos = bssidInfos;
+ settings.apLostThreshold = apLostThreshold;
sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings);
}