Merge "Refactor TextUtils.join() and add documentation"
diff --git a/Android.mk b/Android.mk
index 8fd8a2e..69c8c2c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -246,6 +246,7 @@
core/java/android/nfc/INfcCardEmulation.aidl \
core/java/android/nfc/INfcFCardEmulation.aidl \
core/java/android/nfc/INfcUnlockHandler.aidl \
+ core/java/android/nfc/INfcDta.aidl \
core/java/android/nfc/ITagRemovedCallback.aidl \
core/java/android/os/IBatteryPropertiesListener.aidl \
core/java/android/os/IBatteryPropertiesRegistrar.aidl \
diff --git a/api/current.txt b/api/current.txt
index e149797..1535435 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27070,7 +27070,8 @@
method public abstract void onSuccess();
}
- public static class WifiP2pManager.Channel {
+ public static class WifiP2pManager.Channel implements java.lang.AutoCloseable {
+ method public void close();
}
public static abstract interface WifiP2pManager.ChannelListener {
diff --git a/api/system-current.txt b/api/system-current.txt
index ff3069a..cf2c3b8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -29868,7 +29868,8 @@
method public abstract void onSuccess();
}
- public static class WifiP2pManager.Channel {
+ public static class WifiP2pManager.Channel implements java.lang.AutoCloseable {
+ method public void close();
}
public static abstract interface WifiP2pManager.ChannelListener {
diff --git a/api/test-current.txt b/api/test-current.txt
index 08df809..98fa305 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -27181,7 +27181,8 @@
method public abstract void onSuccess();
}
- public static class WifiP2pManager.Channel {
+ public static class WifiP2pManager.Channel implements java.lang.AutoCloseable {
+ method public void close();
}
public static abstract interface WifiP2pManager.ChannelListener {
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index f991efe..6801618 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -29,6 +29,7 @@
import android.nfc.INfcFCardEmulation;
import android.nfc.INfcUnlockHandler;
import android.nfc.ITagRemovedCallback;
+import android.nfc.INfcDta;
import android.os.Bundle;
/**
@@ -40,7 +41,7 @@
INfcCardEmulation getNfcCardEmulationInterface();
INfcFCardEmulation getNfcFCardEmulationInterface();
INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg);
-
+ INfcDta getNfcDtaInterface(in String pkg);
int getState();
boolean disable(boolean saveState);
boolean enable();
diff --git a/core/java/android/nfc/INfcDta.aidl b/core/java/android/nfc/INfcDta.aidl
new file mode 100644
index 0000000..4cc5927
--- /dev/null
+++ b/core/java/android/nfc/INfcDta.aidl
@@ -0,0 +1,34 @@
+ /*
+ * Copyright (C) 2017 NXP Semiconductors
+ *
+ * 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 android.nfc;
+
+import android.os.Bundle;
+
+/**
+ * {@hide}
+ */
+interface INfcDta {
+
+ void enableDta();
+ void disableDta();
+ boolean enableServer(String serviceName, int serviceSap, int miu,
+ int rwSize,int testCaseId);
+ void disableServer();
+ boolean enableClient(String serviceName, int miu, int rwSize,
+ int testCaseId);
+ void disableClient();
+ boolean registerMessageService(String msgServiceName);
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 48869c7..debef63 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -16,8 +16,6 @@
package android.nfc;
-import java.util.HashMap;
-
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -43,6 +41,7 @@
import android.util.Log;
import java.io.IOException;
+import java.util.HashMap;
/**
* Represents the local NFC adapter.
@@ -627,6 +626,23 @@
}
/**
+ * Returns the binder interface to the NFC-DTA test interface.
+ * @hide
+ */
+ public INfcDta getNfcDtaInterface() {
+ if (mContext == null) {
+ throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+ + " NFC extras APIs");
+ }
+ try {
+ return sService.getNfcDtaInterface(mContext.getPackageName());
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return null;
+ }
+ }
+
+ /**
* NFC service dead - attempt best effort recovery
* @hide
*/
diff --git a/core/java/android/nfc/dta/NfcDta.java b/core/java/android/nfc/dta/NfcDta.java
new file mode 100644
index 0000000..8801662
--- /dev/null
+++ b/core/java/android/nfc/dta/NfcDta.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 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 android.nfc.dta;
+
+import android.content.Context;
+import android.nfc.INfcDta;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * This class provides the primary API for DTA operations.
+ * @hide
+ */
+public final class NfcDta {
+ private static final String TAG = "NfcDta";
+
+ private static INfcDta sService;
+ private static HashMap<Context, NfcDta> sNfcDtas = new HashMap<Context, NfcDta>();
+
+ private final Context mContext;
+
+ private NfcDta(Context context, INfcDta service) {
+ mContext = context.getApplicationContext();
+ sService = service;
+ }
+
+ /**
+ * Helper to get an instance of this class.
+ *
+ * @param adapter A reference to an NfcAdapter object.
+ * @return
+ */
+ public static synchronized NfcDta getInstance(NfcAdapter adapter) {
+ if (adapter == null) throw new NullPointerException("NfcAdapter is null");
+ Context context = adapter.getContext();
+ if (context == null) {
+ Log.e(TAG, "NfcAdapter context is null.");
+ throw new UnsupportedOperationException();
+ }
+
+ NfcDta manager = sNfcDtas.get(context);
+ if (manager == null) {
+ INfcDta service = adapter.getNfcDtaInterface();
+ if (service == null) {
+ Log.e(TAG, "This device does not implement the INfcDta interface.");
+ throw new UnsupportedOperationException();
+ }
+ manager = new NfcDta(context, service);
+ sNfcDtas.put(context, manager);
+ }
+ return manager;
+ }
+
+ /**
+ * Enables DTA mode
+ *
+ * @return true/false if enabling was successful
+ */
+ public boolean enableDta() {
+ try {
+ sService.enableDta();
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Disables DTA mode
+ *
+ * @return true/false if disabling was successful
+ */
+ public boolean disableDta() {
+ try {
+ sService.disableDta();
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Enables Server
+ *
+ * @return true/false if enabling was successful
+ */
+ public boolean enableServer(String serviceName, int serviceSap, int miu,
+ int rwSize, int testCaseId) {
+ try {
+ return sService.enableServer(serviceName, serviceSap, miu, rwSize, testCaseId);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Disables Server
+ *
+ * @return true/false if disabling was successful
+ */
+ public boolean disableServer() {
+ try {
+ sService.disableServer();
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Enables Client
+ *
+ * @return true/false if enabling was successful
+ */
+ public boolean enableClient(String serviceName, int miu, int rwSize,
+ int testCaseId) {
+ try {
+ return sService.enableClient(serviceName, miu, rwSize, testCaseId);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Disables client
+ *
+ * @return true/false if disabling was successful
+ */
+ public boolean disableClient() {
+ try {
+ sService.disableClient();
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Registers Message Service
+ *
+ * @return true/false if registration was successful
+ */
+ public boolean registerMessageService(String msgServiceName) {
+ try {
+ return sService.registerMessageService(msgServiceName);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+}
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index b5a8aca..34fe07b 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -177,43 +177,38 @@
* @hide
*/
public static boolean is24HourFormat(Context context, int userHandle) {
- String value = Settings.System.getStringForUser(context.getContentResolver(),
+ final String value = Settings.System.getStringForUser(context.getContentResolver(),
Settings.System.TIME_12_24, userHandle);
-
- if (value == null) {
- Locale locale = context.getResources().getConfiguration().locale;
-
- synchronized (sLocaleLock) {
- if (sIs24HourLocale != null && sIs24HourLocale.equals(locale)) {
- return sIs24Hour;
- }
- }
-
- java.text.DateFormat natural =
- java.text.DateFormat.getTimeInstance(java.text.DateFormat.LONG, locale);
-
- if (natural instanceof SimpleDateFormat) {
- SimpleDateFormat sdf = (SimpleDateFormat) natural;
- String pattern = sdf.toPattern();
-
- if (pattern.indexOf('H') >= 0) {
- value = "24";
- } else {
- value = "12";
- }
- } else {
- value = "12";
- }
-
- synchronized (sLocaleLock) {
- sIs24HourLocale = locale;
- sIs24Hour = value.equals("24");
- }
-
- return sIs24Hour;
+ if (value != null) {
+ return value.equals("24");
}
- return value.equals("24");
+ final Locale locale = context.getResources().getConfiguration().locale;
+
+ synchronized (sLocaleLock) {
+ if (sIs24HourLocale != null && sIs24HourLocale.equals(locale)) {
+ return sIs24Hour;
+ }
+ }
+
+ final java.text.DateFormat natural =
+ java.text.DateFormat.getTimeInstance(java.text.DateFormat.LONG, locale);
+
+ final boolean is24Hour;
+ if (natural instanceof SimpleDateFormat) {
+ final SimpleDateFormat sdf = (SimpleDateFormat) natural;
+ final String pattern = sdf.toPattern();
+ is24Hour = hasDesignator(pattern, 'H');
+ } else {
+ is24Hour = false;
+ }
+
+ synchronized (sLocaleLock) {
+ sIs24HourLocale = locale;
+ sIs24Hour = is24Hour;
+ }
+
+ return is24Hour;
}
/**
@@ -249,17 +244,18 @@
/**
* Returns a {@link java.text.DateFormat} object that can format the time according
- * to the current locale and the user's 12-/24-hour clock preference.
+ * to the context's locale and the user's 12-/24-hour clock preference.
* @param context the application context
* @return the {@link java.text.DateFormat} object that properly formats the time.
*/
public static java.text.DateFormat getTimeFormat(Context context) {
- return new java.text.SimpleDateFormat(getTimeFormatString(context));
+ final Locale locale = context.getResources().getConfiguration().locale;
+ return new java.text.SimpleDateFormat(getTimeFormatString(context), locale);
}
/**
* Returns a String pattern that can be used to format the time according
- * to the current locale and the user's 12-/24-hour clock preference.
+ * to the context's locale and the user's 12-/24-hour clock preference.
* @param context the application context
* @hide
*/
@@ -269,45 +265,48 @@
/**
* Returns a String pattern that can be used to format the time according
- * to the current locale and the user's 12-/24-hour clock preference.
+ * to the context's locale and the user's 12-/24-hour clock preference.
* @param context the application context
* @param userHandle the user handle of the user to query the format for
* @hide
*/
public static String getTimeFormatString(Context context, int userHandle) {
- LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
+ final LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
return is24HourFormat(context, userHandle) ? d.timeFormat_Hm : d.timeFormat_hm;
}
/**
* Returns a {@link java.text.DateFormat} object that can format the date
- * in short form according to the current locale.
+ * in short form according to the context's locale.
*
* @param context the application context
* @return the {@link java.text.DateFormat} object that properly formats the date.
*/
public static java.text.DateFormat getDateFormat(Context context) {
- return java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT);
+ final Locale locale = context.getResources().getConfiguration().locale;
+ return java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT, locale);
}
/**
* Returns a {@link java.text.DateFormat} object that can format the date
- * in long form (such as {@code Monday, January 3, 2000}) for the current locale.
+ * in long form (such as {@code Monday, January 3, 2000}) for the context's locale.
* @param context the application context
* @return the {@link java.text.DateFormat} object that formats the date in long form.
*/
public static java.text.DateFormat getLongDateFormat(Context context) {
- return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG);
+ final Locale locale = context.getResources().getConfiguration().locale;
+ return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG, locale);
}
/**
* Returns a {@link java.text.DateFormat} object that can format the date
- * in medium form (such as {@code Jan 3, 2000}) for the current locale.
+ * in medium form (such as {@code Jan 3, 2000}) for the context's locale.
* @param context the application context
* @return the {@link java.text.DateFormat} object that formats the date in long form.
*/
public static java.text.DateFormat getMediumDateFormat(Context context) {
- return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM);
+ final Locale locale = context.getResources().getConfiguration().locale;
+ return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM, locale);
}
/**
@@ -320,11 +319,13 @@
* order returned here.
*/
public static char[] getDateFormatOrder(Context context) {
- return ICU.getDateFormatOrder(getDateFormatString());
+ return ICU.getDateFormatOrder(getDateFormatString(context));
}
- private static String getDateFormatString() {
- java.text.DateFormat df = java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT);
+ private static String getDateFormatString(Context context) {
+ final Locale locale = context.getResources().getConfiguration().locale;
+ java.text.DateFormat df = java.text.DateFormat.getDateInstance(
+ java.text.DateFormat.SHORT, locale);
if (df instanceof SimpleDateFormat) {
return ((SimpleDateFormat) df).toPattern();
}
@@ -375,6 +376,9 @@
* Test if a format string contains the given designator. Always returns
* {@code false} if the input format is {@code null}.
*
+ * Note that this is intended for searching for designators, not arbitrary
+ * characters. So searching for a literal single quote would not work correctly.
+ *
* @hide
*/
public static boolean hasDesignator(CharSequence inFormat, char designator) {
@@ -382,52 +386,21 @@
final int length = inFormat.length();
- int c;
- int count;
-
- for (int i = 0; i < length; i += count) {
- count = 1;
- c = inFormat.charAt(i);
-
+ boolean insideQuote = false;
+ for (int i = 0; i < length; i++) {
+ final char c = inFormat.charAt(i);
if (c == QUOTE) {
- count = skipQuotedText(inFormat, i, length);
- } else if (c == designator) {
- return true;
+ insideQuote = !insideQuote;
+ } else if (!insideQuote) {
+ if (c == designator) {
+ return true;
+ }
}
}
return false;
}
- private static int skipQuotedText(CharSequence s, int i, int len) {
- if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
- return 2;
- }
-
- int count = 1;
- // skip leading quote
- i++;
-
- while (i < len) {
- char c = s.charAt(i);
-
- if (c == QUOTE) {
- count++;
- // QUOTEQUOTE -> QUOTE
- if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
- i++;
- } else {
- break;
- }
- } else {
- i++;
- count++;
- }
- }
-
- return count;
- }
-
/**
* Given a format string and a {@link java.util.Calendar} object, returns a CharSequence
* containing the requested date.
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index abbd793..3a3646b 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
+import android.graphics.LeakyTypefaceStorage;
import android.graphics.Typeface;
import android.os.Parcel;
import android.text.ParcelableSpan;
@@ -30,11 +31,12 @@
* resource.
*/
public class TextAppearanceSpan extends MetricAffectingSpan implements ParcelableSpan {
- private final String mTypeface;
+ private final String mFamilyName;
private final int mStyle;
private final int mTextSize;
private final ColorStateList mTextColor;
private final ColorStateList mTextColorLink;
+ private final Typeface mTypeface;
/**
* Uses the specified TextAppearance resource to determine the
@@ -55,7 +57,7 @@
*/
public TextAppearanceSpan(Context context, int appearance, int colorList) {
ColorStateList textColor;
-
+
TypedArray a =
context.obtainStyledAttributes(appearance,
com.android.internal.R.styleable.TextAppearance);
@@ -68,28 +70,33 @@
TextAppearance_textSize, -1);
mStyle = a.getInt(com.android.internal.R.styleable.TextAppearance_textStyle, 0);
- String family = a.getString(com.android.internal.R.styleable.TextAppearance_fontFamily);
- if (family != null) {
- mTypeface = family;
+ mTypeface = a.getFont(com.android.internal.R.styleable.TextAppearance_fontFamily);
+ if (mTypeface != null) {
+ mFamilyName = null;
} else {
- int tf = a.getInt(com.android.internal.R.styleable.TextAppearance_typeface, 0);
+ String family = a.getString(com.android.internal.R.styleable.TextAppearance_fontFamily);
+ if (family != null) {
+ mFamilyName = family;
+ } else {
+ int tf = a.getInt(com.android.internal.R.styleable.TextAppearance_typeface, 0);
- switch (tf) {
- case 1:
- mTypeface = "sans";
- break;
+ switch (tf) {
+ case 1:
+ mFamilyName = "sans";
+ break;
- case 2:
- mTypeface = "serif";
- break;
+ case 2:
+ mFamilyName = "serif";
+ break;
- case 3:
- mTypeface = "monospace";
- break;
+ case 3:
+ mFamilyName = "monospace";
+ break;
- default:
- mTypeface = null;
- break;
+ default:
+ mFamilyName = null;
+ break;
+ }
}
}
@@ -102,7 +109,7 @@
textColor = a.getColorStateList(colorList);
a.recycle();
}
-
+
mTextColor = textColor;
}
@@ -112,15 +119,16 @@
*/
public TextAppearanceSpan(String family, int style, int size,
ColorStateList color, ColorStateList linkColor) {
- mTypeface = family;
+ mFamilyName = family;
mStyle = style;
mTextSize = size;
mTextColor = color;
mTextColorLink = linkColor;
+ mTypeface = null;
}
public TextAppearanceSpan(Parcel src) {
- mTypeface = src.readString();
+ mFamilyName = src.readString();
mStyle = src.readInt();
mTextSize = src.readInt();
if (src.readInt() != 0) {
@@ -133,8 +141,9 @@
} else {
mTextColorLink = null;
}
+ mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src);
}
-
+
public int getSpanTypeId() {
return getSpanTypeIdInternal();
}
@@ -143,7 +152,7 @@
public int getSpanTypeIdInternal() {
return TextUtils.TEXT_APPEARANCE_SPAN;
}
-
+
public int describeContents() {
return 0;
}
@@ -154,7 +163,7 @@
/** @hide */
public void writeToParcelInternal(Parcel dest, int flags) {
- dest.writeString(mTypeface);
+ dest.writeString(mFamilyName);
dest.writeInt(mStyle);
dest.writeInt(mTextSize);
if (mTextColor != null) {
@@ -169,6 +178,7 @@
} else {
dest.writeInt(0);
}
+ LeakyTypefaceStorage.writeTypefaceToParcel(mTypeface, dest);
}
/**
@@ -176,7 +186,7 @@
* if it does not specify one.
*/
public String getFamily() {
- return mTypeface;
+ return mFamilyName;
}
/**
@@ -226,9 +236,14 @@
@Override
public void updateMeasureState(TextPaint ds) {
- if (mTypeface != null || mStyle != 0) {
+ final Typeface styledTypeface;
+ int style = 0;
+
+ if (mTypeface != null) {
+ style = mStyle;
+ styledTypeface = Typeface.create(mTypeface, style);
+ } else if (mFamilyName != null || mStyle != 0) {
Typeface tf = ds.getTypeface();
- int style = 0;
if (tf != null) {
style = tf.getStyle();
@@ -236,15 +251,19 @@
style |= mStyle;
- if (mTypeface != null) {
- tf = Typeface.create(mTypeface, style);
+ if (mFamilyName != null) {
+ styledTypeface = Typeface.create(mFamilyName, style);
} else if (tf == null) {
- tf = Typeface.defaultFromStyle(style);
+ styledTypeface = Typeface.defaultFromStyle(style);
} else {
- tf = Typeface.create(tf, style);
+ styledTypeface = Typeface.create(tf, style);
}
+ } else {
+ styledTypeface = null;
+ }
- int fake = style & ~tf.getStyle();
+ if (styledTypeface != null) {
+ int fake = style & ~styledTypeface.getStyle();
if ((fake & Typeface.BOLD) != 0) {
ds.setFakeBoldText(true);
@@ -254,7 +273,7 @@
ds.setTextSkewX(-0.25f);
}
- ds.setTypeface(tf);
+ ds.setTypeface(styledTypeface);
}
if (mTextSize > 0) {
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
index 2f1abe9..0122e49 100644
--- a/core/java/android/util/AtomicFile.java
+++ b/core/java/android/util/AtomicFile.java
@@ -202,6 +202,15 @@
}
/**
+ * @hide
+ * Checks if the original or backup file exists.
+ * @return whether the original or backup file exists.
+ */
+ public boolean exists() {
+ return mBaseName.exists() || mBackupName.exists();
+ }
+
+ /**
* Gets the last modified time of the atomic file.
* {@hide}
*
diff --git a/graphics/java/android/graphics/LeakyTypefaceStorage.java b/graphics/java/android/graphics/LeakyTypefaceStorage.java
new file mode 100644
index 0000000..618e60d
--- /dev/null
+++ b/graphics/java/android/graphics/LeakyTypefaceStorage.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 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 android.graphics;
+
+import com.android.internal.annotations.GuardedBy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Process;
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+
+/**
+ * This class is used for Parceling Typeface object.
+ * Note: Typeface object can not be passed over the process boundary.
+ *
+ * @hide
+ */
+public class LeakyTypefaceStorage {
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static final ArrayList<Typeface> sStorage = new ArrayList<>();
+ @GuardedBy("sLock")
+ private static final ArrayMap<Typeface, Integer> sTypefaceMap = new ArrayMap<>();
+
+ /**
+ * Write typeface to parcel.
+ *
+ * You can't transfer Typeface to a different process. {@link readTypefaceFromParcel} will
+ * return {@code null} if the {@link readTypefaceFromParcel} is called in a different process.
+ *
+ * @param typeface A {@link Typeface} to be written.
+ * @param parcel A {@link Parcel} object.
+ */
+ public static void writeTypefaceToParcel(@Nullable Typeface typeface, @NonNull Parcel parcel) {
+ parcel.writeInt(Process.myPid());
+ synchronized (sLock) {
+ final int id;
+ final Integer i = sTypefaceMap.get(typeface);
+ if (i != null) {
+ id = i.intValue();
+ } else {
+ id = sStorage.size();
+ sStorage.add(typeface);
+ sTypefaceMap.put(typeface, id);
+ }
+ parcel.writeInt(id);
+ }
+ }
+
+ /**
+ * Read typeface from parcel.
+ *
+ * If the {@link Typeface} was created in another process, this method returns null.
+ *
+ * @param parcel A {@link Parcel} object
+ * @return A {@link Typeface} object.
+ */
+ public static @Nullable Typeface readTypefaceFromParcel(@NonNull Parcel parcel) {
+ final int pid = parcel.readInt();
+ final int typefaceId = parcel.readInt();
+ if (pid != Process.myPid()) {
+ return null; // The Typeface was created and written in another process.
+ }
+ synchronized (sLock) {
+ return sStorage.get(typefaceId);
+ }
+ }
+}
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 54be8fd..9862564 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -1134,7 +1134,9 @@
}
public SubscriptionCallback getCallback(Context context, Bundle options) {
- options.setClassLoader(context.getClassLoader());
+ if (options != null) {
+ options.setClassLoader(context.getClassLoader());
+ }
for (int i = 0; i < mOptionsList.size(); ++i) {
if (MediaBrowserUtils.areSameOptions(mOptionsList.get(i), options)) {
return mCallbacks.get(i);
@@ -1144,7 +1146,9 @@
}
public void putCallback(Context context, Bundle options, SubscriptionCallback callback) {
- options.setClassLoader(context.getClassLoader());
+ if (options != null) {
+ options.setClassLoader(context.getClassLoader());
+ }
for (int i = 0; i < mOptionsList.size(); ++i) {
if (MediaBrowserUtils.areSameOptions(mOptionsList.get(i), options)) {
mCallbacks.set(i, callback);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 0f8096a..6086005 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -749,7 +749,7 @@
if (WifiTracker.sVerboseLogging) {
// Add RSSI/band information for this config, what was seen up to 6 seconds ago
// verbose WiFi Logging is only turned on thru developers settings
- if (mInfo != null && mNetworkInfo != null) { // This is the active connection
+ if (isActive() && mInfo != null) {
summary.append(" f=" + Integer.toString(mInfo.getFrequency()));
}
summary.append(" " + getVisibilityStatus());
@@ -814,7 +814,7 @@
long now = System.currentTimeMillis();
- if (mInfo != null) {
+ if (isActive() && mInfo != null) {
bssid = mInfo.getBSSID();
if (bssid != null) {
visibility.append(" ").append(bssid);
@@ -1084,7 +1084,7 @@
// are still seen, we will investigate further.
update(config); // Notifies the AccessPointListener of the change
}
- if (mRssi != info.getRssi()) {
+ if (mRssi != info.getRssi() && info.getRssi() != WifiInfo.INVALID_RSSI) {
mRssi = info.getRssi();
updated = true;
} else if (mNetworkInfo != null && networkInfo != null
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 1fb8058..35c730e 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -655,6 +655,57 @@
}
@Test
+ public void testUpdateWithDifferentRssi_returnsTrue() {
+ int networkId = 123;
+ int rssi = -55;
+ WifiConfiguration config = new WifiConfiguration();
+ config.networkId = networkId;
+ WifiInfo wifiInfo = new WifiInfo();
+ wifiInfo.setNetworkId(networkId);
+ wifiInfo.setRssi(rssi);
+
+ NetworkInfo networkInfo =
+ new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
+ networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTING, "", "");
+
+ AccessPoint ap = new TestAccessPointBuilder(mContext)
+ .setNetworkInfo(networkInfo)
+ .setNetworkId(networkId)
+ .setRssi(rssi)
+ .setWifiInfo(wifiInfo)
+ .build();
+
+ NetworkInfo newInfo = new NetworkInfo(networkInfo); // same values
+ wifiInfo.setRssi(rssi + 1);
+ assertThat(ap.update(config, wifiInfo, newInfo)).isTrue();
+ }
+
+ @Test
+ public void testUpdateWithInvalidRssi_returnsFalse() {
+ int networkId = 123;
+ int rssi = -55;
+ WifiConfiguration config = new WifiConfiguration();
+ config.networkId = networkId;
+ WifiInfo wifiInfo = new WifiInfo();
+ wifiInfo.setNetworkId(networkId);
+ wifiInfo.setRssi(rssi);
+
+ NetworkInfo networkInfo =
+ new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
+ networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTING, "", "");
+
+ AccessPoint ap = new TestAccessPointBuilder(mContext)
+ .setNetworkInfo(networkInfo)
+ .setNetworkId(networkId)
+ .setRssi(rssi)
+ .setWifiInfo(wifiInfo)
+ .build();
+
+ NetworkInfo newInfo = new NetworkInfo(networkInfo); // same values
+ wifiInfo.setRssi(WifiInfo.INVALID_RSSI);
+ assertThat(ap.update(config, wifiInfo, newInfo)).isFalse();
+ }
+ @Test
public void testUpdateWithConfigChangeOnly_returnsFalseButInvokesListener() {
int networkId = 123;
int rssi = -55;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 15e6e45..146b349 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2594,7 +2594,7 @@
synchronized (mLock) {
final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
File globalFile = getSettingsFile(key);
- if (globalFile.exists()) {
+ if (SettingsState.stateFileExists(globalFile)) {
return;
}
@@ -2631,7 +2631,7 @@
// Every user has secure settings and if no file we need to migrate.
final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
File secureFile = getSettingsFile(secureKey);
- if (secureFile.exists()) {
+ if (SettingsState.stateFileExists(secureFile)) {
return;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 5f4b239..d3ac11a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -689,17 +689,11 @@
private void readStateSyncLocked() {
FileInputStream in;
- if (!mStatePersistFile.exists()) {
- Slog.i(LOG_TAG, "No settings state " + mStatePersistFile);
- addHistoricalOperationLocked(HISTORICAL_OPERATION_INITIALIZE, null);
- return;
- }
try {
in = new AtomicFile(mStatePersistFile).openRead();
} catch (FileNotFoundException fnfe) {
- String message = "No settings state " + mStatePersistFile;
- Slog.wtf(LOG_TAG, message);
- Slog.i(LOG_TAG, message);
+ Slog.i(LOG_TAG, "No settings state " + mStatePersistFile);
+ addHistoricalOperationLocked(HISTORICAL_OPERATION_INITIALIZE, null);
return;
}
try {
@@ -715,6 +709,16 @@
}
}
+ /**
+ * Uses AtomicFile to check if the file or its backup exists.
+ * @param file The file to check for existence
+ * @return whether the original or backup exist
+ */
+ public static boolean stateFileExists(File file) {
+ AtomicFile stateFile = new AtomicFile(file);
+ return stateFile.exists();
+ }
+
private void parseStateLocked(XmlPullParser parser)
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index f5efcaa..56fb73f 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -31,7 +31,7 @@
android:layout_height="wrap_content"
android:textColor="?attr/wallpaperTextColor"
style="@style/widget_label"
- android:letterSpacing="0.15"
+ android:letterSpacing="0.05"
android:gravity="center"
/>
<TextView android:id="@+id/alarm_status"
@@ -42,7 +42,7 @@
android:drawableTint="?attr/wallpaperTextColorSecondary"
android:drawableTintMode="src_in"
android:textColor="?attr/wallpaperTextColorSecondary"
- android:letterSpacing="0.15"
+ android:letterSpacing="0.05"
style="@style/widget_label"
android:layout_marginStart="6dp"
android:gravity="center"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9793615..c8e6021 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -801,6 +801,9 @@
<!-- The shortest-edge size of the expanded PiP. -->
<dimen name="pip_expanded_shortest_edge_size">160dp</dimen>
+ <!-- The additional offset to apply to the IME animation to account for the input field. -->
+ <dimen name="pip_ime_offset">48dp</dimen>
+
<!-- The padding between actions in the PiP in landscape Note that the PiP does not reflect
the configuration of the device, so we can't use -land resources. -->
<dimen name="pip_between_action_padding_land">8dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 278fdc3..d3be19d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -27,6 +27,7 @@
import android.app.IActivityManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -122,6 +123,7 @@
private boolean mIsMinimized;
private boolean mIsImeShowing;
private int mImeHeight;
+ private int mImeOffset;
private float mSavedSnapFraction = -1f;
private boolean mSendingHoverAccessibilityEvents;
private boolean mMovementWithinMinimize;
@@ -192,8 +194,11 @@
};
mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mMenuController,
mSnapAlgorithm, mFlingAnimationUtils);
- mExpandedShortestEdgeSize = context.getResources().getDimensionPixelSize(
+
+ Resources res = context.getResources();
+ mExpandedShortestEdgeSize = res.getDimensionPixelSize(
R.dimen.pip_expanded_shortest_edge_size);
+ mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
// Register the listener for input consumer touch events
inputConsumerController.setTouchListener(this::handleTouchEvent);
@@ -265,7 +270,6 @@
mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds,
mIsImeShowing ? mImeHeight : 0);
-
// If this is from an IME adjustment, then we should move the PiP so that it is not occluded
// by the IME
if (fromImeAdjustement) {
@@ -278,18 +282,22 @@
? expandedMovementBounds
: normalMovementBounds;
if (mIsImeShowing) {
- // IME visible
+ // IME visible, apply the IME offset if the space allows for it
+ final int imeOffset = toMovementBounds.bottom - Math.max(toMovementBounds.top,
+ toMovementBounds.bottom - mImeOffset);
if (bounds.top == mMovementBounds.bottom) {
// If the PIP is currently resting on top of the IME, then adjust it with
- // the hiding IME
- bounds.offsetTo(bounds.left, toMovementBounds.bottom);
+ // the showing IME
+ bounds.offsetTo(bounds.left, toMovementBounds.bottom - imeOffset);
} else {
- bounds.offset(0, Math.min(0, toMovementBounds.bottom - bounds.top));
+ bounds.offset(0, Math.min(0, toMovementBounds.bottom - imeOffset
+ - bounds.top));
}
} else {
// IME hidden
- if (bounds.top == mMovementBounds.bottom) {
- // If the PIP is resting on top of the IME, then adjust it with the hiding IME
+ if (bounds.top >= (mMovementBounds.bottom - mImeOffset)) {
+ // If the PIP is resting on top of the IME, then adjust it with the hiding
+ // IME
bounds.offsetTo(bounds.left, toMovementBounds.bottom);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 81ec6a7..4e728f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -19,13 +19,14 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.os.SystemProperties;
import android.service.quicksettings.Tile;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Switch;
-
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.net.DataUsageController;
@@ -38,7 +39,6 @@
import com.android.systemui.qs.CellTileView;
import com.android.systemui.qs.CellTileView.SignalIcon;
import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.SignalTileView;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
@@ -46,8 +46,17 @@
/** Quick settings tile: Cellular **/
public class CellularTile extends QSTileImpl<SignalState> {
- static final Intent CELLULAR_SETTINGS = new Intent().setComponent(new ComponentName(
- "com.android.settings", "com.android.settings.Settings$DataUsageSummaryActivity"));
+ private static final ComponentName CELLULAR_SETTING_COMPONENT = new ComponentName(
+ "com.android.settings", "com.android.settings.Settings$DataUsageSummaryActivity");
+ private static final ComponentName DATA_PLAN_CELLULAR_COMPONENT = new ComponentName(
+ "com.android.settings", "com.android.settings.Settings$DataPlanUsageSummaryActivity");
+
+ private static final Intent CELLULAR_SETTINGS =
+ new Intent().setComponent(CELLULAR_SETTING_COMPONENT);
+ private static final Intent DATA_PLAN_CELLULAR_SETTINGS =
+ new Intent().setComponent(DATA_PLAN_CELLULAR_COMPONENT);
+
+ private static final String ENABLE_SETTINGS_DATA_PLAN = "enable.settings.data.plan";
private final NetworkController mController;
private final DataUsageController mDataController;
@@ -90,7 +99,7 @@
@Override
public Intent getLongClickIntent() {
- return CELLULAR_SETTINGS;
+ return getCellularSettingIntent(mContext);
}
@Override
@@ -103,7 +112,9 @@
if (mDataController.isMobileDataSupported()) {
showDetail(true);
} else {
- mActivityStarter.postStartActivityDismissingKeyguard(CELLULAR_SETTINGS, 0);
+ mActivityStarter
+ .postStartActivityDismissingKeyguard(getCellularSettingIntent(mContext),
+ 0 /* delay */);
}
}
@@ -240,7 +251,28 @@
public void setMobileDataEnabled(boolean enabled) {
mDetailAdapter.setMobileDataEnabled(enabled);
}
- };
+ }
+
+ static Intent getCellularSettingIntent(Context context) {
+ // TODO(b/62349208): We should replace feature flag check below with data plans
+ // availability check. If the data plans are available we display the data plans usage
+ // summary otherwise we display data usage summary without data plans.
+ boolean isDataPlanFeatureEnabled =
+ SystemProperties.getBoolean(ENABLE_SETTINGS_DATA_PLAN, false /* default */);
+ context.getPackageManager()
+ .setComponentEnabledSetting(
+ DATA_PLAN_CELLULAR_COMPONENT,
+ isDataPlanFeatureEnabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ context.getPackageManager()
+ .setComponentEnabledSetting(
+ CELLULAR_SETTING_COMPONENT,
+ isDataPlanFeatureEnabled ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ return isDataPlanFeatureEnabled ? DATA_PLAN_CELLULAR_SETTINGS : CELLULAR_SETTINGS;
+ }
private final class CellularDetailAdapter implements DetailAdapter {
@@ -258,7 +290,7 @@
@Override
public Intent getSettingsIntent() {
- return CELLULAR_SETTINGS;
+ return getCellularSettingIntent(mContext);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index b796451..8b62beb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -14,18 +14,16 @@
package com.android.systemui.qs.tiles;
-import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.service.quicksettings.Tile;
import android.widget.Switch;
-
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.DataSaverController;
@@ -57,9 +55,8 @@
@Override
public Intent getLongClickIntent() {
- return CellularTile.CELLULAR_SETTINGS;
+ return CellularTile.getCellularSettingIntent(mContext);
}
-
@Override
protected void handleClick() {
if (mState.value
@@ -73,12 +70,7 @@
dialog.setTitle(com.android.internal.R.string.data_saver_enable_title);
dialog.setMessage(com.android.internal.R.string.data_saver_description);
dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- toggleDataSaver();
- }
- });
+ (OnClickListener) (dialogInterface, which) -> toggleDataSaver());
dialog.setNegativeButton(com.android.internal.R.string.cancel, null);
dialog.setShowForAllUsers(true);
dialog.show();
@@ -126,4 +118,4 @@
public void onDataSaverChanged(boolean isDataSaving) {
refreshState(isDataSaving);
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 8046250..7bc1a39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -1974,6 +1974,9 @@
if (mGuts != null) {
mGuts.setActualHeight(height);
}
+ if (mMenuRow.getMenuView() != null) {
+ mMenuRow.onHeightUpdate();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index fddc446..99b4b07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -431,7 +431,7 @@
if (mParent == null || mMenuItems.size() == 0 || mMenuContainer == null) {
return;
}
- int parentHeight = mParent.getCollapsedHeight();
+ int parentHeight = mParent.getActualHeight();
float translationY;
if (parentHeight < mVertSpaceForIcons) {
translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index e5f68ad..1889806 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -244,7 +244,7 @@
boolean aboveShelf = ViewState.getFinalTranslationZ(row) > baseZHeight;
boolean isLastChild = child == lastChild;
float rowTranslationY = row.getTranslationY();
- if (isLastChild || aboveShelf || backgroundForceHidden) {
+ if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
notificationClipEnd = shelfStart + getIntrinsicHeight();
} else {
notificationClipEnd = shelfStart - mPaddingBetweenElements;
diff --git a/telephony/java/android/telephony/Telephony.java b/telephony/java/android/telephony/Telephony.java
index 765c73b..216d28c 100644
--- a/telephony/java/android/telephony/Telephony.java
+++ b/telephony/java/android/telephony/Telephony.java
@@ -2788,6 +2788,13 @@
public static final String USER_VISIBLE = "user_visible";
/**
+ * Is the user allowed to edit this APN?
+ * <p>Type: INTEGER (boolean) </p>
+ * @hide
+ */
+ public static final String USER_EDITABLE = "user_editable";
+
+ /**
* Following are possible values for the EDITED field
* @hide
*/
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 0d4359e..8075e17 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -41,6 +41,8 @@
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
+import dalvik.system.CloseGuard;
+
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -668,15 +670,21 @@
* Most p2p operations require a Channel as an argument. An instance of Channel is obtained
* by doing a call on {@link #initialize}
*/
- public static class Channel {
- Channel(Context context, Looper looper, ChannelListener l, Binder binder) {
+ public static class Channel implements AutoCloseable {
+ /** @hide */
+ public Channel(Context context, Looper looper, ChannelListener l, Binder binder,
+ WifiP2pManager p2pManager) {
mAsyncChannel = new AsyncChannel();
mHandler = new P2pHandler(looper);
mChannelListener = l;
mContext = context;
mBinder = binder;
+ mP2pManager = p2pManager;
+
+ mCloseGuard.open("close");
}
private final static int INVALID_LISTENER_KEY = 0;
+ private final WifiP2pManager mP2pManager;
private ChannelListener mChannelListener;
private ServiceResponseListener mServRspListener;
private DnsSdServiceResponseListener mDnsSdServRspListener;
@@ -686,6 +694,41 @@
private final Object mListenerMapLock = new Object();
private int mListenerKey = 0;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ /**
+ * Close the current P2P connection and indicate to the P2P service that connections
+ * created by the app can be removed.
+ */
+ public void close() {
+ if (mP2pManager == null) {
+ Log.w(TAG, "Channel.close(): Null mP2pManager!?");
+ } else {
+ try {
+ mP2pManager.mService.close(mBinder);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ mAsyncChannel.disconnect();
+ mCloseGuard.close();
+ }
+
+ /** @hide */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
/* package */ final Binder mBinder;
private AsyncChannel mAsyncChannel;
@@ -913,11 +956,12 @@
Messenger messenger, Binder binder) {
if (messenger == null) return null;
- Channel c = new Channel(srcContext, srcLooper, listener, binder);
+ Channel c = new Channel(srcContext, srcLooper, listener, binder, this);
if (c.mAsyncChannel.connectSync(srcContext, c.mHandler, messenger)
== AsyncChannel.STATUS_SUCCESSFUL) {
return c;
} else {
+ c.close();
return null;
}
}
@@ -1422,24 +1466,6 @@
}
/**
- * Close the current P2P connection and clean-up any configuration requested by the
- * current app. Takes same action as taken when the app dies.
- *
- * @param c is the channel created at {@link #initialize}
- *
- * @hide
- */
- public void close(Channel c) {
- try {
- if (c != null) {
- mService.close(c.mBinder);
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Get a handover request message for use in WFA NFC Handover transfer.
* @hide
*/
diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk
index 8dc244f..afab1a3 100644
--- a/wifi/tests/Android.mk
+++ b/wifi/tests/Android.mk
@@ -50,6 +50,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
+ core-test-rules \
guava \
mockito-target-minus-junit4 \
frameworks-base-testutils \
diff --git a/wifi/tests/src/android/net/wifi/p2p/WifiP2pManagerTest.java b/wifi/tests/src/android/net/wifi/p2p/WifiP2pManagerTest.java
new file mode 100644
index 0000000..1e8382f
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/p2p/WifiP2pManagerTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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 android.net.wifi.p2p;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.test.TestLooper;
+
+import libcore.junit.util.ResourceLeakageDetector;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test harness for WifiP2pManager.
+ */
+public class WifiP2pManagerTest {
+ private WifiP2pManager mDut;
+ private TestLooper mTestLooper;
+
+ @Mock
+ public Context mContextMock;
+ @Mock
+ IWifiP2pManager mP2pServiceMock;
+
+ @Rule
+ public ResourceLeakageDetector.LeakageDetectorRule leakageDetectorRule =
+ ResourceLeakageDetector.getRule();
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mDut = new WifiP2pManager(mP2pServiceMock);
+ mTestLooper = new TestLooper();
+ }
+
+ /**
+ * Validate that on finalize we close the channel and flag a resource leakage.
+ */
+ @Test
+ public void testChannelFinalize() throws Exception {
+ WifiP2pManager.Channel channel = new WifiP2pManager.Channel(mContextMock,
+ mTestLooper.getLooper(), null, null, mDut);
+
+ leakageDetectorRule.assertUnreleasedResourceCount(channel, 1);
+ }
+
+ /**
+ * Validate that when close is called on a channel it frees up resources (i.e. don't
+ * get flagged again on finalize).
+ */
+ @Test
+ public void testChannelClose() throws Exception {
+ WifiP2pManager.Channel channel = new WifiP2pManager.Channel(mContextMock,
+ mTestLooper.getLooper(), null, null, mDut);
+
+ channel.close();
+ verify(mP2pServiceMock).close(any());
+
+ leakageDetectorRule.assertUnreleasedResourceCount(channel, 0);
+ }
+}