Merge "Avoid race condition when broadcasting device list changed." into pi-dev
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a591eaf..54cb095 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4922,7 +4922,8 @@
CharSequence[] replyText = mN.extras.getCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY);
if (!p.ambient && validRemoteInput && replyText != null
- && replyText.length > 0 && !TextUtils.isEmpty(replyText[0])) {
+ && replyText.length > 0 && !TextUtils.isEmpty(replyText[0])
+ && p.maxRemoteInputHistory > 0) {
boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
big.setViewVisibility(R.id.notification_material_reply_text_1_container,
@@ -4937,13 +4938,15 @@
ColorStateList.valueOf(
isColorized() ? getPrimaryTextColor() : resolveContrastColor()));
- if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1])) {
+ if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1])
+ && p.maxRemoteInputHistory > 1) {
big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
big.setTextViewText(R.id.notification_material_reply_text_2,
processTextSpans(replyText[1]));
setTextViewColorSecondary(big, R.id.notification_material_reply_text_2);
- if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2])) {
+ if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2])
+ && p.maxRemoteInputHistory > 2) {
big.setViewVisibility(
R.id.notification_material_reply_text_3, View.VISIBLE);
big.setTextViewText(R.id.notification_material_reply_text_3,
@@ -5106,7 +5109,13 @@
return null;
}
- return applyStandardTemplateWithActions(getBigBaseLayoutResource(), null /* result */);
+ // We only want at most a single remote input history to be shown here, otherwise
+ // the content would become squished.
+ StandardTemplateParams p = mParams.reset().fillTextsFrom(this)
+ .setMaxRemoteInputHistory(1);
+ return applyStandardTemplateWithActions(getBigBaseLayoutResource(),
+ p,
+ null /* result */);
}
/**
@@ -5975,6 +5984,12 @@
* object.
*/
public static abstract class Style {
+
+ /**
+ * The number of items allowed simulatanously in the remote input history.
+ * @hide
+ */
+ static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3;
private CharSequence mBigContentTitle;
/**
@@ -7376,7 +7391,14 @@
return messages;
}
- static Message getMessageFromBundle(Bundle bundle) {
+ /**
+ * @return The message that is stored in the bundle or null if the message couldn't be
+ * resolved.
+ *
+ * @hide
+ */
+ @Nullable
+ public static Message getMessageFromBundle(Bundle bundle) {
try {
if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
return null;
@@ -7434,6 +7456,11 @@
* @see Notification#bigContentView
*/
public static class InboxStyle extends Style {
+
+ /**
+ * The number of lines of remote input history allowed until we start reducing lines.
+ */
+ private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1;
private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
public InboxStyle() {
@@ -7533,6 +7560,28 @@
if (mBuilder.mActions.size() > 0) {
maxRows--;
}
+ CharSequence[] remoteInputHistory = mBuilder.mN.extras.getCharSequenceArray(
+ EXTRA_REMOTE_INPUT_HISTORY);
+ if (remoteInputHistory != null
+ && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
+ // Let's remove some messages to make room for the remote input history.
+ // 1 is always able to fit, but let's remove them if they are 2 or 3
+ int numRemoteInputs = Math.min(remoteInputHistory.length,
+ MAX_REMOTE_INPUT_HISTORY_LINES);
+ int totalNumRows = mTexts.size() + numRemoteInputs
+ - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION;
+ if (totalNumRows > maxRows) {
+ int overflow = totalNumRows - maxRows;
+ if (mTexts.size() > maxRows) {
+ // Heuristic: if the Texts don't fit anyway, we'll rather drop the last
+ // few messages, even with the remote input
+ maxRows -= overflow;
+ } else {
+ // otherwise we drop the first messages
+ i = overflow;
+ }
+ }
+ }
while (i < mTexts.size() && i < maxRows) {
CharSequence str = mTexts.get(i);
if (!TextUtils.isEmpty(str)) {
@@ -9603,6 +9652,7 @@
CharSequence title;
CharSequence text;
CharSequence headerTextSecondary;
+ int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
boolean hideLargeIcon;
boolean hideReplyIcon;
@@ -9612,6 +9662,7 @@
title = null;
text = null;
headerTextSecondary = null;
+ maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
return this;
}
@@ -9663,5 +9714,15 @@
this.text = b.processLegacyText(text, ambient);
return this;
}
+
+ /**
+ * Set the maximum lines of remote input history lines allowed.
+ * @param maxRemoteInputHistory The number of lines.
+ * @return The builder for method chaining.
+ */
+ public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) {
+ this.maxRemoteInputHistory = maxRemoteInputHistory;
+ return this;
+ }
}
}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 2306e5f..16c1f6d 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -21,18 +21,15 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
-import android.graphics.Paint.Align;
-import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.Keyboard.Key;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.GestureDetector;
@@ -662,11 +659,13 @@
invalidateAllKeys();
mKeyboardChanged = false;
}
- final Canvas canvas = mCanvas;
- canvas.clipRect(mDirtyRect, Op.REPLACE);
if (mKeyboard == null) return;
+ mCanvas.save();
+ final Canvas canvas = mCanvas;
+ canvas.clipRect(mDirtyRect);
+
final Paint paint = mPaint;
final Drawable keyBackground = mKeyBackground;
final Rect clipRegion = mClipRegion;
@@ -758,7 +757,7 @@
paint.setColor(0xFF00FF00);
canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
}
-
+ mCanvas.restore();
mDrawPending = false;
mDirtyRect.setEmpty();
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c3b8f39..c5cb1f5 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -238,6 +238,14 @@
public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL";
/**
+ * Key for passing a {@link android.net.captiveportal.CaptivePortalProbeSpec} to the captive
+ * portal login activity.
+ * {@hide}
+ */
+ public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC =
+ "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
+
+ /**
* Key for passing a user agent string to the captive portal login activity.
* {@hide}
*/
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 15a0ee5..d75d439 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -85,6 +85,21 @@
private static final long HANDLE_MAGIC = 0xcafed00dL;
private static final int HANDLE_MAGIC_SIZE = 32;
+ // A boolean to control how getAllByName()/getByName() behaves in the face
+ // of Private DNS.
+ //
+ // When true, these calls will request that DNS resolution bypass any
+ // Private DNS that might otherwise apply. Use of this feature is restricted
+ // and permission checks are made by netd (attempts to bypass Private DNS
+ // without appropriate permission are silently turned into vanilla DNS
+ // requests). This only affects DNS queries made using this network object.
+ //
+ // It it not parceled to receivers because (a) it can be set or cleared at
+ // anytime and (b) receivers should be explicit about attempts to bypass
+ // Private DNS so that the intent of the code is easily determined and
+ // code search audits are possible.
+ private boolean mPrivateDnsBypass = false;
+
/**
* @hide
*/
@@ -108,7 +123,7 @@
* @throws UnknownHostException if the address lookup fails.
*/
public InetAddress[] getAllByName(String host) throws UnknownHostException {
- return InetAddress.getAllByNameOnNet(host, netId);
+ return InetAddress.getAllByNameOnNet(host, getNetIdForResolv());
}
/**
@@ -122,7 +137,32 @@
* if the address lookup fails.
*/
public InetAddress getByName(String host) throws UnknownHostException {
- return InetAddress.getByNameOnNet(host, netId);
+ return InetAddress.getByNameOnNet(host, getNetIdForResolv());
+ }
+
+ /**
+ * Specify whether or not Private DNS should be bypassed when attempting
+ * to use {@link getAllByName()}/{@link getByName()} methods on the given
+ * instance for hostname resolution.
+ *
+ * @hide
+ */
+ public void setPrivateDnsBypass(boolean bypass) {
+ mPrivateDnsBypass = bypass;
+ }
+
+ /**
+ * Returns a netid marked with the Private DNS bypass flag.
+ *
+ * This flag must be kept in sync with the NETID_USE_LOCAL_NAMESERVERS flag
+ * in system/netd/include/NetdClient.h.
+ *
+ * @hide
+ */
+ public int getNetIdForResolv() {
+ return mPrivateDnsBypass
+ ? (int) (0x80000000L | (long) netId) // Non-portable DNS resolution flag.
+ : netId;
}
/**
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index 66cdc99..ddf63ca 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -84,6 +84,10 @@
* @return true if the transaction was successful.
*/
public boolean requestTime(String host, int timeout, Network network) {
+ // This flag only affects DNS resolution and not other socket semantics,
+ // therefore it's safe to set unilaterally rather than take more
+ // defensive measures like making a copy.
+ network.setPrivateDnsBypass(true);
InetAddress address = null;
try {
address = network.getByName(host);
diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
index 614c0b8..1634694 100644
--- a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
+++ b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
@@ -16,6 +16,8 @@
package android.net.captiveportal;
+import android.annotation.Nullable;
+
/**
* Result of calling isCaptivePortal().
* @hide
@@ -23,6 +25,7 @@
public final class CaptivePortalProbeResult {
public static final int SUCCESS_CODE = 204;
public static final int FAILED_CODE = 599;
+ public static final int PORTAL_CODE = 302;
public static final CaptivePortalProbeResult FAILED = new CaptivePortalProbeResult(FAILED_CODE);
public static final CaptivePortalProbeResult SUCCESS =
@@ -32,15 +35,23 @@
public final String redirectUrl; // Redirect destination returned from Internet probe.
public final String detectUrl; // URL where a 204 response code indicates
// captive portal has been appeased.
+ @Nullable
+ public final CaptivePortalProbeSpec probeSpec;
public CaptivePortalProbeResult(int httpResponseCode) {
this(httpResponseCode, null, null);
}
public CaptivePortalProbeResult(int httpResponseCode, String redirectUrl, String detectUrl) {
+ this(httpResponseCode, redirectUrl, detectUrl, null);
+ }
+
+ public CaptivePortalProbeResult(int httpResponseCode, String redirectUrl, String detectUrl,
+ CaptivePortalProbeSpec probeSpec) {
mHttpResponseCode = httpResponseCode;
this.redirectUrl = redirectUrl;
this.detectUrl = detectUrl;
+ this.probeSpec = probeSpec;
}
public boolean isSuccessful() {
diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
new file mode 100644
index 0000000..57a926a
--- /dev/null
+++ b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2018 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.captiveportal;
+
+import static android.net.captiveportal.CaptivePortalProbeResult.PORTAL_CODE;
+import static android.net.captiveportal.CaptivePortalProbeResult.SUCCESS_CODE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/** @hide */
+public abstract class CaptivePortalProbeSpec {
+ public static final String HTTP_LOCATION_HEADER_NAME = "Location";
+
+ private static final String TAG = CaptivePortalProbeSpec.class.getSimpleName();
+ private static final String REGEX_SEPARATOR = "@@/@@";
+ private static final String SPEC_SEPARATOR = "@@,@@";
+
+ private final String mEncodedSpec;
+ private final URL mUrl;
+
+ CaptivePortalProbeSpec(String encodedSpec, URL url) {
+ mEncodedSpec = encodedSpec;
+ mUrl = url;
+ }
+
+ /**
+ * Parse a {@link CaptivePortalProbeSpec} from a {@link String}.
+ *
+ * <p>The valid format is a URL followed by two regular expressions, each separated by "@@/@@".
+ * @throws MalformedURLException The URL has invalid format for {@link URL#URL(String)}.
+ * @throws ParseException The string is empty, does not match the above format, or a regular
+ * expression is invalid for {@link Pattern#compile(String)}.
+ */
+ @NonNull
+ public static CaptivePortalProbeSpec parseSpec(String spec) throws ParseException,
+ MalformedURLException {
+ if (TextUtils.isEmpty(spec)) {
+ throw new ParseException("Empty probe spec", 0 /* errorOffset */);
+ }
+
+ String[] splits = TextUtils.split(spec, REGEX_SEPARATOR);
+ if (splits.length != 3) {
+ throw new ParseException("Probe spec does not have 3 parts", 0 /* errorOffset */);
+ }
+
+ final int statusRegexPos = splits[0].length() + REGEX_SEPARATOR.length();
+ final int locationRegexPos = statusRegexPos + splits[1].length() + REGEX_SEPARATOR.length();
+ final Pattern statusRegex = parsePatternIfNonEmpty(splits[1], statusRegexPos);
+ final Pattern locationRegex = parsePatternIfNonEmpty(splits[2], locationRegexPos);
+
+ return new RegexMatchProbeSpec(spec, new URL(splits[0]), statusRegex, locationRegex);
+ }
+
+ @Nullable
+ private static Pattern parsePatternIfNonEmpty(String pattern, int pos) throws ParseException {
+ if (TextUtils.isEmpty(pattern)) {
+ return null;
+ }
+ try {
+ return Pattern.compile(pattern);
+ } catch (PatternSyntaxException e) {
+ throw new ParseException(
+ String.format("Invalid status pattern [%s]: %s", pattern, e),
+ pos /* errorOffset */);
+ }
+ }
+
+ /**
+ * Parse a {@link CaptivePortalProbeSpec} from a {@link String}, or return a fallback spec
+ * based on the status code of the provided URL if the spec cannot be parsed.
+ */
+ @Nullable
+ public static CaptivePortalProbeSpec parseSpecOrNull(@Nullable String spec) {
+ if (spec != null) {
+ try {
+ return parseSpec(spec);
+ } catch (ParseException | MalformedURLException e) {
+ Log.e(TAG, "Invalid probe spec: " + spec, e);
+ // Fall through
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Parse a config String to build an array of {@link CaptivePortalProbeSpec}.
+ *
+ * <p>Each spec is separated by @@,@@ and follows the format for {@link #parseSpec(String)}.
+ * <p>This method does not throw but ignores any entry that could not be parsed.
+ */
+ public static CaptivePortalProbeSpec[] parseCaptivePortalProbeSpecs(String settingsVal) {
+ List<CaptivePortalProbeSpec> specs = new ArrayList<>();
+ if (settingsVal != null) {
+ for (String spec : TextUtils.split(settingsVal, SPEC_SEPARATOR)) {
+ try {
+ specs.add(parseSpec(spec));
+ } catch (ParseException | MalformedURLException e) {
+ Log.e(TAG, "Invalid probe spec: " + spec, e);
+ }
+ }
+ }
+
+ if (specs.isEmpty()) {
+ Log.e(TAG, String.format("could not create any validation spec from %s", settingsVal));
+ }
+ return specs.toArray(new CaptivePortalProbeSpec[specs.size()]);
+ }
+
+ /**
+ * Get the probe result from HTTP status and location header.
+ */
+ public abstract CaptivePortalProbeResult getResult(int status, @Nullable String locationHeader);
+
+ public String getEncodedSpec() {
+ return mEncodedSpec;
+ }
+
+ public URL getUrl() {
+ return mUrl;
+ }
+
+ /**
+ * Implementation of {@link CaptivePortalProbeSpec} that is based on configurable regular
+ * expressions for the HTTP status code and location header (if any). Matches indicate that
+ * the page is not a portal.
+ * This probe cannot fail: it always returns SUCCESS_CODE or PORTAL_CODE
+ */
+ private static class RegexMatchProbeSpec extends CaptivePortalProbeSpec {
+ @Nullable
+ final Pattern mStatusRegex;
+ @Nullable
+ final Pattern mLocationHeaderRegex;
+
+ RegexMatchProbeSpec(
+ String spec, URL url, Pattern statusRegex, Pattern locationHeaderRegex) {
+ super(spec, url);
+ mStatusRegex = statusRegex;
+ mLocationHeaderRegex = locationHeaderRegex;
+ }
+
+ @Override
+ public CaptivePortalProbeResult getResult(int status, String locationHeader) {
+ final boolean statusMatch = safeMatch(String.valueOf(status), mStatusRegex);
+ final boolean locationMatch = safeMatch(locationHeader, mLocationHeaderRegex);
+ final int returnCode = statusMatch && locationMatch ? SUCCESS_CODE : PORTAL_CODE;
+ return new CaptivePortalProbeResult(
+ returnCode, locationHeader, getUrl().toString(), this);
+ }
+ }
+
+ private static boolean safeMatch(@Nullable String value, @Nullable Pattern pattern) {
+ // No value is a match ("no location header" passes the location rule for non-redirects)
+ return pattern == null || TextUtils.isEmpty(value) || pattern.matcher(value).matches();
+ }
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 619ec23..221abed 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -245,8 +245,10 @@
* New in version 31:
* - New cellular network types.
* - Deferred job metrics.
+ * New in version 32:
+ * - Ambient display properly output in data dump.
*/
- static final int CHECKIN_VERSION = 31;
+ static final int CHECKIN_VERSION = 32;
/**
* Old version, we hit 9 and ran out of room, need to remove.
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 5e23932..3017f25 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -45,6 +45,7 @@
in String[] disallowedPackages);
UserInfo createRestrictedProfile(String name, int parentUserHandle);
void setUserEnabled(int userHandle);
+ void setUserAdmin(int userId);
void evictCredentialEncryptionKey(int userHandle);
boolean removeUser(int userHandle);
boolean removeUserEvenWhenDisallowed(int userHandle);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 11afd21..b591851 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2087,12 +2087,33 @@
* Also ephemeral users can be disabled to indicate that their removal is in progress and they
* shouldn't be re-entered. Therefore ephemeral users should not be re-enabled once disabled.
*
- * @param userHandle the id of the profile to enable
+ * @param userId the id of the profile to enable
* @hide
*/
- public void setUserEnabled(@UserIdInt int userHandle) {
+ public void setUserEnabled(@UserIdInt int userId) {
try {
- mService.setUserEnabled(userHandle);
+ mService.setUserEnabled(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Assigns admin privileges to the user, if such a user exists.
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} and
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permissions.
+ *
+ * @param userHandle the id of the user to become admin
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.MANAGE_USERS
+ })
+ public void setUserAdmin(@UserIdInt int userHandle) {
+ try {
+ mService.setUserAdmin(userHandle);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 4d238c0..ff5502f 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -17,6 +17,7 @@
package android.os;
import android.annotation.Nullable;
+import android.content.ContentResolver;
import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
import android.hardware.vibrator.V1_2.Effect;
@@ -275,7 +276,12 @@
if (uris[i] == null) {
continue;
}
- if (Uri.parse(uris[i]).equals(uri)) {
+ ContentResolver cr = context.getContentResolver();
+ Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
+ if (mappedUri == null) {
+ continue;
+ }
+ if (mappedUri.equals(uri)) {
return get(RINGTONES[i]);
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bbbcbbb..0510418 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7114,6 +7114,35 @@
public static final String UI_NIGHT_MODE = "ui_night_mode";
/**
+ * The current device UI theme mode effect SystemUI and Launcher.<br/>
+ * <b>Values:</b><br/>
+ * 0 - The mode that theme will controlled by wallpaper color.<br/>
+ * 1 - The mode that will always light theme.<br/>
+ * 2 - The mode that will always dark theme.<br/>
+ *
+ * @hide
+ */
+ public static final String THEME_MODE = "theme_mode";
+
+ /**
+ * THEME_MODE value for wallpaper mode.
+ * @hide
+ */
+ public static final int THEME_MODE_WALLPAPER = 0;
+
+ /**
+ * THEME_MODE value for light theme mode.
+ * @hide
+ */
+ public static final int THEME_MODE_LIGHT = 1;
+
+ /**
+ * THEME_MODE value for dark theme mode.
+ * @hide
+ */
+ public static final int THEME_MODE_DARK = 2;
+
+ /**
* Whether screensavers are enabled.
* @hide
*/
@@ -10253,6 +10282,15 @@
"captive_portal_other_fallback_urls";
/**
+ * A list of captive portal detection specifications used in addition to the fallback URLs.
+ * Each spec has the format url@@/@@statusCodeRegex@@/@@contentRegex. Specs are separated
+ * by "@@,@@".
+ * @hide
+ */
+ public static final String CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS =
+ "captive_portal_fallback_probe_specs";
+
+ /**
* Whether to use HTTPS for network validation. This is enabled by default and the setting
* needs to be set to 0 to disable it. This setting is a misnomer because captive portals
* don't actually use HTTPS, but it's consistent with the other settings.
@@ -11566,6 +11604,13 @@
/** @hide */ public static final int EMULATE_DISPLAY_CUTOUT_ON = 1;
/**
+ * A colon separated list of keys for Settings Slices.
+ *
+ * @hide
+ */
+ public static final String BLOCKED_SLICES = "blocked_slices";
+
+ /**
* Defines global zen mode. ZEN_MODE_OFF, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
* or ZEN_MODE_NO_INTERRUPTIONS.
*
diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java
index 537065d..1d5c6f1 100644
--- a/core/java/android/webkit/SslErrorHandler.java
+++ b/core/java/android/webkit/SslErrorHandler.java
@@ -36,6 +36,10 @@
/**
* Proceed with the SSL certificate.
+ * <p>
+ * It is not recommended to proceed past SSL errors and this method should
+ * generally not be used; see {@link WebViewClient#onReceivedSslError} for
+ * more information.
*/
public void proceed() {}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 4aa1c4a..95fe963 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -113,6 +113,20 @@
* the new WebView as an argument. If the host application chooses not to
* honor the request, it should return {@code false} from this method. The default
* implementation of this method does nothing and hence returns {@code false}.
+ * <p>
+ * Applications should typically not allow windows to be created when the
+ * {@code isUserGesture} flag is false, as this may be an unwanted popup.
+ * <p>
+ * Applications should be careful how they display the new window: don't simply
+ * overlay it over the existing WebView as this may mislead the user about which
+ * site they are viewing. If your application displays the URL of the main page,
+ * make sure to also display the URL of the new window in a similar fashion. If
+ * your application does not display URLs, consider disallowing the creation of
+ * new windows entirely.
+ * <p class="note"><b>Note:</b> There is no trustworthy way to tell which page
+ * requested the new window: the request might originate from a third-party iframe
+ * inside the WebView.
+ *
* @param view The WebView from which the request for a new window
* originated.
* @param isDialog {@code true} if the new window should be a dialog, rather than
@@ -149,6 +163,11 @@
* from the view system if necessary. At this point, WebCore has stopped
* any loading in this window and has removed any cross-scripting ability
* in javascript.
+ * <p>
+ * As with {@link #onCreateWindow}, the application should ensure that any
+ * URL or security indicator displayed is updated so that the user can tell
+ * that the page they were interacting with has been closed.
+ *
* @param window The WebView that needs to be closed.
*/
public void onCloseWindow(WebView window) {}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 10748ac..d7f1d6e 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1019,11 +1019,21 @@
* If {@code null}, defaults to 'text/html'.
* <p>
* The 'data' scheme URL formed by this method uses the default US-ASCII
- * charset. If you need need to set a different charset, you should form a
+ * charset. If you need to set a different charset, you should form a
* 'data' scheme URL which explicitly specifies a charset parameter in the
* mediatype portion of the URL and call {@link #loadUrl(String)} instead.
* Note that the charset obtained from the mediatype portion of a data URL
* always overrides that specified in the HTML or XML document itself.
+ * <p>
+ * Content loaded using this method will have a {@code window.origin} value
+ * of {@code "null"}. This must not be considered to be a trusted origin
+ * by the application or by any JavaScript code running inside the WebView
+ * (for example, event sources in DOM event handlers or web messages),
+ * because malicious content can also create frames with a null origin. If
+ * you need to identify the main frame's origin in a trustworthy way, you
+ * should use {@link #loadDataWithBaseURL(String,String,String,String,String)
+ * loadDataWithBaseURL()} with a valid HTTP or HTTPS base URL to set the
+ * origin.
*
* @param data a String of data in the given encoding
* @param mimeType the MIME type of the data, e.g. 'text/html'.
@@ -1057,6 +1067,15 @@
* <p>
* Note that the baseUrl is sent in the 'Referer' HTTP header when
* requesting subresources (images, etc.) of the page loaded using this method.
+ * <p>
+ * If a valid HTTP or HTTPS base URL is not specified in {@code baseUrl}, then
+ * content loaded using this method will have a {@code window.origin} value
+ * of {@code "null"}. This must not be considered to be a trusted origin
+ * by the application or by any JavaScript code running inside the WebView
+ * (for example, event sources in DOM event handlers or web messages),
+ * because malicious content can also create frames with a null origin. If
+ * you need to identify the main frame's origin in a trustworthy way, you
+ * should use a valid HTTP or HTTPS base URL to set the origin.
*
* @param baseUrl the URL to use as the page's base URL. If {@code null} defaults to
* 'about:blank'.
@@ -2056,6 +2075,13 @@
* <p>
* A target origin can be set as a wildcard ("*"). However this is not recommended.
* See the page above for security issues.
+ * <p>
+ * Content loaded via {@link #loadData(String,String,String)} will not have a
+ * valid origin, and thus cannot be sent messages securely. If you need to send
+ * messages using this function, you should use
+ * {@link #loadDataWithBaseURL(String,String,String,String,String)} with a valid
+ * HTTP or HTTPS {@code baseUrl} to define a valid origin that can be used for
+ * messaging.
*
* @param message the WebMessage
* @param targetOrigin the target origin.
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 6df1655..f686b66 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -368,6 +368,15 @@
* handler.proceed(). Note that the decision may be retained for use in
* response to future SSL errors. The default behavior is to cancel the
* load.
+ * <p>
+ * Applications are advised not to prompt the user about SSL errors, as
+ * the user is unlikely to be able to make an informed security decision
+ * and WebView does not provide any UI for showing the details of the
+ * error in a meaningful way.
+ * <p>
+ * Application overrides of this method may display custom error pages or
+ * silently log issues, but it is strongly recommended to always call
+ * handler.cancel() and never allow proceeding past errors.
*
* @param view The WebView that is initiating the callback.
* @param handler An SslErrorHandler object that will handle the user's
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 7116f3a..d135040 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -207,6 +207,8 @@
mAvatarView.setTranslationY(0.0f);
mSenderName.setAlpha(1.0f);
mSenderName.setTranslationY(0.0f);
+ mIsolatedMessage = null;
+ mMessages = null;
sInstancePool.release(MessagingGroup.this);
}
};
@@ -267,6 +269,11 @@
@Override
public int getMeasuredType() {
+ if (mIsolatedMessage != null) {
+ // We only want to show one group if we have an inline image, so let's return shortened
+ // to avoid displaying the other ones.
+ return MEASURED_SHORTENED;
+ }
boolean hasNormal = false;
for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
View child = mMessageContainer.getChildAt(i);
@@ -292,9 +299,6 @@
}
}
}
- if (mMessageContainer.getChildCount() == 0 && mIsolatedMessage != null) {
- return mIsolatedMessage.getMeasuredType();
- }
return MEASURED_NORMAL;
}
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index 9048f87..c7ea781 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -206,7 +206,17 @@
final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
int childTop;
- int childLeft = centerAligned ? left + (right - left) / 2 - mTotalWidth / 2 : 0;
+ int childLeft;
+ if (centerAligned) {
+ childLeft = mPaddingLeft + left + (right - left) / 2 - mTotalWidth / 2;
+ } else {
+ childLeft = mPaddingLeft;
+ int absoluteGravity = Gravity.getAbsoluteGravity(Gravity.START, getLayoutDirection());
+ if (absoluteGravity == Gravity.RIGHT) {
+ childLeft += right - left - mTotalWidth;
+ }
+ }
+
// Where bottom of child should go
final int height = bottom - top;
@@ -216,18 +226,6 @@
final int count = getChildCount();
- final int layoutDirection = getLayoutDirection();
- switch (Gravity.getAbsoluteGravity(Gravity.START, layoutDirection)) {
- case Gravity.RIGHT:
- childLeft += mPaddingLeft + right - left - mTotalWidth;
- break;
-
- case Gravity.LEFT:
- default:
- childLeft += mPaddingLeft;
- break;
- }
-
int start = 0;
int dir = 1;
//In case of RTL, start drawing from the last child.
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 2e8c7f9..471170b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -55,7 +55,7 @@
<!-- Width of the navigation bar when it is placed vertically on the screen in car mode -->
<dimen name="navigation_bar_width_car_mode">96dp</dimen>
<!-- Height of notification icons in the status bar -->
- <dimen name="status_bar_icon_size">24dip</dimen>
+ <dimen name="status_bar_icon_size">22dip</dimen>
<!-- Size of the giant number (unread count) in the notifications -->
<dimen name="status_bar_content_number_size">48sp</dimen>
<!-- Margin at the edge of the screen to ignore touch events for in the windowshade. -->
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 5b7fc6e..fc00f1b 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -132,6 +132,7 @@
Settings.Global.BLE_SCAN_LOW_LATENCY_WINDOW_MS,
Settings.Global.BLE_SCAN_LOW_LATENCY_INTERVAL_MS,
Settings.Global.BLE_SCAN_BACKGROUND_MODE,
+ Settings.Global.BLOCKED_SLICES,
Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
@@ -155,6 +156,7 @@
Settings.Global.CAPTIVE_PORTAL_HTTP_URL,
Settings.Global.CAPTIVE_PORTAL_MODE,
Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS,
+ Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS,
Settings.Global.CAPTIVE_PORTAL_SERVER,
Settings.Global.CAPTIVE_PORTAL_USE_HTTPS,
Settings.Global.CAPTIVE_PORTAL_USER_AGENT,
@@ -591,6 +593,7 @@
Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
Settings.Secure.SKIP_FIRST_USE_HINTS, // candidate?
Settings.Secure.SMS_DEFAULT_APPLICATION,
+ Settings.Secure.THEME_MODE,
Settings.Secure.TRUST_AGENTS_INITIALIZED,
Settings.Secure.TV_INPUT_CUSTOM_LABELS,
Settings.Secure.TV_INPUT_HIDDEN_INPUTS,
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 9828275..a768dd3 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -207,8 +207,9 @@
* after {@link Image#close Image.close()} has been called.
* </p>
* @return the HardwareBuffer associated with this Image or null if this Image doesn't support
- * this feature (e.g. {@link android.media.ImageWriter ImageWriter} or
- * {@link android.media.MediaCodec MediaCodec} don't).
+ * this feature. (Unsupported use cases include Image instances obtained through
+ * {@link android.media.MediaCodec MediaCodec}, and on versions prior to Android P,
+ * {@link android.media.ImageWriter ImageWriter}).
*/
@Nullable
public HardwareBuffer getHardwareBuffer() {
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 397768a..4c0153f 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -20,6 +20,7 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.camera2.utils.SurfaceUtils;
+import android.hardware.HardwareBuffer;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -58,12 +59,17 @@
* </p>
* <p>
* If the application already has an Image from {@link ImageReader}, the
- * application can directly queue this Image into ImageWriter (via
- * {@link #queueInputImage}), potentially with zero buffer copies. For the
- * {@link ImageFormat#PRIVATE PRIVATE} format Images produced by
- * {@link ImageReader}, this is the only way to send Image data to ImageWriter,
- * as the Image data aren't accessible by the application.
+ * application can directly queue this Image into the ImageWriter (via
+ * {@link #queueInputImage}), potentially with zero buffer copies. This
+ * even works if the image format of the ImageWriter is
+ * {@link ImageFormat#PRIVATE PRIVATE}, and prior to Android P is the only
+ * way to enqueue images into such an ImageWriter. Starting in Android P
+ * private images may also be accessed through their hardware buffers
+ * (when available) through the {@link Image#getHardwareBuffer()} method.
+ * Attempting to access the planes of a private image, will return an
+ * empty array.
* </p>
+ * <p>
* Once new input Images are queued into an ImageWriter, it's up to the
* downstream components (e.g. {@link ImageReader} or
* {@link android.hardware.camera2.CameraDevice}) to consume the Images. If the
@@ -257,29 +263,26 @@
* <p>
* If the format of ImageWriter is {@link ImageFormat#PRIVATE PRIVATE} (
* {@link ImageWriter#getFormat()} == {@link ImageFormat#PRIVATE}), the
- * image buffer is inaccessible to the application, and calling this method
- * will result in an {@link IllegalStateException}. Instead, the application
- * should acquire images from some other component (e.g. an
+ * image buffer is accessible to the application only through the hardware
+ * buffer obtained through {@link Image#getHardwareBuffer()}. (On Android
+ * versions prior to P, dequeueing private buffers will cause an
+ * {@link IllegalStateException} to be thrown). Alternatively,
+ * the application can acquire images from some other component (e.g. an
* {@link ImageReader}), and queue them directly to this ImageWriter via the
* {@link ImageWriter#queueInputImage queueInputImage()} method.
* </p>
*
* @return The next available input Image from this ImageWriter.
* @throws IllegalStateException if {@code maxImages} Images are currently
- * dequeued, or the ImageWriter format is
- * {@link ImageFormat#PRIVATE PRIVATE}, or the input
- * {@link android.view.Surface Surface} has been abandoned by the
- * consumer component that provided the {@link android.view.Surface Surface}.
+ * dequeued, or the input {@link android.view.Surface Surface}
+ * has been abandoned by the consumer component that provided
+ * the {@link android.view.Surface Surface}. Prior to Android
+ * P, throws if the ImageWriter format is
+ * {@link ImageFormat#PRIVATE PRIVATE}.
* @see #queueInputImage
* @see Image#close
*/
public Image dequeueInputImage() {
- if (mWriterFormat == ImageFormat.PRIVATE) {
- throw new IllegalStateException(
- "PRIVATE format ImageWriter doesn't support this operation since the images are"
- + " inaccessible to the application!");
- }
-
if (mDequeuedImages.size() >= mMaxImages) {
throw new IllegalStateException("Already dequeued max number of Images " + mMaxImages);
}
@@ -743,6 +746,13 @@
}
@Override
+ public HardwareBuffer getHardwareBuffer() {
+ throwISEIfImageIsInvalid();
+
+ return nativeGetHardwareBuffer();
+ }
+
+ @Override
public Plane[] getPlanes() {
throwISEIfImageIsInvalid();
@@ -863,6 +873,8 @@
private synchronized native int nativeGetHeight();
private synchronized native int nativeGetFormat();
+
+ private synchronized native HardwareBuffer nativeGetHardwareBuffer();
}
// Native implemented ImageWriter methods.
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 392a1eb..ada91be 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -2848,8 +2848,12 @@
mInbandTrackIndices.set(i);
}
+ if (tracks[i] == null) {
+ Log.w(TAG, "unexpected NULL track at index " + i);
+ }
// newly appeared inband track
- if (tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
+ if (tracks[i] != null
+ && tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
SubtitleTrack track = mSubtitleController.addTrack(
tracks[i].getFormat());
mIndexTrackPairs.add(Pair.create(i, track));
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index 11659e4..f8f7a90 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -25,11 +25,14 @@
#include <gui/Surface.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
+#include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <private/android/AHardwareBufferHelpers.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <stdint.h>
#include <inttypes.h>
+#include <android/hardware_buffer_jni.h>
#define IMAGE_BUFFER_JNI_ID "mNativeBuffer"
#define IMAGE_FORMAT_UNKNOWN 0 // This is the same value as ImageFormat#UNKNOWN.
@@ -701,6 +704,20 @@
return static_cast<jint>(publicFmt);
}
+static jobject Image_getHardwareBuffer(JNIEnv* env, jobject thiz) {
+ GraphicBuffer* buffer;
+ Image_getNativeContext(env, thiz, &buffer, NULL);
+ if (buffer == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Image is not initialized");
+ return NULL;
+ }
+ AHardwareBuffer* b = AHardwareBuffer_from_GraphicBuffer(buffer);
+ // don't user the public AHardwareBuffer_toHardwareBuffer() because this would force us
+ // to link against libandroid.so
+ return android_hardware_HardwareBuffer_createFromAHardwareBuffer(env, b);
+}
+
static void Image_setFenceFd(JNIEnv* env, jobject thiz, int fenceFd) {
ALOGV("%s:", __FUNCTION__);
env->SetIntField(thiz, gSurfaceImageClassInfo.mNativeFenceFd, reinterpret_cast<jint>(fenceFd));
@@ -818,10 +835,12 @@
static JNINativeMethod gImageMethods[] = {
{"nativeCreatePlanes", "(II)[Landroid/media/ImageWriter$WriterSurfaceImage$SurfacePlane;",
- (void*)Image_createSurfacePlanes },
- {"nativeGetWidth", "()I", (void*)Image_getWidth },
- {"nativeGetHeight", "()I", (void*)Image_getHeight },
- {"nativeGetFormat", "()I", (void*)Image_getFormat },
+ (void*)Image_createSurfacePlanes },
+ {"nativeGetWidth", "()I", (void*)Image_getWidth },
+ {"nativeGetHeight", "()I", (void*)Image_getHeight },
+ {"nativeGetFormat", "()I", (void*)Image_getFormat },
+ {"nativeGetHardwareBuffer", "()Landroid/hardware/HardwareBuffer;",
+ (void*)Image_getHardwareBuffer },
};
int register_android_media_ImageWriter(JNIEnv *env) {
diff --git a/packages/SettingsLib/res/values-as/arrays.xml b/packages/SettingsLib/res/values-as/arrays.xml
index d0973f0..b8353ee 100644
--- a/packages/SettingsLib/res/values-as/arrays.xml
+++ b/packages/SettingsLib/res/values-as/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"সংযোগৰ ক্ষমতা অনুযায়ী সৰ্বোত্তম"</item>
<item msgid="364670732877872677">"উত্তম প্ৰচেষ্টা (অভিযোজিত বিট ৰেইট)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">", সক্ৰিয়"</item>
+ <item msgid="8962366465966010158">", সক্ৰিয় (মিডিয়া)"</item>
+ <item msgid="4046665544396189228">", সক্ৰিয় (ফ\'ন)"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"অফ কৰক"</item>
<item msgid="1593289376502312923">"৬৪কে."</item>
diff --git a/packages/SettingsLib/res/values-bn/arrays.xml b/packages/SettingsLib/res/values-bn/arrays.xml
index e186bb4..6340034 100644
--- a/packages/SettingsLib/res/values-bn/arrays.xml
+++ b/packages/SettingsLib/res/values-bn/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"সংযোগের গুণমানের জন্য অপটিমাইজ করা হয়েছে"</item>
<item msgid="364670732877872677">"সেরা প্রচেষ্টা (অ্যাডাপ্টিভ বিট রেট)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">", চালু আছে"</item>
+ <item msgid="8962366465966010158">", চালু আছে (মিডিয়া)"</item>
+ <item msgid="4046665544396189228">", চালু আছে (ফোন)"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"বন্ধ আছে"</item>
<item msgid="1593289376502312923">"৬৪K"</item>
diff --git a/packages/SettingsLib/res/values-gu/arrays.xml b/packages/SettingsLib/res/values-gu/arrays.xml
index 30a9339..ecf5525 100644
--- a/packages/SettingsLib/res/values-gu/arrays.xml
+++ b/packages/SettingsLib/res/values-gu/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"કનેક્શનની ગુણવત્તા માટે ઓપ્ટિમાઇઝ કર્યું"</item>
<item msgid="364670732877872677">"ઉત્તમ પ્રયાસ (અનુકૂલનશીલ બિટ રેટ)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">", સક્રિય"</item>
+ <item msgid="8962366465966010158">", સક્રિય (મીડિયા)"</item>
+ <item msgid="4046665544396189228">", સક્રિય (ફોન)"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"બંધ"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-hi/arrays.xml b/packages/SettingsLib/res/values-hi/arrays.xml
index 0e34b77..d1d0157 100644
--- a/packages/SettingsLib/res/values-hi/arrays.xml
+++ b/packages/SettingsLib/res/values-hi/arrays.xml
@@ -140,9 +140,9 @@
</string-array>
<string-array name="bluetooth_audio_active_device_summaries">
<item msgid="4862957058729193940"></item>
- <item msgid="6481691720774549651">"चालू है"</item>
- <item msgid="8962366465966010158">"चालू है (सिर्फ़ मीडिया के लिए)"</item>
- <item msgid="4046665544396189228">"चालू है (सिर्फ़ फ़ोन के लिए)"</item>
+ <item msgid="6481691720774549651">", चालू है"</item>
+ <item msgid="8962366465966010158">", चालू है (सिर्फ़ मीडिया के लिए)"</item>
+ <item msgid="4046665544396189228">", चालू है (सिर्फ़ फ़ोन के लिए)"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"बंद"</item>
diff --git a/packages/SettingsLib/res/values-is/arrays.xml b/packages/SettingsLib/res/values-is/arrays.xml
index 3aef4ce..b073371 100644
--- a/packages/SettingsLib/res/values-is/arrays.xml
+++ b/packages/SettingsLib/res/values-is/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"Fínstillt fyrir gæði tengingar"</item>
<item msgid="364670732877872677">"Bestu mögulegu gæði (breytilegur bitahraði)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">", virkt"</item>
+ <item msgid="8962366465966010158">", virkt (hljóð- og myndefni)"</item>
+ <item msgid="4046665544396189228">", virkt (sími)"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Slökkt"</item>
<item msgid="1593289376502312923">"64 k"</item>
diff --git a/packages/SettingsLib/res/values-iw/arrays.xml b/packages/SettingsLib/res/values-iw/arrays.xml
index 037c23f..8b6b8ca 100644
--- a/packages/SettingsLib/res/values-iw/arrays.xml
+++ b/packages/SettingsLib/res/values-iw/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"אופטימיזציה להשגת איכות חיבור מרבית"</item>
<item msgid="364670732877872677">"האיכות הטובה ביותר (קצב העברת נתונים מותאם)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">", פעיל"</item>
+ <item msgid="8962366465966010158">", פעיל (מדיה)"</item>
+ <item msgid="4046665544396189228">", פעיל (טלפון)"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"כבוי"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-kn/arrays.xml b/packages/SettingsLib/res/values-kn/arrays.xml
index a66af95..75d5dcb 100644
--- a/packages/SettingsLib/res/values-kn/arrays.xml
+++ b/packages/SettingsLib/res/values-kn/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"ಸಂಪರ್ಕ ಗುಣಮಟ್ಟಕ್ಕಾಗಿ ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗಿದೆ"</item>
<item msgid="364670732877872677">"ಅತ್ಯುತ್ತಮ ಪ್ರಯತ್ನ (ಹೊಂದಿಸಬಹುದಾದ ಬಿಟ್ ಪ್ರಮಾಣ)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">"ಸಕ್ರಿಯ"</item>
+ <item msgid="8962366465966010158">", ಸಕ್ರಿಯ (ಮಾಧ್ಯಮ)"</item>
+ <item msgid="4046665544396189228">", ಸಕ್ರಿಯ (ಫೋನ್)"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ಆಫ್"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-ml/arrays.xml b/packages/SettingsLib/res/values-ml/arrays.xml
index 74689e3..3337913 100644
--- a/packages/SettingsLib/res/values-ml/arrays.xml
+++ b/packages/SettingsLib/res/values-ml/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"കണക്ഷൻ നിലവാരമുയർത്താൻ ഒപ്റ്റിമൈസ് ചെയ്തു"</item>
<item msgid="364670732877872677">"മികച്ച സംവിധാനം (അനുയോജ്യമായ ബിറ്റ് റേറ്റ്)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">", സജീവം"</item>
+ <item msgid="8962366465966010158">", സജീവ (മീഡിയ)"</item>
+ <item msgid="4046665544396189228">", സജീവമായ (ഫോൺ)"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ഓഫ്"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-mr/arrays.xml b/packages/SettingsLib/res/values-mr/arrays.xml
index 841e564..f5bf2ea 100644
--- a/packages/SettingsLib/res/values-mr/arrays.xml
+++ b/packages/SettingsLib/res/values-mr/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"कनेक्शन गुणवत्तेसाठी ऑप्टिमाइझ केले"</item>
<item msgid="364670732877872677">"सर्वोत्तम प्रयत्न (अनुकूल बिट रेट)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">", अॅक्टिव्ह"</item>
+ <item msgid="8962366465966010158">", अॅक्टिव्ह (मीडिया)"</item>
+ <item msgid="4046665544396189228">", अॅक्टिव्ह (फोन)"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"बंद"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-ne/arrays.xml b/packages/SettingsLib/res/values-ne/arrays.xml
index 95a26d2..4ed55cc 100644
--- a/packages/SettingsLib/res/values-ne/arrays.xml
+++ b/packages/SettingsLib/res/values-ne/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"जडानको गुणस्तर सुधार्न अनुकूलन गरिएको"</item>
<item msgid="364670732877872677">"उत्कृष्ट प्रयास (अनुकूलनीय बिट दर)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">", सक्रिय"</item>
+ <item msgid="8962366465966010158">", सक्रिय (मिडिया)"</item>
+ <item msgid="4046665544396189228">", सक्रिय (फोन)"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"निष्क्रिय गर्नुहोस्"</item>
<item msgid="1593289376502312923">"६४के"</item>
diff --git a/packages/SettingsLib/res/values-or/arrays.xml b/packages/SettingsLib/res/values-or/arrays.xml
index 9da5d6e..18de8f9 100644
--- a/packages/SettingsLib/res/values-or/arrays.xml
+++ b/packages/SettingsLib/res/values-or/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"ସଂଯୋଗର ଗୁଣବତ୍ତା ପାଇଁ ଅନୁକୂଳିତ"</item>
<item msgid="364670732877872677">"ସର୍ବୋତ୍ତମ ପ୍ରୟାସ (ଅନୁକୂଳ ବିଟ୍ ରେଟ୍)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">", ସକ୍ରିୟ"</item>
+ <item msgid="8962366465966010158">", ସକ୍ରିୟ (ମିଡିଆ)"</item>
+ <item msgid="4046665544396189228">", ସକ୍ରିୟ (ଫୋନ୍)"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ଅଫ୍"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml
index 2b3b919..ddb40ce 100644
--- a/packages/SettingsLib/res/values-te/arrays.xml
+++ b/packages/SettingsLib/res/values-te/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"కనెక్షన్ నాణ్యత కోసం అనుకూలీకరించబడింది"</item>
<item msgid="364670732877872677">"ఉత్తమ కృషి (అనుకూల బిట్ రేట్)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">", సక్రియంగా ఉంది"</item>
+ <item msgid="8962366465966010158">", (మీడియా) సక్రియంగా ఉంది"</item>
+ <item msgid="4046665544396189228">", (ఫోన్) సక్రియంగా ఉంది"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ఆఫ్"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-ur/arrays.xml b/packages/SettingsLib/res/values-ur/arrays.xml
index 6e2f904..c6eadd98 100644
--- a/packages/SettingsLib/res/values-ur/arrays.xml
+++ b/packages/SettingsLib/res/values-ur/arrays.xml
@@ -138,9 +138,12 @@
<item msgid="4681409244565426925">"کنکشن کے معیار کیلئے بہتر بنایا گيا"</item>
<item msgid="364670732877872677">"بہترین کوشش (اڈاپٹیو بٹ ریٹ)"</item>
</string-array>
- <!-- no translation found for bluetooth_audio_active_device_summaries:1 (6481691720774549651) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:2 (8962366465966010158) -->
- <!-- no translation found for bluetooth_audio_active_device_summaries:3 (4046665544396189228) -->
+ <string-array name="bluetooth_audio_active_device_summaries">
+ <item msgid="4862957058729193940"></item>
+ <item msgid="6481691720774549651">"، فعال"</item>
+ <item msgid="8962366465966010158">"، فعال (میڈیا)"</item>
+ <item msgid="4046665544396189228">"، فعال (فون)"</item>
+ </string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"آف"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index e902c92..b5d48b4 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -42,7 +42,7 @@
android:singleLine="true"
android:ellipsize="start"
android:inputType="textShortMessage|textAutoCorrect|textCapSentences"
- android:imeOptions="actionSend" />
+ android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
<FrameLayout
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c1458b2..0c7c600 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -34,10 +34,10 @@
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
<!-- Height of the battery icon in the status bar. -->
- <dimen name="status_bar_battery_icon_height">14.5dp</dimen>
+ <dimen name="status_bar_battery_icon_height">13.0dp</dimen>
<!-- Width of the battery icon in the status bar. -->
- <dimen name="status_bar_battery_icon_width">9.5dp</dimen>
+ <dimen name="status_bar_battery_icon_width">8.5dp</dimen>
<!-- The font size for the clock in the status bar. -->
<dimen name="status_bar_clock_size">14sp</dimen>
@@ -960,6 +960,10 @@
add about 88dp of height to the notifications. -->
<dimen name="smart_reply_button_max_height">100dp</dimen>
+ <!-- The extra height that we allow a notification with a remote input history to be taller than
+ the regular notification, when we have remote input history texts present. -->
+ <dimen name="remote_input_history_extra_height">60dp</dimen>
+
<!-- Fingerprint Dialog values -->
<dimen name="fingerprint_dialog_fp_icon_size">64dp</dimen>
<dimen name="fingerprint_dialog_animation_translation_offset">350dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index beb3c53..9c9f021 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -151,7 +151,6 @@
@Override
public void start() {
- sDependency = this;
// TODO: Think about ways to push these creation rules out of Dependency to cut down
// on imports.
mProviders.put(TIME_TICK_HANDLER, () -> {
@@ -331,6 +330,8 @@
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
+
+ sDependency = this;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 139215a0..2746a71 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -22,8 +22,8 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static com.android.systemui.tuner.TunablePadding.FLAG_START;
import static com.android.systemui.tuner.TunablePadding.FLAG_END;
+import static com.android.systemui.tuner.TunablePadding.FLAG_START;
import android.annotation.Dimension;
import android.app.Fragment;
@@ -66,6 +66,7 @@
import com.android.systemui.tuner.TunablePadding;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.leak.RotationUtils;
/**
* An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
@@ -77,6 +78,9 @@
private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
+ private DisplayManager mDisplayManager;
+ private DisplayManager.DisplayListener mDisplayListener;
+
private int mRoundedDefault;
private int mRoundedDefaultTop;
private int mRoundedDefaultBottom;
@@ -84,7 +88,7 @@
private View mBottomOverlay;
private float mDensity;
private WindowManager mWindowManager;
- private boolean mLandscape;
+ private int mRotation;
@Override
public void start() {
@@ -104,6 +108,28 @@
if (padding != 0) {
setupPadding(padding);
}
+
+ mDisplayListener = new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ // do nothing
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ // do nothing
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ updateOrientation();
+ }
+ };
+
+ mRotation = -1;
+ mDisplayManager = (DisplayManager) mContext.getSystemService(
+ Context.DISPLAY_SERVICE);
+ mDisplayManager.registerDisplayListener(mDisplayListener, null);
}
private void setupDecorations() {
@@ -169,17 +195,22 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
- boolean newLanscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
- if (newLanscape != mLandscape) {
- mLandscape = newLanscape;
+ updateOrientation();
+ }
+
+ protected void updateOrientation() {
+ int newRotation = RotationUtils.getExactRotation(mContext);
+ if (newRotation != mRotation) {
+ mRotation = newRotation;
if (mOverlay != null) {
updateLayoutParams();
updateViews();
}
- }
- if (shouldDrawCutout() && mOverlay == null) {
- setupDecorations();
+
+ if (shouldDrawCutout() && mOverlay == null) {
+ setupDecorations();
+ }
}
}
@@ -188,16 +219,28 @@
View topRight = mOverlay.findViewById(R.id.right);
View bottomLeft = mBottomOverlay.findViewById(R.id.left);
View bottomRight = mBottomOverlay.findViewById(R.id.right);
- if (mLandscape) {
- // Flip corners
- View tmp = topRight;
- topRight = bottomLeft;
- bottomLeft = tmp;
+
+ if (mRotation == RotationUtils.ROTATION_NONE) {
+ updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
+ updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
+ updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
+ updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
+ } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) {
+ updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
+ updateView(topRight, Gravity.BOTTOM | Gravity.LEFT, 270);
+ updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);;
+ updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
+ } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
+ updateView(topLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
+ updateView(topRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
+ updateView(bottomLeft, Gravity.TOP | Gravity.LEFT, 0);
+ updateView(bottomRight, Gravity.TOP | Gravity.RIGHT, 90);
+ } else if (mRotation == RotationUtils.ROTATION_SEASCAPE) {
+ updateView(topLeft, Gravity.BOTTOM | Gravity.RIGHT, 180);
+ updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
+ updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
+ updateView(bottomRight, Gravity.TOP | Gravity.LEFT, 0);
}
- updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
- updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
- updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
- updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
updateWindowVisibilities();
}
@@ -269,9 +312,14 @@
}
lp.setTitle("ScreenDecorOverlay");
- lp.gravity = Gravity.TOP | Gravity.LEFT;
+ if (mRotation == RotationUtils.ROTATION_SEASCAPE
+ || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
+ lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ } else {
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
+ }
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- if (mLandscape) {
+ if (isLandscape(mRotation)) {
lp.width = WRAP_CONTENT;
lp.height = MATCH_PARENT;
}
@@ -281,7 +329,12 @@
private WindowManager.LayoutParams getBottomLayoutParams() {
WindowManager.LayoutParams lp = getWindowLayoutParams();
lp.setTitle("ScreenDecorOverlayBottom");
- lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ if (mRotation == RotationUtils.ROTATION_SEASCAPE
+ || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
+ } else {
+ lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ }
return lp;
}
@@ -568,4 +621,9 @@
return cutoutBounds;
}
}
+
+ private boolean isLandscape(int rotation) {
+ return rotation == RotationUtils.ROTATION_LANDSCAPE || rotation ==
+ RotationUtils.ROTATION_SEASCAPE;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 721890f..03a6398 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -56,6 +56,7 @@
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import java.util.function.Consumer;
@@ -148,5 +149,7 @@
providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context));
providers.put(KeyguardDismissUtil.class, KeyguardDismissUtil::new);
providers.put(SmartReplyController.class, () -> new SmartReplyController());
+ providers.put(RemoteInputQuickSettingsDisabler.class,
+ () -> new RemoteInputQuickSettingsDisabler(context));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 90140ff..f14d396 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -42,7 +42,7 @@
* hiding the wallpaper and changing the display mode is necessary to hide
* the black frame that's inherent to hardware specs.
*/
- public static final int ENTER_DOZE_HIDE_WALLPAPER_DELAY = 2000;
+ public static final int ENTER_DOZE_HIDE_WALLPAPER_DELAY = 4500;
private final DozeMachine.Service mDozeService;
private final Handler mHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index cbd1ca1..b146cfc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -35,6 +35,7 @@
import android.view.ViewTreeObserver;
import android.widget.FrameLayout.LayoutParams;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.R.id;
@@ -43,6 +44,7 @@
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
+import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.stack.StackStateAnimator;
public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks {
@@ -72,6 +74,9 @@
private float mLastQSExpansion = -1;
private boolean mQsDisabled;
+ private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler =
+ Dependency.get(RemoteInputQuickSettingsDisabler.class);
+
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
@@ -191,6 +196,8 @@
@Override
public void disable(int state1, int state2, boolean animate) {
+ state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
+
final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
if (disabled == mQsDisabled) return;
mQsDisabled = disabled;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
index 32e51b3..9f82bcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
@@ -19,8 +19,10 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.DisplayCutout;
import android.view.View;
import android.widget.TextView;
@@ -44,9 +46,10 @@
private boolean mPublicMode;
private int mMaxWidth;
private View mRootView;
- private int mLeftCutOutInset;
- private int mLeftInset;
+ private int mSysWinInset;
+ private int mCutOutInset;
private Rect mIconDrawingRect = new Rect();
+ private Point mPoint;
private Runnable mOnDrawingRectChangedListener;
public HeadsUpStatusBarView(Context context) {
@@ -137,9 +140,20 @@
int bottom = top + mIconPlaceholder.getHeight();
mLayoutedIconRect.set(left, top, right, bottom);
updateDrawingRect();
- int targetPadding = mAbsoluteStartPadding + mLeftInset + mLeftCutOutInset;
+ int targetPadding = mAbsoluteStartPadding + mSysWinInset + mCutOutInset;
if (left != targetPadding) {
- int newPadding = targetPadding - left + getPaddingStart();
+ int start;
+ if (isLayoutRtl()) {
+ if (mPoint == null) {
+ mPoint = new Point();
+ }
+ getDisplay().getRealSize(mPoint);
+ start = (mPoint.x - right);
+ } else {
+ start = left;
+ }
+
+ int newPadding = targetPadding - start + getPaddingStart();
setPaddingRelative(newPadding, 0, mEndMargin, 0);
}
if (mFirstLayout) {
@@ -152,7 +166,11 @@
}
public void setPanelTranslation(float translationX) {
- setTranslationX(translationX - mLeftCutOutInset);
+ if (isLayoutRtl()) {
+ setTranslationX(translationX + mCutOutInset);
+ } else {
+ setTranslationX(translationX - mCutOutInset);
+ }
updateDrawingRect();
}
@@ -167,15 +185,19 @@
@Override
protected boolean fitSystemWindows(Rect insets) {
- mLeftInset = insets.left;
- mLeftCutOutInset = (getRootWindowInsets().getDisplayCutout() != null)
- ? getRootWindowInsets().getDisplayCutout().getSafeInsetLeft() : 0;
-
+ boolean isRtl = isLayoutRtl();
+ mSysWinInset = isRtl ? insets.right : insets.left;
+ DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
+ mCutOutInset = (displayCutout != null)
+ ? (isRtl ? displayCutout.getSafeInsetRight() : displayCutout.getSafeInsetLeft())
+ : 0;
// For Double Cut Out mode, the System window navigation bar is at the right
- // hand side of the left cut out. In this condition, mLeftInset include the left cut
- // out width so we set mLeftCutOutInset to be 0.
- if (mLeftInset != 0) {
- mLeftCutOutInset = 0;
+ // side of the left cut out. In this condition, mSysWinInset include the left cut
+ // out width so we set mCutOutInset to be 0. For RTL, the condition is the same.
+ // The navigation bar is at the left side of the right cut out and include the
+ // right cut out width.
+ if (mSysWinInset != 0) {
+ mCutOutInset = 0;
}
return super.fitSystemWindows(insets);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 952c961..70dad19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -189,6 +189,7 @@
if (mExpandedSmartReplyView != null) {
notificationMaxHeight += mExpandedSmartReplyView.getHeightUpperLimit();
}
+ notificationMaxHeight += mExpandedWrapper.getExtraMeasureHeight();
int size = notificationMaxHeight;
ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams();
boolean useExactly = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 419e262..e24d65a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -32,10 +32,13 @@
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.app.Person;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.SystemClock;
import android.service.notification.NotificationListenerService.Ranking;
@@ -50,6 +53,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.ForegroundServiceController;
@@ -117,6 +121,11 @@
*/
public Boolean mIsSystemNotification;
+ /**
+ * Has the user sent a reply through this Notification.
+ */
+ private boolean hasSentReply;
+
public Entry(StatusBarNotification n) {
this.key = n.getKey();
this.notification = n;
@@ -298,6 +307,40 @@
lastRemoteInputSent = NOT_LAUNCHED_YET;
remoteInputTextWhenReset = null;
}
+
+ public void setHasSentReply() {
+ hasSentReply = true;
+ }
+
+ public boolean isLastMessageFromReply() {
+ if (!hasSentReply) {
+ return false;
+ }
+ Bundle extras = notification.getNotification().extras;
+ CharSequence[] replyTexts = extras.getCharSequenceArray(
+ Notification.EXTRA_REMOTE_INPUT_HISTORY);
+ if (!ArrayUtils.isEmpty(replyTexts)) {
+ return true;
+ }
+ Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+ if (messages != null && messages.length > 0) {
+ Parcelable message = messages[messages.length - 1];
+ if (message instanceof Bundle) {
+ Notification.MessagingStyle.Message lastMessage =
+ Notification.MessagingStyle.Message.getMessageFromBundle(
+ (Bundle) message);
+ if (lastMessage != null) {
+ Person senderPerson = lastMessage.getSenderPerson();
+ if (senderPerson == null) {
+ return true;
+ }
+ Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
+ return Objects.equals(user, senderPerson);
+ }
+ }
+ }
+ return false;
+ }
}
private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index d040f7c..ba265d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -18,13 +18,13 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.car.user.CarUserManagerHelper;
import android.content.Context;
import android.view.View;
import android.view.ViewStub;
import android.support.v7.widget.GridLayoutManager;
+import com.android.settingslib.users.UserManagerHelper;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -37,7 +37,7 @@
private final UserGridRecyclerView mUserGridView;
private final int mShortAnimDuration;
private final StatusBar mStatusBar;
- private final CarUserManagerHelper mCarUserManagerHelper;
+ private final UserManagerHelper mUserManagerHelper;
private int mCurrentForegroundUserId;
private boolean mShowing;
@@ -52,7 +52,7 @@
mUserGridView.buildAdapter();
mUserGridView.setUserSelectionListener(this::onUserSelected);
- mCarUserManagerHelper = new CarUserManagerHelper(context);
+ mUserManagerHelper = new UserManagerHelper(context);
updateCurrentForegroundUser();
mShortAnimDuration = mContainer.getResources()
@@ -84,11 +84,11 @@
}
private boolean foregroundUserChanged() {
- return mCurrentForegroundUserId != mCarUserManagerHelper.getCurrentForegroundUserId();
+ return mCurrentForegroundUserId != mUserManagerHelper.getForegroundUserId();
}
private void updateCurrentForegroundUser() {
- mCurrentForegroundUserId = mCarUserManagerHelper.getCurrentForegroundUserId();
+ mCurrentForegroundUserId = mUserManagerHelper.getForegroundUserId();
}
private void onUserSelected(UserGridRecyclerView.UserRecord record) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index dda25ec..aaf1989 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -21,7 +21,6 @@
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
-import android.car.user.CarUserManagerHelper;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.UserInfo;
@@ -42,6 +41,7 @@
import androidx.car.widget.PagedListView;
import com.android.internal.util.UserIcons;
+import com.android.settingslib.users.UserManagerHelper;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -53,16 +53,16 @@
* One of the uses of this is for the lock screen in auto.
*/
public class UserGridRecyclerView extends PagedListView implements
- CarUserManagerHelper.OnUsersUpdateListener {
+ UserManagerHelper.OnUsersUpdateListener {
private UserSelectionListener mUserSelectionListener;
private UserAdapter mAdapter;
- private CarUserManagerHelper mCarUserManagerHelper;
+ private UserManagerHelper mUserManagerHelper;
private Context mContext;
public UserGridRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
- mCarUserManagerHelper = new CarUserManagerHelper(mContext);
+ mUserManagerHelper = new UserManagerHelper(mContext);
}
/**
@@ -71,7 +71,7 @@
@Override
public void onFinishInflate() {
super.onFinishInflate();
- mCarUserManagerHelper.registerOnUsersUpdateListener(this);
+ mUserManagerHelper.registerOnUsersUpdateListener(this);
}
/**
@@ -80,7 +80,7 @@
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mCarUserManagerHelper.unregisterOnUsersUpdateListener();
+ mUserManagerHelper.unregisterOnUsersUpdateListener();
}
/**
@@ -89,7 +89,7 @@
* @return the adapter
*/
public void buildAdapter() {
- List<UserRecord> userRecords = createUserRecords(mCarUserManagerHelper
+ List<UserRecord> userRecords = createUserRecords(mUserManagerHelper
.getAllUsers());
mAdapter = new UserAdapter(mContext, userRecords);
super.setAdapter(mAdapter);
@@ -103,19 +103,19 @@
continue;
}
boolean isForeground =
- mCarUserManagerHelper.getCurrentForegroundUserId() == userInfo.id;
+ mUserManagerHelper.getForegroundUserId() == userInfo.id;
UserRecord record = new UserRecord(userInfo, false /* isStartGuestSession */,
false /* isAddUser */, isForeground);
userRecords.add(record);
}
// Add guest user record if the foreground user is not a guest
- if (!mCarUserManagerHelper.isForegroundUserGuest()) {
+ if (!mUserManagerHelper.foregroundUserIsGuestUser()) {
userRecords.add(addGuestUserRecord());
}
// Add add user record if the foreground user can add users
- if (mCarUserManagerHelper.canForegroundUserAddUsers()) {
+ if (mUserManagerHelper.foregroundUserCanAddUsers()) {
userRecords.add(addUserRecord());
}
@@ -149,7 +149,7 @@
@Override
public void onUsersUpdate() {
mAdapter.clearUsers();
- mAdapter.updateUsers(createUserRecords(mCarUserManagerHelper.getAllUsers()));
+ mAdapter.updateUsers(createUserRecords(mUserManagerHelper.getAllUsers()));
mAdapter.notifyDataSetChanged();
}
@@ -213,7 +213,7 @@
// If the user selects Guest, start the guest session.
if (userRecord.mIsStartGuestSession) {
- mCarUserManagerHelper.startNewGuestSession(mGuestName);
+ mUserManagerHelper.startNewGuestSession(mGuestName);
return;
}
@@ -240,14 +240,14 @@
return;
}
// If the user doesn't want to be a guest or add a user, switch to the user selected
- mCarUserManagerHelper.switchToUser(userRecord.mInfo);
+ mUserManagerHelper.switchToUser(userRecord.mInfo);
});
}
private Bitmap getUserRecordIcon(UserRecord userRecord) {
if (userRecord.mIsStartGuestSession) {
- return mCarUserManagerHelper.getGuestDefaultIcon();
+ return mUserManagerHelper.getGuestDefaultIcon();
}
if (userRecord.mIsAddUser) {
@@ -255,7 +255,7 @@
.getDrawable(R.drawable.car_add_circle_round));
}
- return mCarUserManagerHelper.getUserIcon(userRecord.mInfo);
+ return mUserManagerHelper.getUserIcon(userRecord.mInfo);
}
@Override
@@ -273,10 +273,7 @@
@Override
protected UserInfo doInBackground(String... userNames) {
- // Default to create a non admin user for now. Need to add logic
- // for user to choose whether they want to create an admin or non-admin
- // user later.
- return mCarUserManagerHelper.createNewNonAdminUser(userNames[0]);
+ return mUserManagerHelper.createNewUser(userNames[0]);
}
@Override
@@ -286,7 +283,7 @@
@Override
protected void onPostExecute(UserInfo user) {
if (user != null) {
- mCarUserManagerHelper.switchToUser(user);
+ mUserManagerHelper.switchToUser(user);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index 2d983c4..03791c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -34,6 +34,7 @@
import com.android.internal.util.NotificationColorUtil;
import com.android.internal.widget.NotificationActionListLayout;
import com.android.systemui.Dependency;
+import com.android.systemui.R;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -58,6 +59,7 @@
private NotificationActionListLayout mActions;
private ArraySet<PendingIntent> mCancelledPendingIntents = new ArraySet<>();
private UiOffloadThread mUiOffloadThread;
+ private View mRemoteInputHistory;
protected NotificationTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
@@ -146,6 +148,8 @@
mActionsContainer = mView.findViewById(com.android.internal.R.id.actions_container);
mActions = mView.findViewById(com.android.internal.R.id.actions);
mReplyAction = mView.findViewById(com.android.internal.R.id.reply_icon_action);
+ mRemoteInputHistory = mView.findViewById(
+ com.android.internal.R.id.notification_material_reply_container);
updatePendingIntentCancellations();
}
@@ -329,6 +333,10 @@
if (mActions != null) {
extra = mActions.getExtraMeasureHeight();
}
+ if (mRemoteInputHistory != null && mRemoteInputHistory.getVisibility() != View.GONE) {
+ extra += mRow.getContext().getResources().getDimensionPixelSize(
+ R.dimen.remote_input_history_extra_height);
+ }
return extra + super.getExtraMeasureHeight();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 6d78d1d..b52e324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -16,8 +16,10 @@
package com.android.systemui.statusbar.phone;
+import android.graphics.Point;
import android.graphics.Rect;
import android.view.View;
+import android.view.WindowInsets;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
@@ -59,6 +61,7 @@
private final View.OnLayoutChangeListener mStackScrollLayoutChangeListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
-> updatePanelTranslation();
+ Point mPoint;
public HeadsUpAppearanceController(
NotificationIconAreaController notificationIconAreaController,
@@ -121,8 +124,54 @@
updateHeader(headsUp.getEntry());
}
+ /** To count the distance from the window right boundary to scroller right boundary. The
+ * distance formula is the following:
+ * Y = screenSize - (SystemWindow's width + Scroller.getRight())
+ * There are four modes MUST to be considered in Cut Out of RTL.
+ * No Cut Out:
+ * Scroller + NB
+ * NB + Scroller
+ * => SystemWindow = NavigationBar's width
+ * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
+ * Corner Cut Out or Tall Cut Out:
+ * cut out + Scroller + NB
+ * NB + Scroller + cut out
+ * => SystemWindow = NavigationBar's width
+ * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
+ * Double Cut Out:
+ * cut out left + Scroller + (NB + cut out right)
+ * SystemWindow = NavigationBar's width + cut out right width
+ * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
+ * (cut out left + NB) + Scroller + cut out right
+ * SystemWindow = NavigationBar's width + cut out left width
+ * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
+ * @return the translation X value for RTL. In theory, it should be negative. i.e. -Y
+ */
+ private int getRtlTranslation() {
+ // TODO: Corner Cut Out still need to handle.
+ if (mPoint == null) {
+ mPoint = new Point();
+ }
+
+ int realDisplaySize = 0;
+ if (mStackScroller.getDisplay() != null) {
+ mStackScroller.getDisplay().getRealSize(mPoint);
+ realDisplaySize = mPoint.x;
+ }
+
+ WindowInsets windowInset = mStackScroller.getRootWindowInsets();
+ return windowInset.getSystemWindowInsetLeft() + mStackScroller.getRight()
+ + windowInset.getSystemWindowInsetRight() - realDisplaySize;
+ }
+
public void updatePanelTranslation() {
- float newTranslation = mStackScroller.getLeft() + mStackScroller.getTranslationX();
+ float newTranslation;
+ if (mStackScroller.isLayoutRtl()) {
+ newTranslation = getRtlTranslation();
+ } else {
+ newTranslation = mStackScroller.getLeft();
+ }
+ newTranslation += mStackScroller.getTranslationX();
mHeadsUpStatusBarView.setPanelTranslation(newTranslation);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index b043100..44d666e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -141,7 +141,7 @@
}
protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
- boolean showAmbient, boolean hideDismissed) {
+ boolean showAmbient, boolean hideDismissed, boolean hideRepliedMessages) {
if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) {
return false;
}
@@ -155,6 +155,10 @@
return false;
}
+ if (hideRepliedMessages && entry.isLastMessageFromReply()) {
+ return false;
+ }
+
// showAmbient == show in shade but not shelf
if (!showAmbient && mEntryManager.getNotificationData().shouldSuppressStatusBar(entry)) {
return false;
@@ -170,14 +174,15 @@
updateStatusBarIcons();
updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons,
- NotificationShelf.SHOW_AMBIENT_ICONS, false /* hideDismissed */);
+ NotificationShelf.SHOW_AMBIENT_ICONS, false /* hideDismissed */,
+ false /* hideRepliedMessages */);
applyNotificationIconsTint();
}
- private void updateStatusBarIcons() {
+ public void updateStatusBarIcons() {
updateIconsForLayout(entry -> entry.icon, mNotificationIcons,
- false /* showAmbient */, true /* hideDismissed */);
+ false /* showAmbient */, true /* hideDismissed */, true /* hideRepliedMessages */);
}
/**
@@ -187,9 +192,11 @@
* @param hostLayout which layout should be updated
* @param showAmbient should ambient notification icons be shown
* @param hideDismissed should dismissed icons be hidden
+ * @param hideRepliedMessages should messages that have been replied to be hidden
*/
private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function,
- NotificationIconContainer hostLayout, boolean showAmbient, boolean hideDismissed) {
+ NotificationIconContainer hostLayout, boolean showAmbient, boolean hideDismissed,
+ boolean hideRepliedMessages) {
ArrayList<StatusBarIconView> toShow = new ArrayList<>(
mNotificationScrollLayout.getChildCount());
@@ -198,7 +205,8 @@
View view = mNotificationScrollLayout.getChildAt(i);
if (view instanceof ExpandableNotificationRow) {
NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
- if (shouldShowNotificationIcon(ent, showAmbient, hideDismissed)) {
+ if (shouldShowNotificationIcon(ent, showAmbient, hideDismissed,
+ hideRepliedMessages)) {
toShow.add(function.apply(ent));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 8c257fe..5dee2a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -129,13 +129,14 @@
public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
public static final int MAX_STATIC_ICONS = 4;
- private static final int MAX_DOTS = 3;
+ private static final int MAX_DOTS = 1;
private boolean mIsStaticLayout = true;
private final HashMap<View, IconState> mIconStates = new HashMap<>();
private int mDotPadding;
private int mStaticDotRadius;
private int mStaticDotDiameter;
+ private int mOverflowWidth;
private int mActualLayoutWidth = NO_VALUE;
private float mActualPaddingEnd = NO_VALUE;
private float mActualPaddingStart = NO_VALUE;
@@ -230,7 +231,7 @@
int top = (int) (centerY - height / 2.0f);
child.layout(0, top, width, top + height);
if (i == 0) {
- mIconSize = child.getWidth();
+ setIconSize(child.getWidth());
}
}
getLocationOnScreen(mAbsolutePosition);
@@ -239,6 +240,11 @@
}
}
+ private void setIconSize(int size) {
+ mIconSize = size;
+ mOverflowWidth = mIconSize + (MAX_DOTS - 1) * (mStaticDotDiameter + mDotPadding);
+ }
+
private void updateState() {
resetViewStates();
calculateIconTranslations();
@@ -391,12 +397,12 @@
iconState.visibleState = StatusBarIconView.STATE_ICON;
boolean isOverflowing =
- (translationX >= (noOverflowAfter ? layoutEnd - mIconSize : overflowStart));
+ (translationX > (noOverflowAfter ? layoutEnd - mIconSize
+ : overflowStart - mIconSize));
if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
firstOverflowIndex = noOverflowAfter && !forceOverflow ? i - 1 : i;
- mVisualOverflowStart = layoutEnd - mIconSize
- - 2 * (mStaticDotDiameter + mDotPadding);
- if (forceOverflow) {
+ mVisualOverflowStart = layoutEnd - mOverflowWidth;
+ if (forceOverflow || mIsStaticLayout) {
mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
}
}
@@ -408,7 +414,7 @@
for (int i = firstOverflowIndex; i < childCount; i++) {
View view = getChildAt(i);
IconState iconState = mIconStates.get(view);
- int dotWidth = mStaticDotRadius * 2 + mDotPadding;
+ int dotWidth = mStaticDotDiameter + mDotPadding;
iconState.xTranslation = translationX;
if (mNumDots < MAX_DOTS) {
if (mNumDots == 0 && iconState.iconAppearAmount < 0.8f) {
@@ -536,7 +542,7 @@
}
private float getMaxOverflowStart() {
- return getLayoutEnd() - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
+ return getLayoutEnd() - mOverflowWidth;
}
public void setChangingViewPositions(boolean changingViewPositions) {
@@ -607,7 +613,7 @@
return 0;
}
- int collapsedPadding = mIconSize + 2 * (mStaticDotDiameter + mDotPadding);
+ int collapsedPadding = mOverflowWidth;
if (collapsedPadding + getFinalTranslationX() > getWidth()) {
collapsedPadding = getWidth() - getFinalTranslationX();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 93e9cdf..c70f034 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -234,6 +234,7 @@
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.PreviewInflater;
+import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -399,6 +400,9 @@
private View mPendingRemoteInputView;
private View mPendingWorkRemoteInputView;
+ private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler =
+ Dependency.get(RemoteInputQuickSettingsDisabler.class);
+
private View mReportRejectedTouch;
private int mMaxAllowedKeyguardNotifications;
@@ -1759,6 +1763,8 @@
*/
@Override
public void disable(int state1, int state2, boolean animate) {
+ state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
+
animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN;
final int old1 = mDisabled1;
final int diff1 = state1 ^ old1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
new file mode 100644
index 0000000..c2933e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
@@ -0,0 +1,82 @@
+/*
+ * 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.statusbar.policy;
+
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.res.Configuration;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.qs.QSFragment;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+/**
+ * Let {@link RemoteInputView} to control the visibility of QuickSetting.
+ */
+public class RemoteInputQuickSettingsDisabler
+ implements ConfigurationController.ConfigurationListener {
+
+ private Context mContext;
+ @VisibleForTesting boolean mRemoteInputActive;
+ @VisibleForTesting boolean misLandscape;
+ private int mLastOrientation;
+ @VisibleForTesting CommandQueue mCommandQueue;
+
+ public RemoteInputQuickSettingsDisabler(Context context) {
+ mContext = context;
+ mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class);
+ mLastOrientation = mContext.getResources().getConfiguration().orientation;
+ Dependency.get(ConfigurationController.class).addCallback(this);
+ }
+
+ public int adjustDisableFlags(int state) {
+ if (mRemoteInputActive && misLandscape) {
+ state |= StatusBarManager.DISABLE2_QUICK_SETTINGS;
+ }
+
+ return state;
+ }
+
+ public void setRemoteInputActive(boolean active){
+ if(mRemoteInputActive != active){
+ mRemoteInputActive = active;
+ recomputeDisableFlags();
+ }
+ }
+
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ if (newConfig.orientation != mLastOrientation) {
+ misLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
+ mLastOrientation = newConfig.orientation;
+ recomputeDisableFlags();
+ }
+ }
+
+ /**
+ * Reapplies the disable flags. Then the method adjustDisableFlags in this class will be invoked
+ * in {@link QSFragment#disable(int, int, boolean)} and
+ * {@link StatusBar#disable(int, int, boolean)}
+ * to modify the disable flags according to the status of mRemoteInputActive and misLandscape.
+ */
+ private void recomputeDisableFlags() {
+ mCommandQueue.recomputeDisableFlags(true);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 310f14c..4ec9ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -53,6 +53,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.NotificationData;
@@ -81,6 +82,7 @@
private RemoteInput[] mRemoteInputs;
private RemoteInput mRemoteInput;
private RemoteInputController mController;
+ private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private NotificationData.Entry mEntry;
@@ -96,6 +98,7 @@
public RemoteInputView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mRemoteInputQuickSettingsDisabler = Dependency.get(RemoteInputQuickSettingsDisabler.class);
}
@Override
@@ -151,6 +154,7 @@
mController.removeRemoteInput(mEntry, mToken);
mEditText.mShowImeOnInputConnection = false;
mController.remoteInputSent(mEntry);
+ mEntry.setHasSentReply();
// Tell ShortcutManager that this package has been "activated". ShortcutManager
// will reset the throttling for this package.
@@ -232,6 +236,9 @@
}
}
}
+
+ mRemoteInputQuickSettingsDisabler.setRemoteInputActive(false);
+
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_CLOSE,
mEntry.notification.getPackageName());
}
@@ -291,6 +298,9 @@
mEditText.setSelection(mEditText.getText().length());
mEditText.requestFocus();
mController.addRemoteInput(mEntry, mToken);
+
+ mRemoteInputQuickSettingsDisabler.setRemoteInputActive(true);
+
updateSendButton();
}
@@ -554,6 +564,14 @@
}
@Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ mRemoteInputView.mRemoteInputQuickSettingsDisabler.setRemoteInputActive(false);
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
public boolean onCheckIsTextEditor() {
// Stop being editable while we're being removed. During removal, we get reattached,
// and editable views get their spellchecking state re-evaluated which is too costly
@@ -565,11 +583,6 @@
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
- //if pinned, set imeOption to keep the behavior like in portrait.
- if (mRemoteInputView != null && mRemoteInputView.mEntry.row.isPinned()) {
- outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI
- | EditorInfo.IME_FLAG_NO_FULLSCREEN;
- }
if (mShowImeOnInputConnection && inputConnection != null) {
final InputMethodManager imm = InputMethodManager.getInstance();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 585787e..924aa01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -213,6 +213,7 @@
Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
RemoteInput.addResultsToIntent(new RemoteInput[]{remoteInput}, intent, results);
RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
+ entry.setHasSentReply();
try {
pendingIntent.send(context, 0, intent);
} catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 91a4b07..db3f0d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -30,7 +30,6 @@
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
-import java.util.Collection;
/**
* A global state to track all input states for the algorithm.
@@ -72,6 +71,7 @@
private ExpandableNotificationRow mExpandingNotification;
private int mDarkTopPadding;
private float mDarkAmount;
+ private boolean mAppearing;
public AmbientState(Context context) {
reload(context);
@@ -436,4 +436,12 @@
public int getDarkTopPadding() {
return mDarkTopPadding;
}
+
+ public void setAppearing(boolean appearing) {
+ mAppearing = appearing;
+ }
+
+ public boolean isAppearing() {
+ return mAppearing;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index bd56d79..a2b33fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -885,7 +885,9 @@
float appearEndPosition = getAppearEndPosition();
float appearStartPosition = getAppearStartPosition();
float appearFraction = 1.0f;
- if (height >= appearEndPosition) {
+ boolean appearing = height < appearEndPosition;
+ mAmbientState.setAppearing(appearing);
+ if (!appearing) {
translationY = 0;
if (mShouldShowShelfOnly) {
stackHeight = mTopPadding + mShelf.getIntrinsicHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 85f33d7..0d50f5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -478,22 +478,6 @@
childState.hidden = false;
}
}
- // Let's hide all the views if we are not expanded. the views might otherwise be visible
- // in the headsup area if a view was swiped away
- if (!mIsExpanded) {
- for (int i = 0; i < childCount; i++) {
- boolean visible = false;
- View child = algorithmState.visibleChildren.get(i);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- visible = row.isHeadsUp() || row.isHeadsUpAnimatingAway();
- }
- if (!visible) {
- ExpandableViewState childState = resultState.getViewStateForView(child);
- childState.hidden = true;
- }
- }
- }
}
private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
@@ -536,6 +520,10 @@
int shelfStart = ambientState.getInnerHeight()
- ambientState.getShelf().getIntrinsicHeight();
+ if (ambientState.isAppearing() && !child.isAboveShelf()) {
+ // Don't show none heads-up notifications while in appearing phase.
+ childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart);
+ }
childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
if (childViewState.yTranslation >= shelfStart) {
childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild();
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java b/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java
index ad20900..11c72c4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java
@@ -23,6 +23,7 @@
public static final int ROTATION_NONE = 0;
public static final int ROTATION_LANDSCAPE = 1;
public static final int ROTATION_SEASCAPE = 2;
+ public static final int ROTATION_UPSIDE_DOWN = 3;
public static int getRotation(Context context) {
Configuration config = context.getResources().getConfiguration();
@@ -36,4 +37,19 @@
}
return ROTATION_NONE;
}
+
+ public static int getExactRotation(Context context) {
+ Configuration config = context.getResources().getConfiguration();
+ int rot = context.getDisplay().getRotation();
+ if (config.smallestScreenWidthDp < 600) {
+ if (rot == Surface.ROTATION_90) {
+ return ROTATION_LANDSCAPE;
+ } else if (rot == Surface.ROTATION_270) {
+ return ROTATION_SEASCAPE;
+ } else if (rot == Surface.ROTATION_180) {
+ return ROTATION_UPSIDE_DOWN;
+ }
+ }
+ return ROTATION_NONE;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index d76b7f0..03474a8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -727,6 +727,12 @@
}
protected void onStateChangedH(State state) {
+ if (mState != null && state != null
+ && mState.ringerModeInternal != state.ringerModeInternal
+ && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
+ mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK));
+ }
+
mState = state;
mDynamic.clear();
// add any new dynamic rows
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 2f05b06..f1bf31d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -36,6 +36,7 @@
import android.content.res.Configuration;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
@@ -54,6 +55,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+@RunWithLooper
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class ScreenDecorationsTest extends SysuiTestCase {
@@ -98,6 +100,8 @@
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 0);
+ mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius_top, 0);
+ mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius_bottom, 0);
mContext.getOrCreateTestableResources()
.addOverride(dimen.rounded_corner_content_padding, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java
new file mode 100644
index 0000000..3b47eae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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 com.android.systemui.statusbar.policy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.CommandQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RemoteInputQuickSettingsDisablerTest extends SysuiTestCase {
+
+ private CommandQueue mCommandQueue;
+ private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mCommandQueue = mock(CommandQueue.class);
+ mContext.putComponent(CommandQueue.class, mCommandQueue);
+
+ mRemoteInputQuickSettingsDisabler = new RemoteInputQuickSettingsDisabler(mContext);
+ }
+
+ @Test
+ public void shouldEnableQuickSetting_afterDeactiviate() {
+ mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE);
+ mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE);
+ assertFalse(mRemoteInputQuickSettingsDisabler.mRemoteInputActive);
+ verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyBoolean());
+ }
+
+ @Test
+ public void shouldDisableQuickSetting_afteActiviate() {
+ mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE);
+ mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE);
+ assertTrue(mRemoteInputQuickSettingsDisabler.mRemoteInputActive);
+ verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyBoolean());
+ }
+
+ @Test
+ public void testChangeToLandscape() {
+ Configuration c = new Configuration(mContext.getResources().getConfiguration());
+ c.orientation = Configuration.ORIENTATION_PORTRAIT;
+ mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
+ c.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
+ assertTrue(mRemoteInputQuickSettingsDisabler.misLandscape);
+ verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyBoolean());
+ }
+
+ @Test
+ public void testChangeToPortrait() {
+ Configuration c = new Configuration(mContext.getResources().getConfiguration());
+ c.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
+ c.orientation = Configuration.ORIENTATION_PORTRAIT;
+ mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
+ assertFalse(mRemoteInputQuickSettingsDisabler.misLandscape);
+ verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyBoolean());
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 938dfca..a6fa4f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -54,6 +54,7 @@
@Mock private RemoteInputController mController;
@Mock private ShortcutManager mShortcutManager;
+ @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private BlockingQueueIntentReceiver mReceiver;
private RemoteInputView mView;
@@ -61,6 +62,9 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mDependency.injectTestDependency(RemoteInputQuickSettingsDisabler.class,
+ mRemoteInputQuickSettingsDisabler);
+
mReceiver = new BlockingQueueIntentReceiver();
mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION), null,
Handler.createAsync(Dependency.get(Dependency.BG_LOOPER)));
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index fa2a0d9..a5311b2 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -53,7 +53,6 @@
import android.view.autofill.AutofillManager;
import android.widget.ImageView;
import android.widget.RemoteViews;
-import android.widget.ScrollView;
import android.widget.TextView;
import com.android.internal.R;
@@ -208,7 +207,7 @@
mSubTitle = info.getDescription();
if (mSubTitle != null) {
writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_SUBTITLE, type);
- final ScrollView subtitleContainer =
+ final ViewGroup subtitleContainer =
view.findViewById(R.id.autofill_save_custom_subtitle);
final TextView subtitleView = new TextView(context);
subtitleView.setText(mSubTitle);
@@ -361,7 +360,7 @@
}
// Finally, add the custom description to the save UI.
- final ScrollView subtitleContainer =
+ final ViewGroup subtitleContainer =
saveUiView.findViewById(R.id.autofill_save_custom_subtitle);
subtitleContainer.addView(customSubtitleView);
subtitleContainer.setVisibility(View.VISIBLE);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index f729cb8..e22a86e 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4674,7 +4674,7 @@
}
private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties oldLp) {
- LinkProperties newLp = networkAgent.linkProperties;
+ LinkProperties newLp = new LinkProperties(networkAgent.linkProperties);
int netId = networkAgent.network.netId;
// The NetworkAgentInfo does not know whether clatd is running on its network or not. Before
@@ -4708,6 +4708,9 @@
}
// TODO - move this check to cover the whole function
if (!Objects.equals(newLp, oldLp)) {
+ synchronized (networkAgent) {
+ networkAgent.linkProperties = newLp;
+ }
notifyIfacesChangedForNetworkStats();
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
}
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 2869114..5a25f48 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -16,6 +16,8 @@
package com.android.server;
+import android.annotation.Nullable;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -47,10 +49,18 @@
import dalvik.system.VMRuntime;
import java.io.FileDescriptor;
+import java.io.Closeable;
+import java.io.InputStream;
+import java.io.DataInputStream;
import java.io.IOException;
+import java.io.EOFException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipException;
+import java.util.zip.ZipEntry;
+
/**
* <p>PinnerService pins important files for key processes in memory.</p>
* <p>Files to pin are specified in the config_defaultPinnerServiceFiles
@@ -60,16 +70,18 @@
public final class PinnerService extends SystemService {
private static final boolean DEBUG = false;
private static final String TAG = "PinnerService";
+ private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); //80MB max
+ private static final String PIN_META_FILENAME = "pinlist.meta";
+ private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
private final Context mContext;
- private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<PinnedFile>();
- private final ArrayList<PinnedFile> mPinnedCameraFiles = new ArrayList<PinnedFile>();
private final boolean mShouldPinCamera;
+ /* These lists protected by PinnerService monitor lock */
+ private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<PinnedFile>();
+ private final ArrayList<PinnedFile> mPinnedCameraFiles = new ArrayList<PinnedFile>();
+
private BinderService mBinderService;
-
- private final long MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); //80MB max
-
private PinnerHandler mPinnerHandler = null;
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -146,18 +158,18 @@
// Files to pin come from the overlay and can be specified per-device config
String[] filesToPin = mContext.getResources().getStringArray(
com.android.internal.R.array.config_defaultPinnerServiceFiles);
- synchronized(this) {
- // Continue trying to pin remaining files even if there is a failure
- for (int i = 0; i < filesToPin.length; i++){
- PinnedFile pf = pinFile(filesToPin[i], 0, 0, 0);
- if (pf != null) {
- mPinnedFiles.add(pf);
- if (DEBUG) {
- Slog.i(TAG, "Pinned file = " + pf.mFilename);
- }
- } else {
- Slog.e(TAG, "Failed to pin file = " + filesToPin[i]);
- }
+ // Continue trying to pin each file even if we fail to pin some of them
+ for (String fileToPin : filesToPin) {
+ PinnedFile pf = pinFile(fileToPin,
+ Integer.MAX_VALUE,
+ /*attemptPinIntrospection=*/false);
+ if (pf == null) {
+ Slog.e(TAG, "Failed to pin file = " + fileToPin);
+ continue;
+ }
+
+ synchronized (this) {
+ mPinnedFiles.add(pf);
}
}
}
@@ -166,44 +178,23 @@
* Handler for camera pinning message
*/
private void handlePinCamera(int userHandle) {
- if (mShouldPinCamera) {
- synchronized(this) {
- boolean success = pinCamera(userHandle);
- if (!success) {
- //this is not necessarily an error
- if (DEBUG) {
- Slog.v(TAG, "Failed to pin camera.");
- }
- }
+ if (!mShouldPinCamera) return;
+ if (!pinCamera(userHandle)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Failed to pin camera.");
}
}
}
- /**
- * determine if the camera app is already pinned by comparing the
- * intent resolution to the pinned files list
- */
- private boolean alreadyPinned(int userHandle) {
- ApplicationInfo cameraInfo = getCameraInfo(userHandle);
- if (cameraInfo == null ) {
- return false;
- }
- for (int i = 0; i < mPinnedCameraFiles.size(); i++) {
- if (mPinnedCameraFiles.get(i).mFilename.equals(cameraInfo.sourceDir)) {
- if (DEBUG) {
- Slog.v(TAG, "Camera is already pinned");
- }
- return true;
- }
- }
- return false;
- }
-
private void unpinCameraApp() {
- for (int i = 0; i < mPinnedCameraFiles.size(); i++) {
- unpinFile(mPinnedCameraFiles.get(i));
+ ArrayList<PinnedFile> pinnedCameraFiles;
+ synchronized (this) {
+ pinnedCameraFiles = new ArrayList<>(mPinnedCameraFiles);
+ mPinnedCameraFiles.clear();
}
- mPinnedCameraFiles.clear();
+ for (PinnedFile pinnedFile : pinnedCameraFiles) {
+ pinnedFile.close();
+ }
}
private boolean isResolverActivity(ActivityInfo info) {
@@ -255,15 +246,19 @@
//pin APK
String camAPK = cameraInfo.sourceDir;
- PinnedFile pf = pinFile(camAPK, 0, 0, MAX_CAMERA_PIN_SIZE);
+ PinnedFile pf = pinFile(camAPK,
+ MAX_CAMERA_PIN_SIZE,
+ /*attemptPinIntrospection=*/true);
if (pf == null) {
Slog.e(TAG, "Failed to pin " + camAPK);
return false;
}
if (DEBUG) {
- Slog.i(TAG, "Pinned " + pf.mFilename);
+ Slog.i(TAG, "Pinned " + pf.fileName);
}
- mPinnedCameraFiles.add(pf);
+ synchronized (this) {
+ mPinnedCameraFiles.add(pf);
+ }
// determine the ABI from either ApplicationInfo or Build
String arch = "arm";
@@ -289,11 +284,13 @@
//not pinning the oat/odex is not a fatal error
for (String file : files) {
- pf = pinFile(file, 0, 0, MAX_CAMERA_PIN_SIZE);
+ pf = pinFile(file, MAX_CAMERA_PIN_SIZE, /*attemptPinIntrospection=*/false);
if (pf != null) {
- mPinnedCameraFiles.add(pf);
+ synchronized (this) {
+ mPinnedCameraFiles.add(pf);
+ }
if (DEBUG) {
- Slog.i(TAG, "Pinned " + pf.mFilename);
+ Slog.i(TAG, "Pinned " + pf.fileName);
}
}
}
@@ -302,70 +299,294 @@
}
- /** mlock length bytes of fileToPin in memory, starting at offset
- * length == 0 means pin from offset to end of file
- * maxSize == 0 means infinite
+ /** mlock length bytes of fileToPin in memory
+ *
+ * If attemptPinIntrospection is true, then treat the file to pin as a zip file and
+ * look for a "pinlist.meta" file in the archive root directory. The structure of this
+ * file is a PINLIST_META as described below:
+ *
+ * <pre>
+ * PINLIST_META: PIN_RANGE*
+ * PIN_RANGE: PIN_START PIN_LENGTH
+ * PIN_START: big endian i32: offset in bytes of pin region from file start
+ * PIN_LENGTH: big endian i32: length of pin region in bytes
+ * </pre>
+ *
+ * (We use big endian because that's what DataInputStream is hardcoded to use.)
+ *
+ * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0,
+ * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file.
+ *
+ * After we open a file, we march through the list of pin ranges and attempt to pin
+ * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last
+ * pinned range to fit.) In this way, by choosing to emit certain PIN_RANGE pairs
+ * before others, file generators can express pins in priority order, making most
+ * effective use of the pinned-page quota.
+ *
+ * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a
+ * meaningful interpretation. Also, a range locking a single byte of a page locks the
+ * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries
+ * are legal, but each pin of a byte counts toward the pin quota regardless of whether
+ * that byte has already been pinned, so the generator of PINLIST_META ought to ensure
+ * that ranges are non-overlapping.
+ *
+ * @param fileToPin Path to file to pin
+ * @param maxBytesToPin Maximum number of bytes to pin
+ * @param attemptPinIntrospection If true, try to open file as a
+ * zip in order to extract the
+ * @return Pinned memory resource owner thing or null on error
*/
- private static PinnedFile pinFile(String fileToPin, long offset, long length, long maxSize) {
- FileDescriptor fd = new FileDescriptor();
+ private static PinnedFile pinFile(String fileToPin,
+ int maxBytesToPin,
+ boolean attemptPinIntrospection) {
+ ZipFile fileAsZip = null;
+ InputStream pinRangeStream = null;
try {
- fd = Os.open(fileToPin,
- OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW,
- OsConstants.O_RDONLY);
-
- StructStat sb = Os.fstat(fd);
-
- if (offset + length > sb.st_size) {
- Os.close(fd);
- Slog.e(TAG, "Failed to pin file " + fileToPin +
- ", request extends beyond end of file. offset + length = "
- + (offset + length) + ", file length = " + sb.st_size);
- return null;
+ if (attemptPinIntrospection) {
+ fileAsZip = maybeOpenZip(fileToPin);
}
- if (length == 0) {
- length = sb.st_size - offset;
+ if (fileAsZip != null) {
+ pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin);
}
- if (maxSize > 0 && length > maxSize) {
- Slog.e(TAG, "Could not pin file " + fileToPin +
- ", size = " + length + ", maxSize = " + maxSize);
- Os.close(fd);
- return null;
- }
+ Slog.d(TAG, "pinRangeStream: " + pinRangeStream);
- long address = Os.mmap(0, length, OsConstants.PROT_READ,
- OsConstants.MAP_PRIVATE, fd, offset);
- Os.close(fd);
-
- Os.mlock(address, length);
-
- return new PinnedFile(address, length, fileToPin);
- } catch (ErrnoException e) {
- Slog.e(TAG, "Could not pin file " + fileToPin + " with error " + e.getMessage());
- if(fd.valid()) {
- try {
- Os.close(fd);
- }
- catch (ErrnoException eClose) {
- Slog.e(TAG, "Failed to close fd, error = " + eClose.getMessage());
- }
- }
- return null;
+ PinRangeSource pinRangeSource = (pinRangeStream != null)
+ ? new PinRangeSourceStream(pinRangeStream)
+ : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */);
+ return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource);
+ } finally {
+ safeClose(pinRangeStream);
+ safeClose(fileAsZip); // Also closes any streams we've opened
}
}
- private static boolean unpinFile(PinnedFile pf) {
+ /**
+ * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the
+ * error, and return null.
+ */
+ private static ZipFile maybeOpenZip(String fileName) {
+ ZipFile zip = null;
try {
- Os.munlock(pf.mAddress, pf.mLength);
- } catch (ErrnoException e) {
- Slog.e(TAG, "Failed to unpin file " + pf.mFilename + " with error " + e.getMessage());
- return false;
+ zip = new ZipFile(fileName);
+ } catch (IOException ex) {
+ Slog.w(TAG,
+ String.format(
+ "could not open \"%s\" as zip: pinning as blob",
+ fileName),
+ ex);
}
- if (DEBUG) {
- Slog.i(TAG, "Unpinned file " + pf.mFilename );
+ return zip;
+ }
+
+ /**
+ * Open a pin metadata file in the zip if one is present.
+ *
+ * @param zipFile Zip file to search
+ * @return Open input stream or null on any error
+ */
+ private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) {
+ ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME);
+ InputStream pinMetaStream = null;
+ if (pinMetaEntry != null) {
+ try {
+ pinMetaStream = zipFile.getInputStream(pinMetaEntry);
+ } catch (IOException ex) {
+ Slog.w(TAG,
+ String.format("error reading pin metadata \"%s\": pinning as blob",
+ fileName),
+ ex);
+ }
}
- return true;
+ return pinMetaStream;
+ }
+
+ private static abstract class PinRangeSource {
+ /** Retrive a range to pin.
+ *
+ * @param outPinRange Receives the pin region
+ * @return True if we filled in outPinRange or false if we're out of pin entries
+ */
+ abstract boolean read(PinRange outPinRange);
+ }
+
+ private static final class PinRangeSourceStatic extends PinRangeSource {
+ private final int mPinStart;
+ private final int mPinLength;
+ private boolean mDone = false;
+
+ PinRangeSourceStatic(int pinStart, int pinLength) {
+ mPinStart = pinStart;
+ mPinLength = pinLength;
+ }
+
+ @Override
+ boolean read(PinRange outPinRange) {
+ outPinRange.start = mPinStart;
+ outPinRange.length = mPinLength;
+ boolean done = mDone;
+ mDone = true;
+ return !done;
+ }
+ }
+
+ private static final class PinRangeSourceStream extends PinRangeSource {
+ private final DataInputStream mStream;
+ private boolean mDone = false;
+
+ PinRangeSourceStream(InputStream stream) {
+ mStream = new DataInputStream(stream);
+ }
+
+ @Override
+ boolean read(PinRange outPinRange) {
+ if (!mDone) {
+ try {
+ outPinRange.start = mStream.readInt();
+ outPinRange.length = mStream.readInt();
+ } catch (IOException ex) {
+ mDone = true;
+ }
+ }
+ return !mDone;
+ }
+ }
+
+ /**
+ * Helper for pinFile.
+ *
+ * @param fileToPin Name of file to pin
+ * @param maxBytesToPin Maximum number of bytes to pin
+ * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes
+ * to pin.
+ * @return PinnedFile or null on error
+ */
+ private static PinnedFile pinFileRanges(
+ String fileToPin,
+ int maxBytesToPin,
+ PinRangeSource pinRangeSource)
+ {
+ FileDescriptor fd = new FileDescriptor();
+ long address = -1;
+ int mapSize = 0;
+
+ try {
+ int openFlags = (OsConstants.O_RDONLY |
+ OsConstants.O_CLOEXEC |
+ OsConstants.O_NOFOLLOW);
+ fd = Os.open(fileToPin, openFlags, 0);
+ mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
+ address = Os.mmap(0, mapSize,
+ OsConstants.PROT_READ,
+ OsConstants.MAP_SHARED,
+ fd, /*offset=*/0);
+
+ PinRange pinRange = new PinRange();
+ int bytesPinned = 0;
+
+ // We pin at page granularity, so make sure the limit is page-aligned
+ if (maxBytesToPin % PAGE_SIZE != 0) {
+ maxBytesToPin -= maxBytesToPin % PAGE_SIZE;
+ }
+
+ while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
+ int pinStart = pinRange.start;
+ int pinLength = pinRange.length;
+ pinStart = clamp(0, pinStart, mapSize);
+ pinLength = clamp(0, pinLength, mapSize - pinStart);
+ pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
+
+ // mlock doesn't require the region to be page-aligned, but we snap the
+ // lock region to page boundaries anyway so that we don't under-count
+ // locking a single byte of a page as a charge of one byte even though the
+ // OS will retain the whole page. Thanks to this adjustment, we slightly
+ // over-count the pin charge of back-to-back pins touching the same page,
+ // but better that than undercounting. Besides: nothing stops pin metafile
+ // creators from making the actual regions page-aligned.
+ pinLength += pinStart % PAGE_SIZE;
+ pinStart -= pinStart % PAGE_SIZE;
+ if (pinLength % PAGE_SIZE != 0) {
+ pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
+ }
+ pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned);
+
+ if (pinLength > 0) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ String.format(
+ "pinning at %s %s bytes of %s",
+ pinStart, pinLength, fileToPin));
+ }
+ Os.mlock(address + pinStart, pinLength);
+ }
+ bytesPinned += pinLength;
+ }
+
+ PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
+ address = -1; // Ownership transferred
+ return pinnedFile;
+ } catch (ErrnoException ex) {
+ Slog.e(TAG, "Could not pin file " + fileToPin, ex);
+ return null;
+ } finally {
+ safeClose(fd);
+ if (address >= 0) {
+ safeMunmap(address, mapSize);
+ }
+ }
+ }
+
+ private static int clamp(int min, int value, int max) {
+ return Math.max(min, Math.min(value, max));
+ }
+
+ private static void safeMunmap(long address, long mapSize) {
+ try {
+ Os.munmap(address, mapSize);
+ } catch (ErrnoException ex) {
+ Slog.w(TAG, "ignoring error in unmap", ex);
+ }
+ }
+
+ /**
+ * Close FD, swallowing irrelevant errors.
+ */
+ private static void safeClose(@Nullable FileDescriptor fd) {
+ if (fd != null && fd.valid()) {
+ try {
+ Os.close(fd);
+ } catch (ErrnoException ex) {
+ // Swallow the exception: non-EBADF errors in close(2)
+ // indicate deferred paging write errors, which we
+ // don't care about here. The underlying file
+ // descriptor is always closed.
+ if (ex.errno == OsConstants.EBADF) {
+ throw new AssertionError(ex);
+ }
+ }
+ }
+ }
+
+ /**
+ * Close closeable thing, swallowing errors.
+ */
+ private static void safeClose(@Nullable Closeable thing) {
+ if (thing != null) {
+ try {
+ thing.close();
+ } catch (IOException ex) {
+ Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
+ }
+ }
+ }
+
+ private synchronized ArrayList<PinnedFile> snapshotPinnedFiles() {
+ int nrPinnedFiles = mPinnedFiles.size() + mPinnedCameraFiles.size();
+ ArrayList<PinnedFile> pinnedFiles = new ArrayList<>(nrPinnedFiles);
+ pinnedFiles.addAll(mPinnedFiles);
+ pinnedFiles.addAll(mPinnedCameraFiles);
+ return pinnedFiles;
}
private final class BinderService extends Binder {
@@ -373,31 +594,44 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
long totalSize = 0;
- pw.println("Pinned Files:");
- synchronized(this) {
- for (int i = 0; i < mPinnedFiles.size(); i++) {
- pw.println(mPinnedFiles.get(i).mFilename);
- totalSize += mPinnedFiles.get(i).mLength;
- }
- for (int i = 0; i < mPinnedCameraFiles.size(); i++) {
- pw.println(mPinnedCameraFiles.get(i).mFilename);
- totalSize += mPinnedCameraFiles.get(i).mLength;
- }
+ for (PinnedFile pinnedFile : snapshotPinnedFiles()) {
+ pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned);
+ totalSize += pinnedFile.bytesPinned;
}
- pw.println("Total size: " + totalSize);
+ pw.format("Total size: %s\n", totalSize);
}
}
- private static class PinnedFile {
- long mAddress;
- long mLength;
- String mFilename;
+ private static final class PinnedFile implements AutoCloseable {
+ private long mAddress;
+ final int mapSize;
+ final String fileName;
+ final int bytesPinned;
- PinnedFile(long address, long length, String filename) {
+ PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
mAddress = address;
- mLength = length;
- mFilename = filename;
+ this.mapSize = mapSize;
+ this.fileName = fileName;
+ this.bytesPinned = bytesPinned;
}
+
+ @Override
+ public void close() {
+ if (mAddress >= 0) {
+ safeMunmap(mAddress, mapSize);
+ mAddress = -1;
+ }
+ }
+
+ @Override
+ public void finalize() {
+ close();
+ }
+ }
+
+ final static class PinRange {
+ int start;
+ int length;
}
final class PinnerHandler extends Handler {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 113f452..98bf740 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15952,7 +15952,10 @@
}
@Override
- public void getMyMemoryState(ActivityManager.RunningAppProcessInfo outInfo) {
+ public void getMyMemoryState(ActivityManager.RunningAppProcessInfo outState) {
+ if (outState == null) {
+ throw new IllegalArgumentException("outState is null");
+ }
enforceNotIsolatedCaller("getMyMemoryState");
final int callingUid = Binder.getCallingUid();
@@ -15963,7 +15966,9 @@
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(Binder.getCallingPid());
}
- fillInProcMemInfo(proc, outInfo, clientTargetSdk);
+ if (proc != null) {
+ fillInProcMemInfo(proc, outState, clientTargetSdk);
+ }
}
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 4521d3a..9b9a380 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -19,7 +19,11 @@
import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
import static android.net.CaptivePortal.APP_RETURN_UNWANTED;
import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
+import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
+import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
+import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -35,6 +39,7 @@
import android.net.TrafficStats;
import android.net.Uri;
import android.net.captiveportal.CaptivePortalProbeResult;
+import android.net.captiveportal.CaptivePortalProbeSpec;
import android.net.dns.ResolvUtil;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
@@ -63,6 +68,7 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -259,6 +265,8 @@
private final URL mCaptivePortalHttpsUrl;
private final URL mCaptivePortalHttpUrl;
private final URL[] mCaptivePortalFallbackUrls;
+ @Nullable
+ private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
@VisibleForTesting
protected boolean mIsCaptivePortalCheckEnabled;
@@ -334,6 +342,7 @@
mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(settings, context));
mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
+ mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
start();
}
@@ -542,8 +551,12 @@
sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response);
}
}));
- intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL,
- mLastPortalProbeResult.detectUrl);
+ final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
+ intent.putExtra(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl);
+ if (probeRes.probeSpec != null) {
+ final String encodedSpec = probeRes.probeSpec.getEncodedSpec();
+ intent.putExtra(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec);
+ }
intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT,
mCaptivePortalUserAgent);
intent.setFlags(
@@ -782,8 +795,9 @@
private void resolveStrictModeHostname() {
try {
// Do a blocking DNS resolution using the network-assigned nameservers.
+ // Do not set AI_ADDRCONFIG in ai_flags so we get all address families in advance.
final InetAddress[] ips = ResolvUtil.blockingResolveAllLocally(
- mNetwork, mPrivateDnsProviderHostname);
+ mNetwork, mPrivateDnsProviderHostname, 0 /* aiFlags */);
mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
} catch (UnknownHostException uhe) {
mPrivateDnsConfig = null;
@@ -882,23 +896,47 @@
}
private URL[] makeCaptivePortalFallbackUrls() {
- String separator = ",";
- String firstUrl = mSettings.getSetting(mContext,
- Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, DEFAULT_FALLBACK_URL);
- String joinedUrls = firstUrl + separator + mSettings.getSetting(mContext,
- Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS, DEFAULT_OTHER_FALLBACK_URLS);
- List<URL> urls = new ArrayList<>();
- for (String s : joinedUrls.split(separator)) {
- URL u = makeURL(s);
- if (u == null) {
- continue;
+ try {
+ String separator = ",";
+ String firstUrl = mSettings.getSetting(mContext,
+ Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, DEFAULT_FALLBACK_URL);
+ String joinedUrls = firstUrl + separator + mSettings.getSetting(mContext,
+ Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS,
+ DEFAULT_OTHER_FALLBACK_URLS);
+ List<URL> urls = new ArrayList<>();
+ for (String s : joinedUrls.split(separator)) {
+ URL u = makeURL(s);
+ if (u == null) {
+ continue;
+ }
+ urls.add(u);
}
- urls.add(u);
+ if (urls.isEmpty()) {
+ Log.e(TAG, String.format("could not create any url from %s", joinedUrls));
+ }
+ return urls.toArray(new URL[urls.size()]);
+ } catch (Exception e) {
+ // Don't let a misconfiguration bootloop the system.
+ Log.e(TAG, "Error parsing configured fallback URLs", e);
+ return new URL[0];
}
- if (urls.isEmpty()) {
- Log.e(TAG, String.format("could not create any url from %s", joinedUrls));
+ }
+
+ private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs() {
+ try {
+ final String settingsValue = mSettings.getSetting(
+ mContext, Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null);
+ // Probe specs only used if configured in settings
+ if (TextUtils.isEmpty(settingsValue)) {
+ return null;
+ }
+
+ return CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue);
+ } catch (Exception e) {
+ // Don't let a misconfiguration bootloop the system.
+ Log.e(TAG, "Error parsing configured fallback probe specs", e);
+ return null;
}
- return urls.toArray(new URL[urls.size()]);
}
private String getCaptivePortalUserAgent() {
@@ -915,6 +953,15 @@
return mCaptivePortalFallbackUrls[idx];
}
+ private CaptivePortalProbeSpec nextFallbackSpec() {
+ if (ArrayUtils.isEmpty(mCaptivePortalFallbackSpecs)) {
+ return null;
+ }
+ // Randomly change spec without memory. Also randomize the first attempt.
+ final int idx = Math.abs(new Random().nextInt()) % mCaptivePortalFallbackSpecs.length;
+ return mCaptivePortalFallbackSpecs[idx];
+ }
+
@VisibleForTesting
protected CaptivePortalProbeResult isCaptivePortal() {
if (!mIsCaptivePortalCheckEnabled) {
@@ -985,7 +1032,7 @@
// unnecessary resolution.
final String host = (proxy != null) ? proxy.getHost() : url.getHost();
sendDnsProbe(host);
- return sendHttpProbe(url, probeType);
+ return sendHttpProbe(url, probeType, null);
}
/** Do a DNS resolution of the given server. */
@@ -1021,7 +1068,8 @@
* @return a CaptivePortalProbeResult inferred from the HTTP response.
*/
@VisibleForTesting
- protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType) {
+ protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType,
+ @Nullable CaptivePortalProbeSpec probeSpec) {
HttpURLConnection urlConnection = null;
int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
String redirectUrl = null;
@@ -1093,7 +1141,12 @@
TrafficStats.setThreadStatsTag(oldTag);
}
logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
- return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString());
+
+ if (probeSpec == null) {
+ return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString());
+ } else {
+ return probeSpec.getResult(httpResponseCode, redirectUrl);
+ }
}
private CaptivePortalProbeResult sendParallelHttpProbes(
@@ -1156,11 +1209,12 @@
if (httpsResult.isPortal() || httpsResult.isSuccessful()) {
return httpsResult;
}
- // If a fallback url exists, use a fallback probe to try again portal detection.
- URL fallbackUrl = nextFallbackUrl();
+ // If a fallback method exists, use it to retry portal detection.
+ // If we have new-style probe specs, use those. Otherwise, use the fallback URLs.
+ final CaptivePortalProbeSpec probeSpec = nextFallbackSpec();
+ final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl();
if (fallbackUrl != null) {
- CaptivePortalProbeResult result =
- sendHttpProbe(fallbackUrl, ValidationProbeEvent.PROBE_FALLBACK);
+ CaptivePortalProbeResult result = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec);
if (result.isPortal()) {
return result;
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 5bd7c0b..ab482bb 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -2706,6 +2706,9 @@
private void normalizePoliciesNL(NetworkPolicy[] policies) {
mNetworkPolicy.clear();
for (NetworkPolicy policy : policies) {
+ if (policy == null) {
+ continue;
+ }
// When two normalized templates conflict, prefer the most
// restrictive policy
policy.template = NetworkTemplate.normalize(policy.template, mMergedSubscriberIds);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e2e6f6d..8f7fa1b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -821,6 +821,7 @@
}
}
r.setVisibility(true, nv.rank, nv.count);
+ maybeRecordInterruptionLocked(r);
nv.recycle();
}
// Note that we might receive this event after notifications
@@ -1928,11 +1929,12 @@
@GuardedBy("mNotificationLock")
protected void maybeRecordInterruptionLocked(NotificationRecord r) {
- if (r.isInterruptive()) {
+ if (r.isInterruptive() && !r.hasRecordedInterruption()) {
mAppUsageStats.reportInterruptiveNotification(r.sbn.getPackageName(),
r.getChannel().getId(),
getRealUserId(r.sbn.getUserId()));
logRecentLocked(r);
+ r.setRecordedInterruption(true);
}
}
@@ -2669,6 +2671,7 @@
if (DBG) Slog.d(TAG, "Marking notification as seen " + keys[i]);
reportSeen(r);
r.setSeen();
+ maybeRecordInterruptionLocked(r);
}
}
}
@@ -4440,7 +4443,7 @@
if (index < 0) {
mNotificationList.add(r);
mUsageStats.registerPostedByApp(r);
- r.setInterruptive(isVisuallyInterruptive(null, r));
+ r.setInterruptive(true);
} else {
old = mNotificationList.get(index);
mNotificationList.set(index, r);
@@ -4449,7 +4452,7 @@
notification.flags |=
old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
r.isUpdate = true;
- r.setInterruptive(isVisuallyInterruptive(old, r));
+ r.setTextChanged(isVisuallyInterruptive(old, r));
}
mNotificationsByKey.put(n.getKey(), r);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 2aec3ea..0f3f44e 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -158,6 +158,8 @@
private final NotificationStats mStats;
private int mUserSentiment;
private boolean mIsInterruptive;
+ private boolean mTextChanged;
+ private boolean mRecordedInterruption;
private int mNumberOfSmartRepliesAdded;
private boolean mHasSeenSmartReplies;
/**
@@ -839,6 +841,9 @@
/** Mark the notification as seen by the user. */
public void setSeen() {
mStats.setSeen();
+ if (mTextChanged) {
+ mIsInterruptive = true;
+ }
}
public void setAuthoritativeRank(int authoritativeRank) {
@@ -935,6 +940,18 @@
mIsInterruptive = interruptive;
}
+ public void setTextChanged(boolean textChanged) {
+ mTextChanged = textChanged;
+ }
+
+ public void setRecordedInterruption(boolean recorded) {
+ mRecordedInterruption = recorded;
+ }
+
+ public boolean hasRecordedInterruption() {
+ return mRecordedInterruption;
+ }
+
public boolean isInterruptive() {
return mIsInterruptive;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f4853f3..df679ae 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -24384,6 +24384,9 @@
if (info.targetSdkVersion < Build.VERSION_CODES.O) {
return false;
}
+ if (isInstantApp(packageName, userId)) {
+ return false;
+ }
String appOpPermission = Manifest.permission.REQUEST_INSTALL_PACKAGES;
String[] packagesDeclaringPermission = getAppOpPermissionPackages(appOpPermission);
if (!ArrayUtils.contains(packagesDeclaringPermission, packageName)) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index dd0a5d2..06c56a0 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -873,9 +873,8 @@
throw new SecurityException("MANAGE_USERS permission is required to start intent "
+ "after disabling quiet mode.");
}
- final boolean hasModifyQuietModePermission = ActivityManager.checkComponentPermission(
- Manifest.permission.MODIFY_QUIET_MODE,
- callingUid, -1, true) == PackageManager.PERMISSION_GRANTED;
+ final boolean hasModifyQuietModePermission = hasPermissionGranted(
+ Manifest.permission.MODIFY_QUIET_MODE, callingUid);
if (hasModifyQuietModePermission) {
return;
}
@@ -1002,6 +1001,30 @@
}
}
+ @Override
+ public void setUserAdmin(int userId) {
+ checkManageUserAndAcrossUsersFullPermission("set user admin");
+
+ synchronized (mPackagesLock) {
+ UserInfo info;
+ synchronized (mUsersLock) {
+ info = getUserInfoLU(userId);
+ }
+ if (info == null || info.isAdmin()) {
+ // Exit if no user found with that id, or the user is already an Admin.
+ return;
+ }
+
+ info.flags ^= UserInfo.FLAG_ADMIN;
+ writeUserLP(getUserDataLU(info.id));
+ }
+
+ // Remove non-admin restrictions.
+ // Keep synchronized with createUserEvenWhenDisallowed.
+ setUserRestriction(UserManager.DISALLOW_SMS, false, userId);
+ setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, false, userId);
+ }
+
/**
* Evicts a user's CE key by stopping and restarting the user.
*
@@ -1122,8 +1145,8 @@
hasManageUsersPermission()) {
return;
}
- if (ActivityManager.checkComponentPermission(Manifest.permission.INTERACT_ACROSS_USERS,
- Binder.getCallingUid(), -1, true) != PackageManager.PERMISSION_GRANTED) {
+ if (!hasPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS,
+ Binder.getCallingUid())) {
throw new SecurityException("You need INTERACT_ACROSS_USERS or MANAGE_USERS permission "
+ "to: check " + name);
}
@@ -1801,17 +1824,26 @@
*/
private static final void checkManageUserAndAcrossUsersFullPermission(String message) {
final int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID && uid != 0
- && ActivityManager.checkComponentPermission(
- Manifest.permission.MANAGE_USERS,
- uid, -1, true) != PackageManager.PERMISSION_GRANTED
- && ActivityManager.checkComponentPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(
- "You need MANAGE_USERS and INTERACT_ACROSS_USERS_FULL permission to: "
- + message);
+
+ if (uid == Process.SYSTEM_UID || uid == 0) {
+ // System UID or root's UID are granted privilege.
+ return;
}
+
+ if (hasPermissionGranted(Manifest.permission.MANAGE_USERS, uid)
+ && hasPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid)) {
+ // Apps with both permissions are granted privilege.
+ return;
+ }
+
+ throw new SecurityException(
+ "You need MANAGE_USERS and INTERACT_ACROSS_USERS_FULL permission to: " + message);
+ }
+
+ private static boolean hasPermissionGranted(String permission, int uid) {
+ return ActivityManager.checkComponentPermission(
+ permission, uid, /* owningUid = */-1, /* exported = */ true) ==
+ PackageManager.PERMISSION_GRANTED;
}
/**
@@ -1872,9 +1904,7 @@
final int callingUid = Binder.getCallingUid();
return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
|| callingUid == Process.ROOT_UID
- || ActivityManager.checkComponentPermission(
- android.Manifest.permission.MANAGE_USERS,
- callingUid, -1, true) == PackageManager.PERMISSION_GRANTED;
+ || hasPermissionGranted(android.Manifest.permission.MANAGE_USERS, callingUid);
}
/**
@@ -1886,12 +1916,8 @@
final int callingUid = Binder.getCallingUid();
return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
|| callingUid == Process.ROOT_UID
- || ActivityManager.checkComponentPermission(
- android.Manifest.permission.MANAGE_USERS,
- callingUid, -1, true) == PackageManager.PERMISSION_GRANTED
- || ActivityManager.checkComponentPermission(
- android.Manifest.permission.CREATE_USERS,
- callingUid, -1, true) == PackageManager.PERMISSION_GRANTED;
+ || hasPermissionGranted(android.Manifest.permission.MANAGE_USERS, callingUid)
+ || hasPermissionGranted(android.Manifest.permission.CREATE_USERS, callingUid);
}
/**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d911761..61400da 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -234,7 +234,6 @@
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayCutout;
-import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.IApplicationToken;
@@ -2292,6 +2291,11 @@
public void onTrustedChanged() {
mWindowManagerFuncs.notifyKeyguardTrustedChanged();
}
+
+ @Override
+ public void onShowingChanged() {
+ mWindowManagerFuncs.onKeyguardShowingAndNotOccludedChanged();
+ }
});
mScreenshotHelper = new ScreenshotHelper(mContext);
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 33eafb9..02dd6e6 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -660,6 +660,11 @@
* abort animations that have no timeout, in case they got stuck.
*/
void triggerAnimationFailsafe();
+
+ /**
+ * The keyguard showing state has changed
+ */
+ void onKeyguardShowingAndNotOccludedChanged();
}
/** Window has been added to the screen. */
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
index e56caf8..1cba1c7 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -94,6 +94,7 @@
public void onShowingStateChanged(boolean showing) {
mIsShowing = showing;
+ mCallback.onShowingChanged();
try {
mKeystoreService.onKeyguardVisibilityChanged(showing, mCurrentUserId);
} catch (RemoteException e) {
@@ -132,6 +133,7 @@
public interface StateCallback {
void onTrustedChanged();
+ void onShowingChanged();
}
public void dump(String prefix, PrintWriter pw) {
diff --git a/services/core/java/com/android/server/updates/ApnDbInstallReceiver.java b/services/core/java/com/android/server/updates/ApnDbInstallReceiver.java
index 3b6f9d6..8d53143 100644
--- a/services/core/java/com/android/server/updates/ApnDbInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/ApnDbInstallReceiver.java
@@ -29,7 +29,7 @@
"update_db");
public ApnDbInstallReceiver() {
- super("/data/misc/", "apns-conf.xml", "metadata/", "version");
+ super("/data/misc/apns/", "apns-conf.xml", "metadata/", "version");
}
@Override
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index a7c203d..2886126 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -51,6 +51,7 @@
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
@@ -75,6 +76,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.service.wallpaper.IWallpaperConnection;
import android.service.wallpaper.IWallpaperEngine;
import android.service.wallpaper.IWallpaperService;
@@ -336,6 +338,102 @@
}
}
+ /**
+ * Observes changes of theme settings. It will check whether to call
+ * notifyWallpaperColorsChanged by the current theme and updated theme.
+ * The light theme and dark theme are controlled by the hint values in Wallpaper colors,
+ * threrfore, if light theme mode is chosen, HINT_SUPPORTS_DARK_THEME in hint will be
+ * removed and then notify listeners.
+ */
+ private class ThemeSettingsObserver extends ContentObserver {
+
+ public ThemeSettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void startObserving(Context context) {
+ context.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.THEME_MODE),
+ false,
+ this);
+ }
+
+ public void stopObserving(Context context) {
+ context.getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onThemeSettingsChanged();
+ }
+ }
+
+ /**
+ * Check whether to call notifyWallpaperColorsChanged. Assumed that the theme mode
+ * was wallpaper theme mode and dark wallpaper was set, therefoe, the theme was dark.
+ * Then theme mode changing to dark theme mode, however, theme should not update since
+ * theme was dark already.
+ */
+ private boolean needUpdateLocked(WallpaperColors colors, int themeMode) {
+ if (colors == null) {
+ return false;
+ }
+
+ if (themeMode == mThemeMode) {
+ return false;
+ }
+
+ boolean result = true;
+ boolean supportDarkTheme =
+ (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
+ switch (themeMode) {
+ case Settings.Secure.THEME_MODE_WALLPAPER:
+ if (mThemeMode == Settings.Secure.THEME_MODE_LIGHT) {
+ result = supportDarkTheme;
+ } else {
+ result = !supportDarkTheme;
+ }
+ break;
+ case Settings.Secure.THEME_MODE_LIGHT:
+ if (mThemeMode == Settings.Secure.THEME_MODE_WALLPAPER) {
+ result = supportDarkTheme;
+ }
+ break;
+ case Settings.Secure.THEME_MODE_DARK:
+ if (mThemeMode == Settings.Secure.THEME_MODE_WALLPAPER) {
+ result = !supportDarkTheme;
+ }
+ break;
+ default:
+ Slog.w(TAG, "unkonwn theme mode " + themeMode);
+ return false;
+ }
+ mThemeMode = themeMode;
+ return result;
+ }
+
+ void onThemeSettingsChanged() {
+ WallpaperData wallpaper;
+ synchronized (mLock) {
+ wallpaper = mWallpaperMap.get(mCurrentUserId);
+ int updatedThemeMode = Settings.Secure.getInt(
+ mContext.getContentResolver(), Settings.Secure.THEME_MODE,
+ Settings.Secure.THEME_MODE_WALLPAPER);
+
+ if (DEBUG) {
+ Slog.v(TAG, "onThemeSettingsChanged, mode = " + updatedThemeMode);
+ }
+
+ if (!needUpdateLocked(wallpaper.primaryColors, updatedThemeMode)) {
+ return;
+ }
+ }
+
+ if (wallpaper != null) {
+ notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM);
+ }
+ }
+
void notifyLockWallpaperChanged() {
final IWallpaperManagerCallback cb = mKeyguardListener;
if (cb != null) {
@@ -413,6 +511,7 @@
}
userAllColorListeners.finishBroadcast();
}
+ wallpaperColors = getThemeColorsLocked(wallpaperColors);
}
final int count = colorListeners.size();
@@ -481,6 +580,40 @@
}
/**
+ * We can easily change theme by modified colors hint. This function will check
+ * current theme mode and return the WallpaperColors fit current theme mode.
+ * If color need modified, it will return a copied WallpaperColors which
+ * its ColorsHint is modified to fit current theme mode.
+ *
+ * @param colors a wallpaper primary colors representation
+ */
+ private WallpaperColors getThemeColorsLocked(WallpaperColors colors) {
+ if (colors == null) {
+ Slog.w(TAG, "Cannot get theme colors because WallpaperColors is null.");
+ return null;
+ }
+
+ int colorHints = colors.getColorHints();
+ boolean supportDarkTheme = (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
+ if (mThemeMode == Settings.Secure.THEME_MODE_WALLPAPER ||
+ (mThemeMode == Settings.Secure.THEME_MODE_LIGHT && !supportDarkTheme) ||
+ (mThemeMode == Settings.Secure.THEME_MODE_DARK && supportDarkTheme)) {
+ return colors;
+ }
+
+ WallpaperColors themeColors = new WallpaperColors(colors.getPrimaryColor(),
+ colors.getSecondaryColor(), colors.getTertiaryColor());
+
+ if (mThemeMode == Settings.Secure.THEME_MODE_LIGHT) {
+ colorHints &= ~WallpaperColors.HINT_SUPPORTS_DARK_THEME;
+ } else if (mThemeMode == Settings.Secure.THEME_MODE_DARK) {
+ colorHints |= WallpaperColors.HINT_SUPPORTS_DARK_THEME;
+ }
+ themeColors.setColorHints(colorHints);
+ return themeColors;
+ }
+
+ /**
* Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
* for display.
*/
@@ -676,6 +809,7 @@
final SparseArray<Boolean> mUserRestorecon = new SparseArray<Boolean>();
int mCurrentUserId = UserHandle.USER_NULL;
boolean mInAmbientMode;
+ int mThemeMode;
static class WallpaperData {
@@ -734,6 +868,7 @@
long lastDiedTime;
boolean wallpaperUpdating;
WallpaperObserver wallpaperObserver;
+ ThemeSettingsObserver themeSettingsObserver;
/**
* List of callbacks registered they should each be notified when the wallpaper is changed.
@@ -1279,6 +1414,10 @@
wallpaper.wallpaperObserver.stopWatching();
wallpaper.wallpaperObserver = null;
}
+ if (wallpaper.themeSettingsObserver != null) {
+ wallpaper.themeSettingsObserver.stopObserving(mContext);
+ wallpaper.themeSettingsObserver = null;
+ }
}
}
@@ -1362,6 +1501,13 @@
systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper);
systemWallpaper.wallpaperObserver.startWatching();
}
+ if (systemWallpaper.themeSettingsObserver == null) {
+ systemWallpaper.themeSettingsObserver = new ThemeSettingsObserver(null);
+ systemWallpaper.themeSettingsObserver.startObserving(mContext);
+ }
+ mThemeMode = Settings.Secure.getInt(
+ mContext.getContentResolver(), Settings.Secure.THEME_MODE,
+ Settings.Secure.THEME_MODE_WALLPAPER);
switchWallpaper(systemWallpaper, reply);
}
@@ -1835,7 +1981,7 @@
}
synchronized (mLock) {
- return wallpaperData.primaryColors;
+ return getThemeColorsLocked(wallpaperData.primaryColors);
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 50d0d0a..a709c55 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -157,10 +157,18 @@
}
WindowState computeFocusedWindow() {
+ // While the keyguard is showing, we must focus anything besides the main display.
+ // Otherwise we risk input not going to the keyguard when the user expects it to.
+ final boolean forceDefaultDisplay = mService.isKeyguardShowingAndNotOccluded();
+
for (int i = mChildren.size() - 1; i >= 0; i--) {
final DisplayContent dc = mChildren.get(i);
final WindowState win = dc.findFocusedWindow();
if (win != null) {
+ if (forceDefaultDisplay && !dc.isDefaultDisplay) {
+ EventLog.writeEvent(0x534e4554, "71786287", win.mOwnerUid, "");
+ continue;
+ }
return win;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cb64142..f42f855 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2846,6 +2846,11 @@
mH.sendEmptyMessage(H.ANIMATION_FAILSAFE);
}
+ @Override
+ public void onKeyguardShowingAndNotOccludedChanged() {
+ mH.sendEmptyMessage(H.RECOMPUTE_FOCUS);
+ }
+
/**
* Starts deferring layout passes. Useful when doing multiple changes but to optimize
* performance, only one layout pass should be done. This can be called multiple times, and
@@ -4626,6 +4631,7 @@
public static final int SET_HAS_OVERLAY_UI = 58;
public static final int SET_RUNNING_REMOTE_ANIMATION = 59;
public static final int ANIMATION_FAILSAFE = 60;
+ public static final int RECOMPUTE_FOCUS = 61;
/**
* Used to denote that an integer field in a message will not be used.
@@ -5052,6 +5058,13 @@
}
}
break;
+ case RECOMPUTE_FOCUS: {
+ synchronized (mWindowMap) {
+ updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+ true /* updateInputWindows */);
+ }
+ }
+ break;
}
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG_WM, "handleMessage: exit");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index dac85b3..c797d8d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2298,8 +2298,8 @@
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG,
"setAnimationLocked: setting mFocusMayChange true");
mService.mFocusMayChange = true;
- setDisplayLayoutNeeded();
}
+ setDisplayLayoutNeeded();
// Window is no longer visible -- make sure if we were waiting
// for it to be displayed before enabling the display, that
// we allow the display to be enabled now.
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 70287db..d247960 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -644,6 +644,11 @@
? null : wallpaperTarget;
final ArraySet<AppWindowToken> openingApps = mService.mOpeningApps;
final ArraySet<AppWindowToken> closingApps = mService.mClosingApps;
+ final AppWindowToken topOpeningApp = getTopApp(mService.mOpeningApps,
+ false /* ignoreHidden */);
+ final AppWindowToken topClosingApp = getTopApp(mService.mClosingApps,
+ true /* ignoreHidden */);
+
boolean openingCanBeWallpaperTarget = canBeWallpaperTarget(openingApps);
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New wallpaper target=" + wallpaperTarget
@@ -677,13 +682,15 @@
"New transit: " + AppTransition.appTransitionToString(transit));
} else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty()
&& !openingApps.contains(oldWallpaper.mAppToken)
- && closingApps.contains(oldWallpaper.mAppToken)) {
+ && closingApps.contains(oldWallpaper.mAppToken)
+ && topClosingApp == oldWallpaper.mAppToken) {
// We are transitioning from an activity with a wallpaper to one without.
transit = TRANSIT_WALLPAPER_CLOSE;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: "
+ AppTransition.appTransitionToString(transit));
} else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw()
&& openingApps.contains(wallpaperTarget.mAppToken)
+ && topOpeningApp == wallpaperTarget.mAppToken
&& transit != TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE) {
// We are transitioning from an activity without
// a wallpaper to now showing the wallpaper
@@ -748,6 +755,31 @@
return false;
}
+ /**
+ * Finds the top app in a list of apps, using its {@link AppWindowToken#getPrefixOrderIndex} to
+ * compare z-order.
+ *
+ * @param apps The list of apps to search.
+ * @param ignoreHidden If set to true, ignores apps that are {@link AppWindowToken#isHidden}.
+ * @return The top {@link AppWindowToken}.
+ */
+ private AppWindowToken getTopApp(ArraySet<AppWindowToken> apps, boolean ignoreHidden) {
+ int topPrefixOrderIndex = Integer.MIN_VALUE;
+ AppWindowToken topApp = null;
+ for (int i = apps.size() - 1; i >= 0; i--) {
+ final AppWindowToken app = apps.valueAt(i);
+ if (ignoreHidden && app.isHidden()) {
+ continue;
+ }
+ final int prefixOrderIndex = app.getPrefixOrderIndex();
+ if (prefixOrderIndex > topPrefixOrderIndex) {
+ topPrefixOrderIndex = prefixOrderIndex;
+ topApp = app;
+ }
+ }
+ return topApp;
+ }
+
private void processApplicationsAnimatingInPlace(int transit) {
if (transit == TRANSIT_TASK_IN_PLACE) {
// Find the focused window
diff --git a/services/net/java/android/net/dns/ResolvUtil.java b/services/net/java/android/net/dns/ResolvUtil.java
index a2a6615..d9d4b96 100644
--- a/services/net/java/android/net/dns/ResolvUtil.java
+++ b/services/net/java/android/net/dns/ResolvUtil.java
@@ -16,6 +16,8 @@
package android.net.dns;
+import static android.system.OsConstants.AI_ADDRCONFIG;
+
import android.net.Network;
import android.net.NetworkUtils;
import android.system.GaiException;
@@ -41,12 +43,17 @@
public static InetAddress[] blockingResolveAllLocally(Network network, String name)
throws UnknownHostException {
+ // Use AI_ADDRCONFIG by default
+ return blockingResolveAllLocally(network, name, AI_ADDRCONFIG);
+ }
+
+ public static InetAddress[] blockingResolveAllLocally(
+ Network network, String name, int aiFlags) throws UnknownHostException {
final StructAddrinfo hints = new StructAddrinfo();
- // Unnecessary, but expressly no AI_ADDRCONFIG.
- hints.ai_flags = 0;
- // Fetch all IP addresses at once to minimize re-resolution.
+ hints.ai_flags = aiFlags;
+ // Other hints identical to the default Inet6AddressImpl implementation
hints.ai_family = OsConstants.AF_UNSPEC;
- hints.ai_socktype = OsConstants.SOCK_DGRAM;
+ hints.ai_socktype = OsConstants.SOCK_STREAM;
final Network networkForResolv = getNetworkWithUseLocalNameserversFlag(network);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index b4dc941..17babe9 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -1628,6 +1628,20 @@
}
}
+ /**
+ * Test that policy set of {null, NetworkPolicy, null} does not crash and restores the valid
+ * NetworkPolicy.
+ */
+ @Test
+ public void testSetNetworkPolicies_withNullPolicies_doesNotThrow() {
+ NetworkPolicy[] policies = new NetworkPolicy[3];
+ policies[1] = buildDefaultFakeMobilePolicy();
+ setNetworkPolicies(policies);
+
+ assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes,
+ true);
+ }
+
private SubscriptionPlan buildMonthlyDataPlan(ZonedDateTime start, long limitBytes) {
return SubscriptionPlan.Builder
.createRecurringMonthly(start)
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index dd9a8ab..7bcb571 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -170,6 +170,31 @@
}
@MediumTest
+ public void testSetUserAdmin() throws Exception {
+ UserInfo userInfo = createUser("SecondaryUser", /*flags=*/ 0);
+
+ // Assert user is not admin and has SMS and calls restrictions.
+ assertFalse(userInfo.isAdmin());
+ assertTrue(mUserManager.hasUserRestriction(UserManager.DISALLOW_SMS,
+ userInfo.getUserHandle()));
+ assertTrue(mUserManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ userInfo.getUserHandle()));
+
+ // Assign admin privileges.
+ mUserManager.setUserAdmin(userInfo.id);
+
+ // Refresh.
+ userInfo = mUserManager.getUserInfo(userInfo.id);
+
+ // Verify user became admin and SMS and call restrictions are lifted.
+ assertTrue(userInfo.isAdmin());
+ assertFalse(mUserManager.hasUserRestriction(UserManager.DISALLOW_SMS,
+ userInfo.getUserHandle()));
+ assertFalse(mUserManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ userInfo.getUserHandle()));
+ }
+
+ @MediumTest
public void testGetProfileParent() throws Exception {
final int primaryUserId = mUserManager.getPrimaryUser().id;
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 845095a..ac196f9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -329,6 +329,21 @@
assertEquals(window1, sWm.mRoot.computeFocusedWindow());
}
+ @Test
+ public void testKeyguard_preventsSecondaryDisplayFocus() throws Exception {
+ final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR,
+ sWm.getDefaultDisplayContentLocked(), "keyguard");
+ assertEquals(keyguard, sWm.mRoot.computeFocusedWindow());
+
+ // Add a window to a second display, and it should be focused
+ final DisplayContent dc = createNewDisplay();
+ final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
+ assertEquals(win, sWm.mRoot.computeFocusedWindow());
+
+ mWmRule.getWindowManagerPolicy().keyguardShowingAndNotOccluded = true;
+ assertEquals(keyguard, sWm.mRoot.computeFocusedWindow());
+ }
+
/**
* This tests setting the maximum ui width on a display.
*/
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 765b3d5..50f3fbe 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -51,6 +51,7 @@
private final Supplier<WindowManagerService> mWmSupplier;
int rotationToReport = 0;
+ boolean keyguardShowingAndNotOccluded = false;
private Runnable mRunnableWhenAddingSplashScreen;
@@ -338,7 +339,7 @@
@Override
public boolean isKeyguardLocked() {
- return false;
+ return keyguardShowingAndNotOccluded;
}
@Override
@@ -358,7 +359,7 @@
@Override
public boolean isKeyguardShowingAndNotOccluded() {
- return false;
+ return keyguardShowingAndNotOccluded;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRule.java b/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRule.java
index 32bfda32..d91079e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRule.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRule.java
@@ -59,6 +59,7 @@
public class WindowManagerServiceRule implements TestRule {
private WindowManagerService mService;
+ private TestWindowManagerPolicy mPolicy;
@Override
public Statement apply(Statement base, Description description) {
@@ -108,7 +109,7 @@
}
mService = WindowManagerService.main(context, ims, true, false,
- false, new TestWindowManagerPolicy(
+ false, mPolicy = new TestWindowManagerPolicy(
WindowManagerServiceRule.this::getWindowManagerService));
mService.onInitReady();
@@ -132,6 +133,7 @@
waitUntilWindowManagerHandlersIdle();
removeServices();
mService = null;
+ mPolicy = null;
}
};
}
@@ -140,6 +142,10 @@
return mService;
}
+ public TestWindowManagerPolicy getWindowManagerPolicy() {
+ return mPolicy;
+ }
+
public void waitUntilWindowManagerHandlersIdle() {
final WindowManagerService wm = getWindowManagerService();
if (wm != null) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3dde7f1..41b4718 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -91,10 +91,12 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.MediaStore;
import android.provider.Settings.Secure;
import android.service.notification.Adjustment;
+import android.service.notification.INotificationListener;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.NotifyingApp;
@@ -160,6 +162,8 @@
@Mock
private NotificationUsageStats mUsageStats;
@Mock
+ private UsageStatsManagerInternal mAppUsageStats;
+ @Mock
private AudioManager mAudioManager;
@Mock
ActivityManager mActivityManager;
@@ -276,7 +280,7 @@
mPackageManager, mPackageManagerClient, mockLightsManager,
mListeners, mAssistants, mConditionProviders,
mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
- mGroupHelper, mAm, mock(UsageStatsManagerInternal.class),
+ mGroupHelper, mAm, mAppUsageStats,
mock(DevicePolicyManagerInternal.class));
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
@@ -3019,4 +3023,46 @@
assertEquals(true, mService.canUseManagedServices("d"));
}
+
+ @Test
+ public void testOnNotificationVisibilityChanged_triggersInterruptionUsageStat() {
+ final NotificationRecord r = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ r.setTextChanged(true);
+ mService.addNotification(r);
+
+ mService.mNotificationDelegate.onNotificationVisibilityChanged(new NotificationVisibility[]
+ {NotificationVisibility.obtain(r.getKey(), 1, 1, true)},
+ new NotificationVisibility[]{});
+
+ verify(mAppUsageStats).reportInterruptiveNotification(anyString(), anyString(), anyInt());
+ }
+
+ @Test
+ public void testSetNotificationsShownFromListener_triggersInterruptionUsageStat()
+ throws RemoteException {
+ final NotificationRecord r = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ r.setTextChanged(true);
+ mService.addNotification(r);
+
+ mBinderService.setNotificationsShownFromListener(null, new String[] {r.getKey()});
+
+ verify(mAppUsageStats).reportInterruptiveNotification(anyString(), anyString(), anyInt());
+ }
+
+ @Test
+ public void testMybeRecordInterruptionLocked_doesNotRecordTwice()
+ throws RemoteException {
+ final NotificationRecord r = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ r.setInterruptive(true);
+ mService.addNotification(r);
+
+ mService.maybeRecordInterruptionLocked(r);
+ mService.maybeRecordInterruptionLocked(r);
+
+ verify(mAppUsageStats, times(1)).reportInterruptiveNotification(
+ anyString(), anyString(), anyInt());
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index e3289ab..c1868e7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -602,4 +602,45 @@
record.setIsAppImportanceLocked(false);
assertEquals(false, record.getIsAppImportanceLocked());
}
+
+ @Test
+ public void testIsInterruptive_textChanged_notSeen() {
+ StatusBarNotification sbn = getNotification(false /*preO */, false /* noisy */,
+ false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, null /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertEquals(false, record.isInterruptive());
+
+ record.setTextChanged(true);
+ assertEquals(false, record.isInterruptive());
+ }
+
+ @Test
+ public void testIsInterruptive_textChanged_seen() {
+ StatusBarNotification sbn = getNotification(false /*preO */, false /* noisy */,
+ false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, null /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertEquals(false, record.isInterruptive());
+
+ record.setTextChanged(true);
+ record.setSeen();
+ assertEquals(true, record.isInterruptive());
+ }
+
+ @Test
+ public void testIsInterruptive_textNotChanged_seen() {
+ StatusBarNotification sbn = getNotification(false /*preO */, false /* noisy */,
+ false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, null /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertEquals(false, record.isInterruptive());
+
+ record.setTextChanged(false);
+ record.setSeen();
+ assertEquals(false, record.isInterruptive());
+ }
}
diff --git a/tests/net/java/android/net/captiveportal/CaptivePortalProbeSpecTest.java b/tests/net/java/android/net/captiveportal/CaptivePortalProbeSpecTest.java
new file mode 100644
index 0000000..40a8b3e
--- /dev/null
+++ b/tests/net/java/android/net/captiveportal/CaptivePortalProbeSpecTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2018 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.captiveportal;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CaptivePortalProbeSpecTest {
+
+ @Test
+ public void testGetResult_Regex() throws MalformedURLException, ParseException {
+ // 2xx status or 404, with an empty (match everything) location regex
+ CaptivePortalProbeSpec statusRegexSpec = CaptivePortalProbeSpec.parseSpec(
+ "http://www.google.com@@/@@2[0-9]{2}|404@@/@@");
+
+ // 404, or 301/302 redirect to some HTTPS page under google.com
+ CaptivePortalProbeSpec redirectSpec = CaptivePortalProbeSpec.parseSpec(
+ "http://google.com@@/@@404|30[12]@@/@@https://([0-9a-z]+\\.)*google\\.com.*");
+
+ assertSuccess(statusRegexSpec.getResult(200, null));
+ assertSuccess(statusRegexSpec.getResult(299, "qwer"));
+ assertSuccess(statusRegexSpec.getResult(404, null));
+ assertSuccess(statusRegexSpec.getResult(404, ""));
+
+ assertPortal(statusRegexSpec.getResult(300, null));
+ assertPortal(statusRegexSpec.getResult(399, "qwer"));
+ assertPortal(statusRegexSpec.getResult(500, null));
+
+ assertSuccess(redirectSpec.getResult(404, null));
+ assertSuccess(redirectSpec.getResult(404, ""));
+ assertSuccess(redirectSpec.getResult(301, "https://www.google.com"));
+ assertSuccess(redirectSpec.getResult(301, "https://www.google.com/test?q=3"));
+ assertSuccess(redirectSpec.getResult(302, "https://google.com/test?q=3"));
+
+ assertPortal(redirectSpec.getResult(299, "https://google.com/test?q=3"));
+ assertPortal(redirectSpec.getResult(299, ""));
+ assertPortal(redirectSpec.getResult(499, null));
+ assertPortal(redirectSpec.getResult(301, "http://login.portal.example.com/loginpage"));
+ assertPortal(redirectSpec.getResult(302, "http://www.google.com/test?q=3"));
+ }
+
+ @Test(expected = ParseException.class)
+ public void testParseSpec_Empty() throws MalformedURLException, ParseException {
+ CaptivePortalProbeSpec.parseSpec("");
+ }
+
+ @Test(expected = ParseException.class)
+ public void testParseSpec_Null() throws MalformedURLException, ParseException {
+ CaptivePortalProbeSpec.parseSpec(null);
+ }
+
+ @Test(expected = ParseException.class)
+ public void testParseSpec_MissingParts() throws MalformedURLException, ParseException {
+ CaptivePortalProbeSpec.parseSpec("http://google.com/@@/@@123");
+ }
+
+ @Test(expected = ParseException.class)
+ public void testParseSpec_TooManyParts() throws MalformedURLException, ParseException {
+ CaptivePortalProbeSpec.parseSpec("http://google.com/@@/@@123@@/@@456@@/@@extra");
+ }
+
+ @Test(expected = ParseException.class)
+ public void testParseSpec_InvalidStatusRegex() throws MalformedURLException, ParseException {
+ CaptivePortalProbeSpec.parseSpec("http://google.com/@@/@@unmatched(parenthesis@@/@@456");
+ }
+
+ @Test(expected = ParseException.class)
+ public void testParseSpec_InvalidLocationRegex() throws MalformedURLException, ParseException {
+ CaptivePortalProbeSpec.parseSpec("http://google.com/@@/@@123@@/@@unmatched[[]bracket");
+ }
+
+ @Test(expected = MalformedURLException.class)
+ public void testParseSpec_EmptyURL() throws MalformedURLException, ParseException {
+ CaptivePortalProbeSpec.parseSpec("@@/@@123@@/@@123");
+ }
+
+ @Test(expected = ParseException.class)
+ public void testParseSpec_NoParts() throws MalformedURLException, ParseException {
+ CaptivePortalProbeSpec.parseSpec("invalid");
+ }
+
+ @Test(expected = MalformedURLException.class)
+ public void testParseSpec_RegexInvalidUrl() throws MalformedURLException, ParseException {
+ CaptivePortalProbeSpec.parseSpec("notaurl@@/@@123@@/@@123");
+ }
+
+ @Test
+ public void testParseSpecOrNull_UsesSpec() {
+ final String specUrl = "http://google.com/probe";
+ final String redirectUrl = "https://google.com/probe";
+ CaptivePortalProbeSpec spec = CaptivePortalProbeSpec.parseSpecOrNull(
+ specUrl + "@@/@@302@@/@@" + redirectUrl);
+ assertEquals(specUrl, spec.getUrl().toString());
+
+ assertPortal(spec.getResult(302, "http://portal.example.com"));
+ assertSuccess(spec.getResult(302, redirectUrl));
+ }
+
+ @Test
+ public void testParseSpecOrNull_UsesFallback() throws MalformedURLException {
+ CaptivePortalProbeSpec spec = CaptivePortalProbeSpec.parseSpecOrNull(null);
+ assertNull(spec);
+
+ spec = CaptivePortalProbeSpec.parseSpecOrNull("");
+ assertNull(spec);
+
+ spec = CaptivePortalProbeSpec.parseSpecOrNull("@@/@@ @@/@@ @@/@@");
+ assertNull(spec);
+
+ spec = CaptivePortalProbeSpec.parseSpecOrNull("invalid@@/@@123@@/@@456");
+ assertNull(spec);
+ }
+
+ @Test
+ public void testParseSpecOrUseStatusCodeFallback_EmptySpec() throws MalformedURLException {
+ CaptivePortalProbeSpec spec = CaptivePortalProbeSpec.parseSpecOrNull("");
+ assertNull(spec);
+ }
+
+ private void assertIsStatusSpec(CaptivePortalProbeSpec spec) {
+ assertSuccess(spec.getResult(204, null));
+ assertSuccess(spec.getResult(204, "1234"));
+
+ assertPortal(spec.getResult(200, null));
+ assertPortal(spec.getResult(301, null));
+ assertPortal(spec.getResult(302, "1234"));
+ assertPortal(spec.getResult(399, ""));
+
+ assertFailed(spec.getResult(404, null));
+ assertFailed(spec.getResult(500, "1234"));
+ }
+
+ private void assertPortal(CaptivePortalProbeResult result) {
+ assertTrue(result.isPortal());
+ }
+
+ private void assertSuccess(CaptivePortalProbeResult result) {
+ assertTrue(result.isSuccessful());
+ }
+
+ private void assertFailed(CaptivePortalProbeResult result) {
+ assertTrue(result.isFailed());
+ }
+}