Merge "Add metrics for Setting dialogs."
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
index 13a5e83..ce0c357 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
@@ -41,7 +41,6 @@
import android.perftests.utils.StubActivity;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
-import android.support.test.annotation.UiThreadTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.InstrumentationRegistry;
@@ -106,20 +105,21 @@
}
@Test
- @UiThreadTest
- public void testEditText() {
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- final EditText editText = setupEditText();
- final KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
- final int steps = 100;
- while (state.keepRunning()) {
- for (int i = 0; i < steps; i++) {
- int offset = (editText.getText().length() * i) / steps;
- editText.setSelection(offset);
- editText.bringPointIntoView(offset);
- editText.onKeyDown(keyEvent.getKeyCode(), keyEvent);
- editText.updateDisplayListIfDirty();
+ public void testEditText() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final EditText editText = setupEditText();
+ final KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
+ final int steps = 100;
+ while (state.keepRunning()) {
+ for (int i = 0; i < steps; i++) {
+ int offset = (editText.getText().length() * i) / steps;
+ editText.setSelection(offset);
+ editText.bringPointIntoView(offset);
+ editText.onKeyDown(keyEvent.getKeyCode(), keyEvent);
+ editText.updateDisplayListIfDirty();
+ }
}
- }
+ });
}
}
diff --git a/cmds/wm/src/com/android/commands/wm/Wm.java b/cmds/wm/src/com/android/commands/wm/Wm.java
index 383cd01..84fb626 100644
--- a/cmds/wm/src/com/android/commands/wm/Wm.java
+++ b/cmds/wm/src/com/android/commands/wm/Wm.java
@@ -21,16 +21,22 @@
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.AndroidException;
import android.util.DisplayMetrics;
+import android.system.Os;
import android.view.Display;
import android.view.IWindowManager;
import com.android.internal.os.BaseCommand;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.DataInputStream;
import java.io.PrintStream;
+import java.lang.Runtime;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -69,7 +75,9 @@
"wm screen-capture: enable/disable screen capture.\n" +
"\n" +
"wm dismiss-keyguard: dismiss the keyguard, prompting the user for auth if " +
- "necessary.\n"
+ "necessary.\n" +
+ "\n" +
+ "wm surface-trace: log surface commands to stdout in a binary format.\n"
);
}
@@ -96,12 +104,29 @@
runSetScreenCapture();
} else if (op.equals("dismiss-keyguard")) {
runDismissKeyguard();
+ } else if (op.equals("surface-trace")) {
+ runSurfaceTrace();
} else {
showError("Error: unknown command '" + op + "'");
return;
}
}
+ private void runSurfaceTrace() throws Exception {
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(FileDescriptor.out);
+ mWm.enableSurfaceTrace(pfd);
+
+ try {
+ // No one is going to wake us up, we are just waiting on SIGINT. Otherwise
+ // the WM can happily continue writing to our stdout.
+ synchronized (this) {
+ this.wait();
+ }
+ } finally {
+ mWm.disableSurfaceTrace();
+ }
+ }
+
private void runSetScreenCapture() throws Exception {
String userIdStr = nextArg();
String enableStr = nextArg();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2595c45..680e518 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1233,7 +1233,7 @@
getIcon(),
title,
actionIntent, // safe to alias
- new Bundle(mExtras),
+ mExtras == null ? new Bundle() : new Bundle(mExtras),
getRemoteInputs(),
getAllowGeneratedReplies());
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 35d3dc3..ee1f190 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1139,78 +1139,88 @@
* PackageManager.ActivityInfo.CONFIG_LAYOUT_DIRECTION}.
*/
public int diff(Configuration delta) {
+ return diff(delta, false /* compareUndefined */);
+ }
+
+ /**
+ * Variation of {@link #diff(Configuration)} with an option to skip checks for undefined values.
+ *
+ * @hide
+ */
+ public int diff(Configuration delta, boolean compareUndefined) {
int changed = 0;
- if (delta.fontScale > 0 && fontScale != delta.fontScale) {
+ if ((compareUndefined || delta.fontScale > 0) && fontScale != delta.fontScale) {
changed |= ActivityInfo.CONFIG_FONT_SCALE;
}
- if (delta.mcc != 0 && mcc != delta.mcc) {
+ if ((compareUndefined || delta.mcc != 0) && mcc != delta.mcc) {
changed |= ActivityInfo.CONFIG_MCC;
}
- if (delta.mnc != 0 && mnc != delta.mnc) {
+ if ((compareUndefined || delta.mnc != 0) && mnc != delta.mnc) {
changed |= ActivityInfo.CONFIG_MNC;
}
fixUpLocaleList();
delta.fixUpLocaleList();
- if (!delta.mLocaleList.isEmpty() && !mLocaleList.equals(delta.mLocaleList)) {
+ if ((compareUndefined || !delta.mLocaleList.isEmpty())
+ && !mLocaleList.equals(delta.mLocaleList)) {
changed |= ActivityInfo.CONFIG_LOCALE;
changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
}
final int deltaScreenLayoutDir = delta.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK;
- if (deltaScreenLayoutDir != SCREENLAYOUT_LAYOUTDIR_UNDEFINED &&
- deltaScreenLayoutDir != (screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)) {
+ if ((compareUndefined || deltaScreenLayoutDir != SCREENLAYOUT_LAYOUTDIR_UNDEFINED)
+ && deltaScreenLayoutDir != (screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)) {
changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
}
- if (delta.touchscreen != TOUCHSCREEN_UNDEFINED
+ if ((compareUndefined || delta.touchscreen != TOUCHSCREEN_UNDEFINED)
&& touchscreen != delta.touchscreen) {
changed |= ActivityInfo.CONFIG_TOUCHSCREEN;
}
- if (delta.keyboard != KEYBOARD_UNDEFINED
+ if ((compareUndefined || delta.keyboard != KEYBOARD_UNDEFINED)
&& keyboard != delta.keyboard) {
changed |= ActivityInfo.CONFIG_KEYBOARD;
}
- if (delta.keyboardHidden != KEYBOARDHIDDEN_UNDEFINED
+ if ((compareUndefined || delta.keyboardHidden != KEYBOARDHIDDEN_UNDEFINED)
&& keyboardHidden != delta.keyboardHidden) {
changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
}
- if (delta.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED
+ if ((compareUndefined || delta.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED)
&& hardKeyboardHidden != delta.hardKeyboardHidden) {
changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
}
- if (delta.navigation != NAVIGATION_UNDEFINED
+ if ((compareUndefined || delta.navigation != NAVIGATION_UNDEFINED)
&& navigation != delta.navigation) {
changed |= ActivityInfo.CONFIG_NAVIGATION;
}
- if (delta.navigationHidden != NAVIGATIONHIDDEN_UNDEFINED
+ if ((compareUndefined || delta.navigationHidden != NAVIGATIONHIDDEN_UNDEFINED)
&& navigationHidden != delta.navigationHidden) {
changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
}
- if (delta.orientation != ORIENTATION_UNDEFINED
+ if ((compareUndefined || delta.orientation != ORIENTATION_UNDEFINED)
&& orientation != delta.orientation) {
changed |= ActivityInfo.CONFIG_ORIENTATION;
}
- if (getScreenLayoutNoDirection(delta.screenLayout) !=
- (SCREENLAYOUT_SIZE_UNDEFINED | SCREENLAYOUT_LONG_UNDEFINED)
+ if ((compareUndefined || getScreenLayoutNoDirection(delta.screenLayout) !=
+ (SCREENLAYOUT_SIZE_UNDEFINED | SCREENLAYOUT_LONG_UNDEFINED))
&& getScreenLayoutNoDirection(screenLayout) !=
- getScreenLayoutNoDirection(delta.screenLayout)) {
+ getScreenLayoutNoDirection(delta.screenLayout)) {
changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
}
- if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED)
+ if ((compareUndefined || delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED))
&& uiMode != delta.uiMode) {
changed |= ActivityInfo.CONFIG_UI_MODE;
}
- if (delta.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED
+ if ((compareUndefined || delta.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED)
&& screenWidthDp != delta.screenWidthDp) {
changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
}
- if (delta.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED
+ if ((compareUndefined || delta.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED)
&& screenHeightDp != delta.screenHeightDp) {
changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
}
- if (delta.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+ if ((compareUndefined || delta.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED)
&& smallestScreenWidthDp != delta.smallestScreenWidthDp) {
changed |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
}
- if (delta.densityDpi != DENSITY_DPI_UNDEFINED
+ if ((compareUndefined || delta.densityDpi != DENSITY_DPI_UNDEFINED)
&& densityDpi != delta.densityDpi) {
changed |= ActivityInfo.CONFIG_DENSITY;
}
diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java
index 9478dc0..45fa15e 100644
--- a/core/java/android/hardware/camera2/DngCreator.java
+++ b/core/java/android/hardware/camera2/DngCreator.java
@@ -27,6 +27,7 @@
import android.media.ExifInterface;
import android.media.Image;
import android.os.SystemClock;
+import android.util.Log;
import android.util.Size;
import java.io.IOException;
@@ -89,17 +90,33 @@
throw new IllegalArgumentException("Null argument to DngCreator constructor");
}
- // Find current time
+ // Find current time in milliseconds since 1970
long currentTime = System.currentTimeMillis();
+ // Assume that sensor timestamp has that timebase to start
+ long timeOffset = 0;
- // Find boot time
- long bootTimeMillis = currentTime - SystemClock.elapsedRealtime();
+ int timestampSource = characteristics.get(
+ CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE);
+
+ if (timestampSource == CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME) {
+ // This means the same timebase as SystemClock.elapsedRealtime(),
+ // which is CLOCK_BOOTTIME
+ timeOffset = currentTime - SystemClock.elapsedRealtime();
+ } else if (timestampSource == CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN) {
+ // This means the same timebase as System.currentTimeMillis(),
+ // which is CLOCK_MONOTONIC
+ timeOffset = currentTime - SystemClock.uptimeMillis();
+ } else {
+ // Unexpected time source - treat as CLOCK_MONOTONIC
+ Log.w(TAG, "Sensor timestamp source is unexpected: " + timestampSource);
+ timeOffset = currentTime - SystemClock.uptimeMillis();
+ }
// Find capture time (nanos since boot)
Long timestamp = metadata.get(CaptureResult.SENSOR_TIMESTAMP);
long captureTime = currentTime;
if (timestamp != null) {
- captureTime = timestamp / 1000000 + bootTimeMillis;
+ captureTime = timestamp / 1000000 + timeOffset;
}
// Format for metadata
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index ec1e102..2bfe33b 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -58,14 +58,14 @@
* This class contains a {@code key} that will be used as the key into the
* {@link SharedPreferences}. It is up to the subclass to decide how to store
* the value.
- *
+ *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For information about building a settings UI with Preferences,
* read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
* guide.</p>
* </div>
- *
+ *
* @attr ref android.R.styleable#Preference_icon
* @attr ref android.R.styleable#Preference_key
* @attr ref android.R.styleable#Preference_title
@@ -89,13 +89,13 @@
private Context mContext;
private PreferenceManager mPreferenceManager;
-
+
/**
* Set when added to hierarchy since we need a unique ID within that
* hierarchy.
*/
private long mId;
-
+
private OnPreferenceChangeListener mOnChangeListener;
private OnPreferenceClickListener mOnClickListener;
@@ -120,22 +120,22 @@
private Object mDefaultValue;
private boolean mDependencyMet = true;
private boolean mParentDependencyMet = true;
-
+
/**
* @see #setShouldDisableView(boolean)
*/
private boolean mShouldDisableView = true;
-
+
private int mLayoutResId = com.android.internal.R.layout.preference;
private int mWidgetLayoutResId;
private boolean mCanRecycleLayout = true;
-
+
private OnPreferenceChangeInternalListener mListener;
-
+
private List<Preference> mDependents;
-
+
private boolean mBaseMethodCalled;
-
+
/**
* Interface definition for a callback to be invoked when the value of this
* {@link Preference} has been changed by the user and is
@@ -147,7 +147,7 @@
* Called when a Preference has been changed by the user. This is
* called before the state of the Preference is about to be updated and
* before the state is persisted.
- *
+ *
* @param preference The changed Preference.
* @param newValue The new value of the Preference.
* @return True to update the state of the Preference with the new value.
@@ -177,14 +177,14 @@
interface OnPreferenceChangeInternalListener {
/**
* Called when this Preference has changed.
- *
+ *
* @param preference This preference.
*/
void onPreferenceChange(Preference preference);
-
+
/**
* Called when this group has added/removed {@link Preference}(s).
- *
+ *
* @param preference This Preference.
*/
void onPreferenceHierarchyChange(Preference preference);
@@ -220,7 +220,7 @@
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes);
for (int i = a.getIndexCount() - 1; i >= 0; i--) {
- int attr = a.getIndex(i);
+ int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.Preference_icon:
mIconResId = a.getResourceId(attr, 0);
@@ -229,16 +229,16 @@
case com.android.internal.R.styleable.Preference_key:
mKey = a.getString(attr);
break;
-
+
case com.android.internal.R.styleable.Preference_title:
mTitleRes = a.getResourceId(attr, 0);
- mTitle = a.getString(attr);
+ mTitle = a.getText(attr);
break;
-
+
case com.android.internal.R.styleable.Preference_summary:
- mSummary = a.getString(attr);
+ mSummary = a.getText(attr);
break;
-
+
case com.android.internal.R.styleable.Preference_order:
mOrder = a.getInt(attr, mOrder);
break;
@@ -254,27 +254,27 @@
case com.android.internal.R.styleable.Preference_widgetLayout:
mWidgetLayoutResId = a.getResourceId(attr, mWidgetLayoutResId);
break;
-
+
case com.android.internal.R.styleable.Preference_enabled:
mEnabled = a.getBoolean(attr, true);
break;
-
+
case com.android.internal.R.styleable.Preference_selectable:
mSelectable = a.getBoolean(attr, true);
break;
-
+
case com.android.internal.R.styleable.Preference_persistent:
mPersistent = a.getBoolean(attr, mPersistent);
break;
-
+
case com.android.internal.R.styleable.Preference_dependency:
mDependencyKey = a.getString(attr);
break;
-
+
case com.android.internal.R.styleable.Preference_defaultValue:
mDefaultValue = onGetDefaultValue(a, attr);
break;
-
+
case com.android.internal.R.styleable.Preference_shouldDisableView:
mShouldDisableView = a.getBoolean(attr, mShouldDisableView);
break;
@@ -312,14 +312,14 @@
public Preference(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
-
+
/**
* Constructor that is called when inflating a Preference from XML. This is
* called when a Preference is being constructed from an XML file, supplying
* attributes that were specified in the XML file. This version uses a
* default style of 0, so the only attribute values applied are those in the
* Context's Theme and the given AttributeSet.
- *
+ *
* @param context The Context this is associated with, through which it can
* access the current theme, resources, {@link SharedPreferences},
* etc.
@@ -333,7 +333,7 @@
/**
* Constructor to create a Preference.
- *
+ *
* @param context The Context in which to store Preference values.
*/
public Preference(Context context) {
@@ -348,7 +348,7 @@
* <p>
* For example, if the value type is String, the body of the method would
* proxy to {@link TypedArray#getString(int)}.
- *
+ *
* @param a The set of attributes.
* @param index The index of the default value attribute.
* @return The default value of this preference type.
@@ -356,20 +356,20 @@
protected Object onGetDefaultValue(TypedArray a, int index) {
return null;
}
-
+
/**
* Sets an {@link Intent} to be used for
* {@link Context#startActivity(Intent)} when this Preference is clicked.
- *
+ *
* @param intent The intent associated with this Preference.
*/
public void setIntent(Intent intent) {
mIntent = intent;
}
-
+
/**
* Return the {@link Intent} associated with this Preference.
- *
+ *
* @return The {@link Intent} last set via {@link #setIntent(Intent)} or XML.
*/
public Intent getIntent() {
@@ -423,7 +423,7 @@
* {@link android.R.id#widget_frame} to be the parent of the specific widget
* for this Preference. It should similarly contain
* {@link android.R.id#title} and {@link android.R.id#summary}.
- *
+ *
* @param layoutResId The layout resource ID to be inflated and returned as
* a {@link View}.
* @see #setWidgetLayoutResource(int)
@@ -436,23 +436,23 @@
mLayoutResId = layoutResId;
}
-
+
/**
* Gets the layout resource that will be shown as the {@link View} for this Preference.
- *
+ *
* @return The layout resource ID.
*/
@LayoutRes
public int getLayoutResource() {
return mLayoutResId;
}
-
+
/**
* Sets the layout for the controllable widget portion of this Preference. This
* is inflated into the main layout. For example, a {@link CheckBoxPreference}
* would specify a custom layout (consisting of just the CheckBox) here,
* instead of creating its own main layout.
- *
+ *
* @param widgetLayoutResId The layout resource ID to be inflated into the
* main layout.
* @see #setLayoutResource(int)
@@ -467,17 +467,17 @@
/**
* Gets the layout resource for the controllable widget portion of this Preference.
- *
+ *
* @return The layout resource ID.
*/
@LayoutRes
public int getWidgetLayoutResource() {
return mWidgetLayoutResId;
}
-
+
/**
* Gets the View that will be shown in the {@link PreferenceActivity}.
- *
+ *
* @param convertView The old View to reuse, if possible. Note: You should
* check that this View is non-null and of an appropriate type
* before using. If it is not possible to convert this View to
@@ -495,7 +495,7 @@
onBindView(convertView);
return convertView;
}
-
+
/**
* Creates the View to be shown for this Preference in the
* {@link PreferenceActivity}. The default behavior is to inflate the main
@@ -504,7 +504,7 @@
* {@link android.R.id#widget_frame}.
* <p>
* Make sure to call through to the superclass's implementation.
- *
+ *
* @param parent The parent that this View will eventually be attached to.
* @return The View that displays this Preference.
* @see #onBindView(View)
@@ -512,10 +512,10 @@
@CallSuper
protected View onCreateView(ViewGroup parent) {
final LayoutInflater layoutInflater =
- (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- final View layout = layoutInflater.inflate(mLayoutResId, parent, false);
-
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ final View layout = layoutInflater.inflate(mLayoutResId, parent, false);
+
final ViewGroup widgetFrame = (ViewGroup) layout
.findViewById(com.android.internal.R.id.widget_frame);
if (widgetFrame != null) {
@@ -527,7 +527,7 @@
}
return layout;
}
-
+
/**
* Binds the created View to the data for this Preference.
* <p>
@@ -535,7 +535,7 @@
* set properties on them.
* <p>
* Make sure to call through to the superclass's implementation.
- *
+ *
* @param view The View that shows this Preference.
* @see #onCreateView(ViewGroup)
*/
@@ -592,7 +592,7 @@
*/
private void setEnabledStateOnViews(View v, boolean enabled) {
v.setEnabled(enabled);
-
+
if (v instanceof ViewGroup) {
final ViewGroup vg = (ViewGroup) v;
for (int i = vg.getChildCount() - 1; i >= 0; i--) {
@@ -600,14 +600,14 @@
}
}
}
-
+
/**
* Sets the order of this Preference with respect to other
* Preference objects on the same level. If this is not specified, the
* default behavior is to sort alphabetically. The
* {@link PreferenceGroup#setOrderingAsAdded(boolean)} can be used to order
* Preference objects based on the order they appear in the XML.
- *
+ *
* @param order The order for this Preference. A lower value will be shown
* first. Use {@link #DEFAULT_ORDER} to sort alphabetically or
* allow ordering from XML.
@@ -622,11 +622,11 @@
notifyHierarchyChanged();
}
}
-
+
/**
* Gets the order of this Preference with respect to other Preference objects
* on the same level.
- *
+ *
* @return The order of this Preference.
* @see #setOrder(int)
*/
@@ -639,7 +639,7 @@
* This title will be placed into the ID
* {@link android.R.id#title} within the View created by
* {@link #onCreateView(ViewGroup)}.
- *
+ *
* @param title The title for this Preference.
*/
public void setTitle(CharSequence title) {
@@ -649,10 +649,10 @@
notifyChanged();
}
}
-
+
/**
* Sets the title for this Preference with a resource ID.
- *
+ *
* @see #setTitle(CharSequence)
* @param titleResId The title as a resource ID.
*/
@@ -660,7 +660,7 @@
setTitle(mContext.getString(titleResId));
mTitleRes = titleResId;
}
-
+
/**
* Returns the title resource ID of this Preference. If the title did
* not come from a resource, 0 is returned.
@@ -675,7 +675,7 @@
/**
* Returns the title of this Preference.
- *
+ *
* @return The title.
* @see #setTitle(CharSequence)
*/
@@ -688,7 +688,7 @@
* This icon will be placed into the ID
* {@link android.R.id#icon} within the View created by
* {@link #onCreateView(ViewGroup)}.
- *
+ *
* @param icon The optional icon for this Preference.
*/
public void setIcon(Drawable icon) {
@@ -701,7 +701,7 @@
/**
* Sets the icon for this Preference with a resource ID.
- *
+ *
* @see #setIcon(Drawable)
* @param iconResId The icon as a resource ID.
*/
@@ -714,7 +714,7 @@
/**
* Returns the icon of this Preference.
- *
+ *
* @return The icon.
* @see #setIcon(Drawable)
*/
@@ -727,7 +727,7 @@
/**
* Returns the summary of this Preference.
- *
+ *
* @return The summary.
* @see #setSummary(CharSequence)
*/
@@ -737,7 +737,7 @@
/**
* Sets the summary for this Preference with a CharSequence.
- *
+ *
* @param summary The summary for the preference.
*/
public void setSummary(CharSequence summary) {
@@ -749,18 +749,18 @@
/**
* Sets the summary for this Preference with a resource ID.
- *
+ *
* @see #setSummary(CharSequence)
* @param summaryResId The summary as a resource.
*/
public void setSummary(@StringRes int summaryResId) {
setSummary(mContext.getString(summaryResId));
}
-
+
/**
* Sets whether this Preference is enabled. If disabled, it will
* not handle clicks.
- *
+ *
* @param enabled Set true to enable it.
*/
public void setEnabled(boolean enabled) {
@@ -773,10 +773,10 @@
notifyChanged();
}
}
-
+
/**
* Checks whether this Preference should be enabled in the list.
- *
+ *
* @return True if this Preference is enabled, false otherwise.
*/
public boolean isEnabled() {
@@ -785,7 +785,7 @@
/**
* Sets whether this Preference is selectable.
- *
+ *
* @param selectable Set true to make it selectable.
*/
public void setSelectable(boolean selectable) {
@@ -794,10 +794,10 @@
notifyChanged();
}
}
-
+
/**
* Checks whether this Preference should be selectable in the list.
- *
+ *
* @return True if it is selectable, false otherwise.
*/
public boolean isSelectable() {
@@ -811,7 +811,7 @@
* For example, set this and {@link #setEnabled(boolean)} to false for
* preferences that are only displaying information and 1) should not be
* clickable 2) should not have the view set to the disabled state.
- *
+ *
* @param shouldDisableView Set true if this preference should disable its view
* when the preference is disabled.
*/
@@ -819,7 +819,7 @@
mShouldDisableView = shouldDisableView;
notifyChanged();
}
-
+
/**
* Checks whether this Preference should disable its view when it's action is disabled.
* @see #setShouldDisableView(boolean)
@@ -832,13 +832,13 @@
/**
* Returns a unique ID for this Preference. This ID should be unique across all
* Preference objects in a hierarchy.
- *
+ *
* @return A unique ID for this Preference.
*/
long getId() {
return mId;
}
-
+
/**
* Processes a click on the preference. This includes saving the value to
* the {@link SharedPreferences}. However, the overridden method should
@@ -847,93 +847,93 @@
*/
protected void onClick() {
}
-
+
/**
* Sets the key for this Preference, which is used as a key to the
* {@link SharedPreferences}. This should be unique for the package.
- *
+ *
* @param key The key for the preference.
*/
public void setKey(String key) {
mKey = key;
-
+
if (mRequiresKey && !hasKey()) {
requireKey();
}
}
-
+
/**
* Gets the key for this Preference, which is also the key used for storing
* values into SharedPreferences.
- *
+ *
* @return The key.
*/
public String getKey() {
return mKey;
}
-
+
/**
* Checks whether the key is present, and if it isn't throws an
* exception. This should be called by subclasses that store preferences in
* the {@link SharedPreferences}.
- *
+ *
* @throws IllegalStateException If there is no key assigned.
*/
void requireKey() {
if (mKey == null) {
throw new IllegalStateException("Preference does not have a key assigned.");
}
-
+
mRequiresKey = true;
}
-
+
/**
* Checks whether this Preference has a valid key.
- *
+ *
* @return True if the key exists and is not a blank string, false otherwise.
*/
public boolean hasKey() {
return !TextUtils.isEmpty(mKey);
}
-
+
/**
* Checks whether this Preference is persistent. If it is, it stores its value(s) into
* the persistent {@link SharedPreferences} storage.
- *
+ *
* @return True if it is persistent.
*/
public boolean isPersistent() {
return mPersistent;
}
-
+
/**
* Checks whether, at the given time this method is called,
* this Preference should store/restore its value(s) into the
* {@link SharedPreferences}. This, at minimum, checks whether this
* Preference is persistent and it currently has a key. Before you
* save/restore from the {@link SharedPreferences}, check this first.
- *
+ *
* @return True if it should persist the value.
*/
protected boolean shouldPersist() {
return mPreferenceManager != null && isPersistent() && hasKey();
}
-
+
/**
* Sets whether this Preference is persistent. When persistent,
* it stores its value(s) into the persistent {@link SharedPreferences}
* storage.
- *
+ *
* @param persistent Set true if it should store its value(s) into the {@link SharedPreferences}.
*/
public void setPersistent(boolean persistent) {
mPersistent = persistent;
}
-
+
/**
* Call this method after the user changes the preference, but before the
* internal state is set. This allows the client to ignore the user value.
- *
+ *
* @param newValue The new value of this Preference.
* @return True if the user value should be set as the preference
* value (and persisted).
@@ -941,11 +941,11 @@
protected boolean callChangeListener(Object newValue) {
return mOnChangeListener == null ? true : mOnChangeListener.onPreferenceChange(this, newValue);
}
-
+
/**
* Sets the callback to be invoked when this Preference is changed by the
* user (but before the internal state has been updated).
- *
+ *
* @param onPreferenceChangeListener The callback to be invoked.
*/
public void setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener) {
@@ -955,7 +955,7 @@
/**
* Returns the callback to be invoked when this Preference is changed by the
* user (but before the internal state has been updated).
- *
+ *
* @return The callback to be invoked.
*/
public OnPreferenceChangeListener getOnPreferenceChangeListener() {
@@ -964,7 +964,7 @@
/**
* Sets the callback to be invoked when this Preference is clicked.
- *
+ *
* @param onPreferenceClickListener The callback to be invoked.
*/
public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) {
@@ -973,7 +973,7 @@
/**
* Returns the callback to be invoked when this Preference is clicked.
- *
+ *
* @return The callback to be invoked.
*/
public OnPreferenceClickListener getOnPreferenceClickListener() {
@@ -982,24 +982,24 @@
/**
* Called when a click should be performed.
- *
+ *
* @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click
* listener should be called in the proper order (between other
* processing). May be null.
* @hide
*/
public void performClick(PreferenceScreen preferenceScreen) {
-
+
if (!isEnabled()) {
return;
}
-
+
onClick();
-
+
if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) {
return;
}
-
+
PreferenceManager preferenceManager = getPreferenceManager();
if (preferenceManager != null) {
PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
@@ -1009,7 +1009,7 @@
return;
}
}
-
+
if (mIntent != null) {
Context context = getContext();
context.startActivity(mIntent);
@@ -1026,19 +1026,19 @@
public boolean onKey(View v, int keyCode, KeyEvent event) {
return false;
}
-
+
/**
* Returns the {@link android.content.Context} of this Preference.
* Each Preference in a Preference hierarchy can be
* from different Context (for example, if multiple activities provide preferences into a single
* {@link PreferenceActivity}). This Context will be used to save the Preference values.
- *
+ *
* @return The Context of this Preference.
*/
public Context getContext() {
return mContext;
}
-
+
/**
* Returns the {@link SharedPreferences} where this Preference can read its
* value(s). Usually, it's easier to use one of the helper read methods:
@@ -1051,7 +1051,7 @@
* right away and hence not show up in the returned
* {@link SharedPreferences}, this is intended behavior to improve
* performance.
- *
+ *
* @return The {@link SharedPreferences} where this Preference reads its
* value(s), or null if it isn't attached to a Preference hierarchy.
* @see #getEditor()
@@ -1060,10 +1060,10 @@
if (mPreferenceManager == null) {
return null;
}
-
+
return mPreferenceManager.getSharedPreferences();
}
-
+
/**
* Returns an {@link SharedPreferences.Editor} where this Preference can
* save its value(s). Usually it's easier to use one of the helper save
@@ -1076,7 +1076,7 @@
* In some cases, writes to this will not be committed right away and hence
* not show up in the SharedPreferences, this is intended behavior to
* improve performance.
- *
+ *
* @return A {@link SharedPreferences.Editor} where this preference saves
* its value(s), or null if it isn't attached to a Preference
* hierarchy.
@@ -1087,15 +1087,15 @@
if (mPreferenceManager == null) {
return null;
}
-
+
return mPreferenceManager.getEditor();
}
-
+
/**
* Returns whether the {@link Preference} should commit its saved value(s) in
* {@link #getEditor()}. This may return false in situations where batch
* committing is being done (by the manager) to improve performance.
- *
+ *
* @return Whether the Preference should commit its saved value(s).
* @see #getEditor()
*/
@@ -1103,13 +1103,13 @@
if (mPreferenceManager == null) {
return false;
}
-
+
return mPreferenceManager.shouldCommit();
}
-
+
/**
* Compares Preference objects based on order (if set), otherwise alphabetically on the titles.
- *
+ *
* @param another The Preference to compare to this one.
* @return 0 if the same; less than 0 if this Preference sorts ahead of <var>another</var>;
* greater than 0 if this Preference sorts after <var>another</var>.
@@ -1131,10 +1131,10 @@
return CharSequences.compareToIgnoreCase(mTitle, another.mTitle);
}
}
-
+
/**
* Sets the internal change listener.
- *
+ *
* @param listener The listener.
* @see #notifyChanged()
*/
@@ -1150,7 +1150,7 @@
mListener.onPreferenceChange(this);
}
}
-
+
/**
* Should be called when a Preference has been
* added/removed from this group, or the ordering should be
@@ -1164,27 +1164,27 @@
/**
* Gets the {@link PreferenceManager} that manages this Preference object's tree.
- *
+ *
* @return The {@link PreferenceManager}.
*/
public PreferenceManager getPreferenceManager() {
return mPreferenceManager;
}
-
+
/**
* Called when this Preference has been attached to a Preference hierarchy.
* Make sure to call the super implementation.
- *
+ *
* @param preferenceManager The PreferenceManager of the hierarchy.
*/
protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
mPreferenceManager = preferenceManager;
-
+
mId = preferenceManager.getNextId();
-
+
dispatchSetInitialValue();
}
-
+
/**
* Called when the Preference hierarchy has been attached to the
* {@link PreferenceActivity}. This can also be called when this
@@ -1198,9 +1198,9 @@
}
private void registerDependency() {
-
+
if (TextUtils.isEmpty(mDependencyKey)) return;
-
+
Preference preference = findPreferenceInHierarchy(mDependencyKey);
if (preference != null) {
preference.registerDependent(this);
@@ -1218,14 +1218,14 @@
}
}
}
-
+
/**
* Finds a Preference in this hierarchy (the whole thing,
* even above/below your {@link PreferenceScreen} screen break) with the given
* key.
* <p>
* This only functions after we have been attached to a hierarchy.
- *
+ *
* @param key The key of the Preference to find.
* @return The Preference that uses the given key.
*/
@@ -1233,16 +1233,16 @@
if (TextUtils.isEmpty(key) || mPreferenceManager == null) {
return null;
}
-
+
return mPreferenceManager.findPreference(key);
}
-
+
/**
* Adds a dependent Preference on this Preference so we can notify it.
* Usually, the dependent Preference registers itself (it's good for it to
* know it depends on something), so please use
* {@link Preference#setDependency(String)} on the dependent Preference.
- *
+ *
* @param dependent The dependent Preference that will be enabled/disabled
* according to the state of this Preference.
*/
@@ -1250,15 +1250,15 @@
if (mDependents == null) {
mDependents = new ArrayList<Preference>();
}
-
+
mDependents.add(dependent);
-
+
dependent.onDependencyChanged(this, shouldDisableDependents());
}
-
+
/**
* Removes a dependent Preference on this Preference.
- *
+ *
* @param dependent The dependent Preference that will be enabled/disabled
* according to the state of this Preference.
* @return Returns the same Preference object, for chaining multiple calls
@@ -1269,21 +1269,21 @@
mDependents.remove(dependent);
}
}
-
+
/**
* Notifies any listening dependents of a change that affects the
* dependency.
- *
+ *
* @param disableDependents Whether this Preference should disable
* its dependents.
*/
public void notifyDependencyChange(boolean disableDependents) {
final List<Preference> dependents = mDependents;
-
+
if (dependents == null) {
return;
}
-
+
final int dependentsCount = dependents.size();
for (int i = 0; i < dependentsCount; i++) {
dependents.get(i).onDependencyChanged(this, disableDependents);
@@ -1292,7 +1292,7 @@
/**
* Called when the dependency changes.
- *
+ *
* @param dependency The Preference that this Preference depends on.
* @param disableDependent Set true to disable this Preference.
*/
@@ -1327,38 +1327,38 @@
/**
* Checks whether this preference's dependents should currently be
* disabled.
- *
+ *
* @return True if the dependents should be disabled, otherwise false.
*/
public boolean shouldDisableDependents() {
return !isEnabled();
}
-
+
/**
* Sets the key of a Preference that this Preference will depend on. If that
* Preference is not set or is off, this Preference will be disabled.
- *
+ *
* @param dependencyKey The key of the Preference that this depends on.
*/
public void setDependency(String dependencyKey) {
// Unregister the old dependency, if we had one
unregisterDependency();
-
+
// Register the new
mDependencyKey = dependencyKey;
registerDependency();
}
-
+
/**
* Returns the key of the dependency on this Preference.
- *
+ *
* @return The key of the dependency.
* @see #setDependency(String)
*/
public String getDependency() {
return mDependencyKey;
}
-
+
/**
* Called when this Preference is being removed from the hierarchy. You
* should remove any references to this Preference that you know about. Make
@@ -1368,18 +1368,18 @@
protected void onPrepareForRemoval() {
unregisterDependency();
}
-
+
/**
* Sets the default value for this Preference, which will be set either if
* persistence is off or persistence is on and the preference is not found
* in the persistent storage.
- *
+ *
* @param defaultValue The default value.
*/
public void setDefaultValue(Object defaultValue) {
mDefaultValue = defaultValue;
}
-
+
private void dispatchSetInitialValue() {
// By now, we know if we are persistent.
final boolean shouldPersist = shouldPersist();
@@ -1391,7 +1391,7 @@
onSetInitialValue(true, null);
}
}
-
+
/**
* Implement this to set the initial value of the Preference.
* <p>
@@ -1403,7 +1403,7 @@
* <p>
* This may not always be called. One example is if it should not persist
* but there is no default value given.
- *
+ *
* @param restorePersistedValue True to restore the persisted value;
* false to use the given <var>defaultValue</var>.
* @param defaultValue The default value for this Preference. Only use this
@@ -1424,14 +1424,14 @@
}
}
}
-
+
/**
* Attempts to persist a String to the {@link android.content.SharedPreferences}.
* <p>
* This will check if this Preference is persistent, get an editor from
* the {@link PreferenceManager}, put in the string, and check if we should commit (and
* commit if so).
- *
+ *
* @param value The value to persist.
* @return True if the Preference is persistent. (This is not whether the
* value was persisted, since we may not necessarily commit if there
@@ -1445,7 +1445,7 @@
// It's already there, so the same as persisting
return true;
}
-
+
SharedPreferences.Editor editor = mPreferenceManager.getEditor();
editor.putString(mKey, value);
tryCommit(editor);
@@ -1453,13 +1453,13 @@
}
return false;
}
-
+
/**
* Attempts to get a persisted String from the {@link android.content.SharedPreferences}.
* <p>
* This will check if this Preference is persistent, get the SharedPreferences
* from the {@link PreferenceManager}, and get the value.
- *
+ *
* @param defaultReturnValue The default value to return if either the
* Preference is not persistent or the Preference is not in the
* shared preferences.
@@ -1471,17 +1471,17 @@
if (!shouldPersist()) {
return defaultReturnValue;
}
-
+
return mPreferenceManager.getSharedPreferences().getString(mKey, defaultReturnValue);
}
-
+
/**
* Attempts to persist a set of Strings to the {@link android.content.SharedPreferences}.
* <p>
* This will check if this Preference is persistent, get an editor from
* the {@link PreferenceManager}, put in the strings, and check if we should commit (and
* commit if so).
- *
+ *
* @param values The values to persist.
* @return True if the Preference is persistent. (This is not whether the
* value was persisted, since we may not necessarily commit if there
@@ -1495,7 +1495,7 @@
// It's already there, so the same as persisting
return true;
}
-
+
SharedPreferences.Editor editor = mPreferenceManager.getEditor();
editor.putStringSet(mKey, values);
tryCommit(editor);
@@ -1510,7 +1510,7 @@
* <p>
* This will check if this Preference is persistent, get the SharedPreferences
* from the {@link PreferenceManager}, and get the value.
- *
+ *
* @param defaultReturnValue The default value to return if either the
* Preference is not persistent or the Preference is not in the
* shared preferences.
@@ -1522,13 +1522,13 @@
if (!shouldPersist()) {
return defaultReturnValue;
}
-
+
return mPreferenceManager.getSharedPreferences().getStringSet(mKey, defaultReturnValue);
}
-
+
/**
* Attempts to persist an int to the {@link android.content.SharedPreferences}.
- *
+ *
* @param value The value to persist.
* @return True if the Preference is persistent. (This is not whether the
* value was persisted, since we may not necessarily commit if there
@@ -1542,7 +1542,7 @@
// It's already there, so the same as persisting
return true;
}
-
+
SharedPreferences.Editor editor = mPreferenceManager.getEditor();
editor.putInt(mKey, value);
tryCommit(editor);
@@ -1550,10 +1550,10 @@
}
return false;
}
-
+
/**
* Attempts to get a persisted int from the {@link android.content.SharedPreferences}.
- *
+ *
* @param defaultReturnValue The default value to return if either this
* Preference is not persistent or this Preference is not in the
* SharedPreferences.
@@ -1566,13 +1566,13 @@
if (!shouldPersist()) {
return defaultReturnValue;
}
-
+
return mPreferenceManager.getSharedPreferences().getInt(mKey, defaultReturnValue);
}
-
+
/**
* Attempts to persist a float to the {@link android.content.SharedPreferences}.
- *
+ *
* @param value The value to persist.
* @return True if this Preference is persistent. (This is not whether the
* value was persisted, since we may not necessarily commit if there
@@ -1594,10 +1594,10 @@
}
return false;
}
-
+
/**
* Attempts to get a persisted float from the {@link android.content.SharedPreferences}.
- *
+ *
* @param defaultReturnValue The default value to return if either this
* Preference is not persistent or this Preference is not in the
* SharedPreferences.
@@ -1610,13 +1610,13 @@
if (!shouldPersist()) {
return defaultReturnValue;
}
-
+
return mPreferenceManager.getSharedPreferences().getFloat(mKey, defaultReturnValue);
}
-
+
/**
* Attempts to persist a long to the {@link android.content.SharedPreferences}.
- *
+ *
* @param value The value to persist.
* @return True if this Preference is persistent. (This is not whether the
* value was persisted, since we may not necessarily commit if there
@@ -1630,7 +1630,7 @@
// It's already there, so the same as persisting
return true;
}
-
+
SharedPreferences.Editor editor = mPreferenceManager.getEditor();
editor.putLong(mKey, value);
tryCommit(editor);
@@ -1638,10 +1638,10 @@
}
return false;
}
-
+
/**
* Attempts to get a persisted long from the {@link android.content.SharedPreferences}.
- *
+ *
* @param defaultReturnValue The default value to return if either this
* Preference is not persistent or this Preference is not in the
* SharedPreferences.
@@ -1654,13 +1654,13 @@
if (!shouldPersist()) {
return defaultReturnValue;
}
-
+
return mPreferenceManager.getSharedPreferences().getLong(mKey, defaultReturnValue);
}
-
+
/**
* Attempts to persist a boolean to the {@link android.content.SharedPreferences}.
- *
+ *
* @param value The value to persist.
* @return True if this Preference is persistent. (This is not whether the
* value was persisted, since we may not necessarily commit if there
@@ -1674,7 +1674,7 @@
// It's already there, so the same as persisting
return true;
}
-
+
SharedPreferences.Editor editor = mPreferenceManager.getEditor();
editor.putBoolean(mKey, value);
tryCommit(editor);
@@ -1682,10 +1682,10 @@
}
return false;
}
-
+
/**
* Attempts to get a persisted boolean from the {@link android.content.SharedPreferences}.
- *
+ *
* @param defaultReturnValue The default value to return if either this
* Preference is not persistent or this Preference is not in the
* SharedPreferences.
@@ -1698,26 +1698,26 @@
if (!shouldPersist()) {
return defaultReturnValue;
}
-
+
return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue);
}
-
+
boolean canRecycleLayout() {
return mCanRecycleLayout;
}
-
+
@Override
public String toString() {
return getFilterableStringBuilder().toString();
}
-
+
/**
* Returns the text that will be used to filter this Preference depending on
* user input.
* <p>
* If overridding and calling through to the superclass, make sure to prepend
* your additions with a space.
- *
+ *
* @return Text as a {@link StringBuilder} that will be used to filter this
* preference. By default, this is the title and summary
* (concatenated with a space).
@@ -1741,9 +1741,9 @@
/**
* Store this Preference hierarchy's frozen state into the given container.
- *
+ *
* @param container The Bundle in which to save the instance of this Preference.
- *
+ *
* @see #restoreHierarchyState
* @see #onSaveInstanceState
*/
@@ -1755,9 +1755,9 @@
* Called by {@link #saveHierarchyState} to store the instance for this Preference and its children.
* May be overridden to modify how the save happens for children. For example, some
* Preference objects may want to not store an instance for their children.
- *
+ *
* @param container The Bundle in which to save the instance of this Preference.
- *
+ *
* @see #saveHierarchyState
* @see #onSaveInstanceState
*/
@@ -1780,7 +1780,7 @@
* state that can later be used to create a new instance with that same
* state. This state should only contain information that is not persistent
* or can be reconstructed later.
- *
+ *
* @return A Parcelable object containing the current dynamic state of
* this Preference, or null if there is nothing interesting to save.
* The default implementation returns null.
@@ -1794,9 +1794,9 @@
/**
* Restore this Preference hierarchy's previously saved state from the given container.
- *
+ *
* @param container The Bundle that holds the previously saved state.
- *
+ *
* @see #saveHierarchyState
* @see #onRestoreInstanceState
*/
@@ -1809,7 +1809,7 @@
* Preference and its children. May be overridden to modify how restoring
* happens to the children of a Preference. For example, some Preference objects may
* not want to save state for their children.
- *
+ *
* @param container The Bundle that holds the previously saved state.
* @see #restoreHierarchyState
* @see #onRestoreInstanceState
@@ -1832,7 +1832,7 @@
* Hook allowing a Preference to re-apply a representation of its internal
* state that had previously been generated by {@link #onSaveInstanceState}.
* This function will never be called with a null state.
- *
+ *
* @param state The saved state that had previously been returned by
* {@link #onSaveInstanceState}.
* @see #onSaveInstanceState
@@ -1856,17 +1856,17 @@
public BaseSavedState(Parcelable superState) {
super(superState);
}
-
+
public static final Parcelable.Creator<BaseSavedState> CREATOR =
new Parcelable.Creator<BaseSavedState>() {
- public BaseSavedState createFromParcel(Parcel in) {
- return new BaseSavedState(in);
- }
+ public BaseSavedState createFromParcel(Parcel in) {
+ return new BaseSavedState(in);
+ }
- public BaseSavedState[] newArray(int size) {
- return new BaseSavedState[size];
- }
- };
+ public BaseSavedState[] newArray(int size) {
+ return new BaseSavedState[size];
+ }
+ };
}
}
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 55df206..ce3e282 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -279,7 +279,9 @@
if (width < 0.0f) break;
i--;
}
- while (i < limit - 1 && mChars[i + 1] == ' ') i++;
+ while (i < limit - 1 && (mChars[i + 1] == ' ' || w[i + 1] == 0.0f)) {
+ i++;
+ }
return limit - i - 1;
}
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 70d183d..bdbe8b0 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -1029,8 +1029,10 @@
for (i = len; i > 0; i--) {
float w = widths[i - 1 + lineStart - widthStart];
-
if (w + sum + ellipsisWidth > avail) {
+ while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
+ i++;
+ }
break;
}
@@ -1076,9 +1078,11 @@
float w = widths[right - 1 + lineStart - widthStart];
if (w + rsum > ravail) {
+ while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
+ right++;
+ }
break;
}
-
rsum += w;
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 1d9f99f..c163393 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -29,6 +29,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IRemoteCallback;
+import android.os.ParcelFileDescriptor;
import android.view.IApplicationToken;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IDockedStackListener;
@@ -256,6 +257,13 @@
void setScreenCaptureDisabled(int userId, boolean disabled);
/**
+ * Testing and debugging infrastructure for writing surface events
+ * to given FD. See RemoteSurfaceTrace.java or Wm.java for format.
+ */
+ void enableSurfaceTrace(in ParcelFileDescriptor fd);
+ void disableSurfaceTrace();
+
+ /**
* Cancels the window transitions for the given task.
*/
void cancelTaskWindowTransition(int taskId);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index e778a7f..6456826 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -308,6 +308,17 @@
mCloseGuard.open("release");
}
+ // This is a transfer constructor, useful for transferring a live SurfaceControl native
+ // object to another Java wrapper which could have some different behavior, e.g.
+ // event logging.
+ public SurfaceControl(SurfaceControl other) {
+ mName = other.mName;
+ mNativeObject = other.mNativeObject;
+ other.mCloseGuard.close();
+ other.mNativeObject = 0;
+ mCloseGuard.open("release");
+ }
+
@Override
protected void finalize() throws Throwable {
try {
diff --git a/core/java/com/android/internal/os/WifiPowerEstimator.java b/core/java/com/android/internal/os/WifiPowerEstimator.java
index 3bd79f7e..d175202 100644
--- a/core/java/com/android/internal/os/WifiPowerEstimator.java
+++ b/core/java/com/android/internal/os/WifiPowerEstimator.java
@@ -38,13 +38,13 @@
}
/**
- * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
+ * Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB.
*/
private static double getWifiPowerPerPacket(PowerProfile profile) {
final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
final double WIFI_POWER = profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
/ 3600;
- return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60);
+ return WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048);
}
@Override
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index cd49d02..4c6350b 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -124,7 +124,6 @@
android/graphics/PathMeasure.cpp \
android/graphics/PathEffect.cpp \
android/graphics/Picture.cpp \
- android/graphics/PorterDuff.cpp \
android/graphics/BitmapRegionDecoder.cpp \
android/graphics/Rasterizer.cpp \
android/graphics/Region.cpp \
@@ -132,7 +131,6 @@
android/graphics/SurfaceTexture.cpp \
android/graphics/Typeface.cpp \
android/graphics/Utils.cpp \
- android/graphics/Xfermode.cpp \
android/graphics/YuvToJpegEncoder.cpp \
android/graphics/pdf/PdfDocument.cpp \
android/graphics/pdf/PdfEditor.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index d75d53e..cdaa4dc 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -127,11 +127,9 @@
extern int register_android_graphics_Path(JNIEnv* env);
extern int register_android_graphics_PathMeasure(JNIEnv* env);
extern int register_android_graphics_Picture(JNIEnv*);
-extern int register_android_graphics_PorterDuff(JNIEnv* env);
extern int register_android_graphics_Rasterizer(JNIEnv* env);
extern int register_android_graphics_Region(JNIEnv* env);
extern int register_android_graphics_SurfaceTexture(JNIEnv* env);
-extern int register_android_graphics_Xfermode(JNIEnv* env);
extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env);
extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
@@ -1343,13 +1341,11 @@
REG_JNI(register_android_graphics_PathMeasure),
REG_JNI(register_android_graphics_PathEffect),
REG_JNI(register_android_graphics_Picture),
- REG_JNI(register_android_graphics_PorterDuff),
REG_JNI(register_android_graphics_Rasterizer),
REG_JNI(register_android_graphics_Region),
REG_JNI(register_android_graphics_Shader),
REG_JNI(register_android_graphics_SurfaceTexture),
REG_JNI(register_android_graphics_Typeface),
- REG_JNI(register_android_graphics_Xfermode),
REG_JNI(register_android_graphics_YuvImage),
REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
REG_JNI(register_android_graphics_drawable_VectorDrawable),
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 9be659e..315dd6b 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -275,10 +275,30 @@
return reinterpret_cast<jlong>(obj->setColorFilter(filter));
}
- static jlong setXfermode(JNIEnv* env, jobject clazz, jlong objHandle, jlong xfermodeHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- SkXfermode* xfermode = reinterpret_cast<SkXfermode*>(xfermodeHandle);
- return reinterpret_cast<jlong>(obj->setXfermode(xfermode));
+ static void setXfermode(JNIEnv* env, jobject clazz, jlong paintHandle, jint xfermodeHandle) {
+ // validate that the Java enum values match our expectations
+ static_assert(0 == SkXfermode::kClear_Mode, "xfermode_mismatch");
+ static_assert(1 == SkXfermode::kSrc_Mode, "xfermode_mismatch");
+ static_assert(2 == SkXfermode::kDst_Mode, "xfermode_mismatch");
+ static_assert(3 == SkXfermode::kSrcOver_Mode, "xfermode_mismatch");
+ static_assert(4 == SkXfermode::kDstOver_Mode, "xfermode_mismatch");
+ static_assert(5 == SkXfermode::kSrcIn_Mode, "xfermode_mismatch");
+ static_assert(6 == SkXfermode::kDstIn_Mode, "xfermode_mismatch");
+ static_assert(7 == SkXfermode::kSrcOut_Mode, "xfermode_mismatch");
+ static_assert(8 == SkXfermode::kDstOut_Mode, "xfermode_mismatch");
+ static_assert(9 == SkXfermode::kSrcATop_Mode, "xfermode_mismatch");
+ static_assert(10 == SkXfermode::kDstATop_Mode, "xfermode_mismatch");
+ static_assert(11 == SkXfermode::kXor_Mode, "xfermode_mismatch");
+ static_assert(16 == SkXfermode::kDarken_Mode, "xfermode_mismatch");
+ static_assert(17 == SkXfermode::kLighten_Mode, "xfermode_mismatch");
+ static_assert(13 == SkXfermode::kModulate_Mode, "xfermode_mismatch");
+ static_assert(14 == SkXfermode::kScreen_Mode, "xfermode_mismatch");
+ static_assert(12 == SkXfermode::kPlus_Mode, "xfermode_mismatch");
+ static_assert(15 == SkXfermode::kOverlay_Mode, "xfermode_mismatch");
+
+ SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(xfermodeHandle);
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ paint->setXfermodeMode(mode);
}
static jlong setPathEffect(JNIEnv* env, jobject clazz, jlong objHandle, jlong effectHandle) {
@@ -961,7 +981,7 @@
{"nGetFillPath","!(JJJ)Z", (void*) PaintGlue::getFillPath},
{"nSetShader","!(JJ)J", (void*) PaintGlue::setShader},
{"nSetColorFilter","!(JJ)J", (void*) PaintGlue::setColorFilter},
- {"nSetXfermode","!(JJ)J", (void*) PaintGlue::setXfermode},
+ {"nSetXfermode","!(JI)V", (void*) PaintGlue::setXfermode},
{"nSetPathEffect","!(JJ)J", (void*) PaintGlue::setPathEffect},
{"nSetMaskFilter","!(JJ)J", (void*) PaintGlue::setMaskFilter},
{"nSetTypeface","!(JJ)J", (void*) PaintGlue::setTypeface},
diff --git a/core/jni/android/graphics/PorterDuff.cpp b/core/jni/android/graphics/PorterDuff.cpp
deleted file mode 100644
index dfc3c9d..0000000
--- a/core/jni/android/graphics/PorterDuff.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/* libs/android_runtime/android/graphics/PorterDuff.cpp
-**
-** Copyright 2006, 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.
-*/
-
-// This file was generated from the C++ include file: SkPorterDuff.h
-// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
-// or one of the auxilary file specifications in device/tools/gluemaker.
-
-#include "jni.h"
-#include "GraphicsJNI.h"
-#include "core_jni_helpers.h"
-
-#include "SkXfermode.h"
-
-namespace android {
-
-class SkPorterDuffGlue {
-public:
-
- static jlong CreateXfermode(JNIEnv* env, jobject, jint modeHandle) {
- // validate that the Java enum values match our expectations
- static_assert(0 == SkXfermode::kClear_Mode, "xfermode_mismatch");
- static_assert(1 == SkXfermode::kSrc_Mode, "xfermode_mismatch");
- static_assert(2 == SkXfermode::kDst_Mode, "xfermode_mismatch");
- static_assert(3 == SkXfermode::kSrcOver_Mode, "xfermode_mismatch");
- static_assert(4 == SkXfermode::kDstOver_Mode, "xfermode_mismatch");
- static_assert(5 == SkXfermode::kSrcIn_Mode, "xfermode_mismatch");
- static_assert(6 == SkXfermode::kDstIn_Mode, "xfermode_mismatch");
- static_assert(7 == SkXfermode::kSrcOut_Mode, "xfermode_mismatch");
- static_assert(8 == SkXfermode::kDstOut_Mode, "xfermode_mismatch");
- static_assert(9 == SkXfermode::kSrcATop_Mode, "xfermode_mismatch");
- static_assert(10 == SkXfermode::kDstATop_Mode, "xfermode_mismatch");
- static_assert(11 == SkXfermode::kXor_Mode, "xfermode_mismatch");
- static_assert(16 == SkXfermode::kDarken_Mode, "xfermode_mismatch");
- static_assert(17 == SkXfermode::kLighten_Mode, "xfermode_mismatch");
- static_assert(13 == SkXfermode::kModulate_Mode, "xfermode_mismatch");
- static_assert(14 == SkXfermode::kScreen_Mode, "xfermode_mismatch");
- static_assert(12 == SkXfermode::kPlus_Mode, "xfermode_mismatch");
- static_assert(15 == SkXfermode::kOverlay_Mode, "xfermode_mismatch");
-
- SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(modeHandle);
- return reinterpret_cast<jlong>(SkXfermode::Create(mode));
- }
-
-};
-
-static const JNINativeMethod methods[] = {
- {"nativeCreateXfermode","(I)J", (void*) SkPorterDuffGlue::CreateXfermode},
-};
-
-int register_android_graphics_PorterDuff(JNIEnv* env) {
- return RegisterMethodsOrDie(env, "android/graphics/PorterDuffXfermode", methods, NELEM(methods));
-}
-
-}
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index de32dd9..9781ae1 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -225,24 +225,13 @@
///////////////////////////////////////////////////////////////////////////////////////////////
-static jlong ComposeShader_create1(JNIEnv* env, jobject o,
- jlong shaderAHandle, jlong shaderBHandle, jlong modeHandle)
-{
- SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
- SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
- SkXfermode* mode = reinterpret_cast<SkXfermode *>(modeHandle);
- SkShader* shader = SkShader::CreateComposeShader(shaderA, shaderB, mode);
- return reinterpret_cast<jlong>(shader);
-}
-
-static jlong ComposeShader_create2(JNIEnv* env, jobject o,
+static jlong ComposeShader_create(JNIEnv* env, jobject o,
jlong shaderAHandle, jlong shaderBHandle, jint xfermodeHandle)
{
SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(xfermodeHandle);
- SkAutoTUnref<SkXfermode> xfermode(SkXfermode::Create(mode));
- SkShader* shader = SkShader::CreateComposeShader(shaderA, shaderB, xfermode.get());
+ SkShader* shader = SkShader::CreateComposeShader(shaderA, shaderB, mode);
return reinterpret_cast<jlong>(shader);
}
@@ -278,8 +267,7 @@
};
static const JNINativeMethod gComposeShaderMethods[] = {
- { "nativeCreate1", "(JJJ)J", (void*)ComposeShader_create1 },
- { "nativeCreate2", "(JJI)J", (void*)ComposeShader_create2 },
+ { "nativeCreate", "(JJI)J", (void*)ComposeShader_create },
};
int register_android_graphics_Shader(JNIEnv* env)
diff --git a/core/jni/android/graphics/Xfermode.cpp b/core/jni/android/graphics/Xfermode.cpp
deleted file mode 100644
index 78975a4f..0000000
--- a/core/jni/android/graphics/Xfermode.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "jni.h"
-//#include "GraphicsJNI.h"
-#include "core_jni_helpers.h"
-
-#include <SkXfermode.h>
-
-namespace android {
-
-class SkXfermodeGlue {
-public:
- static void finalizer(JNIEnv* env, jobject, jlong objHandle)
- {
- SkXfermode* obj = reinterpret_cast<SkXfermode *>(objHandle);
- SkSafeUnref(obj);
- }
-};
-
-///////////////////////////////////////////////////////////////////////////////
-
-static const JNINativeMethod gXfermodeMethods[] = {
- {"finalizer", "(J)V", (void*) SkXfermodeGlue::finalizer}
-};
-
-int register_android_graphics_Xfermode(JNIEnv* env) {
- android::RegisterMethodsOrDie(env, "android/graphics/Xfermode", gXfermodeMethods,
- NELEM(gXfermodeMethods));
- return 0;
-}
-
-}
diff --git a/core/res/res/layout-watch/alert_dialog_material.xml b/core/res/res/layout-watch/alert_dialog_material.xml
index 002dde8..2fe13de 100644
--- a/core/res/res/layout-watch/alert_dialog_material.xml
+++ b/core/res/res/layout-watch/alert_dialog_material.xml
@@ -45,7 +45,7 @@
<!-- Content Panel -->
<FrameLayout android:id="@+id/contentPanel"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:clipToPadding="false">
<TextView android:id="@+id/message"
android:layout_width="match_parent"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9940f28..aca2bcc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6965,9 +6965,11 @@
<attr name="icon" />
<!-- The key to store the Preference value. -->
<attr name="key" format="string" />
- <!-- The title for the Preference in a PreferenceActivity screen. -->
+ <!-- The title for the Preference. In API 25 and earlier, this value is read as a
+ plain string with styling information stripped. -->
<attr name="title" />
- <!-- The summary for the Preference in a PreferenceActivity screen. -->
+ <!-- The summary for the Preference. In API 25 and earlier, this value is read as a
+ plain string with styling information stripped. -->
<attr name="summary" />
<!-- The order for the Preference (lower values are to be ordered first). If this is not
specified, the default ordering will be alphabetic. -->
diff --git a/docs/html/develop/index.jd b/docs/html/develop/index.jd
index bd933f4..cee8327 100644
--- a/docs/html/develop/index.jd
+++ b/docs/html/develop/index.jd
@@ -14,24 +14,26 @@
<div class="wrap">
<div class="cols dac-hero-content">
<div class="col-1of2 col-push-1of2 dac-hero-figure">
- <img class="dac-hero-image" src="/images/develop/hero_image_studio5_2x.png" srcset="/images/develop/hero_image_studio5.png 1x, /images/develop/hero_image_studio5_2x.png 2x">
+ <img class="dac-hero-image" style="padding-top:32px"
+ src="/images/develop/hero-layout-editor_2x.png">
</div>
<div class="col-1of2 col-pull-1of2" style="margin-bottom:40px">
<h1 class="dac-hero-title">
<a style="color:inherit" href="{@docRoot}studio/index.html">
- Android Studio 2.1,<br>now available!</a></h1>
+ Android Studio 2.2 <nobr>is here!</nobr></a></h1>
-<p class="dac-hero-description">Android Studio provides the fastest tools for
-building apps on every type of Android device.</p>
+<p class="dac-hero-description">The latest version of Android Studio includes a
+rewritten <b>layout editor</b> with the new constraint layout,
+helping you build rich UI with less work.</p>
-<p class="dac-hero-description">The latest version, Android Studio 2.1, adds
-support for N Preview development on top of the faster Android Emulator and
-Instant Run feature from 2.0.</p>
+
+<p class="dac-hero-description">With over a dozen new features, Android Studio
+2.2 helps you code faster and smarter.</p>
<p style="margin-top:24px">
<a class="dac-hero-cta" href="{@docRoot}studio/index.html">
<span class="dac-sprite dac-auto-chevron"></span>
- Get Android Studio
+ Get Android Studio 2.2
</a>
<wbr>
<a class="dac-hero-cta" href="{@docRoot}studio/releases/index.html">
diff --git a/docs/html/google/play/billing/billing_admin.jd b/docs/html/google/play/billing/billing_admin.jd
index 292cfcc..ad09f1f 100644
--- a/docs/html/google/play/billing/billing_admin.jd
+++ b/docs/html/google/play/billing/billing_admin.jd
@@ -235,7 +235,7 @@
<p>"<em>product_id</em>","<em>publish_state</em>","<em>purchase_type</em>","<em>autotranslate</em>
","<em>locale</em>; <em>title</em>; <em>description</em>","<em>autofill</em>","<em>country</em>;
-<em>price</em>"
+<em>price</em>", "<em>pricing_template_id</em>"
</p>
<p>Descriptions and usage details are provided below.</p>
@@ -316,8 +316,9 @@
<p>"true","<em>default_price_in_home_currency</em>"
</li>
<li>
- <p>If <em>autofill</em> is set to <code>false</code>, you need to specify a <em>country</em>
- and a <em>price</em> for each currency, and you must use the following syntax:</p>
+ <p>If <em>autofill</em> is set to <code>false</code>, you need to either specify the <em>pricing_template_id</em>
+ that is linked to the in-app product or specify a <em>country</em> and a <em>price</em> for each currency.
+ If you choose to specify countries and prices, you must use the following syntax:</p>
<p>"false", "<em>home_country</em>; <em>default_price_in_home_currency</em>; <em>country_2</em>;
<em>country_2_price</em>; <em>country_3</em>; <em>country_3_price</em>; ..."</p>
</li>
@@ -335,11 +336,41 @@
</dd>
<dt>price</dt>
<dd>
+ <p>
+ If you use this value, you shouldn't specify a value for the <em>pricing_template_id</em>.
+ </p>
+ <p>
This is equivalent to the Price in the In-app Products UI. The price must be specified in
micro-units. To convert a currency value to micro-units, you multiply the real value by
1,000,000.
For example, if you want to sell an in-app item for $1.99, you specify <code>1990000</code> in the
<em>price</em> field.
+ </p>
+ </dd>
+ <dt>pricing_template_id</dt>
+ <dd>
+ <p>
+ If you use this value, you should set <em>autofill</em> to
+ <code>false</code> and leave the <em>price</em> column empty.
+ </p>
+ <p>
+ This value represents the ID of the pricing template that you've linked to
+ the in-app product. This ID appears under a pricing template's name
+ on the <strong>Pricing template</strong> page. If an in-app product isn't
+ linked to a pricing template, its <em>pricing_template_id</em> value is
+ empty.
+ </p>
+ <p>
+ If you import a CSV file and choose to overwrite the product list, you can
+ update the links between in-app products and pricing templates by changing
+ the value of an in-app product's <em>pricing_template_id</em>. Leave the
+ value empty to unlink an in-app product from all pricing templates.
+ </p>
+ <p>
+ <strong>Note: </strong>You can link up to 100 app prices or in-app product
+ prices with a particular pricing template. Therefore, don't specify the same
+ <em>pricing_template_id</em> value in more than 100 rows of your CSV file.
+ </p>
</dd>
</dl>
@@ -432,8 +463,11 @@
<p>
When creating a pricing template, you provide new pricing information that you
- can apply to paid apps and in-app products. To add a pricing template, do the
- following:
+ can apply to paid apps and in-app products. You can link the prices of up to
+ 100 apps and in-app products to a single pricing template.
+</p>
+</p>
+ To add a pricing template, do the following:
</p>
<ol>
diff --git a/docs/html/guide/topics/resources/multilingual-support.jd b/docs/html/guide/topics/resources/multilingual-support.jd
index 8d8484b..28699fe 100644
--- a/docs/html/guide/topics/resources/multilingual-support.jd
+++ b/docs/html/guide/topics/resources/multilingual-support.jd
@@ -88,15 +88,17 @@
<h2 id="postN">Improvements to Resource-Resolution Strategy</h2>
<p>Android 7.0 (API level 24) brings more robust resource resolution, and
- finds better fallbacks automatically. However, to speed up resolution and
- improve
+ finds better fallbacks automatically.
+ However, to speed up resolution and improve
maintainability, you should store resources in the most common parent dialect.
- For example, if you were storing Spanish resources in the {@code es-US}
- directory
- before, move them into the {@code es-419} directory, which contains Latin
- American Spanish.
- Similarly, if you have resource strings in a folder named {@code en-GB}, rename
- the folder to {@code en-001} (international English), because the most common
+ For example, if you were storing Spanish resources
+ in the {@code values-es-rUS} directory
+ before, move them into the {@code values-b+es+419} directory,
+ which contains Latin American Spanish.
+ Similarly, if you have resource strings in a
+ directory named {@code values-en-rGB}, rename
+ the directory to {@code values-b+en+001} (International
+ English), because the most common
parent for <code>en-GB</code> strings is {@code en-001}.
The following examples explain why these practices improve performance and
reliability of resource resolution.</p>
diff --git a/docs/html/images/develop/hero-layout-editor_2x.png b/docs/html/images/develop/hero-layout-editor_2x.png
new file mode 100644
index 0000000..56dfbf3
--- /dev/null
+++ b/docs/html/images/develop/hero-layout-editor_2x.png
Binary files differ
diff --git a/docs/html/index.jd b/docs/html/index.jd
index fe5dada..32360fd 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -63,6 +63,40 @@
</div><!-- end .wrap -->
</div><!-- end .dac-actions -->
+
+<section class="dac-expand dac-hero" style="background-color:#FFF0B4;">
+ <div class="wrap" style="max-width:1000px;margin-top:0;overflow:auto">
+ <div class="col-7of16 col-push-8of16 dac-hero-figure">
+ <a href="/studio/index.html">
+ <img class="dac-hero-image" style="padding-top:24px"
+ src="/studio/images/hero_image_studio_2-2_2x.png">
+ </a>
+ </div>
+ <div class="col-7of16 col-pull-6of16">
+ <h1 class="dac-hero-title" style="color:#004d40">Android Studio 2.2!</h1>
+<p class="dac-hero-description" style="color:#004d40">The latest update is
+packed with over a dozen new features, like a rewritten layout editor with the
+new constraint layout, support for Android 7.0 Nougat, Espresso test recording,
+enhanced Jack compiler / Java 8 support, expanded C++ support with CMake and
+NDK-Build, and much more!</p>
+<p class="dac-hero-description" style="color:#004d40">Android Studio 2.2
+helps you code faster and smarter.</p>
+
+<p style="margin-top:24px">
+ <a class="dac-hero-cta" href="/studio/index.html" style="color:#004d40">
+ <span class="dac-sprite dac-auto-chevron"></span>
+ Get Android Studio 2.2
+ </a>
+ <wbr>
+ <a class="dac-hero-cta" href="/studio/releases/index.html" style="color:#004d40">
+ <span class="dac-sprite dac-auto-chevron"></span>
+ See the release notes</a>
+</p>
+ </div>
+ </div>
+</section>
+
+
<section class="dac-section dac-light" id="build-apps"><div class="wrap">
<h1 class="dac-section-title">Build Beautiful Apps</h1>
<div class="dac-section-subtitle">
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index b2adcf6..08a68f4 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -21,23 +21,8 @@
*/
public class ComposeShader extends Shader {
- private static final int TYPE_XFERMODE = 1;
- private static final int TYPE_PORTERDUFFMODE = 2;
-
- /**
- * Type of the ComposeShader: can be either TYPE_XFERMODE or TYPE_PORTERDUFFMODE
- */
- private int mType;
-
- private Xfermode mXferMode;
- private PorterDuff.Mode mPorterDuffMode;
-
- /**
- * Hold onto the shaders to avoid GC.
- */
- @SuppressWarnings({"UnusedDeclaration"})
+ private int mPorterDuffMode;
private final Shader mShaderA;
- @SuppressWarnings({"UnusedDeclaration"})
private final Shader mShaderB;
/** Create a new compose shader, given shaders A, B, and a combining mode.
@@ -49,12 +34,7 @@
is null, then SRC_OVER is assumed.
*/
public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) {
- mType = TYPE_XFERMODE;
- mShaderA = shaderA;
- mShaderB = shaderB;
- mXferMode = mode;
- init(nativeCreate1(shaderA.getNativeInstance(), shaderB.getNativeInstance(),
- (mode != null) ? mode.native_instance : 0));
+ this(shaderA, shaderB, mode.porterDuffMode);
}
/** Create a new compose shader, given shaders A, B, and a combining PorterDuff mode.
@@ -65,12 +45,15 @@
@param mode The PorterDuff mode that combines the colors from the two shaders.
*/
public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) {
- mType = TYPE_PORTERDUFFMODE;
+ this(shaderA, shaderB, mode.nativeInt);
+ }
+
+ private ComposeShader(Shader shaderA, Shader shaderB, int nativeMode) {
mShaderA = shaderA;
mShaderB = shaderB;
- mPorterDuffMode = mode;
- init(nativeCreate2(shaderA.getNativeInstance(), shaderB.getNativeInstance(),
- mode.nativeInt));
+ mPorterDuffMode = nativeMode;
+ init(nativeCreate(shaderA.getNativeInstance(), shaderB.getNativeInstance(),
+ nativeMode));
}
/**
@@ -78,24 +61,12 @@
*/
@Override
protected Shader copy() {
- final ComposeShader copy;
- switch (mType) {
- case TYPE_XFERMODE:
- copy = new ComposeShader(mShaderA.copy(), mShaderB.copy(), mXferMode);
- break;
- case TYPE_PORTERDUFFMODE:
- copy = new ComposeShader(mShaderA.copy(), mShaderB.copy(), mPorterDuffMode);
- break;
- default:
- throw new IllegalArgumentException(
- "ComposeShader should be created with either Xfermode or PorterDuffMode");
- }
+ final ComposeShader copy = new ComposeShader(
+ mShaderA.copy(), mShaderB.copy(), mPorterDuffMode);
copyLocalMatrix(copy);
return copy;
}
- private static native long nativeCreate1(long native_shaderA, long native_shaderB,
- long native_mode);
- private static native long nativeCreate2(long native_shaderA, long native_shaderB,
+ private static native long nativeCreate(long native_shaderA, long native_shaderB,
int porterDuffMode);
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 81bbfa9..4abe50f 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1096,10 +1096,11 @@
* @return xfermode
*/
public Xfermode setXfermode(Xfermode xfermode) {
- long xfermodeNative = 0;
- if (xfermode != null)
- xfermodeNative = xfermode.native_instance;
- nSetXfermode(mNativePaint, xfermodeNative);
+ int newMode = xfermode != null ? xfermode.porterDuffMode : Xfermode.DEFAULT;
+ int curMode = mXfermode != null ? mXfermode.porterDuffMode : Xfermode.DEFAULT;
+ if (newMode != curMode) {
+ nSetXfermode(mNativePaint, newMode);
+ }
mXfermode = xfermode;
return xfermode;
}
@@ -2694,8 +2695,7 @@
private static native long nSetShader(long paintPtr, long shader);
private static native long nSetColorFilter(long paintPtr,
long filter);
- private static native long nSetXfermode(long paintPtr,
- long xfermode);
+ private static native void nSetXfermode(long paintPtr, int xfermode);
private static native long nSetPathEffect(long paintPtr,
long effect);
private static native long nSetMaskFilter(long paintPtr,
diff --git a/graphics/java/android/graphics/PorterDuffXfermode.java b/graphics/java/android/graphics/PorterDuffXfermode.java
index d9d7689..5104410 100644
--- a/graphics/java/android/graphics/PorterDuffXfermode.java
+++ b/graphics/java/android/graphics/PorterDuffXfermode.java
@@ -18,19 +18,11 @@
public class PorterDuffXfermode extends Xfermode {
/**
- * @hide
- */
- public final PorterDuff.Mode mode;
-
- /**
* Create an xfermode that uses the specified porter-duff mode.
*
* @param mode The porter-duff mode that is applied
*/
public PorterDuffXfermode(PorterDuff.Mode mode) {
- this.mode = mode;
- native_instance = nativeCreateXfermode(mode.nativeInt);
+ porterDuffMode = mode.nativeInt;
}
-
- private static native long nativeCreateXfermode(int mode);
}
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index c049e41..a5da5d0 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -29,17 +29,6 @@
* objects drawn with that paint have the xfermode applied.
*/
public class Xfermode {
-
- protected void finalize() throws Throwable {
- try {
- finalizer(native_instance);
- native_instance = 0;
- } finally {
- super.finalize();
- }
- }
-
- private static native void finalizer(long native_instance);
-
- long native_instance;
+ static final int DEFAULT = PorterDuff.Mode.SRC_OVER.nativeInt;
+ int porterDuffMode = DEFAULT;
}
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 3aca867..af20f8a 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -573,6 +573,12 @@
* </p>
*/
public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
+ if (getColorFilter() instanceof PorterDuffColorFilter) {
+ PorterDuffColorFilter existing = (PorterDuffColorFilter) getColorFilter();
+ if (existing.getColor() == color && existing.getMode() == mode) {
+ return;
+ }
+ }
setColorFilter(new PorterDuffColorFilter(color, mode));
}
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index cd759cb..158938a 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -68,3 +68,13 @@
EXPECT_FALSE(failFilter->asColorMode(nullptr, nullptr));
}
}
+
+TEST(SkiaBehavior, porterDuffCreateIsCached) {
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kOverlay_Mode);
+ auto expected = paint.getXfermode();
+ paint.setXfermodeMode(SkXfermode::kClear_Mode);
+ ASSERT_NE(expected, paint.getXfermode());
+ paint.setXfermodeMode(SkXfermode::kOverlay_Mode);
+ ASSERT_EQ(expected, paint.getXfermode());
+}
diff --git a/packages/SettingsLib/tests/Android.mk b/packages/SettingsLib/tests/Android.mk
index 1ed1121..4208522 100644
--- a/packages/SettingsLib/tests/Android.mk
+++ b/packages/SettingsLib/tests/Android.mk
@@ -24,8 +24,10 @@
LOCAL_PACKAGE_NAME := SettingsLibTests
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target \
- android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ espresso-core \
+ mockito-target-minus-junit4
include frameworks/base/packages/SettingsLib/common.mk
diff --git a/packages/SettingsLib/tests/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java b/packages/SettingsLib/tests/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
index 0f73a34..4d7d4cf 100644
--- a/packages/SettingsLib/tests/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
+++ b/packages/SettingsLib/tests/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
@@ -16,11 +16,9 @@
package com.android.settingslib.drawer;
-import static junit.framework.Assert.assertEquals;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.verify;
-
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
import android.annotation.Nullable;
import android.content.Intent;
import android.content.pm.UserInfo;
@@ -30,8 +28,11 @@
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
+
import com.android.settingslib.drawer.SettingsDrawerActivity;
import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.R;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -39,6 +40,16 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SettingsDrawerActivityTest {
@@ -96,6 +107,38 @@
verify(mUserManager, times(2)).getUserInfo(REMOVED_USER.getIdentifier());
}
+ @Test
+ public void startActivityWithNoExtra_showNoHamburgerMenu() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.startActivitySync(new Intent(instrumentation.getTargetContext(),
+ TestActivity.class));
+
+ onView(withContentDescription(R.string.content_description_menu_button))
+ .check(doesNotExist());
+ }
+
+ @Test
+ public void startActivityWithExtraToHideMenu_showNoHamburgerMenu() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
+ .putExtra(TestActivity.EXTRA_SHOW_MENU, false);
+ instrumentation.startActivitySync(intent);
+
+ onView(withContentDescription(R.string.content_description_menu_button))
+ .check(doesNotExist());
+ }
+
+ @Test
+ public void startActivityWithExtraToShowMenu_showHamburgerMenu() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
+ .putExtra(TestActivity.EXTRA_SHOW_MENU, true);
+ instrumentation.startActivitySync(intent);
+
+ onView(withContentDescription(R.string.content_description_menu_button))
+ .check(matches(isDisplayed()));
+ }
+
/**
* Test Activity in this test.
*
@@ -103,5 +146,4 @@
* AndroidManifest.xml
*/
public static class TestActivity extends SettingsDrawerActivity {}
-
}
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 8c1ce24..71bfe85 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -23,6 +23,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
LOCAL_STATIC_ANDROID_LIBRARIES := \
+ SystemUIPluginLib \
Keyguard \
android-support-v7-recyclerview \
android-support-v7-preference \
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3cc16de..8ed1be5 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -138,6 +138,9 @@
android:protectionLevel="signature" />
<uses-permission android:name="com.android.systemui.permission.SELF" />
+ <permission android:name="com.android.systemui.permission.PLUGIN"
+ android:protectionLevel="signature" />
+
<!-- Adding Quick Settings tiles -->
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
diff --git a/packages/SystemUI/plugin/Android.mk b/packages/SystemUI/plugin/Android.mk
new file mode 100644
index 0000000..86527db
--- /dev/null
+++ b/packages/SystemUI/plugin/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := SystemUIPluginLib
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAR_EXCLUDE_FILES := none
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/packages/SystemUI/plugin/AndroidManifest.xml b/packages/SystemUI/plugin/AndroidManifest.xml
new file mode 100644
index 0000000..7c057dc
--- /dev/null
+++ b/packages/SystemUI/plugin/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.plugins">
+
+ <uses-sdk
+ android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SystemUI/plugin/ExamplePlugin/Android.mk b/packages/SystemUI/plugin/ExamplePlugin/Android.mk
new file mode 100644
index 0000000..4c82c75
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_PACKAGE_NAME := ExamplePlugin
+
+LOCAL_JAVA_LIBRARIES := SystemUIPluginLib
+
+LOCAL_CERTIFICATE := platform
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/plugin/ExamplePlugin/AndroidManifest.xml b/packages/SystemUI/plugin/ExamplePlugin/AndroidManifest.xml
new file mode 100644
index 0000000..bd2c71c
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.plugin.testoverlayplugin">
+
+ <uses-permission android:name="com.android.systemui.permission.PLUGIN" />
+
+ <application>
+ <service android:name=".SampleOverlayPlugin">
+ <intent-filter>
+ <action android:name="com.android.systemui.action.PLUGIN_OVERLAY" />
+ </intent-filter>
+ </service>
+ </application>
+
+</manifest>
diff --git a/packages/SystemUI/plugin/ExamplePlugin/res/layout/colored_overlay.xml b/packages/SystemUI/plugin/ExamplePlugin/res/layout/colored_overlay.xml
new file mode 100644
index 0000000..b2910cb
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/res/layout/colored_overlay.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 2016, 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.
+-->
+
+<com.android.systemui.plugin.testoverlayplugin.CustomView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#80ff0000" />
diff --git a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java
new file mode 100644
index 0000000..5fdbbf9
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 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.plugin.testoverlayplugin;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * View with some logging to show that its being run.
+ */
+public class CustomView extends View {
+
+ private static final String TAG = "CustomView";
+
+ public CustomView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ Log.d(TAG, "new instance");
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ Log.d(TAG, "onAttachedToWindow");
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ Log.d(TAG, "onDetachedFromWindow");
+ }
+}
diff --git a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java
new file mode 100644
index 0000000..a2f84dc
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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.plugin.testoverlayplugin;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.OverlayPlugin;
+
+public class SampleOverlayPlugin implements OverlayPlugin {
+ private static final String TAG = "SampleOverlayPlugin";
+ private Context mPluginContext;
+
+ private View mStatusBarView;
+ private View mNavBarView;
+
+ @Override
+ public int getVersion() {
+ Log.d(TAG, "getVersion " + VERSION);
+ return VERSION;
+ }
+
+ @Override
+ public void onCreate(Context sysuiContext, Context pluginContext) {
+ Log.d(TAG, "onCreate");
+ mPluginContext = pluginContext;
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy");
+ if (mStatusBarView != null) {
+ mStatusBarView.post(
+ () -> ((ViewGroup) mStatusBarView.getParent()).removeView(mStatusBarView));
+ }
+ if (mNavBarView != null) {
+ mNavBarView.post(() -> ((ViewGroup) mNavBarView.getParent()).removeView(mNavBarView));
+ }
+ }
+
+ @Override
+ public void setup(View statusBar, View navBar) {
+ Log.d(TAG, "Setup");
+
+ if (statusBar instanceof ViewGroup) {
+ mStatusBarView = LayoutInflater.from(mPluginContext)
+ .inflate(R.layout.colored_overlay, (ViewGroup) statusBar, false);
+ ((ViewGroup) statusBar).addView(mStatusBarView);
+ }
+ if (navBar instanceof ViewGroup) {
+ mNavBarView = LayoutInflater.from(mPluginContext)
+ .inflate(R.layout.colored_overlay, (ViewGroup) navBar, false);
+ ((ViewGroup) navBar).addView(mNavBarView);
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
new file mode 100644
index 0000000..91a2604
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 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.plugins;
+
+import android.view.View;
+
+public interface OverlayPlugin extends Plugin {
+
+ String ACTION = "com.android.systemui.action.PLUGIN_OVERLAY";
+ int VERSION = 1;
+
+ void setup(View statusBar, View navBar);
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
new file mode 100644
index 0000000..b31b199
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 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.plugins;
+
+import android.content.Context;
+
+/**
+ * Plugins are separate APKs that
+ * are expected to implement interfaces provided by SystemUI. Their
+ * code is dynamically loaded into the SysUI process which can allow
+ * for multiple prototypes to be created and run on a single android
+ * build.
+ *
+ * PluginLifecycle:
+ * <pre class="prettyprint">
+ *
+ * plugin.onCreate(Context sysuiContext, Context pluginContext);
+ * --- This is always called before any other calls
+ *
+ * pluginListener.onPluginConnected(Plugin p);
+ * --- This lets the plugin hook know that a plugin is now connected.
+ *
+ * ** Any other calls back and forth between sysui/plugin **
+ *
+ * pluginListener.onPluginDisconnected(Plugin p);
+ * --- Lets the plugin hook know that it should stop interacting with
+ * this plugin and drop all references to it.
+ *
+ * plugin.onDestroy();
+ * --- Finally the plugin can perform any cleanup to ensure that its not
+ * leaking into the SysUI process.
+ *
+ * Any time a plugin APK is updated the plugin is destroyed and recreated
+ * to load the new code/resources.
+ *
+ * </pre>
+ *
+ * Creating plugin hooks:
+ *
+ * To create a plugin hook, first create an interface in
+ * frameworks/base/packages/SystemUI/plugin that extends Plugin.
+ * Include in it any hooks you want to be able to call into from
+ * sysui and create callback interfaces for anything you need to
+ * pass through into the plugin.
+ *
+ * Then to attach to any plugins simply add a plugin listener and
+ * onPluginConnected will get called whenever new plugins are installed,
+ * updated, or enabled. Like this example from SystemUIApplication:
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * PluginManager.getInstance(this).addPluginListener(OverlayPlugin.COMPONENT,
+ * new PluginListener<OverlayPlugin>() {
+ * @Override
+ * public void onPluginConnected(OverlayPlugin plugin) {
+ * PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
+ * if (phoneStatusBar != null) {
+ * plugin.setup(phoneStatusBar.getStatusBarWindow(),
+ * phoneStatusBar.getNavigationBarView());
+ * }
+ * }
+ * }, OverlayPlugin.VERSION, true /* Allow multiple plugins *\/);
+ * }
+ * </pre>
+ * Note the VERSION included here. Any time incompatible changes in the
+ * interface are made, this version should be changed to ensure old plugins
+ * aren't accidentally loaded. Since the plugin library is provided by
+ * SystemUI, default implementations can be added for new methods to avoid
+ * version changes when possible.
+ *
+ * Implementing a Plugin:
+ *
+ * See the ExamplePlugin for an example Android.mk on how to compile
+ * a plugin. Note that SystemUILib is not static for plugins, its classes
+ * are provided by SystemUI.
+ *
+ * Plugin security is based around a signature permission, so plugins must
+ * hold the following permission in their manifest.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <uses-permission android:name="com.android.systemui.permission.PLUGIN" />
+ * }
+ * </pre>
+ *
+ * A plugin is found through a querying for services, so to let SysUI know
+ * about it, create a service with a name that points at your implementation
+ * of the plugin interface with the action accompanying it:
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <service android:name=".TestOverlayPlugin">
+ * <intent-filter>
+ * <action android:name="com.android.systemui.action.PLUGIN_COMPONENT" />
+ * </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ */
+public interface Plugin {
+
+ /**
+ * Should be implemented as the following directly referencing the version constant
+ * from the plugin interface being implemented, this will allow recompiles to automatically
+ * pick up the current version.
+ * <pre class="prettyprint">
+ * {@literal
+ * public int getVersion() {
+ * return VERSION;
+ * }
+ * }
+ * @return
+ */
+ int getVersion();
+
+ default void onCreate(Context sysuiContext, Context pluginContext) {
+ }
+
+ default void onDestroy() {
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
new file mode 100644
index 0000000..2a7139c
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2016 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.plugins;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.LayoutInflater;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.system.PathClassLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver {
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = "PluginInstanceManager";
+ private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
+
+ private final Context mContext;
+ private final PluginListener<T> mListener;
+ private final String mAction;
+ private final boolean mAllowMultiple;
+ private final int mVersion;
+
+ @VisibleForTesting
+ final MainHandler mMainHandler;
+ @VisibleForTesting
+ final PluginHandler mPluginHandler;
+ private final boolean isDebuggable;
+ private final PackageManager mPm;
+ private final ClassLoaderFactory mClassLoaderFactory;
+
+ PluginInstanceManager(Context context, String action, PluginListener<T> listener,
+ boolean allowMultiple, Looper looper, int version) {
+ this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version,
+ Build.IS_DEBUGGABLE, new ClassLoaderFactory());
+ }
+
+ @VisibleForTesting
+ PluginInstanceManager(Context context, PackageManager pm, String action,
+ PluginListener<T> listener, boolean allowMultiple, Looper looper, int version,
+ boolean debuggable, ClassLoaderFactory classLoaderFactory) {
+ mMainHandler = new MainHandler(Looper.getMainLooper());
+ mPluginHandler = new PluginHandler(looper);
+ mContext = context;
+ mPm = pm;
+ mAction = action;
+ mListener = listener;
+ mAllowMultiple = allowMultiple;
+ mVersion = version;
+ isDebuggable = debuggable;
+ mClassLoaderFactory = classLoaderFactory;
+ }
+
+ public void startListening() {
+ if (DEBUG) Log.d(TAG, "startListening");
+ mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL);
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(this, filter);
+ filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
+ mContext.registerReceiver(this, filter);
+ }
+
+ public void stopListening() {
+ if (DEBUG) Log.d(TAG, "stopListening");
+ ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
+ for (PluginInfo plugin : plugins) {
+ mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED,
+ plugin.mPlugin).sendToTarget();
+ }
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Log.d(TAG, "onReceive " + intent);
+ if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+ mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL);
+ } else {
+ Uri data = intent.getData();
+ String pkgName = data.getEncodedSchemeSpecificPart();
+ mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkgName).sendToTarget();
+ if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
+ mPluginHandler.obtainMessage(PluginHandler.QUERY_PKG, pkgName).sendToTarget();
+ }
+ }
+ }
+
+ public boolean checkAndDisable(String className) {
+ boolean disableAny = false;
+ ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
+ for (PluginInfo info : plugins) {
+ if (className.startsWith(info.mPackage)) {
+ disable(info);
+ disableAny = true;
+ }
+ }
+ return disableAny;
+ }
+
+ public void disableAll() {
+ ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
+ plugins.forEach(this::disable);
+ }
+
+ private void disable(PluginInfo info) {
+ // Live by the sword, die by the sword.
+ // Misbehaving plugins get disabled and won't come back until uninstall/reinstall.
+
+ // If a plugin is detected in the stack of a crash then this will be called for that
+ // plugin, if the plugin causing a crash cannot be identified, they are all disabled
+ // assuming one of them must be bad.
+ Log.w(TAG, "Disabling plugin " + info.mPackage + "/" + info.mClass);
+ mPm.setComponentEnabledSetting(
+ new ComponentName(info.mPackage, info.mClass),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ private class MainHandler extends Handler {
+ private static final int PLUGIN_CONNECTED = 1;
+ private static final int PLUGIN_DISCONNECTED = 2;
+
+ public MainHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case PLUGIN_CONNECTED:
+ if (DEBUG) Log.d(TAG, "onPluginConnected");
+ PluginInfo<T> info = (PluginInfo<T>) msg.obj;
+ info.mPlugin.onCreate(mContext, info.mPluginContext);
+ mListener.onPluginConnected(info.mPlugin);
+ break;
+ case PLUGIN_DISCONNECTED:
+ if (DEBUG) Log.d(TAG, "onPluginDisconnected");
+ mListener.onPluginDisconnected((T) msg.obj);
+ ((T) msg.obj).onDestroy();
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+ }
+
+ static class ClassLoaderFactory {
+ public ClassLoader createClassLoader(String path, ClassLoader base) {
+ return new PathClassLoader(path, base);
+ }
+ }
+
+ private class PluginHandler extends Handler {
+ private static final int QUERY_ALL = 1;
+ private static final int QUERY_PKG = 2;
+ private static final int REMOVE_PKG = 3;
+
+ private final ArrayList<PluginInfo<T>> mPlugins = new ArrayList<>();
+
+ public PluginHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case QUERY_ALL:
+ if (DEBUG) Log.d(TAG, "queryAll " + mAction);
+ for (int i = mPlugins.size() - 1; i >= 0; i--) {
+ PluginInfo<T> plugin = mPlugins.get(i);
+ mListener.onPluginDisconnected(plugin.mPlugin);
+ plugin.mPlugin.onDestroy();
+ }
+ mPlugins.clear();
+ handleQueryPlugins(null);
+ break;
+ case REMOVE_PKG:
+ String pkg = (String) msg.obj;
+ for (int i = mPlugins.size() - 1; i >= 0; i--) {
+ final PluginInfo<T> plugin = mPlugins.get(i);
+ if (plugin.mPackage.equals(pkg)) {
+ mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED,
+ plugin.mPlugin).sendToTarget();
+ mPlugins.remove(i);
+ }
+ }
+ break;
+ case QUERY_PKG:
+ String p = (String) msg.obj;
+ if (DEBUG) Log.d(TAG, "queryPkg " + p + " " + mAction);
+ if (mAllowMultiple || (mPlugins.size() == 0)) {
+ handleQueryPlugins(p);
+ } else {
+ if (DEBUG) Log.d(TAG, "Too many of " + mAction);
+ }
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ private void handleQueryPlugins(String pkgName) {
+ // This isn't actually a service and shouldn't ever be started, but is
+ // a convenient PM based way to manage our plugins.
+ Intent intent = new Intent(mAction);
+ if (pkgName != null) {
+ intent.setPackage(pkgName);
+ }
+ List<ResolveInfo> result =
+ mPm.queryIntentServices(intent, 0);
+ if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins");
+ if (result.size() > 1 && !mAllowMultiple) {
+ // TODO: Show warning.
+ Log.w(TAG, "Multiple plugins found for " + mAction);
+ return;
+ }
+ for (ResolveInfo info : result) {
+ ComponentName name = new ComponentName(info.serviceInfo.packageName,
+ info.serviceInfo.name);
+ PluginInfo<T> t = handleLoadPlugin(name);
+ if (t == null) continue;
+ mMainHandler.obtainMessage(mMainHandler.PLUGIN_CONNECTED, t).sendToTarget();
+ mPlugins.add(t);
+ }
+ }
+
+ protected PluginInfo<T> handleLoadPlugin(ComponentName component) {
+ // This was already checked, but do it again here to make extra extra sure, we don't
+ // use these on production builds.
+ if (!isDebuggable) {
+ // Never ever ever allow these on production builds, they are only for prototyping.
+ Log.d(TAG, "Somehow hit second debuggable check");
+ return null;
+ }
+ String pkg = component.getPackageName();
+ String cls = component.getClassName();
+ try {
+ PackageManager pm = mPm;
+ ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
+ // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on
+ if (pm.checkPermission(PLUGIN_PERMISSION, pkg)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "Plugin doesn't have permission: " + pkg);
+ return null;
+ }
+ // Create our own ClassLoader so we can use our own code as the parent.
+ ClassLoader classLoader = mClassLoaderFactory.createClassLoader(info.sourceDir,
+ getClass().getClassLoader());
+ Context pluginContext = new PluginContextWrapper(
+ mContext.createApplicationContext(info, 0), classLoader);
+ Class<?> pluginClass = Class.forName(cls, true, classLoader);
+ T plugin = (T) pluginClass.newInstance();
+ if (plugin.getVersion() != mVersion) {
+ // TODO: Warn user.
+ Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion()
+ + ", expected " + mVersion);
+ return null;
+ }
+ if (DEBUG) Log.d(TAG, "createPlugin");
+ return new PluginInfo(pkg, cls, plugin, pluginContext);
+ } catch (Exception e) {
+ Log.w(TAG, "Couldn't load plugin: " + pkg, e);
+ return null;
+ }
+ }
+ }
+
+ public static class PluginContextWrapper extends ContextWrapper {
+ private final ClassLoader mClassLoader;
+ private LayoutInflater mInflater;
+
+ public PluginContextWrapper(Context base, ClassLoader classLoader) {
+ super(base);
+ mClassLoader = classLoader;
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return mClassLoader;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (LAYOUT_INFLATER_SERVICE.equals(name)) {
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
+ }
+ return mInflater;
+ }
+ return getBaseContext().getSystemService(name);
+ }
+ }
+
+ private static class PluginInfo<T> {
+ private final Context mPluginContext;
+ private T mPlugin;
+ private String mClass;
+ private String mPackage;
+
+ public PluginInfo(String pkg, String cls, T plugin, Context pluginContext) {
+ mPlugin = plugin;
+ mClass = cls;
+ mPackage = pkg;
+ mPluginContext = pluginContext;
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
new file mode 100644
index 0000000..b2f92d6
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.plugins;
+
+/**
+ * Interface for listening to plugins being connected.
+ */
+public interface PluginListener<T extends Plugin> {
+ /**
+ * Called when the plugin has been loaded and is ready to be used.
+ * This may be called multiple times if multiple plugins are allowed.
+ * It may also be called in the future if the plugin package changes
+ * and needs to be reloaded.
+ */
+ void onPluginConnected(T plugin);
+
+ /**
+ * Called when a plugin has been uninstalled/updated and should be removed
+ * from use.
+ */
+ default void onPluginDisconnected(T plugin) {
+ // Optional.
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
new file mode 100644
index 0000000..aa0b3c5
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 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.plugins;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+
+/**
+ * @see Plugin
+ */
+public class PluginManager {
+
+ private static PluginManager sInstance;
+
+ private final HandlerThread mBackgroundThread;
+ private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap
+ = new ArrayMap<>();
+ private final Context mContext;
+ private final PluginInstanceManagerFactory mFactory;
+ private final boolean isDebuggable;
+
+ private PluginManager(Context context) {
+ this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE,
+ Thread.getDefaultUncaughtExceptionHandler());
+ }
+
+ @VisibleForTesting
+ PluginManager(Context context, PluginInstanceManagerFactory factory, boolean debuggable,
+ UncaughtExceptionHandler defaultHandler) {
+ mContext = context;
+ mFactory = factory;
+ mBackgroundThread = new HandlerThread("Plugins");
+ mBackgroundThread.start();
+ isDebuggable = debuggable;
+
+ PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
+ defaultHandler);
+ Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
+ }
+
+ public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+ int version) {
+ addPluginListener(action, listener, version, false);
+ }
+
+ public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+ int version, boolean allowMultiple) {
+ if (!isDebuggable) {
+ // Never ever ever allow these on production builds, they are only for prototyping.
+ return;
+ }
+ PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
+ allowMultiple, mBackgroundThread.getLooper(), version);
+ p.startListening();
+ mPluginMap.put(listener, p);
+ }
+
+ public void removePluginListener(PluginListener<?> listener) {
+ if (!isDebuggable) {
+ // Never ever ever allow these on production builds, they are only for prototyping.
+ return;
+ }
+ if (!mPluginMap.containsKey(listener)) return;
+ mPluginMap.remove(listener).stopListening();
+ }
+
+ public static PluginManager getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new PluginManager(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+
+ @VisibleForTesting
+ public static class PluginInstanceManagerFactory {
+ public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
+ String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
+ int version) {
+ return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
+ version);
+ }
+ }
+
+ private class PluginExceptionHandler implements UncaughtExceptionHandler {
+ private final UncaughtExceptionHandler mHandler;
+
+ private PluginExceptionHandler(UncaughtExceptionHandler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable throwable) {
+ // Search for and disable plugins that may have been involved in this crash.
+ boolean disabledAny = checkStack(throwable);
+ if (!disabledAny) {
+ // We couldn't find any plugins involved in this crash, just to be safe
+ // disable all the plugins, so we can be sure that SysUI is running as
+ // best as possible.
+ for (PluginInstanceManager manager : mPluginMap.values()) {
+ manager.disableAll();
+ }
+ }
+
+ // Run the normal exception handler so we can crash and cleanup our state.
+ mHandler.uncaughtException(thread, throwable);
+ }
+
+ private boolean checkStack(Throwable throwable) {
+ if (throwable == null) return false;
+ boolean disabledAny = false;
+ for (StackTraceElement element : throwable.getStackTrace()) {
+ for (PluginInstanceManager manager : mPluginMap.values()) {
+ disabledAny |= manager.checkAndDisable(element.getClassName());
+ }
+ }
+ return disabledAny | checkStack(throwable.getCause());
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginUtils.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginUtils.java
new file mode 100644
index 0000000..af49d43
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginUtils.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 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.plugins;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+
+public class PluginUtils {
+
+ public static void setId(Context sysuiContext, View view, String id) {
+ int i = sysuiContext.getResources().getIdentifier(id, "id", sysuiContext.getPackageName());
+ view.setId(i);
+ }
+}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 9182f7e..364885a 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -37,3 +37,6 @@
-keep class ** extends android.support.v14.preference.PreferenceFragment
-keep class com.android.systemui.tuner.*
+-keep class com.android.systemui.plugins.** {
+ public protected **;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 4b9ae2a..e300aff 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -29,7 +29,11 @@
import android.os.UserHandle;
import android.util.Log;
+import com.android.systemui.plugins.OverlayPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.HashMap;
import java.util.Map;
@@ -183,6 +187,18 @@
mServices[i].onBootCompleted();
}
}
+ PluginManager.getInstance(this).addPluginListener(OverlayPlugin.ACTION,
+ new PluginListener<OverlayPlugin>() {
+ @Override
+ public void onPluginConnected(OverlayPlugin plugin) {
+ PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
+ if (phoneStatusBar != null) {
+ plugin.setup(phoneStatusBar.getStatusBarWindow(),
+ phoneStatusBar.getNavigationBarView());
+ }
+ }
+ }, OverlayPlugin.VERSION, true /* Allow multiple plugins */);
+
mServicesStarted = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 222e8df..9e5b881 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -43,6 +43,7 @@
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.inputmethod.InputMethodManager;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
@@ -52,7 +53,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-public class NavigationBarView extends LinearLayout {
+public class NavigationBarView extends FrameLayout {
final static boolean DEBUG = false;
final static String TAG = "StatusBar/NavBarView";
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 5d6ac12..23967aa 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -34,6 +34,7 @@
frameworks/base/packages/SystemUI/res \
LOCAL_STATIC_ANDROID_LIBRARIES := \
+ SystemUIPluginLib \
Keyguard \
android-support-v7-recyclerview \
android-support-v7-preference \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 869805e..d943eb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -17,13 +17,17 @@
import android.content.Context;
import android.support.test.InstrumentationRegistry;
-import android.test.AndroidTestCase;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.MessageQueue;
import org.junit.Before;
/**
* Base class that does System UI specific setup.
*/
public class SysuiTestCase {
+
+ private Handler mHandler;
protected Context mContext;
@Before
@@ -34,4 +38,65 @@
protected Context getContext() {
return mContext;
}
+
+ protected void waitForIdleSync() {
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+ waitForIdleSync(mHandler);
+ }
+
+ protected void waitForIdleSync(Handler h) {
+ validateThread(h.getLooper());
+ Idler idler = new Idler(null);
+ h.getLooper().getQueue().addIdleHandler(idler);
+ // Ensure we are non-idle, so the idle handler can run.
+ h.post(new EmptyRunnable());
+ idler.waitForIdle();
+ }
+
+ private static final void validateThread(Looper l) {
+ if (Looper.myLooper() == l) {
+ throw new RuntimeException(
+ "This method can not be called from the looper being synced");
+ }
+ }
+
+ public static final class EmptyRunnable implements Runnable {
+ public void run() {
+ }
+ }
+
+ public static final class Idler implements MessageQueue.IdleHandler {
+ private final Runnable mCallback;
+ private boolean mIdle;
+
+ public Idler(Runnable callback) {
+ mCallback = callback;
+ mIdle = false;
+ }
+
+ @Override
+ public boolean queueIdle() {
+ if (mCallback != null) {
+ mCallback.run();
+ }
+ synchronized (this) {
+ mIdle = true;
+ notifyAll();
+ }
+ return false;
+ }
+
+ public void waitForIdle() {
+ synchronized (this) {
+ while (!mIdle) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
new file mode 100644
index 0000000..ab7de39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2016 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.plugins;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginInstanceManager.ClassLoaderFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PluginInstanceManagerTest extends SysuiTestCase {
+
+ // Static since the plugin needs to be generated by the PluginInstanceManager using newInstance.
+ private static Plugin sMockPlugin;
+
+ private HandlerThread mHandlerThread;
+ private Context mContextWrapper;
+ private PackageManager mMockPm;
+ private PluginListener mMockListener;
+ private PluginInstanceManager mPluginInstanceManager;
+
+ @Before
+ public void setup() throws Exception {
+ mHandlerThread = new HandlerThread("test_thread");
+ mHandlerThread.start();
+ mContextWrapper = new MyContextWrapper(getContext());
+ mMockPm = mock(PackageManager.class);
+ mMockListener = mock(PluginListener.class);
+ mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
+ mMockListener, true, mHandlerThread.getLooper(), 1, true,
+ new TestClassLoaderFactory());
+ sMockPlugin = mock(Plugin.class);
+ when(sMockPlugin.getVersion()).thenReturn(1);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ sMockPlugin = null;
+ }
+
+ @Test
+ public void testNoPlugins() {
+ when(mMockPm.queryIntentServices(Mockito.any(), Mockito.anyInt())).thenReturn(
+ Collections.emptyList());
+ mPluginInstanceManager.startListening();
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+ verify(mMockListener, Mockito.never()).onPluginConnected(
+ ArgumentCaptor.forClass(Plugin.class).capture());
+ }
+
+ @Test
+ public void testPluginCreate() {
+ createPlugin();
+
+ // Verify startup lifecycle
+ verify(sMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(),
+ ArgumentCaptor.forClass(Context.class).capture());
+ verify(mMockListener).onPluginConnected(ArgumentCaptor.forClass(Plugin.class).capture());
+ }
+
+ @Test
+ public void testPluginDestroy() {
+ createPlugin(); // Get into valid created state.
+
+ mPluginInstanceManager.stopListening();
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+ // Verify shutdown lifecycle
+ verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(sMockPlugin).onDestroy();
+ }
+
+ @Test
+ public void testIncorrectVersion() {
+ setupFakePmQuery();
+ when(sMockPlugin.getVersion()).thenReturn(2);
+
+ mPluginInstanceManager.startListening();
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+ // Plugin shouldn't be connected because it is the wrong version.
+ verify(mMockListener, Mockito.never()).onPluginConnected(
+ ArgumentCaptor.forClass(Plugin.class).capture());
+ }
+
+ @Test
+ public void testReloadOnChange() {
+ createPlugin(); // Get into valid created state.
+
+ // Send a package changed broadcast.
+ Intent i = new Intent(Intent.ACTION_PACKAGE_CHANGED,
+ Uri.fromParts("package", "com.android.systemui", null));
+ mPluginInstanceManager.onReceive(mContextWrapper, i);
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+ // Verify the old one was destroyed.
+ verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(sMockPlugin).onDestroy();
+ // Also verify we got a second onCreate.
+ verify(sMockPlugin, Mockito.times(2)).onCreate(
+ ArgumentCaptor.forClass(Context.class).capture(),
+ ArgumentCaptor.forClass(Context.class).capture());
+ verify(mMockListener, Mockito.times(2)).onPluginConnected(
+ ArgumentCaptor.forClass(Plugin.class).capture());
+ }
+
+ @Test
+ public void testNonDebuggable() {
+ // Create a version that thinks the build is not debuggable.
+ mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
+ mMockListener, true, mHandlerThread.getLooper(), 1, false,
+ new TestClassLoaderFactory());
+ setupFakePmQuery();
+
+ mPluginInstanceManager.startListening();
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);;
+
+ // Non-debuggable build should receive no plugins.
+ verify(mMockListener, Mockito.never()).onPluginConnected(
+ ArgumentCaptor.forClass(Plugin.class).capture());
+ }
+
+ @Test
+ public void testCheckAndDisable() {
+ createPlugin(); // Get into valid created state.
+
+ // Start with an unrelated class.
+ boolean result = mPluginInstanceManager.checkAndDisable(Activity.class.getName());
+ assertFalse(result);
+ verify(mMockPm, Mockito.never()).setComponentEnabledSetting(
+ ArgumentCaptor.forClass(ComponentName.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture());
+
+ // Now hand it a real class and make sure it disables the plugin.
+ result = mPluginInstanceManager.checkAndDisable(TestPlugin.class.getName());
+ assertTrue(result);
+ verify(mMockPm).setComponentEnabledSetting(
+ ArgumentCaptor.forClass(ComponentName.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture());
+ }
+
+ @Test
+ public void testDisableAll() {
+ createPlugin(); // Get into valid created state.
+
+ mPluginInstanceManager.disableAll();
+
+ verify(mMockPm).setComponentEnabledSetting(
+ ArgumentCaptor.forClass(ComponentName.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture());
+ }
+
+ private void setupFakePmQuery() {
+ List<ResolveInfo> list = new ArrayList<>();
+ ResolveInfo info = new ResolveInfo();
+ info.serviceInfo = new ServiceInfo();
+ info.serviceInfo.packageName = "com.android.systemui";
+ info.serviceInfo.name = TestPlugin.class.getName();
+ list.add(info);
+ when(mMockPm.queryIntentServices(Mockito.any(), Mockito.anyInt())).thenReturn(list);
+
+ when(mMockPm.checkPermission(Mockito.anyString(), Mockito.anyString())).thenReturn(
+ PackageManager.PERMISSION_GRANTED);
+
+ try {
+ ApplicationInfo appInfo = getContext().getApplicationInfo();
+ when(mMockPm.getApplicationInfo(Mockito.anyString(), Mockito.anyInt())).thenReturn(
+ appInfo);
+ } catch (NameNotFoundException e) {
+ // Shouldn't be possible, but if it is, we want to fail.
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void createPlugin() {
+ setupFakePmQuery();
+
+ mPluginInstanceManager.startListening();
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);
+ }
+
+ private static class TestClassLoaderFactory extends ClassLoaderFactory {
+ @Override
+ public ClassLoader createClassLoader(String path, ClassLoader base) {
+ return base;
+ }
+ }
+
+ // Real context with no registering/unregistering of receivers.
+ private static class MyContextWrapper extends ContextWrapper {
+ public MyContextWrapper(Context base) {
+ super(base);
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ return null;
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ }
+ }
+
+ public static class TestPlugin implements Plugin {
+ @Override
+ public int getVersion() {
+ return sMockPlugin.getVersion();
+ }
+
+ @Override
+ public void onCreate(Context sysuiContext, Context pluginContext) {
+ sMockPlugin.onCreate(sysuiContext, pluginContext);
+ }
+
+ @Override
+ public void onDestroy() {
+ sMockPlugin.onDestroy();
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
new file mode 100644
index 0000000..56e742a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 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.plugins;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginManager.PluginInstanceManagerFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PluginManagerTest extends SysuiTestCase {
+
+ private PluginInstanceManagerFactory mMockFactory;
+ private PluginInstanceManager mMockPluginInstance;
+ private PluginManager mPluginManager;
+ private PluginListener mMockListener;
+
+ private UncaughtExceptionHandler mRealExceptionHandler;
+ private UncaughtExceptionHandler mMockExceptionHandler;
+ private UncaughtExceptionHandler mPluginExceptionHandler;
+
+ @Before
+ public void setup() throws Exception {
+ mRealExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
+ mMockExceptionHandler = mock(UncaughtExceptionHandler.class);
+ mMockFactory = mock(PluginInstanceManagerFactory.class);
+ mMockPluginInstance = mock(PluginInstanceManager.class);
+ when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(),
+ Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt()))
+ .thenReturn(mMockPluginInstance);
+ mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler);
+ resetExceptionHandler();
+ mMockListener = mock(PluginListener.class);
+ }
+
+ @Test
+ public void testAddListener() {
+ mPluginManager.addPluginListener("myAction", mMockListener, 1);
+
+ verify(mMockPluginInstance).startListening();
+ }
+
+ @Test
+ public void testRemoveListener() {
+ mPluginManager.addPluginListener("myAction", mMockListener, 1);
+
+ mPluginManager.removePluginListener(mMockListener);
+ verify(mMockPluginInstance).stopListening();
+ }
+
+ @Test
+ public void testNonDebuggable() {
+ mPluginManager = new PluginManager(getContext(), mMockFactory, false,
+ mMockExceptionHandler);
+ resetExceptionHandler();
+ mPluginManager.addPluginListener("myAction", mMockListener, 1);
+
+ verify(mMockPluginInstance, Mockito.never()).startListening();
+ }
+
+ @Test
+ public void testExceptionHandler_foundPlugin() {
+ mPluginManager.addPluginListener("myAction", mMockListener, 1);
+ when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(true);
+
+ mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());
+
+ verify(mMockPluginInstance, Mockito.atLeastOnce()).checkAndDisable(
+ ArgumentCaptor.forClass(String.class).capture());
+ verify(mMockPluginInstance, Mockito.never()).disableAll();
+ verify(mMockExceptionHandler).uncaughtException(
+ ArgumentCaptor.forClass(Thread.class).capture(),
+ ArgumentCaptor.forClass(Throwable.class).capture());
+ }
+
+ @Test
+ public void testExceptionHandler_noFoundPlugin() {
+ mPluginManager.addPluginListener("myAction", mMockListener, 1);
+ when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(false);
+
+ mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());
+
+ verify(mMockPluginInstance, Mockito.atLeastOnce()).checkAndDisable(
+ ArgumentCaptor.forClass(String.class).capture());
+ verify(mMockPluginInstance).disableAll();
+ verify(mMockExceptionHandler).uncaughtException(
+ ArgumentCaptor.forClass(Thread.class).capture(),
+ ArgumentCaptor.forClass(Throwable.class).capture());
+ }
+
+ private void resetExceptionHandler() {
+ mPluginExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
+ // Set back the real exception handler so the test can crash if it wants to.
+ Thread.setDefaultUncaughtExceptionHandler(mRealExceptionHandler);
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f740b4b..aa53676 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1715,6 +1715,7 @@
}
if (ar != null && mCompatModePackages.getPackageNotifyUnsupportedZoomLocked(
ar.packageName)) {
+ // TODO(multi-display): Show dialog on appropriate display.
mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
ActivityManagerService.this, mContext, ar.info.applicationInfo);
mUnsupportedDisplaySizeDialog.show();
@@ -3778,16 +3779,19 @@
app.killed = false;
app.killedByAm = false;
checkTime(startTime, "startProcess: starting to update pids map");
+ ProcessRecord oldApp;
synchronized (mPidsSelfLocked) {
- ProcessRecord oldApp;
- // If there is already an app occupying that pid that hasn't been cleaned up
- if ((oldApp = mPidsSelfLocked.get(startResult.pid)) != null && !app.isolated) {
- // Clean up anything relating to this pid first
- Slog.w(TAG, "Reusing pid " + startResult.pid
- + " while app is still mapped to it");
- cleanUpApplicationRecordLocked(oldApp, false, false, -1,
- true /*replacingPid*/);
- }
+ oldApp = mPidsSelfLocked.get(startResult.pid);
+ }
+ // If there is already an app occupying that pid that hasn't been cleaned up
+ if (oldApp != null && !app.isolated) {
+ // Clean up anything relating to this pid first
+ Slog.w(TAG, "Reusing pid " + startResult.pid
+ + " while app is still mapped to it");
+ cleanUpApplicationRecordLocked(oldApp, false, false, -1,
+ true /*replacingPid*/);
+ }
+ synchronized (mPidsSelfLocked) {
this.mPidsSelfLocked.put(startResult.pid, app);
if (isActivityProcess) {
Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
@@ -19037,8 +19041,8 @@
}
/**
- * Decide based on the configuration whether we should shouw the ANR,
- * crash, etc dialogs. The idea is that if there is no affordence to
+ * Decide based on the configuration whether we should show the ANR,
+ * crash, etc dialogs. The idea is that if there is no affordance to
* press the on-screen buttons, or the user experience would be more
* greatly impacted than the crash itself, we shouldn't show the dialog.
*
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 06eeb2c..b82e28d 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -118,6 +118,7 @@
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import com.android.internal.app.IVoiceInteractor;
@@ -228,6 +229,13 @@
// stack and the new stack will be on top of all stacks.
static final int REMOVE_TASK_MODE_MOVING_TO_TOP = 2;
+ // The height/width divide used when fitting a task within a bounds with method
+ // {@link #fitWithinBounds}.
+ // We always want the task to to be visible in the bounds without affecting its size when
+ // fitting. To make sure this is the case, we don't adjust the task left or top side pass
+ // the input bounds right or bottom side minus the width or height divided by this value.
+ private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3;
+
final ActivityManagerService mService;
final WindowManagerService mWindowManager;
private final RecentTasks mRecentTasks;
@@ -321,6 +329,12 @@
/** The attached Display's unique identifier, or -1 if detached */
int mDisplayId;
+ /** Temp variables used during override configuration update. */
+ private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
+ private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
+ private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
+ private final Rect tempRect2 = new Rect();
+
/** Run all ActivityStacks through this */
final ActivityStackSupervisor mStackSupervisor;
@@ -4524,6 +4538,86 @@
}
}
+ /** Update override configurations of all tasks in the stack. */
+ void updateOverrideConfiguration(Rect stackBounds, Rect tempTaskBounds,
+ Rect tempTaskInsetBounds) {
+
+ final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : stackBounds;
+ final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds;
+
+ mTmpBounds.clear();
+ mTmpConfigs.clear();
+ mTmpInsetBounds.clear();
+
+ for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
+ final TaskRecord task = mTaskHistory.get(i);
+ if (task.isResizeable()) {
+ if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+ // For freeform stack we don't adjust the size of the tasks to match that
+ // of the stack, but we do try to make sure the tasks are still contained
+ // with the bounds of the stack.
+ tempRect2.set(task.mBounds);
+ fitWithinBounds(tempRect2, stackBounds);
+ task.updateOverrideConfiguration(tempRect2);
+ } else {
+ task.updateOverrideConfiguration(taskBounds, insetBounds);
+ }
+ }
+
+ mTmpConfigs.put(task.taskId, task.mOverrideConfig);
+ mTmpBounds.put(task.taskId, task.mBounds);
+ if (tempTaskInsetBounds != null) {
+ mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
+ }
+ }
+
+ // We might trigger a configuration change. Save the current task bounds for freezing.
+ mWindowManager.prepareFreezingTaskBounds(mStackId);
+ mFullscreen = mWindowManager.resizeStack(mStackId, stackBounds, mTmpConfigs, mTmpBounds,
+ mTmpInsetBounds);
+ setBounds(stackBounds);
+ }
+
+
+ /**
+ * Adjust bounds to stay within stack bounds.
+ *
+ * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way
+ * that keep them unchanged, but be contained within the stack bounds.
+ *
+ * @param bounds Bounds to be adjusted.
+ * @param stackBounds Bounds within which the other bounds should remain.
+ */
+ private static void fitWithinBounds(Rect bounds, Rect stackBounds) {
+ if (stackBounds == null || stackBounds.contains(bounds)) {
+ return;
+ }
+
+ if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) {
+ final int maxRight = stackBounds.right
+ - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER);
+ int horizontalDiff = stackBounds.left - bounds.left;
+ if ((horizontalDiff < 0 && bounds.left >= maxRight)
+ || (bounds.left + horizontalDiff >= maxRight)) {
+ horizontalDiff = maxRight - bounds.left;
+ }
+ bounds.left += horizontalDiff;
+ bounds.right += horizontalDiff;
+ }
+
+ if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) {
+ final int maxBottom = stackBounds.bottom
+ - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER);
+ int verticalDiff = stackBounds.top - bounds.top;
+ if ((verticalDiff < 0 && bounds.top >= maxBottom)
+ || (bounds.top + verticalDiff >= maxBottom)) {
+ verticalDiff = maxBottom - bounds.top;
+ }
+ bounds.top += verticalDiff;
+ bounds.bottom += verticalDiff;
+ }
+ }
+
/**
* Make sure the given activity matches the current configuration. Returns false if the activity
* had to be destroyed. Returns true if the configuration is the same, or the activity will
@@ -4673,7 +4767,7 @@
// that has come back from the app after going idle. In that case
// we just want to leave the official config object now in the
// activity and do nothing else.
- int taskChanges = oldTaskOverride.diff(taskConfig);
+ int taskChanges = oldTaskOverride.diff(taskConfig, true /* skipUndefined */);
// We don't want to use size changes if they don't cross boundaries that are important to
// the app.
if ((taskChanges & CONFIG_SCREEN_SIZE) != 0) {
@@ -4692,48 +4786,6 @@
taskChanges &= ~CONFIG_SMALLEST_SCREEN_SIZE;
}
}
- return catchConfigChangesFromUnset(taskConfig, oldTaskOverride, taskChanges);
- }
-
- private static int catchConfigChangesFromUnset(Configuration taskConfig,
- Configuration oldTaskOverride, int taskChanges) {
- if (taskChanges == 0) {
- // {@link Configuration#diff} doesn't catch changes from unset values.
- // Check for changes we care about.
- if (oldTaskOverride.orientation != taskConfig.orientation) {
- taskChanges |= CONFIG_ORIENTATION;
- }
- // We want to explicitly track situations where the size configuration goes from
- // undefined to defined. We don't care about crossing the threshold in that case,
- // because there is no threshold.
- final int oldHeight = oldTaskOverride.screenHeightDp;
- final int newHeight = taskConfig.screenHeightDp;
- final int undefinedHeight = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
- if ((oldHeight == undefinedHeight && newHeight != undefinedHeight)
- || (oldHeight != undefinedHeight && newHeight == undefinedHeight)) {
- taskChanges |= CONFIG_SCREEN_SIZE;
- }
- final int oldWidth = oldTaskOverride.screenWidthDp;
- final int newWidth = taskConfig.screenWidthDp;
- final int undefinedWidth = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
- if ((oldWidth == undefinedWidth && newWidth != undefinedWidth)
- || (oldWidth != undefinedWidth && newWidth == undefinedWidth)) {
- taskChanges |= CONFIG_SCREEN_SIZE;
- }
- final int oldSmallest = oldTaskOverride.smallestScreenWidthDp;
- final int newSmallest = taskConfig.smallestScreenWidthDp;
- final int undefinedSmallest = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
- if ((oldSmallest == undefinedSmallest && newSmallest != undefinedSmallest)
- || (oldSmallest != undefinedSmallest && newSmallest == undefinedSmallest)) {
- taskChanges |= CONFIG_SMALLEST_SCREEN_SIZE;
- }
- final int oldLayout = oldTaskOverride.screenLayout;
- final int newLayout = taskConfig.screenLayout;
- if ((oldLayout == SCREENLAYOUT_UNDEFINED && newLayout != SCREENLAYOUT_UNDEFINED)
- || (oldLayout != SCREENLAYOUT_UNDEFINED && newLayout == SCREENLAYOUT_UNDEFINED)) {
- taskChanges |= CONFIG_SCREEN_LAYOUT;
- }
- }
return taskChanges;
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 80d51e5..8602bb6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -267,13 +267,6 @@
/** Action restriction: launching the activity is restricted by an app op. */
private static final int ACTIVITY_RESTRICTION_APPOP = 2;
- // The height/width divide used when fitting a task within a bounds with method
- // {@link #fitWithinBounds}.
- // We always want the task to to be visible in the bounds without affecting its size when
- // fitting. To make sure this is the case, we don't adjust the task left or top side pass
- // the input bounds right or bottom side minus the width or height divided by this value.
- private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3;
-
/** Status Bar Service **/
private IBinder mToken = new Binder();
private IStatusBarService mStatusBarService;
@@ -402,13 +395,11 @@
/** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
boolean inResumeTopActivity;
- // temp. rects used during resize calculation so we don't need to create a new object each time.
+ /**
+ * Temporary rect used during docked stack resize calculation so we don't need to create a new
+ * object each time.
+ */
private final Rect tempRect = new Rect();
- private final Rect tempRect2 = new Rect();
-
- private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
- private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
- private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
// The default minimal size that will be used if the activity doesn't specify its minimal size.
// It will be calculated when the default display gets added.
@@ -1912,6 +1903,7 @@
if (!createStaticStackIfNeeded || !StackId.isStaticStack(stackId)) {
return null;
}
+ // TODO(multi-display): Allow creating stacks on secondary displays.
return createStackOnDisplay(stackId, Display.DEFAULT_DISPLAY, createOnTop);
}
@@ -1923,14 +1915,6 @@
return allStacks;
}
- IBinder getHomeActivityToken() {
- ActivityRecord homeActivity = getHomeActivity();
- if (homeActivity != null) {
- return homeActivity.appToken;
- }
- return null;
- }
-
ActivityRecord getHomeActivity() {
return getHomeActivityForUser(mCurrentUser);
}
@@ -2060,39 +2044,7 @@
return;
}
- mTmpBounds.clear();
- mTmpConfigs.clear();
- mTmpInsetBounds.clear();
- final ArrayList<TaskRecord> tasks = stack.getAllTasks();
- final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
- final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds;
- for (int i = tasks.size() - 1; i >= 0; i--) {
- final TaskRecord task = tasks.get(i);
- if (task.isResizeable()) {
- if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
- // For freeform stack we don't adjust the size of the tasks to match that
- // of the stack, but we do try to make sure the tasks are still contained
- // with the bounds of the stack.
- tempRect2.set(task.mBounds);
- fitWithinBounds(tempRect2, bounds);
- task.updateOverrideConfiguration(tempRect2);
- } else {
- task.updateOverrideConfiguration(taskBounds, insetBounds);
- }
- }
-
- mTmpConfigs.put(task.taskId, task.mOverrideConfig);
- mTmpBounds.put(task.taskId, task.mBounds);
- if (tempTaskInsetBounds != null) {
- mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
- }
- }
-
- // We might trigger a configuration change. Save the current task bounds for freezing.
- mWindowManager.prepareFreezingTaskBounds(stack.mStackId);
- stack.mFullscreen = mWindowManager.resizeStack(stack.mStackId, bounds, mTmpConfigs,
- mTmpBounds, mTmpInsetBounds);
- stack.setBounds(bounds);
+ stack.updateOverrideConfiguration(bounds, tempTaskBounds, tempTaskInsetBounds);
}
void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
@@ -4371,45 +4323,6 @@
}
}
- /**
- * Adjust bounds to stay within stack bounds.
- *
- * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way
- * that keep them unchanged, but be contained within the stack bounds.
- *
- * @param bounds Bounds to be adjusted.
- * @param stackBounds Bounds within which the other bounds should remain.
- */
- private static void fitWithinBounds(Rect bounds, Rect stackBounds) {
- if (stackBounds == null || stackBounds.contains(bounds)) {
- return;
- }
-
- if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) {
- final int maxRight = stackBounds.right
- - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER);
- int horizontalDiff = stackBounds.left - bounds.left;
- if ((horizontalDiff < 0 && bounds.left >= maxRight)
- || (bounds.left + horizontalDiff >= maxRight)) {
- horizontalDiff = maxRight - bounds.left;
- }
- bounds.left += horizontalDiff;
- bounds.right += horizontalDiff;
- }
-
- if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) {
- final int maxBottom = stackBounds.bottom
- - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER);
- int verticalDiff = stackBounds.top - bounds.top;
- if ((verticalDiff < 0 && bounds.top >= maxBottom)
- || (bounds.top + verticalDiff >= maxBottom)) {
- verticalDiff = maxBottom - bounds.top;
- }
- bounds.top += verticalDiff;
- bounds.bottom += verticalDiff;
- }
- }
-
ActivityStack findStackBehind(ActivityStack stack) {
// TODO(multi-display): We are only looking for stacks on the default display.
final ActivityDisplay display = mActivityDisplays.get(Display.DEFAULT_DISPLAY);
@@ -4525,6 +4438,8 @@
* entry will be the focused activity.
*/
public List<IBinder> getTopVisibleActivities() {
+ // TODO(multi-display): Get rid of DEFAULT_DISPLAY here. Used in
+ // VoiceInteractionManagerServiceImpl#showSessionLocked.
final ActivityDisplay display = mActivityDisplays.get(Display.DEFAULT_DISPLAY);
if (display == null) {
return Collections.EMPTY_LIST;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 475b155..2b00f86 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -220,6 +220,7 @@
void applyDisplaySize(WindowManagerService wm) {
if (!mHaveDisplaySize) {
Point p = new Point();
+ // TODO(multi-display): Compute based on sum of all connected displays' resolutions.
wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p);
if (p.x != 0 && p.y != 0) {
updateOomLevels(p.x, p.y, true);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 013d61b..6cc4d73 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1580,7 +1580,6 @@
extracted.smallestScreenWidthDp = config.smallestScreenWidthDp;
extracted.orientation = config.orientation;
extracted.screenLayout = config.screenLayout;
- extracted.fontScale = config.fontScale;
return extracted;
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index bfa02bb..f9aa66b 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -76,6 +76,7 @@
final boolean voiceInteraction;
+ // TODO: Use getParent instead?
Task mTask;
/** @see WindowContainer#fillsParent() */
private boolean mFillsParent;
@@ -324,6 +325,8 @@
// The token is not closing nor opening, so even if there is an animation set, that
// doesn't mean that it goes through the normal app transition cycle so we have
// to inform the docked controller about visibility change.
+ // TODO(multi-display): notify docked divider on all displays where visibility was
+ // affected.
mService.getDefaultDisplayContentLocked().getDockedDividerController()
.notifyAppVisibilityChanged();
}
@@ -373,7 +376,8 @@
mIsExiting = false;
removeAllWindows();
if (mTask != null) {
- mTask.detachChild(this);
+ mTask.mStack.mExitingAppTokens.remove(this);
+ mTask.removeChild(this);
}
}
@@ -734,7 +738,8 @@
} else {
mFrozenMergedConfig.offer(new Configuration(mTask.mPreparedFrozenMergedConfig));
}
- mTask.mPreparedFrozenMergedConfig.setToDefaults();
+ // Calling unset() to make it equal to Configuration.EMPTY.
+ mTask.mPreparedFrozenMergedConfig.unset();
}
/**
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index 052b2f5..b99dda1 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -95,7 +95,7 @@
}
private void constructSurface(WindowManagerService service) {
- SurfaceControl.openTransaction();
+ service.openSurfaceTransaction();
try {
if (DEBUG_SURFACE_TRACE) {
mDimSurface = new WindowSurfaceController.SurfaceTrace(service.mFxSession,
@@ -116,7 +116,7 @@
} catch (Exception e) {
Slog.e(TAG_WM, "Exception creating Dim surface", e);
} finally {
- SurfaceControl.closeTransaction();
+ service.closeSurfaceTransaction();
}
}
@@ -227,12 +227,12 @@
mBounds.set(bounds);
if (isDimming() && !mLastBounds.equals(bounds)) {
try {
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
adjustBounds();
} catch (RuntimeException e) {
Slog.w(TAG, "Failure setting size", e);
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ee73dee..e1aa98d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -55,6 +55,7 @@
import android.view.animation.Animation;
import com.android.internal.util.FastPrintWriter;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
@@ -371,7 +372,7 @@
mStacks.add(addIndex, stack);
}
- // TODO: Don't forget to switch to WC.detachChild
+ // TODO: Don't forget to switch to WC.removeChild
void detachChild(TaskStack stack) {
detachStack(stack);
if (stack.detachFromDisplay()) {
@@ -772,8 +773,7 @@
final TaskStack appStack = wtoken.mTask.mStack;
// TODO: Use WindowContainer.compareTo() once everything is using WindowContainer
- if ((focusedAppStack == appStack
- && appStack.isFirstGreaterThanSecond(focusedApp, wtoken))
+ if ((focusedAppStack == appStack && focusedApp.compareTo(wtoken) > 0)
|| mStacks.indexOf(focusedAppStack) > mStacks.indexOf(appStack)) {
// App stack below focused app stack. No focus for you!!!
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM,
@@ -1179,4 +1179,18 @@
taskForResize = null;
}
}
+
+ void enableSurfaceTrace(FileDescriptor fd) {
+ for (int i = mWindows.size() - 1; i >= 0; i--) {
+ final WindowState win = mWindows.get(i);
+ win.mWinAnimator.enableSurfaceTrace(fd);
+ }
+ }
+
+ void disableSurfaceTrace() {
+ for (int i = mWindows.size() - 1; i >= 0; i--) {
+ final WindowState win = mWindows.get(i);
+ win.mWinAnimator.disableSurfaceTrace();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 573eca1..ff676e9 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -203,7 +203,7 @@
? mDisplayContent.mBaseDisplayWidth
: mDisplayContent.mBaseDisplayHeight;
mService.mPolicy.getStableInsetsLw(rotation, dw, dh, mTmpRect);
- config.setToDefaults();
+ config.unset();
config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
config.screenWidthDp = (int)
(mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, baseConfig.uiMode) /
@@ -465,7 +465,7 @@
}
void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
final TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(targetStackId);
final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
boolean visibleAndValid = visible && stack != null && dockedStack != null;
@@ -482,7 +482,7 @@
if (!visibleAndValid) {
mDimLayer.hide();
}
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
}
/**
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index d0167bb..588bb76 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -432,14 +432,14 @@
// Move the surface to the given touch
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
TAG_WM, ">>> OPEN TRANSACTION notifyMoveLw");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG "
+ mSurfaceControl + ": pos=(" +
(int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLw");
}
diff --git a/services/core/java/com/android/server/wm/RemoteEventTrace.java b/services/core/java/com/android/server/wm/RemoteEventTrace.java
new file mode 100644
index 0000000..9f65ba3
--- /dev/null
+++ b/services/core/java/com/android/server/wm/RemoteEventTrace.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 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.wm;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.DataOutputStream;
+
+import android.util.Slog;
+import android.os.Debug;
+
+// Counterpart to remote surface trace for events which are not tied to a particular surface.
+class RemoteEventTrace {
+ private static final String TAG = "RemoteEventTrace";
+
+ // We terminate all our messages with a recognizable marker, to avoid issues
+ // with partial reads (which ADB makes impossible to avoid).
+ static final byte[] sigil = {(byte)0xfc, (byte)0xfc, (byte)0xfc, (byte)0xfc};
+
+ private final WindowManagerService mService;
+ private final DataOutputStream mOut;
+
+ RemoteEventTrace(WindowManagerService service, FileDescriptor fd) {
+ mService = service;
+ mOut = new DataOutputStream(new FileOutputStream(fd, false));
+ }
+
+ void openSurfaceTransaction() {
+ try {
+ mOut.writeUTF("OpenTransaction");
+ writeSigil();
+ } catch (Exception e) {
+ logException(e);
+ mService.disableSurfaceTrace();
+ }
+ }
+
+ void closeSurfaceTransaction() {
+ try {
+ mOut.writeUTF("CloseTransaction");
+ writeSigil();
+ } catch (Exception e) {
+ logException(e);
+ mService.disableSurfaceTrace();
+ }
+ }
+
+ private void writeSigil() throws Exception {
+ mOut.write(RemoteEventTrace.sigil, 0, 4);
+ }
+
+ static void logException(Exception e) {
+ Slog.i(TAG, "Exception writing to SurfaceTrace (client vanished?): " + e.toString());
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
new file mode 100644
index 0000000..0508fdf
--- /dev/null
+++ b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2016 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.wm;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.util.Slog;
+import android.view.SurfaceControl;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.DataOutputStream;
+
+// A surface control subclass which logs events to a FD in binary format.
+// This can be used in our CTS tests to enable a pattern similar to mocking
+// the surface control.
+//
+// See cts/hostsidetests/../../SurfaceTraceReceiver.java for parsing side.
+class RemoteSurfaceTrace extends SurfaceControl {
+ static final String TAG = "RemoteSurfaceTrace";
+
+ final FileDescriptor mWriteFd;
+ final DataOutputStream mOut;
+
+ final WindowManagerService mService;
+ final WindowState mWindow;
+
+ RemoteSurfaceTrace(FileDescriptor fd, SurfaceControl wrapped, WindowState window) {
+ super(wrapped);
+
+ mWriteFd = fd;
+ mOut = new DataOutputStream(new FileOutputStream(fd, false));
+
+ mWindow = window;
+ mService = mWindow.mService;
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ writeFloatEvent("Alpha", alpha);
+ super.setAlpha(alpha);
+ }
+
+ @Override
+ public void setLayer(int zorder) {
+ writeIntEvent("Layer", zorder);
+ super.setLayer(zorder);
+ }
+
+ @Override
+ public void setPosition(float x, float y) {
+ writeFloatEvent("Position", x, y);
+ super.setPosition(x, y);
+ }
+
+ @Override
+ public void setGeometryAppliesWithResize() {
+ writeEvent("GeometryAppliesWithResize");
+ super.setGeometryAppliesWithResize();
+ }
+
+ @Override
+ public void setSize(int w, int h) {
+ writeIntEvent("Size", w, h);
+ super.setSize(w, h);
+ }
+
+ @Override
+ public void setWindowCrop(Rect crop) {
+ writeRectEvent("Crop", crop);
+ super.setWindowCrop(crop);
+ }
+
+ @Override
+ public void setFinalCrop(Rect crop) {
+ writeRectEvent("FinalCrop", crop);
+ super.setFinalCrop(crop);
+ }
+
+ @Override
+ public void setLayerStack(int layerStack) {
+ writeIntEvent("LayerStack", layerStack);
+ super.setLayerStack(layerStack);
+ }
+
+ @Override
+ public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+ writeFloatEvent("Matrix", dsdx, dtdx, dsdy, dtdy);
+ super.setMatrix(dsdx, dtdx, dsdy, dtdy);
+ }
+
+ @Override
+ public void hide() {
+ writeEvent("Hide");
+ super.hide();
+ }
+
+ @Override
+ public void show() {
+ writeEvent("Show");
+ super.show();
+ }
+
+ private void writeEvent(String tag) {
+ try {
+ mOut.writeUTF(tag);
+ mOut.writeUTF(mWindow.getWindowTag().toString());
+ writeSigil();
+ } catch (Exception e) {
+ RemoteEventTrace.logException(e);
+ mService.disableSurfaceTrace();
+ }
+ }
+
+ private void writeIntEvent(String tag, int... values) {
+ try {
+ mOut.writeUTF(tag);
+ mOut.writeUTF(mWindow.getWindowTag().toString());
+ for (int value: values) {
+ mOut.writeInt(value);
+ }
+ writeSigil();
+ } catch (Exception e) {
+ RemoteEventTrace.logException(e);
+ mService.disableSurfaceTrace();
+ }
+ }
+
+ private void writeFloatEvent(String tag, float... values) {
+ try {
+ mOut.writeUTF(tag);
+ mOut.writeUTF(mWindow.getWindowTag().toString());
+ for (float value: values) {
+ mOut.writeFloat(value);
+ }
+ writeSigil();
+ } catch (Exception e) {
+ RemoteEventTrace.logException(e);
+ mService.disableSurfaceTrace();
+ }
+ }
+
+ private void writeRectEvent(String tag, Rect value) {
+ writeFloatEvent(tag, value.left, value.top, value.right, value.bottom);
+ }
+
+ private void writeSigil() throws Exception {
+ mOut.write(RemoteEventTrace.sigil, 0, 4);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index c118a21..83337ca 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -147,6 +147,8 @@
private boolean mMoreStartFrame;
long mHalfwayPoint;
+ private final WindowManagerService mService;
+
public void printTo(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl);
pw.print(" mWidth="); pw.print(mWidth);
@@ -216,7 +218,8 @@
public ScreenRotationAnimation(Context context, DisplayContent displayContent,
SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation,
- boolean isSecure) {
+ boolean isSecure, WindowManagerService service) {
+ mService = service;
mContext = context;
mDisplayContent = displayContent;
displayContent.getLogicalDisplayRect(mOriginalDisplayRect);
@@ -253,7 +256,7 @@
if (!inTransaction) {
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
">>> OPEN TRANSACTION ScreenRotationAnimation");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
}
try {
@@ -295,7 +298,7 @@
setRotationInTransaction(originalRotation);
} finally {
if (!inTransaction) {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation");
}
@@ -546,7 +549,7 @@
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
// Compute the transformation matrix that must be applied
// the the black frame to make it stay in the initial position
@@ -566,7 +569,7 @@
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -577,7 +580,7 @@
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
// Compute the transformation matrix that must be applied
// the the black frame to make it stay in the initial position
@@ -606,7 +609,7 @@
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -617,7 +620,7 @@
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
@@ -628,7 +631,7 @@
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index e9ce0ea..edd3a3b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -346,7 +346,7 @@
final SurfaceControl surfaceControl = mService.mDragState.mSurfaceControl;
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
TAG_WM, ">>> OPEN TRANSACTION performDrag");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
surfaceControl.setPosition(touchX - thumbCenterX,
touchY - thumbCenterY);
@@ -354,7 +354,7 @@
surfaceControl.setLayerStack(display.getLayerStack());
surfaceControl.show();
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
TAG_WM, "<<< CLOSE TRANSACTION performDrag");
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f730fda..982f12e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -21,9 +21,6 @@
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.content.pm.ActivityInfo.RESIZE_MODE_CROP_WINDOWS;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -37,14 +34,13 @@
import android.util.Slog;
import android.view.DisplayInfo;
import android.view.Surface;
-import android.view.animation.Animation;
import android.view.SurfaceControl;
import com.android.server.EventLogTags;
import java.io.PrintWriter;
-class Task implements DimLayer.DimLayerUser {
+class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser {
static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
// Return value from {@link setBounds} indicating no change was made to the Task bounds.
static final int BOUNDS_CHANGE_NONE = 0;
@@ -53,8 +49,8 @@
// Return value from {@link setBounds} indicating the size of the Task bounds changed.
static final int BOUNDS_CHANGE_SIZE = 1 << 1;
+ // TODO: Track parent marks like this in WindowContainer.
TaskStack mStack;
- private final AppTokenList mAppTokens = new AppTokenList();
final int mTaskId;
final int mUserId;
boolean mDeferRemoval = false;
@@ -112,18 +108,22 @@
}
void addAppToken(int addPos, AppWindowToken wtoken, int resizeMode, boolean homeTask) {
- final int lastPos = mAppTokens.size();
+ final int lastPos = mChildren.size();
if (addPos >= lastPos) {
addPos = lastPos;
} else {
for (int pos = 0; pos < lastPos && pos < addPos; ++pos) {
- if (mAppTokens.get(pos).removed) {
+ if (mChildren.get(pos).removed) {
// addPos assumes removed tokens are actually gone.
++addPos;
}
}
}
- mAppTokens.add(addPos, wtoken);
+
+ if (wtoken.mParent != null) {
+ wtoken.mParent.removeChild(wtoken);
+ }
+ addChild(wtoken, addPos);
wtoken.mTask = this;
mDeferRemoval = false;
mResizeMode = resizeMode;
@@ -131,15 +131,16 @@
}
private boolean hasWindowsAlive() {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- if (mAppTokens.get(i).hasWindowsAlive()) {
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ if (mChildren.get(i).hasWindowsAlive()) {
return true;
}
}
return false;
}
- void removeLocked() {
+ @Override
+ void removeIfPossible() {
if (hasWindowsAlive() && mStack.isAnimating()) {
if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
mDeferRemoval = true;
@@ -152,10 +153,11 @@
if (content != null) {
content.mDimLayerController.removeDimLayerUser(this);
}
- mStack.removeTask(this);
+ mParent.removeChild(this);
mService.mTaskIdToTask.delete(mTaskId);
}
+ // Change to use reparenting in WC when TaskStack is switched to use WC.
void moveTaskToStack(TaskStack stack, boolean toTop) {
if (stack == mStack) {
return;
@@ -163,9 +165,7 @@
if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: removing taskId=" + mTaskId
+ " from stack=" + mStack);
EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
- if (mStack != null) {
- mStack.removeTask(this);
- }
+ mParent.removeChild(this);
stack.addTask(this, toTop);
}
@@ -174,53 +174,37 @@
if (DEBUG_STACK) Slog.i(TAG, "positionTaskInStack: removing taskId=" + mTaskId
+ " from stack=" + mStack);
EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
- mStack.removeTask(this);
+ mStack.removeChild(this);
}
stack.positionTask(this, position, showForAllUsers());
resizeLocked(bounds, config, false /* force */);
- for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
- mAppTokens.get(activityNdx).notifyMovedInStack();
+ for (int activityNdx = mChildren.size() - 1; activityNdx >= 0; --activityNdx) {
+ mChildren.get(activityNdx).notifyMovedInStack();
}
}
- boolean checkCompleteDeferredRemoval() {
- boolean stillDeferringRemoval = false;
-
- for (int tokenNdx = mAppTokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
- final AppWindowToken token = mAppTokens.get(tokenNdx);
- stillDeferringRemoval |= token.checkCompleteDeferredRemoval();
+ @Override
+ void removeChild(AppWindowToken token) {
+ if (!mChildren.contains(token)) {
+ Slog.e(TAG, "removeChild: token=" + this + " not found.");
+ return;
}
- return stillDeferringRemoval;
- }
+ super.removeChild(token);
- // TODO: Don't forget to switch to WC.detachChild
- void detachChild(AppWindowToken wtoken) {
- if (!removeAppToken(wtoken)) {
- Slog.e(TAG, "detachChild: token=" + this + " not found.");
- }
- mStack.mExitingAppTokens.remove(wtoken);
- }
-
- boolean removeAppToken(AppWindowToken wtoken) {
- boolean removed = mAppTokens.remove(wtoken);
- if (mAppTokens.size() == 0) {
+ if (mChildren.isEmpty()) {
EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeAppToken: last token");
if (mDeferRemoval) {
- removeLocked();
+ removeIfPossible();
}
}
- wtoken.mTask = null;
- /* Leave mTaskId for now, it might be useful for debug
- wtoken.mTaskId = -1;
- */
- return removed;
+ token.mTask = null;
}
void setSendingToBottom(boolean toBottom) {
- for (int appTokenNdx = 0; appTokenNdx < mAppTokens.size(); appTokenNdx++) {
- mAppTokens.get(appTokenNdx).sendingToBottom = toBottom;
+ for (int appTokenNdx = 0; appTokenNdx < mChildren.size(); appTokenNdx++) {
+ mChildren.get(appTokenNdx).sendingToBottom = toBottom;
}
}
@@ -370,13 +354,10 @@
/** Return true if the current bound can get outputted to the rest of the system as-is. */
private boolean useCurrentBounds() {
final DisplayContent displayContent = mStack.getDisplayContent();
- if (mFullscreen
+ return mFullscreen
|| !StackId.isTaskResizeableByDockedStack(mStack.mStackId)
|| displayContent == null
- || displayContent.getDockedStackVisibleForUserLocked() != null) {
- return true;
- }
- return false;
+ || displayContent.getDockedStackVisibleForUserLocked() != null;
}
/** Original bounds of the task if applicable, otherwise fullscreen rect. */
@@ -407,8 +388,8 @@
*/
boolean getMaxVisibleBounds(Rect out) {
boolean foundTop = false;
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken token = mAppTokens.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final AppWindowToken token = mChildren.get(i);
// skip hidden (or about to hide) apps
if (token.mIsExiting || token.clientHidden || token.hiddenRequested) {
continue;
@@ -444,8 +425,8 @@
final DisplayContent displayContent = mStack.getDisplayContent();
// It doesn't matter if we in particular are part of the resize, since we couldn't have
// a DimLayer anyway if we weren't visible.
- final boolean dockedResizing = displayContent != null ?
- displayContent.mDividerControllerLocked.isResizing() : false;
+ final boolean dockedResizing = displayContent != null
+ && displayContent.mDividerControllerLocked.isResizing();
if (useCurrentBounds()) {
if (inFreeformWorkspace() && getMaxVisibleBounds(out)) {
return;
@@ -471,10 +452,11 @@
return;
}
- // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
- // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
- // system.
- displayContent.getLogicalDisplayRect(out);
+ // The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
+ // not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
+ if (displayContent != null) {
+ displayContent.getLogicalDisplayRect(out);
+ }
}
void setDragResizing(boolean dragResizing, int dragResizeMode) {
@@ -489,12 +471,6 @@
}
}
- private void resetDragResizingChangeReported() {
- for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
- mAppTokens.get(activityNdx).resetDragResizingChangeReported();
- }
- }
-
boolean isDragResizing() {
return mDragResizing;
}
@@ -503,24 +479,6 @@
return mDragResizeMode;
}
- /**
- * Adds all of the tasks windows to {@link WindowManagerService#mWaitingForDrawn} if drag
- * resizing state of the window has been changed.
- */
- void setWaitingForDrawnIfResizingChanged() {
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- mAppTokens.get(i).setWaitingForDrawnIfResizingChanged();
- }
- }
-
- boolean detachFromDisplay() {
- boolean didSomething = false;
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- didSomething |= mAppTokens.get(i).detachFromDisplay();
- }
- return didSomething;
- }
-
void updateDisplayInfo(final DisplayContent displayContent) {
if (displayContent == null) {
return;
@@ -556,59 +514,23 @@
}
}
- private void onResize() {
- for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
- mAppTokens.get(activityNdx).onResize();
- }
- }
-
- private void onMovedByResize() {
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- mAppTokens.get(i).onMovedByResize();
- }
- }
-
- /**
- * Cancels any running app transitions associated with the task.
- */
+ /** Cancels any running app transitions associated with the task. */
void cancelTaskWindowTransition() {
- for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
- mAppTokens.get(activityNdx).mAppAnimator.clearAnimation();
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ mChildren.get(i).mAppAnimator.clearAnimation();
}
}
- /**
- * Cancels any running thumbnail transitions associated with the task.
- */
+ /** Cancels any running thumbnail transitions associated with the task. */
void cancelTaskThumbnailTransition() {
- for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
- mAppTokens.get(activityNdx).mAppAnimator.clearThumbnail();
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ mChildren.get(i).mAppAnimator.clearThumbnail();
}
}
boolean showForAllUsers() {
- final int tokensCount = mAppTokens.size();
- return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
- }
-
- boolean hasContentToDisplay() {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken appToken = mAppTokens.get(i);
- if (appToken.hasContentToDisplay()) {
- return true;
- }
- }
- return false;
- }
-
- boolean isVisible() {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken appToken = mAppTokens.get(i);
- if (appToken.isVisible()) {
- return true;
- }
- }
- return false;
+ final int tokensCount = mChildren.size();
+ return (tokensCount != 0) && mChildren.get(tokensCount - 1).showForAllUsers;
}
boolean inHomeStack() {
@@ -633,8 +555,8 @@
}
AppWindowToken getTopVisibleAppToken() {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken token = mAppTokens.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final AppWindowToken token = mChildren.get(i);
// skip hidden (or about to hide) apps
if (!token.mIsExiting && !token.clientHidden && !token.hiddenRequested) {
return token;
@@ -663,82 +585,21 @@
return mStack.getDisplayContent().getDisplayInfo();
}
- /**
- * See {@link WindowManagerService#overridePlayingAppAnimationsLw}
- */
- void overridePlayingAppAnimations(Animation a) {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- mAppTokens.get(i).overridePlayingAppAnimations(a);
- }
- }
-
void forceWindowsScaleable(boolean force) {
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- mAppTokens.get(i).forceWindowsScaleableInTransaction(force);
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ mChildren.get(i).forceWindowsScaleableInTransaction(force);
}
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
}
}
- boolean isAnimating() {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken aToken = mAppTokens.get(i);
- if (aToken.isAnimating()) {
- return true;
- }
- }
- return false;
- }
-
- void checkAppWindowsReadyToShow(int displayId) {
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- final AppWindowToken aToken = mAppTokens.get(i);
- aToken.checkAppWindowsReadyToShow(displayId);
- }
- }
-
- void updateAllDrawn(int displayId) {
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- final AppWindowToken aToken = mAppTokens.get(i);
- aToken.updateAllDrawn(displayId);
- }
- }
-
- void stepAppWindowsAnimation(long currentTime, int displayId) {
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- final AppWindowToken aToken = mAppTokens.get(i);
- aToken.stepAppWindowsAnimation(currentTime, displayId);
- }
- }
-
- void onAppTransitionDone() {
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- final AppWindowToken token = mAppTokens.get(i);
- token.onAppTransitionDone();
- }
- }
-
- // TODO: Use WindowContainer.compareTo() once everything is using WindowContainer
- boolean isFirstGreaterThanSecond(AppWindowToken first, AppWindowToken second) {
- return mAppTokens.indexOf(first) > mAppTokens.indexOf(second);
- }
-
- int rebuildWindowList(DisplayContent dc, int addIndex) {
- final int count = mAppTokens.size();
- for (int i = 0; i < count; i++) {
- final AppWindowToken token = mAppTokens.get(i);
- addIndex = token.rebuildWindowList(dc, addIndex);
- }
- return addIndex;
- }
-
void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
DisplayContent.GetWindowOnDisplaySearchResult result) {
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- final AppWindowToken current = mAppTokens.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final AppWindowToken current = mChildren.get(i);
if (current == token) {
// We have reach the token we are interested in. End search.
result.reachedToken = true;
@@ -756,8 +617,8 @@
void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
DisplayContent.GetWindowOnDisplaySearchResult result) {
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- final AppWindowToken current = mAppTokens.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final AppWindowToken current = mChildren.get(i);
if (!result.reachedToken) {
if (current == token) {
// We have reached the token we are interested in. Get whichever window occurs
@@ -775,36 +636,14 @@
}
}
+ @Override
boolean fillsParent() {
return mFullscreen || !StackId.isTaskResizeAllowed(mStack.mStackId);
}
- // TODO: Remove once switched to use WindowContainer
- int getOrientation() {
- int candidate = SCREEN_ORIENTATION_UNSET;
-
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- final AppWindowToken token = mAppTokens.get(i);
- final int orientation = token.getOrientation();
-
- if (orientation == SCREEN_ORIENTATION_BEHIND) {
- candidate = orientation;
- continue;
- }
-
- if (orientation != SCREEN_ORIENTATION_UNSET) {
- if (token.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
- return orientation;
- }
- }
- }
-
- return candidate;
- }
-
@Override
public String toString() {
- return "{taskId=" + mTaskId + " appTokens=" + mAppTokens + " mdr=" + mDeferRemoval + "}";
+ return "{taskId=" + mTaskId + " appTokens=" + mChildren + " mdr=" + mDeferRemoval + "}";
}
String getName() {
@@ -816,15 +655,6 @@
return "Task=" + mTaskId;
}
- void dumpChildrenNames(PrintWriter pw, String prefix) {
- final String childPrefix = prefix + prefix;
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- final AppWindowToken token = mAppTokens.get(i);
- pw.println("#" + i + " " + getName());
- token.dumpChildrenNames(pw, childPrefix);
- }
- }
-
public void dump(String prefix, PrintWriter pw) {
final String doublePrefix = prefix + " ";
@@ -832,13 +662,13 @@
pw.println(doublePrefix + "mFullscreen=" + mFullscreen);
pw.println(doublePrefix + "mBounds=" + mBounds.toShortString());
pw.println(doublePrefix + "mdr=" + mDeferRemoval);
- pw.println(doublePrefix + "appTokens=" + mAppTokens);
+ pw.println(doublePrefix + "appTokens=" + mChildren);
pw.println(doublePrefix + "mTempInsetBounds=" + mTempInsetBounds.toShortString());
final String triplePrefix = doublePrefix + " ";
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken wtoken = mAppTokens.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final AppWindowToken wtoken = mChildren.get(i);
pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
wtoken.dump(pw, triplePrefix);
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index c097128..21db840 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -459,13 +459,13 @@
mCurrentDimSide = dimSide;
if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
if (mCurrentDimSide == CTRL_NONE) {
mDimLayer.hide();
} else {
showDimLayer();
}
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
}
/**
@@ -477,7 +477,7 @@
*/
private int getDimSide(int x) {
if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
- || !mTask.mStack.isFullscreen()
+ || !mTask.mStack.fillsParent()
|| mService.mGlobalConfiguration.orientation != ORIENTATION_LANDSCAPE) {
return CTRL_NONE;
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index dc54b3fd..b24a06a 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -21,9 +21,7 @@
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.DOCKED_BOTTOM;
@@ -48,7 +46,6 @@
import android.util.SparseArray;
import android.view.DisplayInfo;
import android.view.Surface;
-import android.view.animation.Animation;
import android.view.WindowManagerPolicy;
import com.android.internal.policy.DividerSnapAlgorithm;
@@ -57,9 +54,8 @@
import com.android.server.EventLogTags;
import java.io.PrintWriter;
-import java.util.ArrayList;
-public class TaskStack implements DimLayer.DimLayerUser,
+public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLayerUser,
BoundsAnimationController.AnimateBoundsUser {
/** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
* restrict IME adjustment so that a min portion of top stack remains visible.*/
@@ -75,12 +71,9 @@
private final WindowManagerService mService;
/** The display this stack sits under. */
+ // TODO: Track parent marks like this in WindowContainer.
private DisplayContent mDisplayContent;
- /** The Tasks that define this stack. Oldest Tasks are at the bottom. The ordering must match
- * mTaskHistory in the ActivityStack with the same mStackId */
- private final ArrayList<Task> mTasks = new ArrayList<>();
-
/** For comparison with DisplayContent bounds. */
private Rect mTmpRect = new Rect();
private Rect mTmpRect2 = new Rect();
@@ -98,7 +91,7 @@
private final Rect mFullyAdjustedImeBounds = new Rect();
/** Whether mBounds is fullscreen */
- private boolean mFullscreen = true;
+ private boolean mFillsParent = true;
// Device rotation as of the last time {@link #mBounds} was set.
int mRotation;
@@ -116,7 +109,7 @@
final AppTokenList mExitingAppTokens = new AppTokenList();
/** Detach this stack from its display when animation completes. */
- // TODO: maybe tie this to WindowContainer#detachChild some how...
+ // TODO: maybe tie this to WindowContainer#removeChild some how...
boolean mDeferDetach;
private final Rect mTmpAdjustedBounds = new Rect();
@@ -128,11 +121,9 @@
private float mAdjustDividerAmount;
private final int mDockedStackMinimizeThickness;
- // If this is true, we are in the bounds animating mode.
- // The task will be down or upscaled to perfectly fit the
- // region it would have been cropped to. We may also avoid
- // certain logic we would otherwise apply while resizing,
- // while resizing in the bounds animating mode.
+ // If this is true, we are in the bounds animating mode. The task will be down or upscaled to
+ // perfectly fit the region it would have been cropped to. We may also avoid certain logic we
+ // would otherwise apply while resizing, while resizing in the bounds animating mode.
private boolean mBoundsAnimating = false;
// Temporary storage for the new bounds that should be used after the configuration change.
@@ -156,20 +147,20 @@
return null;
}
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- if (mTasks.get(i).isHomeTask()) {
- return mTasks.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ if (mChildren.get(i).isHomeTask()) {
+ return mChildren.get(i);
}
}
return null;
}
boolean hasMultipleTaskWithHomeTaskNotTop() {
- return mTasks.size() > 1 && !mTasks.get(mTasks.size() - 1).isHomeTask();
+ return mChildren.size() > 1 && !mChildren.get(mChildren.size() - 1).isHomeTask();
}
boolean topTaskIsOnTopLauncher() {
- return mTasks.get(mTasks.size() - 1).isOnTopLauncher();
+ return mChildren.get(mChildren.size() - 1).isOnTopLauncher();
}
/**
@@ -185,8 +176,8 @@
setBounds(stackBounds);
// Update bounds of containing tasks.
- for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = mTasks.get(taskNdx);
+ for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+ final Task task = mChildren.get(taskNdx);
Configuration config = configs.get(task.mTaskId);
if (config != null) {
Rect bounds = taskBounds.get(task.mTaskId);
@@ -201,8 +192,8 @@
}
void prepareFreezingTaskBounds() {
- for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = mTasks.get(taskNdx);
+ for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+ final Task task = mChildren.get(taskNdx);
task.prepareFreezingBounds();
}
}
@@ -243,29 +234,29 @@
}
private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
- if (mFullscreen) {
+ if (mFillsParent) {
return;
}
final boolean alignBottom = mAdjustedForIme && getDockSide() == DOCKED_TOP;
// Update bounds of containing tasks.
- for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = mTasks.get(taskNdx);
+ for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+ final Task task = mChildren.get(taskNdx);
task.alignToAdjustedBounds(adjustedBounds, tempInsetBounds, alignBottom);
}
}
private boolean setBounds(Rect bounds) {
- boolean oldFullscreen = mFullscreen;
+ boolean oldFullscreen = mFillsParent;
int rotation = Surface.ROTATION_0;
int density = DENSITY_DPI_UNDEFINED;
if (mDisplayContent != null) {
mDisplayContent.getLogicalDisplayRect(mTmpRect);
rotation = mDisplayContent.getDisplayInfo().rotation;
density = mDisplayContent.getDisplayInfo().logicalDensityDpi;
- mFullscreen = bounds == null;
- if (mFullscreen) {
+ mFillsParent = bounds == null;
+ if (mFillsParent) {
bounds = mTmpRect;
}
}
@@ -274,7 +265,7 @@
// Can't set to fullscreen if we don't have a display to get bounds from...
return false;
}
- if (mBounds.equals(bounds) && oldFullscreen == mFullscreen && mRotation == rotation) {
+ if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) {
return false;
}
@@ -302,7 +293,7 @@
/** Return true if the current bound can get outputted to the rest of the system as-is. */
private boolean useCurrentBounds() {
- if (mFullscreen
+ if (mFillsParent
|| !StackId.isResizeableByDockedStack(mStackId)
|| mDisplayContent == null
|| mDisplayContent.getDockedStackLocked() != null) {
@@ -342,13 +333,13 @@
return;
}
- for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
- mTasks.get(taskNdx).updateDisplayInfo(mDisplayContent);
+ for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+ mChildren.get(taskNdx).updateDisplayInfo(mDisplayContent);
}
if (bounds != null) {
setBounds(bounds);
return;
- } else if (mFullscreen) {
+ } else if (mFillsParent) {
setBounds(null);
return;
}
@@ -381,7 +372,7 @@
return false;
}
- if (mFullscreen) {
+ if (mFillsParent) {
// Update stack bounds again since rotation changed since updateDisplayInfo().
setBounds(null);
// Return false since we don't need the client to resize.
@@ -481,16 +472,7 @@
dividerSize);
}
- boolean isAnimating() {
- for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = mTasks.get(taskNdx);
- if (task.isAnimating()) {
- return true;
- }
- }
- return false;
- }
-
+ // TODO: Checkout the call points of this method and the ones below to see how they can fit in WC.
void addTask(Task task, boolean toTop) {
addTask(task, toTop, task.showForAllUsers());
}
@@ -502,14 +484,17 @@
* @param showForAllUsers Whether to show the task regardless of the current user.
*/
void addTask(Task task, boolean toTop, boolean showForAllUsers) {
- positionTask(task, toTop ? mTasks.size() : 0, showForAllUsers);
+ positionTask(task, toTop ? mChildren.size() : 0, showForAllUsers);
}
+ // TODO: We should really have users as a window container in the hierarchy so that we don't
+ // have to do complicated things like we are doing in this method and also also the method to
+ // just be WindowContainer#addChild
void positionTask(Task task, int position, boolean showForAllUsers) {
final boolean canShowTask =
showForAllUsers || mService.isCurrentProfileLocked(task.mUserId);
- mTasks.remove(task);
- int stackSize = mTasks.size();
+ removeChild(task);
+ int stackSize = mChildren.size();
int minPosition = 0;
int maxPosition = stackSize;
@@ -523,11 +508,13 @@
if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM,
"positionTask: task=" + task + " position=" + position);
- mTasks.add(position, task);
+ addChild(task, position);
task.mStack = this;
task.updateDisplayInfo(mDisplayContent);
- boolean toTop = position == mTasks.size() - 1;
+ boolean toTop = position == mChildren.size() - 1;
if (toTop) {
+ // TODO: Have a WidnowContainer method that moves all parents of a container to the
+ // front for cases like this.
mDisplayContent.moveStack(this, true);
}
@@ -549,7 +536,7 @@
*/
private int computeMinPosition(int minPosition, int size) {
while (minPosition < size) {
- final Task tmpTask = mTasks.get(minPosition);
+ final Task tmpTask = mChildren.get(minPosition);
final boolean canShowTmpTask =
tmpTask.showForAllUsers()
|| mService.isCurrentProfileLocked(tmpTask.mUserId);
@@ -568,7 +555,7 @@
*/
private int computeMaxPosition(int maxPosition) {
while (maxPosition > 0) {
- final Task tmpTask = mTasks.get(maxPosition - 1);
+ final Task tmpTask = mChildren.get(maxPosition - 1);
final boolean canShowTmpTask =
tmpTask.showForAllUsers()
|| mService.isCurrentProfileLocked(tmpTask.mUserId);
@@ -580,16 +567,17 @@
return maxPosition;
}
+ // TODO: Have functionality in WC to move things to the bottom or top. Also, look at the call
+ // points for this methods to see if we need functionality to move something to the front/bottom
+ // with its parents.
void moveTaskToTop(Task task) {
if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "moveTaskToTop: task=" + task + " Callers="
+ Debug.getCallers(6));
- mTasks.remove(task);
addTask(task, true);
}
void moveTaskToBottom(Task task) {
if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "moveTaskToBottom: task=" + task);
- mTasks.remove(task);
addTask(task, false);
}
@@ -598,11 +586,18 @@
* back.
* @param task The Task to delete.
*/
- void removeTask(Task task) {
- if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "removeTask: task=" + task);
- mTasks.remove(task);
+ @Override
+ void removeChild(Task task) {
+ if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "removeChild: task=" + task);
+ if (!mChildren.contains(task)) {
+ Slog.e(TAG_WM, "removeChild: task=" + this + " not found.");
+ return;
+ }
+
+ super.removeChild(task);
+
if (mDisplayContent != null) {
- if (mTasks.isEmpty()) {
+ if (mChildren.isEmpty()) {
mDisplayContent.moveStack(this, false);
}
mDisplayContent.layoutNeeded = true;
@@ -629,7 +624,7 @@
final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
if (mStackId == DOCKED_STACK_ID
|| (dockedStack != null && StackId.isResizeableByDockedStack(mStackId)
- && !dockedStack.isFullscreen())) {
+ && !dockedStack.fillsParent())) {
// The existence of a docked stack affects the size of other static stack created since
// the docked stack occupies a dedicated region on screen, but only if the dock stack is
// not fullscreen. If it's fullscreen, it means that we are in the transition of
@@ -769,14 +764,10 @@
1 /*allowResizeInDockedMode*/, bounds).sendToTarget();
}
+ // TODO: Should this really be removeImmidiately or removeChild?
boolean detachFromDisplay() {
EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
-
- boolean didSomething = false;
- for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
- didSomething |= mTasks.get(taskNdx).detachFromDisplay();
- }
-
+ boolean didSomething = super.detachFromDisplay();
close();
return didSomething;
}
@@ -797,13 +788,14 @@
}
}
+ // TODO: Should each user have there own stacks?
void switchUser() {
- int top = mTasks.size();
+ int top = mChildren.size();
for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
- Task task = mTasks.get(taskNdx);
+ Task task = mChildren.get(taskNdx);
if (mService.isCurrentProfileLocked(task.mUserId) || task.showForAllUsers()) {
- mTasks.remove(taskNdx);
- mTasks.add(task);
+ mChildren.remove(taskNdx);
+ mChildren.add(task);
--top;
}
}
@@ -906,8 +898,8 @@
* to the list of to be drawn windows the service is waiting for.
*/
void beginImeAdjustAnimation() {
- for (int j = mTasks.size() - 1; j >= 0; j--) {
- final Task task = mTasks.get(j);
+ for (int j = mChildren.size() - 1; j >= 0; j--) {
+ final Task task = mChildren.get(j);
if (task.hasContentToDisplay()) {
task.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
task.setWaitingForDrawnIfResizingChanged();
@@ -919,8 +911,8 @@
* Resets the resizing state of all windows.
*/
void endImeAdjustAnimation() {
- for (int j = mTasks.size() - 1; j >= 0; j--) {
- mTasks.get(j).setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
+ for (int j = mChildren.size() - 1; j >= 0; j--) {
+ mChildren.get(j).setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
}
}
@@ -1083,22 +1075,13 @@
return mMinimizeAmount != 0f;
}
- void dumpChildrenNames(PrintWriter pw, String prefix) {
- final String childPrefix = prefix + prefix;
- for (int j = mTasks.size() - 1; j >= 0; j--) {
- final Task task = mTasks.get(j);
- pw.println("#" + j + " " + getName());
- task.dumpChildrenNames(pw, childPrefix);
- }
- }
-
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "mStackId=" + mStackId);
pw.println(prefix + "mDeferDetach=" + mDeferDetach);
- pw.println(prefix + "mFullscreen=" + mFullscreen);
+ pw.println(prefix + "mFillsParent=" + mFillsParent);
pw.println(prefix + "mBounds=" + mBounds.toShortString());
if (mMinimizeAmount != 0f) {
- pw.println(prefix + "mMinimizeAmout=" + mMinimizeAmount);
+ pw.println(prefix + "mMinimizeAmount=" + mMinimizeAmount);
}
if (mAdjustedForIme) {
pw.println(prefix + "mAdjustedForIme=true");
@@ -1108,8 +1091,8 @@
if (!mAdjustedBounds.isEmpty()) {
pw.println(prefix + "mAdjustedBounds=" + mAdjustedBounds.toShortString());
}
- for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; taskNdx--) {
- mTasks.get(taskNdx).dump(prefix + " ", pw);
+ for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
+ mChildren.get(taskNdx).dump(prefix + " ", pw);
}
if (mAnimationBackgroundSurface.isDimming()) {
pw.println(prefix + "mWindowAnimationBackgroundSurface:");
@@ -1130,20 +1113,21 @@
/** Fullscreen status of the stack without adjusting for other factors in the system like
* visibility of docked stack.
- * Most callers should be using {@link #isFullscreen} as it take into consideration other
+ * Most callers should be using {@link #fillsParent} as it take into consideration other
* system factors. */
boolean getRawFullscreen() {
- return mFullscreen;
+ return mFillsParent;
}
@Override
public boolean dimFullscreen() {
- return mStackId == HOME_STACK_ID || isFullscreen();
+ return mStackId == HOME_STACK_ID || fillsParent();
}
- boolean isFullscreen() {
+ @Override
+ boolean fillsParent() {
if (useCurrentBounds()) {
- return mFullscreen;
+ return mFillsParent;
}
// The bounds has been adjusted to accommodate for a docked stack, but the docked stack
// is not currently visible. Go ahead a represent it as fullscreen to the rest of the
@@ -1158,7 +1142,7 @@
@Override
public String toString() {
- return "{stackId=" + mStackId + " tasks=" + mTasks + "}";
+ return "{stackId=" + mStackId + " tasks=" + mChildren + "}";
}
String getName() {
@@ -1222,19 +1206,12 @@
return false;
}
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- final Task task = mTasks.get(i);
- if (task.isVisible()) {
- return true;
- }
- }
-
- return false;
+ return super.isVisible();
}
boolean hasTaskForUser(int userId) {
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- final Task task = mTasks.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final Task task = mChildren.get(i);
if (task.mUserId == userId) {
return true;
}
@@ -1248,8 +1225,8 @@
return -1;
}
- for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = mTasks.get(taskNdx);
+ for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+ final Task task = mChildren.get(taskNdx);
final WindowState win = task.getTopVisibleAppMainWindow();
if (win == null) {
continue;
@@ -1274,8 +1251,8 @@
return;
}
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- final Task task = mTasks.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final Task task = mChildren.get(i);
if (task.isFullscreen()) {
results.searchDone = true;
return;
@@ -1306,8 +1283,8 @@
void setTouchExcludeRegion(Task focusedTask, int delta, Region touchExcludeRegion,
Rect contentRect, Rect postExclude) {
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- final Task task = mTasks.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final Task task = mChildren.get(i);
AppWindowToken token = task.getTopVisibleAppToken();
if (token == null || !token.hasContentToDisplay()) {
continue;
@@ -1429,15 +1406,6 @@
return mBoundsAnimating;
}
- /**
- * See {@link WindowManagerService#overridePlayingAppAnimationsLw}
- */
- void overridePlayingAppAnimations(Animation a) {
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- mTasks.get(i).overridePlayingAppAnimations(a);
- }
- }
-
/** Returns true if a removal action is still being deferred. */
boolean checkCompleteDeferredRemoval() {
if (isAnimating()) {
@@ -1447,33 +1415,11 @@
mDisplayContent.detachChild(this);
}
- boolean stillDeferringRemoval = false;
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- final Task task = mTasks.get(i);
- stillDeferringRemoval |= task.checkCompleteDeferredRemoval();
- }
- return stillDeferringRemoval;
- }
-
- void checkAppWindowsReadyToShow(int displayId) {
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- final Task task = mTasks.get(i);
- task.checkAppWindowsReadyToShow(displayId);
- }
- }
-
- void updateAllDrawn(int displayId) {
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- final Task task = mTasks.get(i);
- task.updateAllDrawn(displayId);
- }
+ return super.checkCompleteDeferredRemoval();
}
void stepAppWindowsAnimation(long currentTime, int displayId) {
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- final Task task = mTasks.get(i);
- task.stepAppWindowsAnimation(currentTime, displayId);
- }
+ super.stepAppWindowsAnimation(currentTime, displayId);
// TODO: Why aren't we just using the loop above for this? mAppAnimator.animating isn't set
// below but is set in the loop above. See if it really matters...
@@ -1495,37 +1441,10 @@
}
}
- void onAppTransitionDone() {
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- final Task task = mTasks.get(i);
- task.onAppTransitionDone();
- }
- }
-
- // TODO: Use WindowContainer.compareTo() once everything is using WindowContainer
- boolean isFirstGreaterThanSecond(AppWindowToken first, AppWindowToken second) {
- final Task firstTask = first.mTask;
- final Task secondTask = second.mTask;
-
- if (firstTask == secondTask) {
- return firstTask.isFirstGreaterThanSecond(first, second);
- }
- return mTasks.indexOf(first) > mTasks.indexOf(second);
- }
-
- int rebuildWindowList(DisplayContent dc, int addIndex) {
- final int count = mTasks.size();
- for (int i = 0; i < count; i++) {
- final Task task = mTasks.get(i);
- addIndex = task.rebuildWindowList(dc, addIndex);
- }
- return addIndex;
- }
-
void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
DisplayContent.GetWindowOnDisplaySearchResult result) {
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- final Task task = mTasks.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final Task task = mChildren.get(i);
task.getWindowOnDisplayBeforeToken(dc, token, result);
if (result.reachedToken) {
// We have reach the token we are interested in. End search.
@@ -1536,8 +1455,8 @@
void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
DisplayContent.GetWindowOnDisplaySearchResult result) {
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- final Task task = mTasks.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final Task task = mChildren.get(i);
task.getWindowOnDisplayAfterToken(dc, token, result);
if (result.foundWindow != null) {
// We have found a window after the token. End search.
@@ -1546,39 +1465,9 @@
}
}
- // TODO: Remove once switched to use WindowContainer
+ @Override
int getOrientation() {
- if (!StackId.canSpecifyOrientation(mStackId)) {
- return SCREEN_ORIENTATION_UNSET;
- }
-
- int candidate = SCREEN_ORIENTATION_UNSET;
-
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- final Task task = mTasks.get(i);
-
- if (!task.isVisible()) {
- continue;
- }
-
- final int orientation = task.getOrientation();
- if (orientation == SCREEN_ORIENTATION_BEHIND) {
- candidate = orientation;
- continue;
- }
-
- if (orientation != SCREEN_ORIENTATION_UNSET) {
- if (task.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
- return orientation;
- }
- }
- }
-
- return candidate;
- }
-
- // TODO: Remove once switched to use WindowContainer
- boolean fillsParent() {
- return mFullscreen;
+ return (StackId.canSpecifyOrientation(mStackId))
+ ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 6665a93..a13b6a1 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -609,7 +609,7 @@
if (SHOW_TRANSACTIONS) Slog.i(
TAG, ">>> OPEN TRANSACTION animateLocked");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
SurfaceControl.setAnimationTransaction();
try {
final int numDisplays = mDisplayContentsAnimators.size();
@@ -686,7 +686,7 @@
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_TRANSACTIONS) Slog.i(
TAG, "<<< CLOSE TRANSACTION animateLocked");
}
@@ -742,7 +742,7 @@
private void removeReplacedWindowsLocked() {
if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION removeReplacedWindows");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
for (int i = mService.mDisplayContents.size() - 1; i >= 0; i--) {
DisplayContent display = mService.mDisplayContents.valueAt(i);
@@ -756,7 +756,7 @@
}
}
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION removeReplacedWindows");
}
mRemoveReplacedWindows = false;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ef05ad2..106284a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import android.annotation.CallSuper;
+import android.view.animation.Animation;
import java.io.PrintWriter;
import java.util.Comparator;
@@ -61,6 +62,11 @@
*/
@CallSuper
protected void addChild(E child, Comparator<E> comparator) {
+ if (child.mParent != null) {
+ throw new IllegalArgumentException("addChild: container=" + child
+ + " is already a child of container=" + child.mParent
+ + " can't add to container=" + this);
+ }
child.mParent = this;
if (mChildren.isEmpty() || comparator == null) {
@@ -79,6 +85,33 @@
mChildren.add(child);
}
+ /** Adds the input window container has a child of this container at the input index. */
+ @CallSuper
+ protected void addChild(E child, int index) {
+ if (child.mParent != null) {
+ throw new IllegalArgumentException("addChild: container=" + child
+ + " is already a child of container=" + child.mParent
+ + " can't add to container=" + this);
+ }
+ child.mParent = this;
+ mChildren.add(index, child);
+ }
+
+ /**
+ * Removes the input child container from this container which is its parent.
+ *
+ * @return True if the container did contain the input child and it was detached.
+ */
+ @CallSuper
+ void removeChild(E child) {
+ if (mChildren.remove(child)) {
+ child.mParent = null;
+ } else {
+ throw new IllegalArgumentException("removeChild: container=" + child
+ + " is not a child of container=" + this);
+ }
+ }
+
/**
* Removes this window container and its children with no regard for what else might be going on
* in the system. For example, the container will be removed during animation if this method is
@@ -97,7 +130,7 @@
}
if (mParent != null) {
- mParent.detachChild(this);
+ mParent.removeChild(this);
}
}
@@ -115,17 +148,6 @@
}
}
- /** Detaches the input child container from this container which is its parent. */
- @CallSuper
- void detachChild(WindowContainer child) {
- if (mChildren.remove(child)) {
- child.mParent = null;
- } else {
- throw new IllegalArgumentException("detachChild: container=" + child
- + " is not a child of container=" + this);
- }
- }
-
/** Returns true if this window container has the input child. */
boolean hasChild(WindowContainer child) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
@@ -233,10 +255,10 @@
* the container has any content to display.
*/
boolean isVisible() {
- // TODO: Will this be more correct if it checks the visibility of its parents? Yes.
- // That is this container is only visible if its parents are visible vs visible if it has a
- // visible child. In that case all overrides will need to call super and return false if
- // this returns false.
+ // TODO: Will this be more correct if it checks the visibility of its parents?
+ // It depends...For example, Tasks and Stacks are only visible if there children are visible
+ // but, WindowState are not visible if there parent are not visible. Maybe have the
+ // container specify which direction to treverse for for visibility?
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
if (wc.isVisible()) {
@@ -303,6 +325,12 @@
}
}
+ void overridePlayingAppAnimations(Animation a) {
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ mChildren.get(i).overridePlayingAppAnimations(a);
+ }
+ }
+
void setOrientation(int orientation) {
mOrientation = orientation;
}
@@ -386,8 +414,8 @@
}
/**
- * Returns -1, 0, or 1 depending on if the input container is greater than, equal to, or lesser
- * than the input container in terms of z-order.
+ * Returns 1, 0, or -1 depending on if this container is greater than, equal to, or lesser than
+ * the input container in terms of z-order.
*/
@Override
public int compareTo(WindowContainer other) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a0b74f8..0e0cf02 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -893,6 +893,31 @@
// current configuration.
private final DisplayContentList mReconfigureOnConfigurationChanged = new DisplayContentList();
+ // State for the RemoteSurfaceTrace system used in testing. If this is enabled SurfaceControl
+ // instances will be replaced with an instance that writes a binary representation of all
+ // commands to mSurfaceTraceFd.
+ boolean mSurfaceTraceEnabled;
+ ParcelFileDescriptor mSurfaceTraceFd;
+ RemoteEventTrace mRemoteEventTrace;
+
+ void openSurfaceTransaction() {
+ synchronized (mWindowMap) {
+ if (mSurfaceTraceEnabled) {
+ mRemoteEventTrace.openSurfaceTransaction();
+ }
+ SurfaceControl.openTransaction();
+ }
+ }
+
+ void closeSurfaceTransaction() {
+ synchronized (mWindowMap) {
+ if (mSurfaceTraceEnabled) {
+ mRemoteEventTrace.closeSurfaceTransaction();
+ }
+ SurfaceControl.closeTransaction();
+ }
+ }
+
/** Listener to notify activity manager about app transitions. */
final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
= new WindowManagerInternal.AppTransitionListener() {
@@ -1065,11 +1090,11 @@
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
- SurfaceControl.openTransaction();
+ openSurfaceTransaction();
try {
createWatermarkInTransaction();
} finally {
- SurfaceControl.closeTransaction();
+ closeSurfaceTransaction();
}
showEmulatorDisplayOverlayIfNeeded();
@@ -1935,6 +1960,44 @@
return false;
}
+ @Override
+ public void enableSurfaceTrace(ParcelFileDescriptor pfd) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
+ throw new SecurityException("Only shell can call enableSurfaceTrace");
+ }
+ final FileDescriptor fd = pfd.getFileDescriptor();
+
+ synchronized (mWindowMap) {
+ if (mSurfaceTraceEnabled) {
+ disableSurfaceTrace();
+ }
+ mSurfaceTraceEnabled = true;
+ mRemoteEventTrace = new RemoteEventTrace(this, fd);
+ mSurfaceTraceFd = pfd;
+ for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+ DisplayContent dc = mDisplayContents.valueAt(displayNdx);
+ dc.enableSurfaceTrace(fd);
+ }
+ }
+ }
+
+ @Override
+ public void disableSurfaceTrace() {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID &&
+ callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException("Only shell can call disableSurfaceTrace");
+ }
+ mSurfaceTraceEnabled = false;
+ mRemoteEventTrace = null;
+ mSurfaceTraceFd = null;
+ for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+ DisplayContent dc = mDisplayContents.valueAt(displayNdx);
+ dc.disableSurfaceTrace();
+ }
+ }
+
/**
* Set mScreenCaptureDisabled for specific user
*/
@@ -1973,7 +2036,7 @@
/**
* Performs some centralized bookkeeping clean-up on the window that is being removed.
* NOTE: Should only be called from {@link WindowState#removeImmediately()}
- * TODO: Maybe better handled with a method {@link WindowContainer#detachChild} if we can
+ * TODO: Maybe better handled with a method {@link WindowContainer#removeChild} if we can
* figure-out a good way to have all parents of a WindowState doing the same thing without
* forgetting to add the wiring when a new parent of WindowState is added.
*/
@@ -2196,7 +2259,7 @@
Slog.i(TAG_WM, ">>> OPEN TRANSACTION repositionChild");
}
- SurfaceControl.openTransaction();
+ openSurfaceTransaction();
try {
@@ -2210,7 +2273,7 @@
}
} finally {
- SurfaceControl.closeTransaction();
+ closeSurfaceTransaction();
if (SHOW_TRANSACTIONS) {
Slog.i(TAG_WM, "<<< CLOSE TRANSACTION repositionChild");
}
@@ -2881,14 +2944,11 @@
Slog.w(TAG_WM, "Attempted to set task id of non-existing app token: " + token);
return;
}
- final Task oldTask = atoken.mTask;
- oldTask.removeAppToken(atoken);
Task newTask = mTaskIdToTask.get(taskId);
if (newTask == null) {
- newTask = createTaskLocked(
- taskId, stackId, oldTask.mUserId, atoken, taskBounds, overrideConfig,
- isOnTopLauncher);
+ newTask = createTaskLocked(taskId, stackId, atoken.mTask.mUserId, atoken,
+ taskBounds, overrideConfig, isOnTopLauncher);
}
newTask.addAppToken(Integer.MAX_VALUE /* at top */, atoken, taskResizeMode, homeTask);
}
@@ -3005,11 +3065,12 @@
config = computeNewConfigurationLocked();
} else if (currentConfig != null) {
- // No obvious action we need to take, but if our current
- // state mismatches the activity manager's, update it,
- // disregarding font scale, which should remain set to
- // the value of the previous configuration.
- mTempConfiguration.setToDefaults();
+ // No obvious action we need to take, but if our current state mismatches the activity
+ // manager's, update it, disregarding font scale, which should remain set to the value
+ // of the previous configuration.
+ // Here we're calling Configuration#unset() instead of setToDefaults() because we need
+ // to keep override configs clear of non-empty values (e.g. fontSize).
+ mTempConfiguration.unset();
mTempConfiguration.updateFrom(currentConfig);
computeScreenConfigurationLocked(mTempConfiguration);
if (currentConfig.diff(mTempConfiguration) != 0) {
@@ -4005,7 +4066,7 @@
if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + taskId);
return;
}
- task.removeLocked();
+ task.removeIfPossible();
}
}
@@ -4973,7 +5034,7 @@
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
">>> OPEN TRANSACTION showCircularMask(visible=" + visible + ")");
- SurfaceControl.openTransaction();
+ openSurfaceTransaction();
try {
if (visible) {
// TODO(multi-display): support multiple displays
@@ -4996,7 +5057,7 @@
mCircularDisplayMask = null;
}
} finally {
- SurfaceControl.closeTransaction();
+ closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION showCircularMask(visible=" + visible + ")");
}
@@ -5008,7 +5069,7 @@
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
">>> OPEN TRANSACTION showEmulatorDisplayOverlay");
- SurfaceControl.openTransaction();
+ openSurfaceTransaction();
try {
if (mEmulatorDisplayOverlay == null) {
mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(
@@ -5021,7 +5082,7 @@
}
mEmulatorDisplayOverlay.setVisibility(true);
} finally {
- SurfaceControl.closeTransaction();
+ closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION showEmulatorDisplayOverlay");
}
@@ -5063,6 +5124,8 @@
if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG_WM,
">>> OPEN TRANSACTION showStrictModeViolation");
+ // TODO: Modify this to use the surface trace once it is not going crazy.
+ // b/31532461
SurfaceControl.openTransaction();
try {
// TODO(multi-display): support multiple displays
@@ -5703,7 +5766,7 @@
if (SHOW_TRANSACTIONS) {
Slog.i(TAG_WM, ">>> OPEN TRANSACTION setRotationUnchecked");
}
- SurfaceControl.openTransaction();
+ openSurfaceTransaction();
}
try {
// NOTE: We disable the rotation in the emulator because
@@ -5728,7 +5791,7 @@
mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
} finally {
if (!inTransaction) {
- SurfaceControl.closeTransaction();
+ closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked");
}
@@ -8077,8 +8140,7 @@
displayContent.layoutNeeded = true;
boolean configChanged = updateOrientationFromAppTokensLocked(false);
- mTempConfiguration.setToDefaults();
- mTempConfiguration.updateFrom(mGlobalConfiguration);
+ mTempConfiguration.setTo(mGlobalConfiguration);
computeScreenConfigurationLocked(mTempConfiguration);
configChanged |= mGlobalConfiguration.diff(mTempConfiguration) != 0;
@@ -8669,7 +8731,7 @@
// TODO(multidisplay): rotation on main screen only.
displayContent.updateDisplayInfo();
screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
- mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure);
+ mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure, this);
mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation);
}
}
@@ -8807,6 +8869,7 @@
if (line != null) {
String[] toks = line.split("%");
if (toks != null && toks.length > 0) {
+ // TODO(multi-display): Show watermarks on secondary displays.
mWatermark = new Watermark(getDefaultDisplayContentLocked().getDisplay(),
mRealDisplayMetrics, mFxSession, toks);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bbb1fbb..60b0d03 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1569,7 +1569,7 @@
boolean isObscuringFullscreen(final DisplayInfo displayInfo) {
Task task = getTask();
- if (task != null && task.mStack != null && !task.mStack.isFullscreen()) {
+ if (task != null && task.mStack != null && !task.mStack.fillsParent()) {
return false;
}
if (!isOpaqueDrawn() || !isFrameFullscreen(displayInfo)) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 788f28d..125368c 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -70,6 +70,7 @@
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
+import java.io.FileDescriptor;
/**
* Keep track of animations and surface operations for a single WindowState.
@@ -1580,7 +1581,7 @@
try {
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setWallpaperOffset");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left,
mWin.mFrame.top + top, false);
calculateSurfaceWindowCrop(mTmpClipRect, mTmpFinalClipRect);
@@ -1589,7 +1590,7 @@
Slog.w(TAG, "Error positioning surface of " + mWin
+ " pos=(" + left + "," + top + ")", e);
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION setWallpaperOffset");
}
@@ -2006,4 +2007,20 @@
DtDy * w.mVScale, false);
}
}
+
+ void enableSurfaceTrace(FileDescriptor fd) {
+ if (mSurfaceController != null) {
+ mSurfaceController.installRemoteTrace(fd);
+ }
+ }
+
+ void disableSurfaceTrace() {
+ if (mSurfaceController != null) {
+ try {
+ mSurfaceController.removeRemoteTrace();
+ } catch (ClassCastException e) {
+ Slog.e(TAG, "Disable surface trace for " + this + " but its not enabled");
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index f5ed9d1..cc72352 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -41,6 +41,7 @@
import android.util.Slog;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -70,6 +71,8 @@
private boolean mHiddenForOtherReasons = true;
private final String title;
+ private final WindowManagerService mService;
+
public WindowSurfaceController(SurfaceSession s,
String name, int w, int h, int format, int flags, WindowStateAnimator animator) {
mAnimator = animator;
@@ -79,6 +82,8 @@
title = name;
+ mService = animator.mService;
+
// For opaque child windows placed under parent windows,
// we use a special SurfaceControl which mirrors commands
// to a black-out layer placed one Z-layer below the surface.
@@ -95,6 +100,19 @@
mSurfaceControl = new SurfaceControl(
s, name, w, h, format, flags);
}
+
+ if (mService.mSurfaceTraceEnabled) {
+ mSurfaceControl = new RemoteSurfaceTrace(mService.mSurfaceTraceFd.getFileDescriptor(),
+ mSurfaceControl, animator.mWin);
+ }
+ }
+
+ void installRemoteTrace(FileDescriptor fd) {
+ mSurfaceControl = new RemoteSurfaceTrace(fd, mSurfaceControl, mAnimator.mWin);
+ }
+
+ void removeRemoteTrace() {
+ mSurfaceControl = new SurfaceControl(mSurfaceControl);
}
@@ -127,7 +145,7 @@
}
void setPositionAndLayer(float left, float top, int layerStack, int layer) {
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
mSurfaceX = left;
mSurfaceY = top;
@@ -146,7 +164,7 @@
mAnimator.reclaimSomeSurfaceMemory("create-init", true);
}
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION setPositionAndLayer");
}
@@ -230,11 +248,11 @@
void setLayer(int layer) {
if (mSurfaceControl != null) {
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
mSurfaceControl.setLayer(layer);
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
}
}
}
@@ -338,11 +356,11 @@
return;
}
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setTransparentRegion");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
mSurfaceControl.setTransparentRegionHint(region);
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION setTransparentRegion");
}
@@ -356,11 +374,11 @@
return;
}
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setOpaqueLocked");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
mSurfaceControl.setOpaque(isOpaque);
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked");
}
}
@@ -373,11 +391,11 @@
return;
}
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setSecureLocked");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
mSurfaceControl.setSecure(isSecure);
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked");
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 5025df6..13d3501 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -303,6 +303,7 @@
mSustainedPerformanceModeCurrent = false;
mService.mTransactionSequence++;
+ // TODO(multi-display): Perform same actions on all displays.
final DisplayContent defaultDisplay = mService.getDefaultDisplayContentLocked();
final DisplayInfo defaultInfo = defaultDisplay.getDisplayInfo();
final int defaultDw = defaultInfo.logicalWidth;
@@ -310,13 +311,13 @@
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
applySurfaceChangesTransaction(recoveringMemory, numDisplays, defaultDw, defaultDh);
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
}
@@ -1256,11 +1257,11 @@
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
- SurfaceControl.openTransaction();
+ mService.openSurfaceTransaction();
try {
mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
} finally {
- SurfaceControl.closeTransaction();
+ mService.closeSurfaceTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 9b92ecda..735efa9 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -180,26 +180,13 @@
}
if (!mChildren.contains(win)) {
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
- mChildren.add(tokenWindowsPos, win);
+ addChild(win, tokenWindowsPos);
}
} else if (dc != null) {
dc.addChildWindowToWindowList(win);
}
}
- int reAddAppWindows(DisplayContent displayContent, int index) {
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
- final WindowState win = mChildren.get(i);
- final DisplayContent winDisplayContent = win.getDisplayContent();
- if (winDisplayContent == displayContent || winDisplayContent == null) {
- win.mDisplayContent = displayContent;
- index = win.reAddWindow(index);
- }
- }
- return index;
- }
-
/** Return the first window in the token window list that isn't a starting window or null. */
WindowState getFirstNonStartingWindow() {
final int count = mChildren.size();
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 19898e3..973f1b9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -88,6 +88,31 @@
}
@Test
+ public void testAdd_AlreadyHasParent() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+
+ boolean gotException = false;
+ try {
+ child1.addChildWindow(child2);
+ } catch (IllegalArgumentException e) {
+ gotException = true;
+ }
+ assertTrue(gotException);
+
+ gotException = false;
+ try {
+ root.addChildWindow(child2);
+ } catch (IllegalArgumentException e) {
+ gotException = true;
+ }
+ assertTrue(gotException);
+ }
+
+ @Test
public void testHasChild() throws Exception {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
final TestWindowContainer root = builder.setLayer(0).build();
@@ -210,7 +235,7 @@
}
@Test
- public void testDetachChild() throws Exception {
+ public void testRemoveChild() throws Exception {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow();
@@ -220,7 +245,7 @@
assertTrue(root.hasChild(child2));
assertTrue(root.hasChild(child21));
- root.detachChild(child2);
+ root.removeChild(child2);
assertFalse(root.hasChild(child2));
assertFalse(root.hasChild(child21));
assertNull(child2.getParentWindow());
@@ -229,7 +254,7 @@
assertTrue(root.hasChild(child11));
try {
// Can only detach our direct children.
- root.detachChild(child11);
+ root.removeChild(child11);
} catch (IllegalArgumentException e) {
gotException = true;
}
@@ -410,6 +435,11 @@
return mChildren.size();
}
+ TestWindowContainer addChildWindow(TestWindowContainer child) {
+ addChild(child, mWindowSubLayerComparator);
+ return child;
+ }
+
TestWindowContainer addChildWindow(TestWindowContainerBuilder childBuilder) {
TestWindowContainer child = childBuilder.build();
addChild(child, mWindowSubLayerComparator);
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 0c75630..7b68a4c 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1082,7 +1082,7 @@
*
* @param connectionManagerPhoneAccount See description at
* {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
- * @param request Details about the incoming call.
+ * @param request Details about the outgoing call.
* @return The {@code Connection} object to satisfy this call, or {@code null} to
* not handle the call.
*/
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index e2b6b54..bdc9c45 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -702,6 +702,13 @@
public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
/**
+ * APN types that user is not allowed to modify
+ * @hide
+ */
+ public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY =
+ "read_only_apn_types_string_array";
+
+ /**
* Boolean indicating if intent for emergency call state changes should be broadcast
* @hide
*/
@@ -1068,6 +1075,7 @@
sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING, "");
sDefaults.putBoolean(KEY_CSP_ENABLED_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_ADDING_APNS_BOOL, true);
+ sDefaults.putStringArray(KEY_READ_ONLY_APN_TYPES_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_SEVERE_WHEN_EXTREME_DISABLED_BOOL, true);
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index b417a1c..fdc68b9 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -140,6 +140,18 @@
/** APN type for Emergency PDN. This is not an IA apn, but is used
* for access to carrier services in an emergency call situation. */
public static final String APN_TYPE_EMERGENCY = "emergency";
+ /** Array of all APN types */
+ public static final String[] APN_TYPES = {APN_TYPE_DEFAULT,
+ APN_TYPE_MMS,
+ APN_TYPE_SUPL,
+ APN_TYPE_DUN,
+ APN_TYPE_HIPRI,
+ APN_TYPE_FOTA,
+ APN_TYPE_IMS,
+ APN_TYPE_CBS,
+ APN_TYPE_IA,
+ APN_TYPE_EMERGENCY
+ };
public static final int RIL_CARD_MAX_APPS = 8;
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index e7edcc5..45f5acd 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -194,7 +194,10 @@
manifestAction["uses-configuration"];
manifestAction["uses-feature"];
manifestAction["supports-screens"];
+
manifestAction["compatible-screens"];
+ manifestAction["compatible-screens"]["screen"];
+
manifestAction["supports-gl-texture"];
// Application actions.
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 6ca13d6..4a70060 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -30,6 +30,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.view.AppTransitionAnimationSpec;
@@ -610,4 +611,12 @@
public Bitmap screenshotWallpaper() throws RemoteException {
return null;
}
+
+ @Override
+ public void enableSurfaceTrace(ParcelFileDescriptor fd) throws RemoteException {
+ }
+
+ @Override
+ public void disableSurfaceTrace() throws RemoteException {
+ }
}
diff --git a/wifi/java/android/net/wifi/nan/ConfigRequest.java b/wifi/java/android/net/wifi/nan/ConfigRequest.java
index 44544de..78e4052 100644
--- a/wifi/java/android/net/wifi/nan/ConfigRequest.java
+++ b/wifi/java/android/net/wifi/nan/ConfigRequest.java
@@ -23,14 +23,14 @@
/**
* Defines a request object to configure a Wi-Fi NAN network. Built using
* {@link ConfigRequest.Builder}. Configuration is requested using
- * {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}.
+ * {@link WifiNanManager#connect(android.os.Handler, ConfigRequest, WifiNanEventCallback)}.
* Note that the actual achieved configuration may be different from the
* requested configuration - since different applications may request different
* configurations.
*
* @hide PROPOSED_NAN_API
*/
-public class ConfigRequest implements Parcelable {
+public final class ConfigRequest implements Parcelable {
/**
* Lower range of possible cluster ID.
*
diff --git a/wifi/java/android/net/wifi/nan/PublishConfig.java b/wifi/java/android/net/wifi/nan/PublishConfig.java
index 71f99d9..6203f95 100644
--- a/wifi/java/android/net/wifi/nan/PublishConfig.java
+++ b/wifi/java/android/net/wifi/nan/PublishConfig.java
@@ -37,7 +37,7 @@
*
* @hide PROPOSED_NAN_API
*/
-public class PublishConfig implements Parcelable {
+public final class PublishConfig implements Parcelable {
/** @hide */
@IntDef({
PUBLISH_TYPE_UNSOLICITED, PUBLISH_TYPE_SOLICITED })
diff --git a/wifi/java/android/net/wifi/nan/SubscribeConfig.java b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
index 7904875..5a2c762 100644
--- a/wifi/java/android/net/wifi/nan/SubscribeConfig.java
+++ b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
@@ -37,7 +37,7 @@
*
* @hide PROPOSED_NAN_API
*/
-public class SubscribeConfig implements Parcelable {
+public final class SubscribeConfig implements Parcelable {
/** @hide */
@IntDef({
SUBSCRIBE_TYPE_PASSIVE, SUBSCRIBE_TYPE_ACTIVE })
diff --git a/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java b/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java
index 6e714f1..029e36a 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java
@@ -23,7 +23,7 @@
/**
* Base class for NAN events callbacks. Should be extended by applications and set when calling
- * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)}. These are callbacks
+ * {@link WifiNanManager#connect(android.os.Handler, WifiNanEventCallback)}. These are callbacks
* applying to the NAN connection as a whole - not to specific publish or subscribe sessions -
* for that see {@link WifiNanSessionCallback}.
*
@@ -46,7 +46,7 @@
/**
* Indicates that a {@link ConfigRequest} passed in
- * {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}
+ * {@link WifiNanManager#connect(android.os.Handler, ConfigRequest, WifiNanEventCallback)}
* couldn't be applied since other connections already exist with an incompatible
* configurations. Failure reason flag for {@link WifiNanEventCallback#onConnectFail(int)}.
*/
@@ -60,7 +60,7 @@
/**
* Called when NAN connect operation
- * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)}
+ * {@link WifiNanManager#connect(android.os.Handler, WifiNanEventCallback)}
* is completed and that we can now start discovery sessions or connections.
*/
public void onConnectSuccess() {
@@ -69,7 +69,7 @@
/**
* Called when NAN connect operation
- * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)} failed.
+ * {@link WifiNanManager#connect(android.os.Handler, WifiNanEventCallback)} failed.
*
* @param reason Failure reason code, see
* {@code WifiNanEventCallback.REASON_*}.
@@ -91,7 +91,7 @@
* <p>
* This callback is only called if the NAN connection enables it using
* {@link ConfigRequest.Builder#setEnableIdentityChangeCallback(boolean)} in
- * {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}
+ * {@link WifiNanManager#connect(android.os.Handler, ConfigRequest, WifiNanEventCallback)}
* . It is disabled by default since it may result in additional wake-ups of the host -
* increasing power.
*
diff --git a/wifi/java/android/net/wifi/nan/WifiNanManager.java b/wifi/java/android/net/wifi/nan/WifiNanManager.java
index 82d22bc..24ee640 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanManager.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanManager.java
@@ -60,7 +60,7 @@
* The class provides access to:
* <ul>
* <li>Initialize a NAN cluster (peer-to-peer synchronization). Refer to
- * {@link #connect(Looper, WifiNanEventCallback)}.
+ * {@link #connect(Handler, WifiNanEventCallback)}.
* <li>Create discovery sessions (publish or subscribe sessions).
* Refer to {@link #publish(PublishConfig, WifiNanSessionCallback)} and
* {@link #subscribe(SubscribeConfig, WifiNanSessionCallback)}.
@@ -77,7 +77,7 @@
* Note that this broadcast is not sticky - you should register for it and then check the
* above API to avoid a race condition.
* <p>
- * An application must use {@link #connect(Looper, WifiNanEventCallback)} to initialize a NAN
+ * An application must use {@link #connect(Handler, WifiNanEventCallback)} to initialize a NAN
* cluster - before making any other NAN operation. NAN cluster membership is a device-wide
* operation - the API guarantees that the device is in a cluster or joins a NAN cluster (or
* starts one if none can be found). Information about connection success (or failure) are
@@ -343,13 +343,13 @@
* Note: a NAN cluster is a shared resource - if the device is already connected to a cluster
* than this function will simply indicate success immediately.
*
- * @param looper The Looper on which to execute all callbacks related to the
+ * @param handler The Handler on whose thread to execute all callbacks related to the
* connection - including all sessions opened as part of this
- * connection.
+ * connection. If a null is provided then the application's main thread will be used.
* @param callback A callback extended from {@link WifiNanEventCallback}.
*/
- public void connect(@NonNull Looper looper, @NonNull WifiNanEventCallback callback) {
- connect(looper, null, callback);
+ public void connect(@Nullable Handler handler, @NonNull WifiNanEventCallback callback) {
+ connect(handler, null, callback);
}
/**
@@ -360,30 +360,31 @@
* An application <b>must</b> call {@link #disconnect()} when done with the Wi-Fi NAN
* connection. Allows requesting a specific configuration using {@link ConfigRequest}. If not
* necessary (default configuration should usually work) use the
- * {@link #connect(Looper, WifiNanEventCallback)} method instead.
+ * {@link #connect(Handler, WifiNanEventCallback)} method instead.
* <p>
* Note: a NAN cluster is a shared resource - if the device is already connected to a cluster
* than this function will simply indicate success immediately.
*
- * @param looper The Looper on which to execute all callbacks related to the
+ * @param handler The Handler on whose thread to execute all callbacks related to the
* connection - including all sessions opened as part of this
- * connection.
+ * connection. If a null is provided then the application's main thread will be used.
* @param configRequest The requested NAN configuration.
* @param callback A callback extended from {@link WifiNanEventCallback}.
*/
- public void connect(@NonNull Looper looper, @Nullable ConfigRequest configRequest,
+ public void connect(@Nullable Handler handler, @Nullable ConfigRequest configRequest,
@NonNull WifiNanEventCallback callback) {
if (VDBG) {
- Log.v(TAG, "connect(): looper=" + looper + ", callback=" + callback + ", configRequest="
- + configRequest);
+ Log.v(TAG,
+ "connect(): handler=" + handler + ", callback=" + callback + ", configRequest="
+ + configRequest);
}
synchronized (mLock) {
- mLooper = looper;
+ mLooper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
try {
mClientId = mService.connect(mBinder, mContext.getOpPackageName(),
- new WifiNanEventCallbackProxy(this, looper, callback), configRequest);
+ new WifiNanEventCallbackProxy(this, mLooper, callback), configRequest);
} catch (RemoteException e) {
mClientId = INVALID_CLIENT_ID;
mLooper = null;
@@ -402,7 +403,7 @@
* connections explicitly before a disconnect.
* <p>
* An application may re-connect after a disconnect using
- * {@link WifiNanManager#connect(Looper, WifiNanEventCallback)} .
+ * {@link WifiNanManager#connect(Handler, WifiNanEventCallback)} .
*/
public void disconnect() {
if (VDBG) Log.v(TAG, "disconnect()");