Merge "Support l11n of Qualcomm® aptX™ strings" into oc-mr1-dev
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index cf0edca..ba488f6 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -187,8 +187,17 @@
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 7ae8522..cb9ecf3 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -97,6 +97,7 @@
import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -2824,14 +2825,14 @@
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 @@
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 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 @@
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 @@
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 0fe56f6..e2e9d53 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -18,12 +18,13 @@
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 @@
}
/**
- * 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 computeCertSha256Digest(packageInfo.signatures[0]);
+ 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());
+ }
+
+ // 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 4b98e35..81ab407 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1926,13 +1926,14 @@
* 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 c89f546..e85e295 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -137,7 +137,7 @@
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 6828150..8aff3b6 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/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index b8c5aca..0de3e52 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -124,8 +124,14 @@
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.
+ */
+ // TODO(b/63073866): change this to have score eviction logic
+ private final Map<String, ScoredNetwork> mScoredNetworkCache = new HashMap<>();
/** Maximum age of scan results to hold onto while actively scanning. **/
private static final long MAX_SCAN_RESULT_AGE_MS = 15000;
@@ -138,6 +144,7 @@
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 +195,7 @@
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 +245,13 @@
mScanResultCache.put(result.BSSID, result);
}
}
+ if (savedState.containsKey(KEY_SCOREDNETWORKCACHE)) {
+ ArrayList<ScoredNetwork> scoredNetworkArrayList =
+ savedState.getParcelableArrayList(KEY_SCOREDNETWORKCACHE);
+ for (ScoredNetwork score : scoredNetworkArrayList) {
+ mScoredNetworkCache.put(score.networkKey.wifiKey.bssid, score);
+ }
+ }
if (savedState.containsKey(KEY_FQDN)) {
mFqdn = savedState.getString(KEY_FQDN);
}
@@ -308,8 +322,8 @@
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 +361,7 @@
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 +439,6 @@
*/
boolean update(WifiNetworkScoreCache scoreCache, boolean scoringUiEnabled) {
boolean scoreChanged = false;
- mScanResultScores.clear();
if (scoringUiEnabled) {
scoreChanged = updateScores(scoreCache);
}
@@ -435,38 +448,79 @@
/**
* Updates the AccessPoint rankingScore and speed, returning true if the data has changed.
*
+ * <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;
-
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);
+ mScoredNetworkCache.put(result.BSSID, score);
}
- // set mSpeed to the connected ScanResult if the AccessPoint is the active network
+ 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);
+ ScoredNetwork score = mScoredNetworkCache.get(mInfo.getBSSID());
if (score != null) {
- mSpeed = score.calculateBadge(mInfo.getRssi());
+ 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 = score.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 (ScoredNetwork score : mScoredNetworkCache.values()) {
+ int speed = score.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);
}
/**
@@ -582,8 +636,6 @@
/** 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 +994,23 @@
}
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) {
+ ScoredNetwork score = mScoredNetworkCache.get(result.BSSID);
+ if (score == 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 score.calculateBadge(result.level);
}
/**
@@ -1067,6 +1125,8 @@
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 +1165,12 @@
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 +1255,23 @@
}
@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/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index ae59d37..083d0c5 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
@@ -66,10 +66,24 @@
public class AccessPointTest {
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 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 +414,7 @@
}
@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 +425,42 @@
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,8 +594,13 @@
}
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,
@@ -574,19 +611,14 @@
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;
@@ -849,4 +881,87 @@
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);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 562210c..f844866 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 @@
});
// 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 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 1b86143..79558a3 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 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.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.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 @@
// 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 @@
// 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());
- 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;
+ 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 @@
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 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 void onConfigurationChanged() {
reloadResources();
- synchronized (mDummyStackView) {
- mDummyStackView.reloadOnConfigurationChange();
+ mDummyStackView.reloadOnConfigurationChange();
+ synchronized (mBackgroundLayoutAlgorithm) {
+ mBackgroundLayoutAlgorithm.reloadOnConfigurationChange(mContext);
}
}
@@ -698,12 +700,6 @@
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 @@
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 @@
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 @@
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 @@
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;
+ ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks();
+ TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
+ TaskStackViewScroller stackScroller = mDummyStackView.getScroller();
- synchronized (mDummyStackView) {
- tasks = mDummyStackView.getStack().getStackTasks();
- stackLayout = mDummyStackView.getStackAlgorithm();
- 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 @@
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 ccaf3cd..71f06cb 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 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.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 @@
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 @@
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 @@
} else {
mBackgroundScrim.setAlpha(0);
}
+ mMultiWindowBackgroundScrim.setAlpha(mBackgroundScrim.getAlpha());
}
}
@@ -300,8 +311,14 @@
/**
* 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 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 @@
// 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 @@
// 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 d810ea4..eaa32ee 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 @@
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 @@
* 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 0469882..694c72f 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 @@
&& (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 f8c0e27..dd98053 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 @@
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 @@
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 6380da5..1c77a7f 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.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.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 @@
} 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 @@
}
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 2b4f4e6..a985b4f 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.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 @@
// 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
// TODO: De-duplicate with updateUpstreamWanted() below.
if (upstreamWanted()) {
mUpstreamWanted = true;
- startOffloadController();
+ mOffload.start();
chooseUpstreamType(true);
mTryCell = false;
}
@@ -1533,7 +1516,7 @@
@Override
public void exit() {
- mOffloadController.stop();
+ mOffload.stop();
mUpstreamNetworkMonitor.stop();
mSimChange.stopListening();
notifyDownstreamsOfNewUpstreamIface(null);
@@ -1545,9 +1528,9 @@
mUpstreamWanted = upstreamWanted();
if (mUpstreamWanted != previousUpstreamWanted) {
if (mUpstreamWanted) {
- startOffloadController();
+ mOffload.start();
} else {
- mOffloadController.stop();
+ mOffload.stop();
}
}
return previousUpstreamWanted;
@@ -1602,12 +1585,9 @@
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 @@
} 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 ef18e4e..6d5c428 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.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 @@
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 @@
mNms = nms;
mStatsProvider = new OffloadTetheringStatsProvider();
mLog = log.forSubComponent(TAG);
+ mDownstreams = new HashMap<>();
mExemptPrefixes = new HashSet<>();
mLastLocalPrefixStrs = new HashSet<>();
@@ -257,6 +260,11 @@
}
}
+ private String currentUpstreamInterface() {
+ return (mUpstreamLinkProperties != null)
+ ? mUpstreamLinkProperties.getInterfaceName() : null;
+ }
+
private void maybeUpdateStats(String iface) {
if (TextUtils.isEmpty(iface)) {
return;
@@ -281,9 +289,7 @@
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 @@
}
private void updateStatsForCurrentUpstream() {
- if (mUpstreamLinkProperties != null) {
- maybeUpdateStats(mUpstreamLinkProperties.getInterfaceName());
- }
+ maybeUpdateStats(currentUpstreamInterface());
}
public void setUpstreamLinkProperties(LinkProperties lp) {
@@ -325,17 +329,42 @@
}
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 @@
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 86ff0a6..865a989 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 @@
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 69678df..57d2502 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 @@
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 @@
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 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 @@
}
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 719c4d4..e1e5b35 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 @@
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 @@
}
}
- 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 @@
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 95b44b2..23d36988 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 @@
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 @@
}
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 @@
+ " 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 bc7f330..ecf9067 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 @@
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 @@
*/
@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 @@
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 f90b3fb..1252aee 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_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 @@
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 @@
}
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 @@
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 @@
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 @@
}
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 0610b94..4698d72 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 @@
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 @@
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 @@
// 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 9b3bc3f..6065268 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -87,6 +87,7 @@
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 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 962aab4..f60694a 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.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 @@
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 0e8960e..fa8feb0 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 @@
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 d5bbed7..622a7be 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 @@
@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 @@
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 @@
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 @@
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 @@
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));
+ }
+ }
}