summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/PackageInfo.java13
-rw-r--r--core/java/android/content/pm/PackageParser.java86
-rw-r--r--core/java/android/util/PackageUtils.java80
-rw-r--r--core/java/android/webkit/WebView.java5
-rw-r--r--core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java2
-rw-r--r--core/res/res/values/attrs_manifest.xml16
-rw-r--r--packages/SettingsLib/res/values/arrays.xml8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java172
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/TimestampedScoredNetwork.java76
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java281
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java146
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java1
-rw-r--r--services/autofill/java/com/android/server/autofill/RemoteFillService.java10
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java30
-rw-r--r--services/core/java/com/android/server/connectivity/Tethering.java128
-rw-r--r--services/core/java/com/android/server/connectivity/tethering/OffloadController.java56
-rw-r--r--services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java38
-rw-r--r--services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java5
-rw-r--r--services/core/java/com/android/server/pm/InstantAppRegistry.java60
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java44
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java13
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotPersister.java44
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotSurface.java25
-rw-r--r--services/net/java/android/net/util/NetworkConstants.java2
-rw-r--r--services/net/java/android/net/util/PrefixUtils.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java3
-rw-r--r--tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java101
31 files changed, 1217 insertions, 298 deletions
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index cf0edcad7e2b..ba488f6a0518 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -187,8 +187,17 @@ public class PackageInfo implements Parcelable {
public static final int REQUESTED_PERMISSION_GRANTED = 1<<1;
/**
- * Array of all signatures read from the package file. This is only filled
- * in if the flag {@link PackageManager#GET_SIGNATURES} was set.
+ * Array of all signatures read from the package file. This is only filled
+ * in if the flag {@link PackageManager#GET_SIGNATURES} was set. A package
+ * must be singed with at least one certificate which is at position zero.
+ * The package can be signed with additional certificates which appear as
+ * subsequent entries.
+ *
+ * <strong>Note:</strong> Signature ordering is not guaranteed to be
+ * stable which means that a package signed with certificates A and B is
+ * equivalent to being signed with certificates B and A. This means that
+ * in case multiple signatures are reported you cannot assume the one at
+ * the first position to be the same across updates.
*/
public Signature[] signatures;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 7ae85225ef1b..cb9ecf3e2bd8 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -97,6 +97,7 @@ import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -2824,14 +2825,14 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
final int version = sa.getInt(
com.android.internal.R.styleable.AndroidManifestUsesStaticLibrary_version, -1);
- String certSha256 = sa.getNonResourceString(com.android.internal.R.styleable
+ String certSha256Digest = sa.getNonResourceString(com.android.internal.R.styleable
.AndroidManifestUsesStaticLibrary_certDigest);
sa.recycle();
// Since an APK providing a static shared lib can only provide the lib - fail if malformed
- if (lname == null || version < 0 || certSha256 == null) {
+ if (lname == null || version < 0 || certSha256Digest == null) {
outError[0] = "Bad uses-static-library declaration name: " + lname + " version: "
- + version + " certDigest" + certSha256;
+ + version + " certDigest" + certSha256Digest;
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
XmlUtils.skipCurrentTag(parser);
return false;
@@ -2848,18 +2849,75 @@ public class PackageParser {
lname = lname.intern();
// We allow ":" delimiters in the SHA declaration as this is the format
// emitted by the certtool making it easy for developers to copy/paste.
- certSha256 = certSha256.replace(":", "").toLowerCase();
+ certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
+
+ // Fot apps targeting O-MR1 we require explicit enumeration of all certs.
+ String[] additionalCertSha256Digests = EmptyArray.STRING;
+ if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.O) {
+ additionalCertSha256Digests = parseAdditionalCertificates(res, parser, outError);
+ if (additionalCertSha256Digests == null) {
+ return false;
+ }
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+
+ final String[] certSha256Digests = new String[additionalCertSha256Digests.length + 1];
+ certSha256Digests[0] = certSha256Digest;
+ System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
+ 1, additionalCertSha256Digests.length);
+
pkg.usesStaticLibraries = ArrayUtils.add(pkg.usesStaticLibraries, lname);
pkg.usesStaticLibrariesVersions = ArrayUtils.appendInt(
pkg.usesStaticLibrariesVersions, version, true);
- pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String.class,
- pkg.usesStaticLibrariesCertDigests, certSha256, true);
-
- XmlUtils.skipCurrentTag(parser);
+ pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
+ pkg.usesStaticLibrariesCertDigests, certSha256Digests, true);
return true;
}
+ private String[] parseAdditionalCertificates(Resources resources, XmlResourceParser parser,
+ String[] outError) throws XmlPullParserException, IOException {
+ String[] certSha256Digests = EmptyArray.STRING;
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ final String nodeName = parser.getName();
+ if (nodeName.equals("additional-certificate")) {
+ final TypedArray sa = resources.obtainAttributes(parser, com.android.internal.
+ R.styleable.AndroidManifestAdditionalCertificate);
+ String certSha256Digest = sa.getNonResourceString(com.android.internal.
+ R.styleable.AndroidManifestAdditionalCertificate_certDigest);
+ sa.recycle();
+
+ if (TextUtils.isEmpty(certSha256Digest)) {
+ outError[0] = "Bad additional-certificate declaration with empty"
+ + " certDigest:" + certSha256Digest;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ XmlUtils.skipCurrentTag(parser);
+ sa.recycle();
+ return null;
+ }
+
+ // We allow ":" delimiters in the SHA declaration as this is the format
+ // emitted by the certtool making it easy for developers to copy/paste.
+ certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
+ certSha256Digests = ArrayUtils.appendElement(String.class,
+ certSha256Digests, certSha256Digest);
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ return certSha256Digests;
+ }
+
private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser,
@@ -5820,7 +5878,7 @@ public class PackageParser {
public ArrayList<String> usesLibraries = null;
public ArrayList<String> usesStaticLibraries = null;
public int[] usesStaticLibrariesVersions = null;
- public String[] usesStaticLibrariesCertDigests = null;
+ public String[][] usesStaticLibrariesCertDigests = null;
public ArrayList<String> usesOptionalLibraries = null;
public String[] usesLibraryFiles = null;
@@ -6318,8 +6376,10 @@ public class PackageParser {
internStringArrayList(usesStaticLibraries);
usesStaticLibrariesVersions = new int[libCount];
dest.readIntArray(usesStaticLibrariesVersions);
- usesStaticLibrariesCertDigests = new String[libCount];
- dest.readStringArray(usesStaticLibrariesCertDigests);
+ usesStaticLibrariesCertDigests = new String[libCount][];
+ for (int i = 0; i < libCount; i++) {
+ usesStaticLibrariesCertDigests[i] = dest.createStringArray();
+ }
}
preferredActivityFilters = new ArrayList<>();
@@ -6465,7 +6525,9 @@ public class PackageParser {
dest.writeInt(usesStaticLibraries.size());
dest.writeStringList(usesStaticLibraries);
dest.writeIntArray(usesStaticLibrariesVersions);
- dest.writeStringArray(usesStaticLibrariesCertDigests);
+ for (String[] usesStaticLibrariesCertDigest : usesStaticLibrariesCertDigests) {
+ dest.writeStringArray(usesStaticLibrariesCertDigest);
+ }
}
dest.writeParcelableList(preferredActivityFilters, flags);
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index 0fe56f6efa21..e2e9d53e7e9e 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -18,12 +18,13 @@ package android.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import android.content.pm.Signature;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
/**
* Helper functions applicable to packages.
@@ -36,32 +37,67 @@ public final class PackageUtils {
}
/**
- * Computes the SHA256 digest of the signing cert for a package.
- * @param packageManager The package manager.
- * @param packageName The package for which to generate the digest.
- * @param userId The user for which to generate the digest.
- * @return The digest or null if the package does not exist for this user.
+ * Computes the SHA256 digests of a list of signatures. Items in the
+ * resulting array of hashes correspond to the signatures in the
+ * input array.
+ * @param signatures The signatures.
+ * @return The digest array.
*/
- public static @Nullable String computePackageCertSha256Digest(
- @NonNull PackageManager packageManager,
- @NonNull String packageName, int userId) {
- final PackageInfo packageInfo;
- try {
- packageInfo = packageManager.getPackageInfoAsUser(packageName,
- PackageManager.GET_SIGNATURES, userId);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
+ public static @NonNull String[] computeSignaturesSha256Digests(
+ @NonNull Signature[] signatures) {
+ final int signatureCount = signatures.length;
+ final String[] digests = new String[signatureCount];
+ for (int i = 0; i < signatureCount; i++) {
+ digests[i] = computeSha256Digest(signatures[i].toByteArray());
+ }
+ return digests;
+ }
+ /**
+ * Computes a SHA256 digest of the signatures' SHA256 digests. First,
+ * individual hashes for each signature is derived in a hexademical
+ * form, then these strings are sorted based the natural ordering, and
+ * finally a hash is derived from these strings' bytes.
+ * @param signatures The signatures.
+ * @return The digest.
+ */
+ public static @NonNull String computeSignaturesSha256Digest(
+ @NonNull Signature[] signatures) {
+ // Shortcut for optimization - most apps singed by a single cert
+ if (signatures.length == 1) {
+ return computeSha256Digest(signatures[0].toByteArray());
}
- return computeCertSha256Digest(packageInfo.signatures[0]);
+
+ // Make sure these are sorted to handle reversed certificates
+ final String[] sha256Digests = computeSignaturesSha256Digests(signatures);
+ return computeSignaturesSha256Digest(sha256Digests);
}
/**
- * Computes the SHA256 digest of a cert.
- * @param signature The signature.
- * @return The digest or null if an error occurs.
+ * Computes a SHA256 digest in of the signatures SHA256 digests. First,
+ * the strings are sorted based the natural ordering, and then a hash is
+ * derived from these strings' bytes.
+ * @param sha256Digests Signature SHA256 hashes in hexademical form.
+ * @return The digest.
*/
- public static @Nullable String computeCertSha256Digest(@NonNull Signature signature) {
- return computeSha256Digest(signature.toByteArray());
+ public static @NonNull String computeSignaturesSha256Digest(
+ @NonNull String[] sha256Digests) {
+ // Shortcut for optimization - most apps singed by a single cert
+ if (sha256Digests.length == 1) {
+ return sha256Digests[0];
+ }
+
+ // Make sure these are sorted to handle reversed certificates
+ Arrays.sort(sha256Digests);
+
+ final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ for (String sha256Digest : sha256Digests) {
+ try {
+ bytes.write(sha256Digest.getBytes());
+ } catch (IOException e) {
+ /* ignore - can't happen */
+ }
+ }
+ return computeSha256Digest(bytes.toByteArray());
}
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 4b98e350f8b5..81ab4078e549 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1926,13 +1926,14 @@ public class WebView extends AbsoluteLayout
* For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
* all public methods (including the inherited ones) can be accessed, see the
* important security note below for implications.
- * <p> Note that injected objects will not
- * appear in JavaScript until the page is next (re)loaded. For example:
+ * <p> Note that injected objects will not appear in JavaScript until the page is next
+ * (re)loaded. JavaScript should be enabled before injecting the object. For example:
* <pre>
* class JsObject {
* {@literal @}JavascriptInterface
* public String toString() { return "injectedObject"; }
* }
+ * webview.getSettings().setJavaScriptEnabled(true);
* webView.addJavascriptInterface(new JsObject(), "injectedObject");
* webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
* webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index c89f546b13a2..e85e29543d23 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -137,7 +137,7 @@ public class KernelUidCpuFreqTimeReader {
sb.append("times=").append("(");
TimeUtils.formatDuration(mLastTimeReadMs, sb); sb.append(",");
TimeUtils.formatDuration(mNowTimeMs, sb); sb.append(")");
- Slog.wtf(TAG, sb.toString());
+ Slog.e(TAG, sb.toString());
return;
}
curUidTimeMs[i] = totalTimeMs;
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 682815014ade..8aff3b6eb749 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1779,6 +1779,11 @@
the library at build time while it offers apps to share code defined in such
libraries. Hence, static libraries are strictly required.
+ <p>On devices running O MR1 or higher, if the library is singed with multiple
+ signing certificates you must to specify the SHA-256 hashes of the additional
+ certificates via adding
+ {@link #AndroidManifestAdditionalCertificate additional-certificate} tags.
+
<p>This appears as a child tag of the
{@link #AndroidManifestApplication application} tag. -->
<declare-styleable name="AndroidManifestUsesStaticLibrary" parent="AndroidManifestApplication">
@@ -1790,6 +1795,17 @@
<attr name="certDigest" format="string" />
</declare-styleable>
+ <!-- The <code>additional-certificate</code> specifies the SHA-256 digest of a static
+ shared library's additional signing certificate. You need to use this tag if the
+ library is singed with more than one certificate.
+
+ <p>This appears as a child tag of the
+ {@link #AndroidManifestUsesStaticLibrary uses-static-library} tag. -->
+ <declare-styleable name="AndroidManifestAdditionalCertificate" parent="AndroidManifestUsesStaticLibrary">
+ <!-- The SHA-256 digest of the library signing certificate. -->
+ <attr name="certDigest" />
+ </declare-styleable>
+
<!-- The <code>supports-screens</code> specifies the screen dimensions an
application supports. By default a modern application supports all
screen sizes and must explicitly disable certain screen sizes here;
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 1f1b67e6abaf..0bf2eda8cc1f 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -121,8 +121,8 @@
<item>Use System Selection (Default)</item>
<item>SBC</item>
<item>AAC</item>
- <item><xliff:g id="aptx">Qualcomm(R) aptX(TM) audio</xliff:g></item>
- <item><xliff:g id="aptx_hd">Qualcomm(R) aptX(TM) HD audio</xliff:g></item>
+ <item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx">aptX™</xliff:g> audio</item>
+ <item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx_hd">aptX™ HD</xliff:g> audio</item>
<item>LDAC</item>
<item>Enable Optional Codecs</item>
<item>Disable Optional Codecs</item>
@@ -145,8 +145,8 @@
<item>Use System Selection (Default)</item>
<item>SBC</item>
<item>AAC</item>
- <item><xliff:g id="aptx">Qualcomm(R) aptX(TM) audio</xliff:g></item>
- <item><xliff:g id="aptx_hd">Qualcomm(R) aptX(TM) HD audio</xliff:g></item>
+ <item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx">aptX™</xliff:g> audio</item>
+ <item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx_hd">aptX™ HD</xliff:g> audio</item>
<item>LDAC</item>
<item>Enable Optional Codecs</item>
<item>Disable Optional Codecs</item>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index b8c5aca4b946..330eaf0e18ba 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -51,6 +51,7 @@ import android.support.annotation.NonNull;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.text.style.TtsSpan;
import android.util.Log;
@@ -124,11 +125,20 @@ public class AccessPoint implements Comparable<AccessPoint> {
private final ConcurrentHashMap<String, ScanResult> mScanResultCache =
new ConcurrentHashMap<String, ScanResult>(32);
- /** Map of BSSIDs to speed values for individual ScanResults. */
- private final Map<String, Integer> mScanResultScores = new HashMap<>();
+ /**
+ * Map of BSSIDs to scored networks for individual bssids.
+ *
+ * <p>This cache should not be evicted with scan results, as the values here are used to
+ * generate a fallback in the absence of scores for the visible APs.
+ */
+ private final Map<String, TimestampedScoredNetwork> mScoredNetworkCache = new HashMap<>();
+
+ /** Maximum age in millis of cached scored networks in {@link #mScoredNetworkCache}. */
+ @VisibleForTesting static final long MAX_CACHED_SCORE_AGE_MILLIS =
+ 24 * DateUtils.DAY_IN_MILLIS;
/** Maximum age of scan results to hold onto while actively scanning. **/
- private static final long MAX_SCAN_RESULT_AGE_MS = 15000;
+ private static final long MAX_SCAN_RESULT_AGE_MILLIS = 15000;
static final String KEY_NETWORKINFO = "key_networkinfo";
static final String KEY_WIFIINFO = "key_wifiinfo";
@@ -138,6 +148,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
static final String KEY_SPEED = "key_speed";
static final String KEY_PSKTYPE = "key_psktype";
static final String KEY_SCANRESULTCACHE = "key_scanresultcache";
+ static final String KEY_SCOREDNETWORKCACHE = "key_scorednetworkcache";
static final String KEY_CONFIG = "key_config";
static final String KEY_FQDN = "key_fqdn";
static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
@@ -188,7 +199,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
private Object mTag;
- private int mSpeed = Speed.NONE;
+ @Speed private int mSpeed = Speed.NONE;
private boolean mIsScoredNetworkMetered = false;
// used to co-relate internal vs returned accesspoint.
@@ -238,6 +249,13 @@ public class AccessPoint implements Comparable<AccessPoint> {
mScanResultCache.put(result.BSSID, result);
}
}
+ if (savedState.containsKey(KEY_SCOREDNETWORKCACHE)) {
+ ArrayList<TimestampedScoredNetwork> scoredNetworkArrayList =
+ savedState.getParcelableArrayList(KEY_SCOREDNETWORKCACHE);
+ for (TimestampedScoredNetwork timedScore : scoredNetworkArrayList) {
+ mScoredNetworkCache.put(timedScore.getScore().networkKey.wifiKey.bssid, timedScore);
+ }
+ }
if (savedState.containsKey(KEY_FQDN)) {
mFqdn = savedState.getString(KEY_FQDN);
}
@@ -308,8 +326,8 @@ public class AccessPoint implements Comparable<AccessPoint> {
this.mNetworkInfo = that.mNetworkInfo;
this.mScanResultCache.clear();
this.mScanResultCache.putAll(that.mScanResultCache);
- this.mScanResultScores.clear();
- this.mScanResultScores.putAll(that.mScanResultScores);
+ this.mScoredNetworkCache.clear();
+ this.mScoredNetworkCache.putAll(that.mScoredNetworkCache);
this.mId = that.mId;
this.mSpeed = that.mSpeed;
this.mIsScoredNetworkMetered = that.mIsScoredNetworkMetered;
@@ -347,7 +365,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
if (isSaved() && !other.isSaved()) return -1;
if (!isSaved() && other.isSaved()) return 1;
- // Faster speeds go before slower speeds
+ // Faster speeds go before slower speeds - but only if visible change in speed label
if (getSpeed() != other.getSpeed()) {
return other.getSpeed() - getSpeed();
}
@@ -425,7 +443,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
*/
boolean update(WifiNetworkScoreCache scoreCache, boolean scoringUiEnabled) {
boolean scoreChanged = false;
- mScanResultScores.clear();
if (scoringUiEnabled) {
scoreChanged = updateScores(scoreCache);
}
@@ -435,38 +452,99 @@ public class AccessPoint implements Comparable<AccessPoint> {
/**
* Updates the AccessPoint rankingScore and speed, returning true if the data has changed.
*
+ * <p>Any cached {@link TimestampedScoredNetwork} objects older than
+ * {@link #MAX_CACHED_SCORE_AGE_MILLIS} will be removed when this method is invoked.
+ *
+ * <p>Precondition: {@link #mRssi} is up to date before invoking this method.
+ *
* @param scoreCache The score cache to use to retrieve scores.
+ * @return true if the set speed has changed
*/
private boolean updateScores(WifiNetworkScoreCache scoreCache) {
- int oldSpeed = mSpeed;
- mSpeed = Speed.NONE;
-
+ long nowMillis = SystemClock.elapsedRealtime();
for (ScanResult result : mScanResultCache.values()) {
ScoredNetwork score = scoreCache.getScoredNetwork(result);
if (score == null) {
continue;
}
-
- int speed = score.calculateBadge(result.level);
- mScanResultScores.put(result.BSSID, speed);
- mSpeed = Math.max(mSpeed, speed);
+ TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID);
+ if (timedScore == null) {
+ mScoredNetworkCache.put(
+ result.BSSID, new TimestampedScoredNetwork(score, nowMillis));
+ } else {
+ // Update data since the has been seen in the score cache
+ timedScore.update(score, nowMillis);
+ }
}
- // set mSpeed to the connected ScanResult if the AccessPoint is the active network
+ // Remove old cached networks
+ long evictionCutoff = nowMillis - MAX_CACHED_SCORE_AGE_MILLIS;
+ Iterator<TimestampedScoredNetwork> iterator = mScoredNetworkCache.values().iterator();
+ iterator.forEachRemaining(timestampedScoredNetwork -> {
+ if (timestampedScoredNetwork.getUpdatedTimestampMillis() < evictionCutoff) {
+ iterator.remove();
+ }
+ });
+
+ return updateSpeed();
+ }
+
+ /**
+ * Updates the internal speed, returning true if the update resulted in a speed label change.
+ */
+ private boolean updateSpeed() {
+ int oldSpeed = mSpeed;
+ mSpeed = generateAverageSpeedForSsid();
+
+ // set speed to the connected ScanResult if the AccessPoint is the active network
if (isActive() && mInfo != null) {
- NetworkKey key = new NetworkKey(new WifiKey(
- AccessPoint.convertToQuotedString(ssid), mInfo.getBSSID()));
- ScoredNetwork score = scoreCache.getScoredNetwork(key);
- if (score != null) {
- mSpeed = score.calculateBadge(mInfo.getRssi());
+ TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(mInfo.getBSSID());
+ if (timedScore != null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Set score using specific access point curve for connected AP: "
+ + getSsidStr());
+ }
+ // TODO(b/63073866): Map using getLevel rather than specific rssi value so score
+ // doesn't change without a visible wifi bar change.
+ int speed = timedScore.getScore().calculateBadge(mInfo.getRssi());
+ if (speed != Speed.NONE) {
+ mSpeed = speed;
+ }
}
}
- if(WifiTracker.sVerboseLogging) {
+ boolean changed = oldSpeed != mSpeed;
+ if(WifiTracker.sVerboseLogging && changed) {
Log.i(TAG, String.format("%s: Set speed to %d", ssid, mSpeed));
}
+ return changed;
+ }
- return oldSpeed != mSpeed;
+ /** Creates a speed value for the current {@link #mRssi} by averaging all non zero badges. */
+ @Speed private int generateAverageSpeedForSsid() {
+ if (mScoredNetworkCache.isEmpty()) {
+ return Speed.NONE;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("Generating fallbackspeed for %s using cache: %s",
+ getSsidStr(), mScoredNetworkCache));
+ }
+
+ int count = 0;
+ int totalSpeed = 0;
+ for (TimestampedScoredNetwork timedScore : mScoredNetworkCache.values()) {
+ int speed = timedScore.getScore().calculateBadge(mRssi);
+ if (speed != Speed.NONE) {
+ count++;
+ totalSpeed += speed;
+ }
+ }
+ int speed = count == 0 ? Speed.NONE : totalSpeed / count;
+ if (WifiTracker.sVerboseLogging) {
+ Log.i(TAG, String.format("%s generated fallback speed is: %d", getSsidStr(), speed));
+ }
+ return roundToClosestSpeedEnum(speed);
}
/**
@@ -501,7 +579,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
ScanResult result = iter.next();
// result timestamp is in microseconds
- if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MS) {
+ if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MILLIS) {
iter.remove();
}
}
@@ -582,8 +660,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
/** Updates {@link #mSeen} based on the scan result cache. */
private void updateSeen() {
- // TODO(sghuman): Set to now if connected
-
long seen = 0;
for (ScanResult result : mScanResultCache.values()) {
if (result.timestamp > seen) {
@@ -942,17 +1018,23 @@ public class AccessPoint implements Comparable<AccessPoint> {
}
stringBuilder.append("=").append(result.frequency);
stringBuilder.append(",").append(result.level);
- if (hasSpeed(result)) {
+ int speed = getSpecificApSpeed(result);
+ if (speed != Speed.NONE) {
stringBuilder.append(",")
- .append(getSpeedLabel(mScanResultScores.get(result.BSSID)));
+ .append(getSpeedLabel(speed));
}
stringBuilder.append("}");
return stringBuilder.toString();
}
- private boolean hasSpeed(ScanResult result) {
- return mScanResultScores.containsKey(result.BSSID)
- && mScanResultScores.get(result.BSSID) != Speed.NONE;
+ @Speed private int getSpecificApSpeed(ScanResult result) {
+ TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID);
+ if (timedScore == null) {
+ return Speed.NONE;
+ }
+ // For debugging purposes we may want to use mRssi rather than result.level as the average
+ // speed wil be determined by mRssi
+ return timedScore.getScore().calculateBadge(result.level);
}
/**
@@ -1067,6 +1149,8 @@ public class AccessPoint implements Comparable<AccessPoint> {
evictOldScanResults();
savedState.putParcelableArrayList(KEY_SCANRESULTCACHE,
new ArrayList<ScanResult>(mScanResultCache.values()));
+ savedState.putParcelableArrayList(KEY_SCOREDNETWORKCACHE,
+ new ArrayList<>(mScoredNetworkCache.values()));
if (mNetworkInfo != null) {
savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
}
@@ -1105,8 +1189,12 @@ public class AccessPoint implements Comparable<AccessPoint> {
updateRssi();
int newLevel = getLevel();
- if (newLevel > 0 && newLevel != oldLevel && mAccessPointListener != null) {
- mAccessPointListener.onLevelChanged(this);
+ if (newLevel > 0 && newLevel != oldLevel) {
+ // Only update labels on visible rssi changes
+ updateSpeed();
+ if (mAccessPointListener != null) {
+ mAccessPointListener.onLevelChanged(this);
+ }
}
// This flag only comes from scans, is not easily saved in config
if (security == SECURITY_PSK) {
@@ -1191,7 +1279,23 @@ public class AccessPoint implements Comparable<AccessPoint> {
}
@Nullable
- private String getSpeedLabel(int speed) {
+ @Speed
+ private int roundToClosestSpeedEnum(int speed) {
+ if (speed < Speed.SLOW) {
+ return Speed.NONE;
+ } else if (speed < (Speed.SLOW + Speed.MODERATE) / 2) {
+ return Speed.SLOW;
+ } else if (speed < (Speed.MODERATE + Speed.FAST) / 2) {
+ return Speed.MODERATE;
+ } else if (speed < (Speed.FAST + Speed.VERY_FAST) / 2) {
+ return Speed.FAST;
+ } else {
+ return Speed.VERY_FAST;
+ }
+ }
+
+ @Nullable
+ private String getSpeedLabel(@Speed int speed) {
switch (speed) {
case Speed.VERY_FAST:
return mContext.getString(R.string.speed_label_very_fast);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 93bf3c7ec20e..3dec1d382026 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -24,6 +24,7 @@ import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.os.Bundle;
import android.support.annotation.Keep;
+
import com.android.settingslib.wifi.AccessPoint.Speed;
import java.util.ArrayList;
@@ -58,6 +59,7 @@ public class TestAccessPointBuilder {
Context mContext;
private ArrayList<ScanResult> mScanResultCache;
+ private ArrayList<TimestampedScoredNetwork> mScoredNetworkCache;
@Keep
public TestAccessPointBuilder(Context context) {
@@ -85,6 +87,9 @@ public class TestAccessPointBuilder {
if (mScanResultCache != null) {
bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, mScanResultCache);
}
+ if (mScoredNetworkCache != null) {
+ bundle.putParcelableArrayList(AccessPoint.KEY_SCOREDNETWORKCACHE, mScoredNetworkCache);
+ }
bundle.putInt(AccessPoint.KEY_SECURITY, mSecurity);
bundle.putInt(AccessPoint.KEY_SPEED, mSpeed);
bundle.putBoolean(AccessPoint.KEY_IS_CARRIER_AP, mIsCarrierAp);
@@ -238,4 +243,10 @@ public class TestAccessPointBuilder {
mCarrierName = carrierName;
return this;
}
+
+ public TestAccessPointBuilder setScoredNetworkCache(
+ ArrayList<TimestampedScoredNetwork> scoredNetworkCache) {
+ mScoredNetworkCache = scoredNetworkCache;
+ return this;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TimestampedScoredNetwork.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TimestampedScoredNetwork.java
new file mode 100644
index 000000000000..cb15a795fe9b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TimestampedScoredNetwork.java
@@ -0,0 +1,76 @@
+/*
+ * 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.settingslib.wifi;
+
+import android.net.ScoredNetwork;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data encapsulation object to associate a time with a {@link ScoredNetwork}
+ */
+class TimestampedScoredNetwork implements Parcelable {
+ private ScoredNetwork mScore;
+ private long mUpdatedTimestampMillis;
+
+ TimestampedScoredNetwork(ScoredNetwork score, long updatedTimestampMillis) {
+ mScore = score;
+ mUpdatedTimestampMillis = updatedTimestampMillis;
+ }
+
+ protected TimestampedScoredNetwork(Parcel in) {
+ mScore = ScoredNetwork.CREATOR.createFromParcel(in);
+ mUpdatedTimestampMillis = in.readLong();
+ }
+
+ public void update(ScoredNetwork score, long updatedTimestampMillis) {
+ mScore = score;
+ mUpdatedTimestampMillis = updatedTimestampMillis;
+ }
+
+ public ScoredNetwork getScore() {
+ return mScore;
+ }
+
+ public long getUpdatedTimestampMillis() {
+ return mUpdatedTimestampMillis;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mScore, flags);
+ dest.writeLong(mUpdatedTimestampMillis);
+ }
+
+ public static final Creator<TimestampedScoredNetwork> CREATOR =
+ new Creator<TimestampedScoredNetwork>() {
+ @Override
+ public TimestampedScoredNetwork createFromParcel(Parcel in) {
+ return new TimestampedScoredNetwork(in);
+ }
+
+ @Override
+ public TimestampedScoredNetwork[] newArray(int size) {
+ return new TimestampedScoredNetwork[size];
+ }
+ };
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index ae59d3781430..6f1b25f6747e 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -21,8 +21,6 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -50,8 +48,8 @@ import android.text.SpannableString;
import android.text.style.TtsSpan;
import com.android.settingslib.R;
-
import com.android.settingslib.wifi.AccessPoint.Speed;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,17 +57,36 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AccessPointTest {
- private static final String TEST_SSID = "test_ssid";
+ private static final String TEST_SSID = "\"test_ssid\"";
+ private static final int NUM_SCAN_RESULTS = 5;
+
+ private static final ArrayList<ScanResult> SCAN_RESULTS = buildScanResultCache();
+
+ private static final RssiCurve FAST_BADGE_CURVE =
+ new RssiCurve(-150, 10, new byte[]{Speed.FAST});
+ public static final String TEST_BSSID = "00:00:00:00:00:00";
+
private Context mContext;
@Mock private RssiCurve mockBadgeCurve;
@Mock private WifiNetworkScoreCache mockWifiNetworkScoreCache;
+ private static ScanResult createScanResult(String ssid, String bssid, int rssi) {
+ ScanResult scanResult = new ScanResult();
+ scanResult.SSID = ssid;
+ scanResult.level = rssi;
+ scanResult.BSSID = bssid;
+ scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
+ scanResult.capabilities = "";
+ return scanResult;
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -400,7 +417,7 @@ public class AccessPointTest {
}
@Test
- public void testSpeedLabel_isDerivedFromConnectedBssid() {
+ public void testSpeedLabel_isDerivedFromConnectedBssidWhenScoreAvailable() {
int rssi = -55;
String bssid = "00:00:00:00:00:00";
int networkId = 123;
@@ -411,24 +428,42 @@ public class AccessPointTest {
info.setBSSID(bssid);
info.setNetworkId(networkId);
+ ArrayList<ScanResult> scanResults = new ArrayList<>();
+ ScanResult scanResultUnconnected = createScanResult(TEST_SSID, "11:11:11:11:11:11", rssi);
+ scanResults.add(scanResultUnconnected);
+
+ ScanResult scanResultConnected = createScanResult(TEST_SSID, bssid, rssi);
+ scanResults.add(scanResultConnected);
+
AccessPoint ap =
new TestAccessPointBuilder(mContext)
.setActive(true)
.setNetworkId(networkId)
.setSsid(TEST_SSID)
- .setScanResultCache(buildScanResultCache())
+ .setScanResultCache(scanResults)
.setWifiInfo(info)
.build();
- NetworkKey key = new NetworkKey(new WifiKey('"' + TEST_SSID + '"', bssid));
- when(mockWifiNetworkScoreCache.getScoredNetwork(key))
+ when(mockWifiNetworkScoreCache.getScoredNetwork(scanResultUnconnected))
.thenReturn(buildScoredNetworkWithMockBadgeCurve());
- when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.FAST);
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) Speed.SLOW);
+
+ int connectedSpeed = Speed.VERY_FAST;
+ RssiCurve connectedBadgeCurve = mock(RssiCurve.class);
+ Bundle attr1 = new Bundle();
+ attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, connectedBadgeCurve);
+ ScoredNetwork connectedScore = new ScoredNetwork(
+ NetworkKey.createFromScanResult(scanResultConnected),
+ connectedBadgeCurve,
+ false /* meteredHint */,
+ attr1);
+ when(mockWifiNetworkScoreCache.getScoredNetwork(scanResultConnected))
+ .thenReturn(connectedScore);
+ when(connectedBadgeCurve.lookupScore(anyInt())).thenReturn((byte) connectedSpeed);
ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
- verify(mockWifiNetworkScoreCache, times(2)).getScoredNetwork(key);
- assertThat(ap.getSpeed()).isEqualTo(AccessPoint.Speed.FAST);
+ assertThat(ap.getSpeed()).isEqualTo(connectedSpeed);
}
@Test
@@ -562,11 +597,16 @@ public class AccessPointTest {
}
private ScoredNetwork buildScoredNetworkWithMockBadgeCurve() {
+ return buildScoredNetworkWithGivenBadgeCurve(mockBadgeCurve);
+
+ }
+
+ private ScoredNetwork buildScoredNetworkWithGivenBadgeCurve(RssiCurve badgeCurve) {
Bundle attr1 = new Bundle();
- attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve);
+ attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, badgeCurve);
return new ScoredNetwork(
- new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")),
- mockBadgeCurve,
+ new NetworkKey(new WifiKey(TEST_SSID, TEST_BSSID)),
+ badgeCurve,
false /* meteredHint */,
attr1);
@@ -574,19 +614,14 @@ public class AccessPointTest {
private AccessPoint createAccessPointWithScanResultCache() {
Bundle bundle = new Bundle();
- ArrayList<ScanResult> scanResults = buildScanResultCache();
- bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, scanResults);
+ bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, SCAN_RESULTS);
return new AccessPoint(mContext, bundle);
}
- private ArrayList<ScanResult> buildScanResultCache() {
+ private static ArrayList<ScanResult> buildScanResultCache() {
ArrayList<ScanResult> scanResults = new ArrayList<>();
for (int i = 0; i < 5; i++) {
- ScanResult scanResult = new ScanResult();
- scanResult.level = i;
- scanResult.BSSID = "bssid-" + i;
- scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
- scanResult.capabilities = "";
+ ScanResult scanResult = createScanResult(TEST_SSID, "bssid-" + i, i);
scanResults.add(scanResult);
}
return scanResults;
@@ -600,6 +635,18 @@ public class AccessPointTest {
return configuration;
}
+ private AccessPoint createApWithFastTimestampedScoredNetworkCache(
+ long elapsedTimeMillis) {
+ TimestampedScoredNetwork recentScore = new TimestampedScoredNetwork(
+ buildScoredNetworkWithGivenBadgeCurve(FAST_BADGE_CURVE),
+ elapsedTimeMillis);
+ return new TestAccessPointBuilder(mContext)
+ .setSsid(TEST_SSID)
+ .setScoredNetworkCache(
+ new ArrayList<>(Arrays.asList(recentScore)))
+ .build();
+ }
+
/**
* Assert that the first AccessPoint appears before the second AccessPoint
* once sorting has been completed.
@@ -849,4 +896,194 @@ public class AccessPointTest {
ap.update(null, wifiInfo, networkInfo);
}
+
+ @Test
+ public void testSpeedLabelAveragesAllBssidScores() {
+ AccessPoint ap = createAccessPointWithScanResultCache();
+
+ int speed1 = Speed.MODERATE;
+ RssiCurve badgeCurve1 = mock(RssiCurve.class);
+ when(badgeCurve1.lookupScore(anyInt())).thenReturn((byte) speed1);
+ when(mockWifiNetworkScoreCache.getScoredNetwork(SCAN_RESULTS.get(0)))
+ .thenReturn(buildScoredNetworkWithGivenBadgeCurve(badgeCurve1));
+ int speed2 = Speed.VERY_FAST;
+ RssiCurve badgeCurve2 = mock(RssiCurve.class);
+ when(badgeCurve2.lookupScore(anyInt())).thenReturn((byte) speed2);
+ when(mockWifiNetworkScoreCache.getScoredNetwork(SCAN_RESULTS.get(1)))
+ .thenReturn(buildScoredNetworkWithGivenBadgeCurve(badgeCurve2));
+
+ int expectedSpeed = (speed1 + speed2) / 2;
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(expectedSpeed);
+ }
+
+ @Test
+ public void testSpeedLabelAverageIgnoresNoSpeedScores() {
+ AccessPoint ap = createAccessPointWithScanResultCache();
+
+ int speed1 = Speed.VERY_FAST;
+ RssiCurve badgeCurve1 = mock(RssiCurve.class);
+ when(badgeCurve1.lookupScore(anyInt())).thenReturn((byte) speed1);
+ when(mockWifiNetworkScoreCache.getScoredNetwork(SCAN_RESULTS.get(0)))
+ .thenReturn(buildScoredNetworkWithGivenBadgeCurve(badgeCurve1));
+ int speed2 = Speed.NONE;
+ RssiCurve badgeCurve2 = mock(RssiCurve.class);
+ when(badgeCurve2.lookupScore(anyInt())).thenReturn((byte) speed2);
+ when(mockWifiNetworkScoreCache.getScoredNetwork(SCAN_RESULTS.get(1)))
+ .thenReturn(buildScoredNetworkWithGivenBadgeCurve(badgeCurve2));
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(speed1);
+ }
+
+ @Test
+ public void testSpeedLabelUsesFallbackScoreWhenConnectedAccessPointScoreUnavailable() {
+ int rssi = -55;
+ String bssid = "00:00:00:00:00:00";
+ int networkId = 123;
+
+ WifiInfo info = new WifiInfo();
+ info.setRssi(rssi);
+ info.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID));
+ info.setBSSID(bssid);
+ info.setNetworkId(networkId);
+
+ ArrayList<ScanResult> scanResults = new ArrayList<>();
+ ScanResult scanResultUnconnected = createScanResult(TEST_SSID, "11:11:11:11:11:11", rssi);
+ scanResults.add(scanResultUnconnected);
+
+ ScanResult scanResultConnected = createScanResult(TEST_SSID, bssid, rssi);
+ scanResults.add(scanResultConnected);
+
+ AccessPoint ap =
+ new TestAccessPointBuilder(mContext)
+ .setActive(true)
+ .setNetworkId(networkId)
+ .setSsid(TEST_SSID)
+ .setScanResultCache(scanResults)
+ .setWifiInfo(info)
+ .build();
+
+ int fallbackSpeed = Speed.SLOW;
+ when(mockWifiNetworkScoreCache.getScoredNetwork(scanResultUnconnected))
+ .thenReturn(buildScoredNetworkWithMockBadgeCurve());
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) fallbackSpeed);
+
+ when(mockWifiNetworkScoreCache.getScoredNetwork(scanResultConnected))
+ .thenReturn(null);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(fallbackSpeed);
+ }
+
+ @Test
+ public void testScoredNetworkCacheBundling() {
+ long timeMillis = SystemClock.elapsedRealtime();
+ AccessPoint ap = createApWithFastTimestampedScoredNetworkCache(timeMillis);
+ Bundle bundle = new Bundle();
+ ap.saveWifiState(bundle);
+
+ ArrayList<TimestampedScoredNetwork> list =
+ bundle.getParcelableArrayList(AccessPoint.KEY_SCOREDNETWORKCACHE);
+ assertThat(list).hasSize(1);
+ assertThat(list.get(0).getUpdatedTimestampMillis()).isEqualTo(timeMillis);
+
+ RssiCurve curve = list.get(0).getScore().attributes.getParcelable(
+ ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE);
+ assertThat(curve).isEqualTo(FAST_BADGE_CURVE);
+ }
+
+ @Test
+ public void testRecentNetworkScoresAreUsedForSpeedLabelGeneration() {
+ AccessPoint ap =
+ createApWithFastTimestampedScoredNetworkCache(SystemClock.elapsedRealtime());
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(Speed.FAST);
+ }
+
+ @Test
+ public void testNetworkScoresAreUsedForSpeedLabelGenerationWhenWithinAgeRange() {
+ long withinRangeTimeMillis =
+ SystemClock.elapsedRealtime() - (AccessPoint.MAX_CACHED_SCORE_AGE_MILLIS - 10000);
+ AccessPoint ap =
+ createApWithFastTimestampedScoredNetworkCache(withinRangeTimeMillis);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(Speed.FAST);
+ }
+
+ @Test
+ public void testOldNetworkScoresAreNotUsedForSpeedLabelGeneration() {
+ long tooOldTimeMillis =
+ SystemClock.elapsedRealtime() - (AccessPoint.MAX_CACHED_SCORE_AGE_MILLIS + 1);
+ AccessPoint ap =
+ createApWithFastTimestampedScoredNetworkCache(tooOldTimeMillis);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(Speed.NONE);
+ }
+
+ @Test
+ public void testUpdateScoresRefreshesScoredNetworkCacheTimestamps () {
+ long tooOldTimeMillis =
+ SystemClock.elapsedRealtime() - (AccessPoint.MAX_CACHED_SCORE_AGE_MILLIS + 1);
+
+ ScoredNetwork scoredNetwork = buildScoredNetworkWithGivenBadgeCurve(FAST_BADGE_CURVE);
+ TimestampedScoredNetwork recentScore = new TimestampedScoredNetwork(
+ scoredNetwork,
+ tooOldTimeMillis);
+ AccessPoint ap = new TestAccessPointBuilder(mContext)
+ .setSsid(TEST_SSID)
+ .setBssid(TEST_BSSID)
+ .setActive(true)
+ .setScoredNetworkCache(
+ new ArrayList(Arrays.asList(recentScore)))
+ .setScanResultCache(SCAN_RESULTS)
+ .build();
+
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(scoredNetwork);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ // Fast should still be returned since cache was updated with recent time
+ assertThat(ap.getSpeed()).isEqualTo(Speed.FAST);
+ }
+
+ @Test
+ public void testUpdateScoresRefreshesScoredNetworkCacheWithNewSpeed () {
+ long tooOldTimeMillis =
+ SystemClock.elapsedRealtime() - (AccessPoint.MAX_CACHED_SCORE_AGE_MILLIS + 1);
+
+ ScoredNetwork scoredNetwork = buildScoredNetworkWithGivenBadgeCurve(FAST_BADGE_CURVE);
+ TimestampedScoredNetwork recentScore = new TimestampedScoredNetwork(
+ scoredNetwork,
+ tooOldTimeMillis);
+ AccessPoint ap = new TestAccessPointBuilder(mContext)
+ .setSsid(TEST_SSID)
+ .setBssid(TEST_BSSID)
+ .setActive(true)
+ .setScoredNetworkCache(
+ new ArrayList(Arrays.asList(recentScore)))
+ .setScanResultCache(SCAN_RESULTS)
+ .build();
+
+ int newSpeed = Speed.MODERATE;
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(buildScoredNetworkWithMockBadgeCurve());
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) newSpeed);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ // Fast should still be returned since cache was updated with recent time
+ assertThat(ap.getSpeed()).isEqualTo(newSpeed);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 562210c759fb..f8448661d799 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -367,7 +367,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
});
// Set the window background
- getWindow().setBackgroundDrawable(mRecentsView.getBackgroundScrim());
+ mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode());
// Create the home intent runnable
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -556,6 +556,9 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
super.onMultiWindowModeChanged(isInMultiWindowMode);
+ // Set the window background
+ mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode);
+
reloadTaskStack(isInMultiWindowMode, true /* sendConfigChangedEvent */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 1b8614313809..79558a33fc4e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -24,7 +24,6 @@ import static android.view.View.MeasureSpec;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationFinishedListener;
import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -36,6 +35,7 @@ import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.SystemClock;
+import android.util.ArraySet;
import android.util.Log;
import android.util.MutableBoolean;
import android.util.Pair;
@@ -76,6 +76,7 @@ import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
import com.android.systemui.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.Task.TaskKey;
import com.android.systemui.recents.model.TaskGrouping;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.model.ThumbnailData;
@@ -110,6 +111,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// duration, then we will toggle recents after this duration.
private final static int FAST_ALT_TAB_DELAY_MS = 225;
+ private final static ArraySet<TaskKey> EMPTY_SET = new ArraySet<>();
+
public final static String RECENTS_PACKAGE = "com.android.systemui";
public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
@@ -129,39 +132,38 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Preloads the next task
RecentsConfiguration config = Recents.getConfiguration();
if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
-
// Load the next task only if we aren't svelte
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+ TaskStack stack = plan.getTaskStack();
+ RecentsActivityLaunchState launchState = new RecentsActivityLaunchState();
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- // This callback is made when a new activity is launched and the old one is paused
- // so ignore the current activity and try and preload the thumbnail for the
- // previous one.
- VisibilityReport visibilityReport;
- synchronized (mDummyStackView) {
- mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */);
- mDummyStackView.setTasks(plan.getTaskStack(), false /* allowNotify */);
- updateDummyStackViewLayout(plan.getTaskStack(),
+ synchronized (mBackgroundLayoutAlgorithm) {
+ // This callback is made when a new activity is launched and the old one is
+ // paused so ignore the current activity and try and preload the thumbnail for
+ // the previous one.
+ updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack,
getWindowRect(null /* windowRectOverride */));
// Launched from app is always the worst case (in terms of how many
// thumbnails/tasks visible)
- RecentsActivityLaunchState launchState = new RecentsActivityLaunchState();
launchState.launchedFromApp = true;
- mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */, launchState);
- visibilityReport = mDummyStackView.computeStackVisibilityReport();
+ mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState);
+ VisibilityReport visibilityReport =
+ mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
+ stack.getStackTasks());
+
+ launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
+ launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks;
+ launchOpts.numVisibleTaskThumbnails = visibilityReport.numVisibleThumbnails;
+ launchOpts.onlyLoadForCache = true;
+ launchOpts.onlyLoadPausedActivities = true;
+ launchOpts.loadThumbnails = true;
}
-
- RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
- launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks;
- launchOpts.numVisibleTaskThumbnails = visibilityReport.numVisibleThumbnails;
- launchOpts.onlyLoadForCache = true;
- launchOpts.onlyLoadPausedActivities = true;
- launchOpts.loadThumbnails = true;
loader.loadTasks(mContext, plan, launchOpts);
}
}
@@ -230,17 +232,15 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
boolean mLaunchedWhileDocking;
// Task launching
- Rect mTaskStackBounds = new Rect();
+ Rect mTmpBounds = new Rect();
TaskViewTransform mTmpTransform = new TaskViewTransform();
- int mStatusBarHeight;
- int mNavBarHeight;
- int mNavBarWidth;
int mTaskBarHeight;
// Header (for transition)
TaskViewHeader mHeaderBar;
final Object mHeaderBarLock = new Object();
- protected TaskStackView mDummyStackView;
+ private TaskStackView mDummyStackView;
+ private TaskStackLayoutAlgorithm mBackgroundLayoutAlgorithm;
// Variables to keep track of if we need to start recents after binding
protected boolean mTriggeredFromAltTab;
@@ -259,6 +259,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
public RecentsImpl(Context context) {
mContext = context;
mHandler = new Handler();
+ mBackgroundLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null);
// Initialize the static foreground thread
ForegroundThread.get();
@@ -288,8 +289,9 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
public void onConfigurationChanged() {
reloadResources();
- synchronized (mDummyStackView) {
- mDummyStackView.reloadOnConfigurationChange();
+ mDummyStackView.reloadOnConfigurationChange();
+ synchronized (mBackgroundLayoutAlgorithm) {
+ mBackgroundLayoutAlgorithm.reloadOnConfigurationChange(mContext);
}
}
@@ -698,12 +700,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
private void reloadResources() {
Resources res = mContext.getResources();
- mStatusBarHeight = res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- mNavBarHeight = res.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_height);
- mNavBarWidth = res.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_width);
mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext,
R.dimen.recents_task_view_header_height,
R.dimen.recents_task_view_header_height,
@@ -719,7 +715,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection());
}
- private void updateDummyStackViewLayout(TaskStack stack, Rect windowRect) {
+ private void updateDummyStackViewLayout(TaskStackLayoutAlgorithm stackLayout,
+ TaskStack stack, Rect windowRect) {
SystemServicesProxy ssp = Recents.getSystemServices();
Rect displayRect = ssp.getDisplayRect();
Rect systemInsets = new Rect();
@@ -735,18 +732,14 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
calculateWindowStableInsets(systemInsets, windowRect, displayRect);
windowRect.offsetTo(0, 0);
- synchronized (mDummyStackView) {
- TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
-
- // Rebind the header bar and draw it for the transition
- stackLayout.setSystemInsets(systemInsets);
- if (stack != null) {
- stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
- systemInsets.left, systemInsets.right, mTaskStackBounds);
- stackLayout.reset();
- stackLayout.initialize(displayRect, windowRect, mTaskStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
- }
+ // Rebind the header bar and draw it for the transition
+ stackLayout.setSystemInsets(systemInsets);
+ if (stack != null) {
+ stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
+ systemInsets.left, systemInsets.right, mTmpBounds);
+ stackLayout.reset();
+ stackLayout.initialize(displayRect, windowRect, mTmpBounds,
+ TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
}
}
@@ -768,26 +761,23 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) {
Rect windowRect = getWindowRect(windowRectOverride);
int taskViewWidth = 0;
- boolean useGridLayout = false;
- synchronized (mDummyStackView) {
- useGridLayout = mDummyStackView.useGridLayout();
- updateDummyStackViewLayout(stack, windowRect);
- if (stack != null) {
- TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
- mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */);
- mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
- // Get the width of a task view so that we know how wide to draw the header bar.
- if (useGridLayout) {
- TaskGridLayoutAlgorithm gridLayout = mDummyStackView.getGridAlgorithm();
- gridLayout.initialize(windowRect);
- taskViewWidth = (int) gridLayout.getTransform(0 /* taskIndex */,
- stack.getTaskCount(), new TaskViewTransform(),
- stackLayout).rect.width();
- } else {
- Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
- if (!taskViewBounds.isEmpty()) {
- taskViewWidth = taskViewBounds.width();
- }
+ boolean useGridLayout = mDummyStackView.useGridLayout();
+ updateDummyStackViewLayout(mDummyStackView.getStackAlgorithm(), stack, windowRect);
+ if (stack != null) {
+ TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
+ mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */);
+ mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
+ // Get the width of a task view so that we know how wide to draw the header bar.
+ if (useGridLayout) {
+ TaskGridLayoutAlgorithm gridLayout = mDummyStackView.getGridAlgorithm();
+ gridLayout.initialize(windowRect);
+ taskViewWidth = (int) gridLayout.getTransform(0 /* taskIndex */,
+ stack.getTaskCount(), new TaskViewTransform(),
+ stackLayout).rect.width();
+ } else {
+ Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
+ if (!taskViewBounds.isEmpty()) {
+ taskViewWidth = taskViewBounds.width();
}
}
}
@@ -870,18 +860,12 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
- ArrayList<Task> tasks;
- TaskStackLayoutAlgorithm stackLayout;
- TaskStackViewScroller stackScroller;
-
- synchronized (mDummyStackView) {
- tasks = mDummyStackView.getStack().getStackTasks();
- stackLayout = mDummyStackView.getStackAlgorithm();
- stackScroller = mDummyStackView.getScroller();
+ ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks();
+ TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
+ TaskStackViewScroller stackScroller = mDummyStackView.getScroller();
- mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */);
- mDummyStackView.updateToInitialState();
- }
+ mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */);
+ mDummyStackView.updateToInitialState();
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
@@ -1044,10 +1028,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
updateHeaderBarLayout(stack, windowOverrideRect);
// Prepare the dummy stack for the transition
- TaskStackLayoutAlgorithm.VisibilityReport stackVr;
- synchronized (mDummyStackView) {
- stackVr = mDummyStackView.computeStackVisibilityReport();
- }
+ TaskStackLayoutAlgorithm.VisibilityReport stackVr =
+ mDummyStackView.computeStackVisibilityReport();
// Update the remaining launch state
launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index ccaf3cd6df52..71f06cbf6e64 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -20,13 +20,17 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import android.animation.Animator;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.ArraySet;
import android.util.AttributeSet;
@@ -37,6 +41,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewPropertyAnimator;
+import android.view.Window;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -117,7 +122,15 @@ public class RecentsView extends FrameLayout {
private float mBusynessFactor;
private GradientDrawable mBackgroundScrim;
- private Animator mBackgroundScrimAnimator;
+ private ColorDrawable mMultiWindowBackgroundScrim;
+ private ValueAnimator mBackgroundScrimAnimator;
+ private Point mTmpDisplaySize = new Point();
+
+ private final AnimatorUpdateListener mUpdateBackgroundScrimAlpha = (animation) -> {
+ int alpha = (Integer) animation.getAnimatedValue();
+ mBackgroundScrim.setAlpha(alpha);
+ mMultiWindowBackgroundScrim.setAlpha(alpha);
+ };
private RecentsTransitionHelper mTransitionHelper;
@ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
@@ -146,10 +159,7 @@ public class RecentsView extends FrameLayout {
mTouchHandler = new RecentsViewTouchHandler(this);
mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
mBackgroundScrim = new GradientDrawable(context);
- mBackgroundScrim.setCallback(this);
-
- boolean usingDarkText = Color.luminance(
- Utils.getColorAttr(mContext, R.attr.wallpaperTextColor)) < 0.5f;
+ mMultiWindowBackgroundScrim = new ColorDrawable();
LayoutInflater inflater = LayoutInflater.from(context);
mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
@@ -244,6 +254,7 @@ public class RecentsView extends FrameLayout {
} else {
mBackgroundScrim.setAlpha(0);
}
+ mMultiWindowBackgroundScrim.setAlpha(mBackgroundScrim.getAlpha());
}
}
@@ -300,8 +311,14 @@ public class RecentsView extends FrameLayout {
/**
* Returns the window background scrim.
*/
- public Drawable getBackgroundScrim() {
- return mBackgroundScrim;
+ public void updateBackgroundScrim(Window window, boolean isInMultiWindow) {
+ if (isInMultiWindow) {
+ mBackgroundScrim.setCallback(null);
+ window.setBackgroundDrawable(mMultiWindowBackgroundScrim);
+ } else {
+ mMultiWindowBackgroundScrim.setCallback(null);
+ window.setBackgroundDrawable(mBackgroundScrim);
+ }
}
/**
@@ -401,6 +418,9 @@ public class RecentsView extends FrameLayout {
*/
public void setScrimColors(ColorExtractor.GradientColors scrimColors, boolean animated) {
mBackgroundScrim.setColors(scrimColors, animated);
+ int alpha = mMultiWindowBackgroundScrim.getAlpha();
+ mMultiWindowBackgroundScrim.setColor(scrimColors.getMainColor());
+ mMultiWindowBackgroundScrim.setAlpha(alpha);
}
@Override
@@ -470,8 +490,10 @@ public class RecentsView extends FrameLayout {
// Needs to know the screen size since the gradient never scales up or down
// even when bounds change.
- mBackgroundScrim.setScreenSize(right - left, bottom - top);
+ mContext.getDisplay().getRealSize(mTmpDisplaySize);
+ mBackgroundScrim.setScreenSize(mTmpDisplaySize.x, mTmpDisplaySize.y);
mBackgroundScrim.setBounds(left, top, right, bottom);
+ mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
if (RecentsDebugFlags.Static.EnableStackActionButton) {
// Layout the stack action button such that its drawable is start-aligned with the
@@ -916,12 +938,12 @@ public class RecentsView extends FrameLayout {
// Calculate the absolute alpha to animate from
final int fromAlpha = mBackgroundScrim.getAlpha();
final int toAlpha = (int) (alpha * 255);
- mBackgroundScrimAnimator = ObjectAnimator.ofInt(mBackgroundScrim, Utilities.DRAWABLE_ALPHA,
- fromAlpha, toAlpha);
+ mBackgroundScrimAnimator = ValueAnimator.ofInt(fromAlpha, toAlpha);
mBackgroundScrimAnimator.setDuration(duration);
mBackgroundScrimAnimator.setInterpolator(toAlpha > fromAlpha
? Interpolators.ALPHA_IN
: Interpolators.ALPHA_OUT);
+ mBackgroundScrimAnimator.addUpdateListener(mUpdateBackgroundScrimAlpha);
mBackgroundScrimAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index d810ea4b3b1c..eaa32eefe795 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -354,7 +354,6 @@ public class TaskStackLayoutAlgorithm {
TaskViewTransform mFrontOfStackTransform = new TaskViewTransform();
public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
- Resources res = context.getResources();
mContext = context;
mCb = cb;
mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
@@ -519,7 +518,7 @@ public class TaskStackLayoutAlgorithm {
* Computes the minimum and maximum scroll progress values and the progress values for each task
* in the stack.
*/
- void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
+ public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
RecentsActivityLaunchState launchState) {
SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 046988271db0..694c72f2fe15 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -714,6 +714,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
&& (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
&& !mZenPanel.isEditing();
+ TransitionManager.endTransitions(mDialogView);
TransitionManager.beginDelayedTransition(mDialogView, getTransition());
if (wasVisible != visible && !visible) {
prepareForCollapse();
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index f8c0e27643b3..dd980535f628 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -562,7 +562,10 @@ final class RemoteFillService implements DeathRecipient {
void onTimeout(RemoteFillService remoteService) {
// NOTE: Must make these 2 calls asynchronously, because the cancellation signal is
// handled by the service, which could block.
- final ICancellationSignal cancellation = mCancellation;
+ final ICancellationSignal cancellation;
+ synchronized (mLock) {
+ cancellation = mCancellation;
+ }
if (cancellation != null) {
remoteService.dispatchOnFillTimeout(cancellation);
}
@@ -587,7 +590,10 @@ final class RemoteFillService implements DeathRecipient {
public boolean cancel() {
if (!super.cancel()) return false;
- final ICancellationSignal cancellation = mCancellation;
+ final ICancellationSignal cancellation;
+ synchronized (mLock) {
+ cancellation = mCancellation;
+ }
if (cancellation != null) {
try {
cancellation.cancel();
diff --git a/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
index 6380da5e2af8..1c77a7f451b4 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
@@ -23,6 +23,7 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.Signature;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.UserHandle;
@@ -30,6 +31,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.PackageUtils;
import android.util.Pair;
+import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
@@ -126,9 +128,18 @@ public final class AccountManagerBackupHelper {
} catch (PackageManager.NameNotFoundException e) {
return false;
}
- String currentCertDigest = PackageUtils.computeCertSha256Digest(
- packageInfo.signatures[0]);
- if (!certDigest.equals(currentCertDigest)) {
+
+ // Before we used only the first signature to compute the SHA 256 but some
+ // apps could be singed by multiple certs and the cert order is undefined.
+ // We prefer the modern computation procedure where all certs are taken
+ // into account but also allow the value from the old computation to allow
+ // restoring backed up grants on an older platform version.
+ final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
+ packageInfo.signatures);
+ final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
+ signaturesSha256Digests);
+ if (!certDigest.equals(signaturesSha256Digest) && (packageInfo.signatures.length <= 1
+ || !certDigest.equals(signaturesSha256Digests[0]))) {
return false;
}
final int uid = packageInfo.applicationInfo.uid;
@@ -169,8 +180,17 @@ public final class AccountManagerBackupHelper {
}
for (String packageName : packageNames) {
- String digest = PackageUtils.computePackageCertSha256Digest(
- packageManager, packageName, userId);
+ final PackageInfo packageInfo;
+ try {
+ packageInfo = packageManager.getPackageInfoAsUser(packageName,
+ PackageManager.GET_SIGNATURES, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.i(TAG, "Skipping backup of account access grant for"
+ + " non-existing package: " + packageName);
+ continue;
+ }
+ final String digest = PackageUtils.computeSignaturesSha256Digest(
+ packageInfo.signatures);
if (digest != null) {
serializer.startTag(null, TAG_PERMISSION);
serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 2b4f4e6a75f7..a985b4fb10b8 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -49,6 +49,7 @@ import android.net.ConnectivityManager;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.IpPrefix;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -1196,6 +1197,7 @@ public class Tethering extends BaseNetworkObserver {
// to tear itself down.
private final ArrayList<TetherInterfaceStateMachine> mNotifyList;
private final IPv6TetheringCoordinator mIPv6TetheringCoordinator;
+ private final OffloadWrapper mOffload;
private static final int UPSTREAM_SETTLE_TIME_MS = 10000;
@@ -1220,33 +1222,11 @@ public class Tethering extends BaseNetworkObserver {
mNotifyList = new ArrayList<>();
mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList, mLog);
+ mOffload = new OffloadWrapper();
setInitialState(mInitialState);
}
- private void startOffloadController() {
- mOffloadController.start();
- sendOffloadExemptPrefixes();
- }
-
- private void sendOffloadExemptPrefixes() {
- sendOffloadExemptPrefixes(mUpstreamNetworkMonitor.getLocalPrefixes());
- }
-
- private void sendOffloadExemptPrefixes(Set<IpPrefix> localPrefixes) {
- // Add in well-known minimum set.
- PrefixUtils.addNonForwardablePrefixes(localPrefixes);
- // Add tragically hardcoded prefixes.
- localPrefixes.add(PrefixUtils.DEFAULT_WIFI_P2P_PREFIX);
-
- // Add prefixes for all downstreams, regardless of IP serving mode.
- for (TetherInterfaceStateMachine tism : mNotifyList) {
- localPrefixes.addAll(PrefixUtils.localPrefixesFrom(tism.linkProperties()));
- }
-
- mOffloadController.setLocalPrefixes(localPrefixes);
- }
-
class InitialState extends State {
@Override
public boolean processMessage(Message message) {
@@ -1404,7 +1384,7 @@ public class Tethering extends BaseNetworkObserver {
protected void handleNewUpstreamNetworkState(NetworkState ns) {
mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns);
- mOffloadController.setUpstreamLinkProperties((ns != null) ? ns.linkProperties : null);
+ mOffload.updateUpstreamNetworkState(ns);
}
private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) {
@@ -1414,9 +1394,12 @@ public class Tethering extends BaseNetworkObserver {
}
if (mode == IControlsTethering.STATE_TETHERED) {
+ // No need to notify OffloadController just yet as there are no
+ // "offload-able" prefixes to pass along. This will handled
+ // when the TISM informs Tethering of its LinkProperties.
mForwardedDownstreams.add(who);
} else {
- mOffloadController.removeDownstreamInterface(who.interfaceName());
+ mOffload.excludeDownstreamInterface(who.interfaceName());
mForwardedDownstreams.remove(who);
}
@@ -1441,7 +1424,7 @@ public class Tethering extends BaseNetworkObserver {
private void handleInterfaceServingStateInactive(TetherInterfaceStateMachine who) {
mNotifyList.remove(who);
mIPv6TetheringCoordinator.removeActiveDownstream(who);
- mOffloadController.removeDownstreamInterface(who.interfaceName());
+ mOffload.excludeDownstreamInterface(who.interfaceName());
mForwardedDownstreams.remove(who);
// If this is a Wi-Fi interface, tell WifiManager of any errors.
@@ -1455,7 +1438,7 @@ public class Tethering extends BaseNetworkObserver {
private void handleUpstreamNetworkMonitorCallback(int arg1, Object o) {
if (arg1 == UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES) {
- sendOffloadExemptPrefixes((Set<IpPrefix>) o);
+ mOffload.sendOffloadExemptPrefixes((Set<IpPrefix>) o);
return;
}
@@ -1525,7 +1508,7 @@ public class Tethering extends BaseNetworkObserver {
// TODO: De-duplicate with updateUpstreamWanted() below.
if (upstreamWanted()) {
mUpstreamWanted = true;
- startOffloadController();
+ mOffload.start();
chooseUpstreamType(true);
mTryCell = false;
}
@@ -1533,7 +1516,7 @@ public class Tethering extends BaseNetworkObserver {
@Override
public void exit() {
- mOffloadController.stop();
+ mOffload.stop();
mUpstreamNetworkMonitor.stop();
mSimChange.stopListening();
notifyDownstreamsOfNewUpstreamIface(null);
@@ -1545,9 +1528,9 @@ public class Tethering extends BaseNetworkObserver {
mUpstreamWanted = upstreamWanted();
if (mUpstreamWanted != previousUpstreamWanted) {
if (mUpstreamWanted) {
- startOffloadController();
+ mOffload.start();
} else {
- mOffloadController.stop();
+ mOffload.stop();
}
}
return previousUpstreamWanted;
@@ -1602,12 +1585,9 @@ public class Tethering extends BaseNetworkObserver {
case EVENT_IFACE_UPDATE_LINKPROPERTIES: {
final LinkProperties newLp = (LinkProperties) message.obj;
if (message.arg1 == IControlsTethering.STATE_TETHERED) {
- mOffloadController.notifyDownstreamLinkProperties(newLp);
+ mOffload.updateDownstreamLinkProperties(newLp);
} else {
- mOffloadController.removeDownstreamInterface(newLp.getInterfaceName());
- // Another interface might be in local-only hotspot mode;
- // resend all local prefixes to the OffloadController.
- sendOffloadExemptPrefixes();
+ mOffload.excludeDownstreamInterface(newLp.getInterfaceName());
}
break;
}
@@ -1722,6 +1702,82 @@ public class Tethering extends BaseNetworkObserver {
} catch (Exception e) {}
}
}
+
+ // A wrapper class to handle multiple situations where several calls to
+ // the OffloadController need to happen together.
+ //
+ // TODO: This suggests that the interface between OffloadController and
+ // Tethering is in need of improvement. Refactor these calls into the
+ // OffloadController implementation.
+ class OffloadWrapper {
+ public void start() {
+ mOffloadController.start();
+ sendOffloadExemptPrefixes();
+ }
+
+ public void stop() {
+ mOffloadController.stop();
+ }
+
+ public void updateUpstreamNetworkState(NetworkState ns) {
+ mOffloadController.setUpstreamLinkProperties(
+ (ns != null) ? ns.linkProperties : null);
+ }
+
+ public void updateDownstreamLinkProperties(LinkProperties newLp) {
+ // Update the list of offload-exempt prefixes before adding
+ // new prefixes on downstream interfaces to the offload HAL.
+ sendOffloadExemptPrefixes();
+ mOffloadController.notifyDownstreamLinkProperties(newLp);
+ }
+
+ public void excludeDownstreamInterface(String ifname) {
+ // This and other interfaces may be in local-only hotspot mode;
+ // resend all local prefixes to the OffloadController.
+ sendOffloadExemptPrefixes();
+ mOffloadController.removeDownstreamInterface(ifname);
+ }
+
+ public void sendOffloadExemptPrefixes() {
+ sendOffloadExemptPrefixes(mUpstreamNetworkMonitor.getLocalPrefixes());
+ }
+
+ public void sendOffloadExemptPrefixes(final Set<IpPrefix> localPrefixes) {
+ // Add in well-known minimum set.
+ PrefixUtils.addNonForwardablePrefixes(localPrefixes);
+ // Add tragically hardcoded prefixes.
+ localPrefixes.add(PrefixUtils.DEFAULT_WIFI_P2P_PREFIX);
+
+ // Maybe add prefixes or addresses for downstreams, depending on
+ // the IP serving mode of each.
+ for (TetherInterfaceStateMachine tism : mNotifyList) {
+ final LinkProperties lp = tism.linkProperties();
+
+ switch (tism.servingMode()) {
+ case IControlsTethering.STATE_UNAVAILABLE:
+ case IControlsTethering.STATE_AVAILABLE:
+ // No usable LinkProperties in these states.
+ continue;
+ case IControlsTethering.STATE_TETHERED:
+ // Only add IPv4 /32 and IPv6 /128 prefixes. The
+ // directly-connected prefixes will be sent as
+ // downstream "offload-able" prefixes.
+ for (LinkAddress addr : lp.getAllLinkAddresses()) {
+ final InetAddress ip = addr.getAddress();
+ if (ip.isLinkLocalAddress()) continue;
+ localPrefixes.add(PrefixUtils.ipAddressAsPrefix(ip));
+ }
+ break;
+ case IControlsTethering.STATE_LOCAL_ONLY:
+ // Add prefixes covering all local IPs.
+ localPrefixes.addAll(PrefixUtils.localPrefixesFrom(lp));
+ break;
+ }
+ }
+
+ mOffloadController.setLocalPrefixes(localPrefixes);
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index ef18e4e0ba75..6d5c428e58f8 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -48,6 +48,7 @@ import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -69,6 +70,7 @@ public class OffloadController {
private final INetworkManagementService mNms;
private final ITetheringStatsProvider mStatsProvider;
private final SharedLog mLog;
+ private final HashMap<String, LinkProperties> mDownstreams;
private boolean mConfigInitialized;
private boolean mControlInitialized;
private LinkProperties mUpstreamLinkProperties;
@@ -100,6 +102,7 @@ public class OffloadController {
mNms = nms;
mStatsProvider = new OffloadTetheringStatsProvider();
mLog = log.forSubComponent(TAG);
+ mDownstreams = new HashMap<>();
mExemptPrefixes = new HashSet<>();
mLastLocalPrefixStrs = new HashSet<>();
@@ -257,6 +260,11 @@ public class OffloadController {
}
}
+ private String currentUpstreamInterface() {
+ return (mUpstreamLinkProperties != null)
+ ? mUpstreamLinkProperties.getInterfaceName() : null;
+ }
+
private void maybeUpdateStats(String iface) {
if (TextUtils.isEmpty(iface)) {
return;
@@ -281,9 +289,7 @@ public class OffloadController {
private boolean maybeUpdateDataLimit(String iface) {
// setDataLimit may only be called while offload is occuring on this upstream.
- if (!started() ||
- mUpstreamLinkProperties == null ||
- !TextUtils.equals(iface, mUpstreamLinkProperties.getInterfaceName())) {
+ if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) {
return true;
}
@@ -296,9 +302,7 @@ public class OffloadController {
}
private void updateStatsForCurrentUpstream() {
- if (mUpstreamLinkProperties != null) {
- maybeUpdateStats(mUpstreamLinkProperties.getInterfaceName());
- }
+ maybeUpdateStats(currentUpstreamInterface());
}
public void setUpstreamLinkProperties(LinkProperties lp) {
@@ -325,17 +329,42 @@ public class OffloadController {
}
public void notifyDownstreamLinkProperties(LinkProperties lp) {
+ final String ifname = lp.getInterfaceName();
+ final LinkProperties oldLp = mDownstreams.put(ifname, new LinkProperties(lp));
+ if (Objects.equals(oldLp, lp)) return;
+
if (!started()) return;
- // TODO: Cache LinkProperties on a per-ifname basis and compute the
- // deltas, calling addDownstream()/removeDownstream() accordingly.
+ final List<RouteInfo> oldRoutes = (oldLp != null) ? oldLp.getRoutes() : new ArrayList<>();
+ final List<RouteInfo> newRoutes = lp.getRoutes();
+
+ // For each old route, if not in new routes: remove.
+ for (RouteInfo oldRoute : oldRoutes) {
+ if (shouldIgnoreDownstreamRoute(oldRoute)) continue;
+ if (!newRoutes.contains(oldRoute)) {
+ mHwInterface.removeDownstreamPrefix(ifname, oldRoute.getDestination().toString());
+ }
+ }
+
+ // For each new route, if not in old routes: add.
+ for (RouteInfo newRoute : newRoutes) {
+ if (shouldIgnoreDownstreamRoute(newRoute)) continue;
+ if (!oldRoutes.contains(newRoute)) {
+ mHwInterface.addDownstreamPrefix(ifname, newRoute.getDestination().toString());
+ }
+ }
}
public void removeDownstreamInterface(String ifname) {
+ final LinkProperties lp = mDownstreams.remove(ifname);
+ if (lp == null) return;
+
if (!started()) return;
- // TODO: Check cache for LinkProperties of ifname and, if present,
- // call removeDownstream() accordingly.
+ for (RouteInfo route : lp.getRoutes()) {
+ if (shouldIgnoreDownstreamRoute(route)) continue;
+ mHwInterface.removeDownstreamPrefix(ifname, route.getDestination().toString());
+ }
}
private boolean isOffloadDisabled() {
@@ -442,6 +471,13 @@ public class OffloadController {
return localPrefixStrs;
}
+ private static boolean shouldIgnoreDownstreamRoute(RouteInfo route) {
+ // Ignore any link-local routes.
+ if (!route.getDestinationLinkAddress().isGlobalPreferred()) return true;
+
+ return false;
+ }
+
public void dump(IndentingPrintWriter pw) {
if (isOffloadDisabled()) {
pw.println("Offload disabled");
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 86ff0a607700..865a98902d0b 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -236,6 +236,44 @@ public class OffloadHardwareInterface {
return results.success;
}
+ public boolean addDownstreamPrefix(String ifname, String prefix) {
+ final String logmsg = String.format("addDownstreamPrefix(%s, %s)", ifname, prefix);
+
+ final CbResults results = new CbResults();
+ try {
+ mOffloadControl.addDownstream(ifname, prefix,
+ (boolean success, String errMsg) -> {
+ results.success = success;
+ results.errMsg = errMsg;
+ });
+ } catch (RemoteException e) {
+ record(logmsg, e);
+ return false;
+ }
+
+ record(logmsg, results);
+ return results.success;
+ }
+
+ public boolean removeDownstreamPrefix(String ifname, String prefix) {
+ final String logmsg = String.format("removeDownstreamPrefix(%s, %s)", ifname, prefix);
+
+ final CbResults results = new CbResults();
+ try {
+ mOffloadControl.removeDownstream(ifname, prefix,
+ (boolean success, String errMsg) -> {
+ results.success = success;
+ results.errMsg = errMsg;
+ });
+ } catch (RemoteException e) {
+ record(logmsg, e);
+ return false;
+ }
+
+ record(logmsg, results);
+ return results.success;
+ }
+
private void record(String msg, Throwable t) {
mLog.e(msg + YIELDS + "exception: " + t);
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 69678df4543d..57d2502c6dc7 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -115,6 +115,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
private final LinkProperties mLinkProperties;
private int mLastError;
+ private int mServingMode;
private String mMyUpstreamIfaceName; // may change over time
private NetworkInterface mNetworkInterface;
private byte[] mHwAddr;
@@ -142,6 +143,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
mLinkProperties = new LinkProperties();
resetLinkProperties();
mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ mServingMode = IControlsTethering.STATE_AVAILABLE;
mInitialState = new InitialState();
mLocalHotspotState = new LocalHotspotState();
@@ -161,6 +163,8 @@ public class TetherInterfaceStateMachine extends StateMachine {
public int lastError() { return mLastError; }
+ public int servingMode() { return mServingMode; }
+
public LinkProperties linkProperties() { return new LinkProperties(mLinkProperties); }
public void stop() { sendMessage(CMD_INTERFACE_DOWN); }
@@ -448,6 +452,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
}
private void sendInterfaceState(int newInterfaceState) {
+ mServingMode = newInterfaceState;
mTetherController.updateInterfaceState(
TetherInterfaceStateMachine.this, newInterfaceState, mLastError);
sendLinkProperties();
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 719c4d44cf9e..e1e5b3555335 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -293,14 +293,35 @@ class InstantAppRegistry {
if (currentCookieFile == null) {
continue;
}
- File expectedCookeFile = computeInstantCookieFile(pkg, userId);
- if (!currentCookieFile.equals(expectedCookeFile)) {
- Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
- + " changed - dropping cookie");
- // Make sure a pending write for the old signed app is cancelled
- mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
- currentCookieFile.delete();
+
+ // Before we used only the first signature to compute the SHA 256 but some
+ // apps could be singed by multiple certs and the cert order is undefined.
+ // We prefer the modern computation procedure where all certs are taken
+ // into account but also allow the value from the old computation to avoid
+ // data loss.
+ final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
+ pkg.mSignatures);
+ final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
+ signaturesSha256Digests);
+
+ // We prefer a match based on all signatures
+ if (currentCookieFile.equals(computeInstantCookieFile(pkg.packageName,
+ signaturesSha256Digest, userId))) {
+ return;
+ }
+
+ // For backwards compatibility we accept match based on first signature
+ if (pkg.mSignatures.length > 1 && currentCookieFile.equals(computeInstantCookieFile(
+ pkg.packageName, signaturesSha256Digests[0], userId))) {
+ return;
}
+
+ // Sorry, you are out of luck - different signatures - nuke data
+ Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
+ + " changed - dropping cookie");
+ // Make sure a pending write for the old signed app is cancelled
+ mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
+ currentCookieFile.delete();
}
}
@@ -968,11 +989,11 @@ class InstantAppRegistry {
}
}
- private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg,
- @UserIdInt int userId) {
- File appDir = getInstantApplicationDir(pkg.packageName, userId);
- String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest(
- pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX;
+ private static @NonNull File computeInstantCookieFile(@NonNull String packageName,
+ @NonNull String sha256Digest, @UserIdInt int userId) {
+ final File appDir = getInstantApplicationDir(packageName, userId);
+ final String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX
+ + sha256Digest + INSTANT_APP_COOKIE_FILE_SIFFIX;
return new File(appDir, cookieFile);
}
@@ -1147,9 +1168,20 @@ class InstantAppRegistry {
public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
@NonNull byte[] cookie) {
- File cookieFile = computeInstantCookieFile(pkg, userId);
+ // Before we used only the first signature to compute the SHA 256 but some
+ // apps could be singed by multiple certs and the cert order is undefined.
+ // We prefer the modern computation procedure where all certs are taken
+ // into account and delete the file derived via the legacy hash computation.
+ File newCookieFile = computeInstantCookieFile(pkg.packageName,
+ PackageUtils.computeSignaturesSha256Digest(pkg.mSignatures), userId);
+ if (pkg.mSignatures.length > 0) {
+ File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId);
+ if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) {
+ oldCookieFile.delete();
+ }
+ }
cancelPendingPersistLPw(pkg, userId);
- addPendingPersistCookieLPw(userId, pkg, cookie, cookieFile);
+ addPendingPersistCookieLPw(userId, pkg, cookie, newCookieFile);
sendMessageDelayed(obtainMessage(userId, pkg),
PERSIST_COOKIE_DELAY_MILLIS);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 95b44b262d43..23d36988228f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10385,16 +10385,19 @@ public class PackageManagerService extends IPackageManager.Stub
ArraySet<String> usesLibraryFiles = null;
if (pkg.usesLibraries != null) {
usesLibraryFiles = addSharedLibrariesLPw(pkg.usesLibraries,
- null, null, pkg.packageName, changingLib, true, null);
+ null, null, pkg.packageName, changingLib, true,
+ pkg.applicationInfo.targetSdkVersion, null);
}
if (pkg.usesStaticLibraries != null) {
usesLibraryFiles = addSharedLibrariesLPw(pkg.usesStaticLibraries,
pkg.usesStaticLibrariesVersions, pkg.usesStaticLibrariesCertDigests,
- pkg.packageName, changingLib, true, usesLibraryFiles);
+ pkg.packageName, changingLib, true,
+ pkg.applicationInfo.targetSdkVersion, usesLibraryFiles);
}
if (pkg.usesOptionalLibraries != null) {
usesLibraryFiles = addSharedLibrariesLPw(pkg.usesOptionalLibraries,
- null, null, pkg.packageName, changingLib, false, usesLibraryFiles);
+ null, null, pkg.packageName, changingLib, false,
+ pkg.applicationInfo.targetSdkVersion, usesLibraryFiles);
}
if (!ArrayUtils.isEmpty(usesLibraryFiles)) {
pkg.usesLibraryFiles = usesLibraryFiles.toArray(new String[usesLibraryFiles.size()]);
@@ -10404,9 +10407,9 @@ public class PackageManagerService extends IPackageManager.Stub
}
private ArraySet<String> addSharedLibrariesLPw(@NonNull List<String> requestedLibraries,
- @Nullable int[] requiredVersions, @Nullable String[] requiredCertDigests,
+ @Nullable int[] requiredVersions, @Nullable String[][] requiredCertDigests,
@NonNull String packageName, @Nullable PackageParser.Package changingLib,
- boolean required, @Nullable ArraySet<String> outUsedLibraries)
+ boolean required, int targetSdk, @Nullable ArraySet<String> outUsedLibraries)
throws PackageManagerException {
final int libCount = requestedLibraries.size();
for (int i = 0; i < libCount; i++) {
@@ -10440,13 +10443,34 @@ public class PackageManagerService extends IPackageManager.Stub
+ " library; failing!");
}
- String expectedCertDigest = requiredCertDigests[i];
- String libCertDigest = PackageUtils.computeCertSha256Digest(
- libPkg.mSignatures[0]);
- if (!libCertDigest.equalsIgnoreCase(expectedCertDigest)) {
+ final String[] expectedCertDigests = requiredCertDigests[i];
+ // For apps targeting O MR1 we require explicit enumeration of all certs.
+ final String[] libCertDigests = (targetSdk > Build.VERSION_CODES.O)
+ ? PackageUtils.computeSignaturesSha256Digests(libPkg.mSignatures)
+ : PackageUtils.computeSignaturesSha256Digests(
+ new Signature[]{libPkg.mSignatures[0]});
+
+ // Take a shortcut if sizes don't match. Note that if an app doesn't
+ // target O we don't parse the "additional-certificate" tags similarly
+ // how we only consider all certs only for apps targeting O (see above).
+ // Therefore, the size check is safe to make.
+ if (expectedCertDigests.length != libCertDigests.length) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires differently signed" +
- " static shared library; failing!");
+ " static sDexLoadReporter.java:45.19hared library; failing!");
+ }
+
+ // Use a predictable order as signature order may vary
+ Arrays.sort(libCertDigests);
+ Arrays.sort(expectedCertDigests);
+
+ final int certCount = libCertDigests.length;
+ for (int j = 0; j < certCount; j++) {
+ if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires differently signed" +
+ " static shared library; failing!");
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index bc7f33021c77..ecf9067b55c9 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -18,6 +18,8 @@ package com.android.server.wm;
import static android.app.ActivityManager.ENABLE_TASK_SNAPSHOTS;
+import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
+import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -188,7 +190,8 @@ class TaskSnapshotController {
*/
@Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
boolean reducedResolution) {
- return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
+ return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution
+ || DISABLE_FULL_SIZED_BITMAPS);
}
/**
@@ -209,14 +212,16 @@ class TaskSnapshotController {
if (mainWindow == null) {
return null;
}
+ final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
+ final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
- -1, -1, false, 1.0f, false, true);
+ -1, -1, false, scaleFraction, false, true);
if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
return null;
}
return new TaskSnapshot(buffer, top.getConfiguration().orientation,
- minRect(mainWindow.mContentInsets, mainWindow.mStableInsets), false /* reduced */,
- 1f /* scale */);
+ minRect(mainWindow.mContentInsets, mainWindow.mStableInsets),
+ isLowRamDevice /* reduced */, scaleFraction /* scale */);
}
private boolean shouldDisableSnapshots() {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index f90b3fb6c1de..1252aee14964 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -21,6 +21,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.TestApi;
+import android.app.ActivityManager;
import android.app.ActivityManager.TaskSnapshot;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
@@ -53,6 +54,7 @@ class TaskSnapshotPersister {
private static final String SNAPSHOTS_DIRNAME = "snapshots";
private static final String REDUCED_POSTFIX = "_reduced";
static final float REDUCED_SCALE = 0.5f;
+ static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
private static final long DELAY_MS = 100;
private static final int QUALITY = 95;
private static final String PROTO_EXTENSION = ".proto";
@@ -183,6 +185,11 @@ class TaskSnapshotPersister {
}
File getBitmapFile(int taskId, int userId) {
+ // Full sized bitmaps are disabled on low ram devices
+ if (DISABLE_FULL_SIZED_BITMAPS) {
+ Slog.wtf(TAG, "This device does not support full sized resolution bitmaps.");
+ return null;
+ }
return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
}
@@ -197,11 +204,15 @@ class TaskSnapshotPersister {
private void deleteSnapshot(int taskId, int userId) {
final File protoFile = getProtoFile(taskId, userId);
- final File bitmapFile = getBitmapFile(taskId, userId);
final File bitmapReducedFile = getReducedResolutionBitmapFile(taskId, userId);
protoFile.delete();
- bitmapFile.delete();
bitmapReducedFile.delete();
+
+ // Low ram devices do not have a full sized file to delete
+ if (!DISABLE_FULL_SIZED_BITMAPS) {
+ final File bitmapFile = getBitmapFile(taskId, userId);
+ bitmapFile.delete();
+ }
}
interface DirectoryResolver {
@@ -323,7 +334,6 @@ class TaskSnapshotPersister {
boolean writeBuffer() {
final File file = getBitmapFile(mTaskId, mUserId);
- final File reducedFile = getReducedResolutionBitmapFile(mTaskId, mUserId);
final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
if (bitmap == null) {
Slog.e(TAG, "Invalid task snapshot hw bitmap");
@@ -331,18 +341,32 @@ class TaskSnapshotPersister {
}
final Bitmap swBitmap = bitmap.copy(Config.ARGB_8888, false /* isMutable */);
- final Bitmap reduced = Bitmap.createScaledBitmap(swBitmap,
- (int) (bitmap.getWidth() * REDUCED_SCALE),
- (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
+ final File reducedFile = getReducedResolutionBitmapFile(mTaskId, mUserId);
+ final Bitmap reduced = mSnapshot.isReducedResolution()
+ ? swBitmap
+ : Bitmap.createScaledBitmap(swBitmap,
+ (int) (bitmap.getWidth() * REDUCED_SCALE),
+ (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
try {
- FileOutputStream fos = new FileOutputStream(file);
- swBitmap.compress(JPEG, QUALITY, fos);
- fos.close();
FileOutputStream reducedFos = new FileOutputStream(reducedFile);
reduced.compress(JPEG, QUALITY, reducedFos);
reducedFos.close();
} catch (IOException e) {
- Slog.e(TAG, "Unable to open " + file + " or " + reducedFile +" for persisting.", e);
+ Slog.e(TAG, "Unable to open " + reducedFile +" for persisting.", e);
+ return false;
+ }
+
+ // For snapshots with reduced resolution, do not create or save full sized bitmaps
+ if (mSnapshot.isReducedResolution()) {
+ return true;
+ }
+
+ try {
+ FileOutputStream fos = new FileOutputStream(file);
+ swBitmap.compress(JPEG, QUALITY, fos);
+ fos.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
return false;
}
return true;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 0610b945d20e..4698d72567c4 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -320,6 +320,10 @@ class TaskSnapshotSurface implements StartingSurface {
mChildSurfaceControl.show();
mChildSurfaceControl.setWindowCrop(crop);
mChildSurfaceControl.setPosition(frame.left, frame.top);
+
+ // Scale the mismatch dimensions to fill the task bounds
+ final float scale = 1 / mSnapshot.getScale();
+ mChildSurfaceControl.setMatrix(scale, 0, 0, scale);
} finally {
SurfaceControl.closeTransaction();
}
@@ -332,6 +336,11 @@ class TaskSnapshotSurface implements StartingSurface {
mSurface.release();
}
+ /**
+ * Calculates the snapshot crop in snapshot coordinate space.
+ *
+ * @return crop rect in snapshot coordinate space.
+ */
@VisibleForTesting
Rect calculateSnapshotCrop() {
final Rect rect = new Rect();
@@ -340,16 +349,28 @@ class TaskSnapshotSurface implements StartingSurface {
// Let's remove all system decorations except the status bar, but only if the task is at the
// very top of the screen.
- rect.inset(insets.left, mTaskBounds.top != 0 ? insets.top : 0, insets.right, insets.bottom);
+ rect.inset((int) (insets.left * mSnapshot.getScale()),
+ mTaskBounds.top != 0 ? (int) (insets.top * mSnapshot.getScale()) : 0,
+ (int) (insets.right * mSnapshot.getScale()),
+ (int) (insets.bottom * mSnapshot.getScale()));
return rect;
}
+ /**
+ * Calculates the snapshot frame in window coordinate space from crop.
+ *
+ * @param crop rect that is in snapshot coordinate space.
+ */
@VisibleForTesting
Rect calculateSnapshotFrame(Rect crop) {
final Rect frame = new Rect(crop);
+ final float scale = mSnapshot.getScale();
+
+ // Rescale the frame from snapshot to window coordinate space
+ frame.scale(1 / scale);
// By default, offset it to to top/left corner
- frame.offsetTo(-crop.left, -crop.top);
+ frame.offsetTo((int) (-crop.left / scale), (int) (-crop.top / scale));
// However, we also need to make space for the navigation bar on the left side.
final int colorViewLeftInset = getColorViewLeftInset(mStableInsets.left,
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index 9b3bc3f0301a..606526816368 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -87,6 +87,7 @@ public final class NetworkConstants {
public static final int IPV4_PROTOCOL_OFFSET = 9;
public static final int IPV4_SRC_ADDR_OFFSET = 12;
public static final int IPV4_DST_ADDR_OFFSET = 16;
+ public static final int IPV4_ADDR_BITS = 32;
public static final int IPV4_ADDR_LEN = 4;
/**
@@ -99,6 +100,7 @@ public final class NetworkConstants {
public static final int IPV6_PROTOCOL_OFFSET = 6;
public static final int IPV6_SRC_ADDR_OFFSET = 8;
public static final int IPV6_DST_ADDR_OFFSET = 24;
+ public static final int IPV6_ADDR_BITS = 128;
public static final int IPV6_ADDR_LEN = 16;
public static final int IPV6_MIN_MTU = 1280;
public static final int RFC7421_PREFIX_LENGTH = 64;
diff --git a/services/net/java/android/net/util/PrefixUtils.java b/services/net/java/android/net/util/PrefixUtils.java
index 962aab459a19..f60694aaedc9 100644
--- a/services/net/java/android/net/util/PrefixUtils.java
+++ b/services/net/java/android/net/util/PrefixUtils.java
@@ -20,6 +20,8 @@ import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import java.net.Inet4Address;
+import java.net.InetAddress;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -68,6 +70,13 @@ public class PrefixUtils {
return new IpPrefix(addr.getAddress(), addr.getPrefixLength());
}
+ public static IpPrefix ipAddressAsPrefix(InetAddress ip) {
+ final int bitLength = (ip instanceof Inet4Address)
+ ? NetworkConstants.IPV4_ADDR_BITS
+ : NetworkConstants.IPV6_ADDR_BITS;
+ return new IpPrefix(ip, bitLength);
+ }
+
private static IpPrefix pfx(String prefixStr) {
return new IpPrefix(prefixStr);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 0e8960e94c56..fa8feb048fd6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -467,7 +467,8 @@ public class PackageParserTest {
pkg.staticSharedLibVersion = 100;
pkg.usesStaticLibraries = new ArrayList<>();
pkg.usesStaticLibraries.add("foo23");
- pkg.usesStaticLibrariesCertDigests = new String[] { "digest" };
+ pkg.usesStaticLibrariesCertDigests = new String[1][];
+ pkg.usesStaticLibrariesCertDigests[0] = new String[] { "digest" };
pkg.usesStaticLibrariesVersions = new int[] { 100 };
pkg.libraryNames = new ArrayList<>();
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index d5bbed7e2aa0..622a7be360e5 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -79,6 +79,15 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class OffloadControllerTest {
+ private static final String RNDIS0 = "test_rndis0";
+ private static final String RMNET0 = "test_rmnet_data0";
+ private static final String WLAN0 = "test_wlan0";
+
+ private static final String IPV6_LINKLOCAL = "fe80::/64";
+ private static final String IPV6_DOC_PREFIX = "2001:db8::/64";
+ private static final String IPV6_DISCARD_PREFIX = "100::/64";
+ private static final String USB_PREFIX = "192.168.42.0/24";
+ private static final String WIFI_PREFIX = "192.168.43.0/24";
@Mock private OffloadHardwareInterface mHardware;
@Mock private ApplicationInfo mApplicationInfo;
@@ -234,10 +243,8 @@ public class OffloadControllerTest {
inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
ArrayList<String> localPrefixes = mStringArrayCaptor.getValue();
assertEquals(4, localPrefixes.size());
- assertTrue(localPrefixes.contains("127.0.0.0/8"));
- assertTrue(localPrefixes.contains("192.0.2.0/24"));
- assertTrue(localPrefixes.contains("fe80::/64"));
- assertTrue(localPrefixes.contains("2001:db8::/64"));
+ assertArrayListContains(localPrefixes,
+ "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64");
inOrder.verifyNoMoreInteractions();
offload.setUpstreamLinkProperties(null);
@@ -352,12 +359,9 @@ public class OffloadControllerTest {
inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
localPrefixes = mStringArrayCaptor.getValue();
assertEquals(6, localPrefixes.size());
- assertTrue(localPrefixes.contains("127.0.0.0/8"));
- assertTrue(localPrefixes.contains("192.0.2.0/24"));
- assertTrue(localPrefixes.contains("fe80::/64"));
- assertTrue(localPrefixes.contains("2001:db8::/64"));
- assertTrue(localPrefixes.contains("2001:db8::6173:7369:676e:6564/128"));
- assertTrue(localPrefixes.contains("2001:db8::7261:6e64:6f6d/128"));
+ assertArrayListContains(localPrefixes,
+ "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64",
+ "2001:db8::6173:7369:676e:6564/128", "2001:db8::7261:6e64:6f6d/128");
// The relevant parts of the LinkProperties have not changed, but at the
// moment we do not de-dup upstream LinkProperties this carefully.
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
@@ -441,6 +445,8 @@ public class OffloadControllerTest {
waitForIdle();
// There is no current upstream, so no stats are fetched.
inOrder.verify(mHardware, never()).getForwardedStats(eq(ethernetIface));
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(null), eq(null), eq(null), eq(null));
inOrder.verifyNoMoreInteractions();
assertEquals(2, stats.size());
@@ -545,4 +551,79 @@ public class OffloadControllerTest {
callback.onStoppedLimitReached();
verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
}
+
+ @Test
+ public void testAddRemoveDownstreams() throws Exception {
+ setupFunctioningHardwareInterface();
+ enableOffload();
+
+ final OffloadController offload = makeOffloadController();
+ offload.start();
+
+ final InOrder inOrder = inOrder(mHardware);
+ inOrder.verify(mHardware, times(1)).initOffloadConfig();
+ inOrder.verify(mHardware, times(1)).initOffloadControl(
+ any(OffloadHardwareInterface.ControlCallback.class));
+ inOrder.verifyNoMoreInteractions();
+
+ // Tethering makes several calls to setLocalPrefixes() before add/remove
+ // downstream calls are made. This is not tested here; only the behavior
+ // of notifyDownstreamLinkProperties() and removeDownstreamInterface()
+ // are tested.
+
+ // [1] USB tethering is started.
+ final LinkProperties usbLinkProperties = new LinkProperties();
+ usbLinkProperties.setInterfaceName(RNDIS0);
+ usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24"));
+ usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX)));
+ offload.notifyDownstreamLinkProperties(usbLinkProperties);
+ inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, USB_PREFIX);
+ inOrder.verifyNoMoreInteractions();
+
+ // [2] Routes for IPv6 link-local prefixes should never be added.
+ usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL)));
+ offload.notifyDownstreamLinkProperties(usbLinkProperties);
+ inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
+ inOrder.verifyNoMoreInteractions();
+
+ // [3] Add an IPv6 prefix for good measure. Only new offload-able
+ // prefixes should be passed to the HAL.
+ usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+ usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX)));
+ offload.notifyDownstreamLinkProperties(usbLinkProperties);
+ inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
+ inOrder.verifyNoMoreInteractions();
+
+ // [4] Adding addresses doesn't affect notifyDownstreamLinkProperties().
+ // The address is passed in by a separate setLocalPrefixes() invocation.
+ usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::2/64"));
+ offload.notifyDownstreamLinkProperties(usbLinkProperties);
+ inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
+
+ // [5] Differences in local routes are converted into addDownstream()
+ // and removeDownstream() invocations accordingly.
+ usbLinkProperties.removeRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0));
+ usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX)));
+ offload.notifyDownstreamLinkProperties(usbLinkProperties);
+ inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
+ inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
+ inOrder.verifyNoMoreInteractions();
+
+ // [6] Removing a downstream interface which was never added causes no
+ // interactions with the HAL.
+ offload.removeDownstreamInterface(WLAN0);
+ inOrder.verifyNoMoreInteractions();
+
+ // [7] Removing an active downstream removes all remaining prefixes.
+ offload.removeDownstreamInterface(RNDIS0);
+ inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, USB_PREFIX);
+ inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ private static void assertArrayListContains(ArrayList<String> list, String... elems) {
+ for (String element : elems) {
+ assertTrue(list.contains(element));
+ }
+ }
}