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