Merge "Import translations. DO NOT MERGE" into oc-mr1-dev
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 3fa8927..757795e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -762,6 +762,10 @@
private boolean mDestroyed;
private boolean mDoReportFullyDrawn = true;
private boolean mRestoredFromBundle;
+
+ /** {@code true} if the activity lifecycle is in a state which supports picture-in-picture.
+ * This only affects the client-side exception, the actual state check still happens in AMS. */
+ private boolean mCanEnterPictureInPicture = false;
/** true if the activity is going through a transient pause */
/*package*/ boolean mTemporaryPause = false;
/** true if the activity is being destroyed in order to recreate it with a new configuration */
@@ -2091,6 +2095,10 @@
if (params == null) {
throw new IllegalArgumentException("Expected non-null picture-in-picture params");
}
+ if (!mCanEnterPictureInPicture) {
+ throw new IllegalStateException("Activity must be resumed to enter"
+ + " picture-in-picture");
+ }
return ActivityManagerNative.getDefault().enterPictureInPictureMode(mToken, params);
} catch (RemoteException e) {
return false;
@@ -6957,25 +6965,29 @@
return mParent != null ? mParent.getActivityToken() : mToken;
}
- final void performCreateCommon() {
+ final void performCreate(Bundle icicle) {
+ performCreate(icicle, null);
+ }
+
+ final void performCreate(Bundle icicle, PersistableBundle persistentState) {
+ mCanEnterPictureInPicture = true;
+ restoreHasCurrentPermissionRequest(icicle);
+ if (persistentState != null) {
+ onCreate(icicle, persistentState);
+ } else {
+ onCreate(icicle);
+ }
+ mActivityTransitionState.readState(icicle);
+
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
mFragments.dispatchActivityCreated();
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
}
- final void performCreate(Bundle icicle) {
- restoreHasCurrentPermissionRequest(icicle);
- onCreate(icicle);
- mActivityTransitionState.readState(icicle);
- performCreateCommon();
- }
-
- final void performCreate(Bundle icicle, PersistableBundle persistentState) {
- restoreHasCurrentPermissionRequest(icicle);
- onCreate(icicle, persistentState);
- mActivityTransitionState.readState(icicle);
- performCreateCommon();
+ final void performNewIntent(Intent intent) {
+ mCanEnterPictureInPicture = true;
+ onNewIntent(intent);
}
final void performStart() {
@@ -7126,6 +7138,9 @@
mDoReportFullyDrawn = false;
mFragments.doLoaderStop(mChangingConfigurations /*retain*/);
+ // Disallow entering picture-in-picture after the activity has been stopped
+ mCanEnterPictureInPicture = false;
+
if (!mStopped) {
if (mWindow != null) {
mWindow.closeAllPanels();
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 467fc95..e260967 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -48,6 +48,7 @@
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.Window;
+
import com.android.internal.content.ReferrerIntent;
import java.io.File;
@@ -1305,7 +1306,7 @@
* @param intent The new intent being received.
*/
public void callActivityOnNewIntent(Activity activity, Intent intent) {
- activity.onNewIntent(intent);
+ activity.performNewIntent(intent);
}
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 4b98e35..81ab407 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1926,13 +1926,14 @@
* For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
* all public methods (including the inherited ones) can be accessed, see the
* important security note below for implications.
- * <p> Note that injected objects will not
- * appear in JavaScript until the page is next (re)loaded. For example:
+ * <p> Note that injected objects will not appear in JavaScript until the page is next
+ * (re)loaded. JavaScript should be enabled before injecting the object. For example:
* <pre>
* class JsObject {
* {@literal @}JavascriptInterface
* public String toString() { return "injectedObject"; }
* }
+ * webview.getSettings().setJavaScriptEnabled(true);
* webView.addJavascriptInterface(new JsObject(), "injectedObject");
* webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
* webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 1f1b67e..0bf2eda 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 b8c5aca..330eaf0 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
*/
boolean update(WifiNetworkScoreCache scoreCache, boolean scoringUiEnabled) {
boolean scoreChanged = false;
- mScanResultScores.clear();
if (scoringUiEnabled) {
scoreChanged = updateScores(scoreCache);
}
@@ -435,38 +452,99 @@
/**
* 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);
- }
-
- // set mSpeed 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(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);
}
}
- if(WifiTracker.sVerboseLogging) {
- Log.i(TAG, String.format("%s: Set speed to %d", ssid, mSpeed));
+ // 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) {
+ 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;
+ }
+ }
}
- return oldSpeed != mSpeed;
+ boolean changed = oldSpeed != mSpeed;
+ if(WifiTracker.sVerboseLogging && changed) {
+ Log.i(TAG, String.format("%s: Set speed to %d", ssid, mSpeed));
+ }
+ return changed;
+ }
+
+ /** 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 @@
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 @@
/** 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 @@
}
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 @@
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 @@
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 @@
}
@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 93bf3c7..3dec1d3 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.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 @@
Context mContext;
private ArrayList<ScanResult> mScanResultCache;
+ private ArrayList<TimestampedScoredNetwork> mScoredNetworkCache;
@Keep
public TestAccessPointBuilder(Context context) {
@@ -85,6 +87,9 @@
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 @@
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 0000000..cb15a79
--- /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 ae59d37..6f1b25f 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 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.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.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 @@
}
@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 @@
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 @@
}
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 @@
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 @@
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 @@
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 562210c..f844866 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -367,7 +367,7 @@
});
// Set the window background
- getWindow().setBackgroundDrawable(mRecentsView.getBackgroundScrim());
+ mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode());
// Create the home intent runnable
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -556,6 +556,9 @@
public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
super.onMultiWindowModeChanged(isInMultiWindowMode);
+ // Set the window background
+ mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode);
+
reloadTaskStack(isInMultiWindowMode, true /* sendConfigChangedEvent */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 1b86143..79558a3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -24,7 +24,6 @@
import android.app.ActivityManager;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationFinishedListener;
import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -36,6 +35,7 @@
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.SystemClock;
+import android.util.ArraySet;
import android.util.Log;
import android.util.MutableBoolean;
import android.util.Pair;
@@ -76,6 +76,7 @@
import com.android.systemui.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.Task.TaskKey;
import com.android.systemui.recents.model.TaskGrouping;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.model.ThumbnailData;
@@ -110,6 +111,8 @@
// duration, then we will toggle recents after this duration.
private final static int FAST_ALT_TAB_DELAY_MS = 225;
+ private final static ArraySet<TaskKey> EMPTY_SET = new ArraySet<>();
+
public final static String RECENTS_PACKAGE = "com.android.systemui";
public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
@@ -129,39 +132,38 @@
// Preloads the next task
RecentsConfiguration config = Recents.getConfiguration();
if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
-
// Load the next task only if we aren't svelte
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+ TaskStack stack = plan.getTaskStack();
+ RecentsActivityLaunchState launchState = new RecentsActivityLaunchState();
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- // This callback is made when a new activity is launched and the old one is paused
- // so ignore the current activity and try and preload the thumbnail for the
- // previous one.
- VisibilityReport visibilityReport;
- synchronized (mDummyStackView) {
- mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */);
- mDummyStackView.setTasks(plan.getTaskStack(), false /* allowNotify */);
- updateDummyStackViewLayout(plan.getTaskStack(),
+ synchronized (mBackgroundLayoutAlgorithm) {
+ // This callback is made when a new activity is launched and the old one is
+ // paused so ignore the current activity and try and preload the thumbnail for
+ // the previous one.
+ updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack,
getWindowRect(null /* windowRectOverride */));
// Launched from app is always the worst case (in terms of how many
// thumbnails/tasks visible)
- RecentsActivityLaunchState launchState = new RecentsActivityLaunchState();
launchState.launchedFromApp = true;
- mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */, launchState);
- visibilityReport = mDummyStackView.computeStackVisibilityReport();
- }
+ mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState);
+ VisibilityReport visibilityReport =
+ mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
+ stack.getStackTasks());
- RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
- launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks;
- launchOpts.numVisibleTaskThumbnails = visibilityReport.numVisibleThumbnails;
- launchOpts.onlyLoadForCache = true;
- launchOpts.onlyLoadPausedActivities = true;
- launchOpts.loadThumbnails = true;
+ launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
+ launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks;
+ launchOpts.numVisibleTaskThumbnails = visibilityReport.numVisibleThumbnails;
+ launchOpts.onlyLoadForCache = true;
+ launchOpts.onlyLoadPausedActivities = true;
+ launchOpts.loadThumbnails = true;
+ }
loader.loadTasks(mContext, plan, launchOpts);
}
}
@@ -230,17 +232,15 @@
boolean mLaunchedWhileDocking;
// Task launching
- Rect mTaskStackBounds = new Rect();
+ Rect mTmpBounds = new Rect();
TaskViewTransform mTmpTransform = new TaskViewTransform();
- int mStatusBarHeight;
- int mNavBarHeight;
- int mNavBarWidth;
int mTaskBarHeight;
// Header (for transition)
TaskViewHeader mHeaderBar;
final Object mHeaderBarLock = new Object();
- protected TaskStackView mDummyStackView;
+ private TaskStackView mDummyStackView;
+ private TaskStackLayoutAlgorithm mBackgroundLayoutAlgorithm;
// Variables to keep track of if we need to start recents after binding
protected boolean mTriggeredFromAltTab;
@@ -259,6 +259,7 @@
public RecentsImpl(Context context) {
mContext = context;
mHandler = new Handler();
+ mBackgroundLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null);
// Initialize the static foreground thread
ForegroundThread.get();
@@ -288,8 +289,9 @@
public void onConfigurationChanged() {
reloadResources();
- synchronized (mDummyStackView) {
- mDummyStackView.reloadOnConfigurationChange();
+ mDummyStackView.reloadOnConfigurationChange();
+ synchronized (mBackgroundLayoutAlgorithm) {
+ mBackgroundLayoutAlgorithm.reloadOnConfigurationChange(mContext);
}
}
@@ -698,12 +700,6 @@
private void reloadResources() {
Resources res = mContext.getResources();
- mStatusBarHeight = res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- mNavBarHeight = res.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_height);
- mNavBarWidth = res.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_width);
mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext,
R.dimen.recents_task_view_header_height,
R.dimen.recents_task_view_header_height,
@@ -719,7 +715,8 @@
mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection());
}
- private void updateDummyStackViewLayout(TaskStack stack, Rect windowRect) {
+ private void updateDummyStackViewLayout(TaskStackLayoutAlgorithm stackLayout,
+ TaskStack stack, Rect windowRect) {
SystemServicesProxy ssp = Recents.getSystemServices();
Rect displayRect = ssp.getDisplayRect();
Rect systemInsets = new Rect();
@@ -735,18 +732,14 @@
calculateWindowStableInsets(systemInsets, windowRect, displayRect);
windowRect.offsetTo(0, 0);
- synchronized (mDummyStackView) {
- TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
-
- // Rebind the header bar and draw it for the transition
- stackLayout.setSystemInsets(systemInsets);
- if (stack != null) {
- stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
- systemInsets.left, systemInsets.right, mTaskStackBounds);
- stackLayout.reset();
- stackLayout.initialize(displayRect, windowRect, mTaskStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
- }
+ // Rebind the header bar and draw it for the transition
+ stackLayout.setSystemInsets(systemInsets);
+ if (stack != null) {
+ stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
+ systemInsets.left, systemInsets.right, mTmpBounds);
+ stackLayout.reset();
+ stackLayout.initialize(displayRect, windowRect, mTmpBounds,
+ TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
}
}
@@ -768,26 +761,23 @@
private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) {
Rect windowRect = getWindowRect(windowRectOverride);
int taskViewWidth = 0;
- boolean useGridLayout = false;
- synchronized (mDummyStackView) {
- useGridLayout = mDummyStackView.useGridLayout();
- updateDummyStackViewLayout(stack, windowRect);
- if (stack != null) {
- TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
- mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */);
- mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
- // Get the width of a task view so that we know how wide to draw the header bar.
- if (useGridLayout) {
- TaskGridLayoutAlgorithm gridLayout = mDummyStackView.getGridAlgorithm();
- gridLayout.initialize(windowRect);
- taskViewWidth = (int) gridLayout.getTransform(0 /* taskIndex */,
- stack.getTaskCount(), new TaskViewTransform(),
- stackLayout).rect.width();
- } else {
- Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
- if (!taskViewBounds.isEmpty()) {
- taskViewWidth = taskViewBounds.width();
- }
+ boolean useGridLayout = mDummyStackView.useGridLayout();
+ updateDummyStackViewLayout(mDummyStackView.getStackAlgorithm(), stack, windowRect);
+ if (stack != null) {
+ TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
+ mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */);
+ mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
+ // Get the width of a task view so that we know how wide to draw the header bar.
+ if (useGridLayout) {
+ TaskGridLayoutAlgorithm gridLayout = mDummyStackView.getGridAlgorithm();
+ gridLayout.initialize(windowRect);
+ taskViewWidth = (int) gridLayout.getTransform(0 /* taskIndex */,
+ stack.getTaskCount(), new TaskViewTransform(),
+ stackLayout).rect.width();
+ } else {
+ Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
+ if (!taskViewBounds.isEmpty()) {
+ taskViewWidth = taskViewBounds.width();
}
}
}
@@ -870,18 +860,12 @@
final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
- ArrayList<Task> tasks;
- TaskStackLayoutAlgorithm stackLayout;
- TaskStackViewScroller stackScroller;
+ ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks();
+ TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
+ TaskStackViewScroller stackScroller = mDummyStackView.getScroller();
- synchronized (mDummyStackView) {
- tasks = mDummyStackView.getStack().getStackTasks();
- stackLayout = mDummyStackView.getStackAlgorithm();
- stackScroller = mDummyStackView.getScroller();
-
- mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */);
- mDummyStackView.updateToInitialState();
- }
+ mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */);
+ mDummyStackView.updateToInitialState();
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
@@ -1044,10 +1028,8 @@
updateHeaderBarLayout(stack, windowOverrideRect);
// Prepare the dummy stack for the transition
- TaskStackLayoutAlgorithm.VisibilityReport stackVr;
- synchronized (mDummyStackView) {
- stackVr = mDummyStackView.computeStackVisibilityReport();
- }
+ TaskStackLayoutAlgorithm.VisibilityReport stackVr =
+ mDummyStackView.computeStackVisibilityReport();
// Update the remaining launch state
launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index ccaf3cd..71f06cb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -20,13 +20,17 @@
import android.animation.Animator;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.ArraySet;
import android.util.AttributeSet;
@@ -37,6 +41,7 @@
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewPropertyAnimator;
+import android.view.Window;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -117,7 +122,15 @@
private float mBusynessFactor;
private GradientDrawable mBackgroundScrim;
- private Animator mBackgroundScrimAnimator;
+ private ColorDrawable mMultiWindowBackgroundScrim;
+ private ValueAnimator mBackgroundScrimAnimator;
+ private Point mTmpDisplaySize = new Point();
+
+ private final AnimatorUpdateListener mUpdateBackgroundScrimAlpha = (animation) -> {
+ int alpha = (Integer) animation.getAnimatedValue();
+ mBackgroundScrim.setAlpha(alpha);
+ mMultiWindowBackgroundScrim.setAlpha(alpha);
+ };
private RecentsTransitionHelper mTransitionHelper;
@ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
@@ -146,10 +159,7 @@
mTouchHandler = new RecentsViewTouchHandler(this);
mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
mBackgroundScrim = new GradientDrawable(context);
- mBackgroundScrim.setCallback(this);
-
- boolean usingDarkText = Color.luminance(
- Utils.getColorAttr(mContext, R.attr.wallpaperTextColor)) < 0.5f;
+ mMultiWindowBackgroundScrim = new ColorDrawable();
LayoutInflater inflater = LayoutInflater.from(context);
mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
@@ -244,6 +254,7 @@
} else {
mBackgroundScrim.setAlpha(0);
}
+ mMultiWindowBackgroundScrim.setAlpha(mBackgroundScrim.getAlpha());
}
}
@@ -300,8 +311,14 @@
/**
* Returns the window background scrim.
*/
- public Drawable getBackgroundScrim() {
- return mBackgroundScrim;
+ public void updateBackgroundScrim(Window window, boolean isInMultiWindow) {
+ if (isInMultiWindow) {
+ mBackgroundScrim.setCallback(null);
+ window.setBackgroundDrawable(mMultiWindowBackgroundScrim);
+ } else {
+ mMultiWindowBackgroundScrim.setCallback(null);
+ window.setBackgroundDrawable(mBackgroundScrim);
+ }
}
/**
@@ -401,6 +418,9 @@
*/
public void setScrimColors(ColorExtractor.GradientColors scrimColors, boolean animated) {
mBackgroundScrim.setColors(scrimColors, animated);
+ int alpha = mMultiWindowBackgroundScrim.getAlpha();
+ mMultiWindowBackgroundScrim.setColor(scrimColors.getMainColor());
+ mMultiWindowBackgroundScrim.setAlpha(alpha);
}
@Override
@@ -470,8 +490,10 @@
// Needs to know the screen size since the gradient never scales up or down
// even when bounds change.
- mBackgroundScrim.setScreenSize(right - left, bottom - top);
+ mContext.getDisplay().getRealSize(mTmpDisplaySize);
+ mBackgroundScrim.setScreenSize(mTmpDisplaySize.x, mTmpDisplaySize.y);
mBackgroundScrim.setBounds(left, top, right, bottom);
+ mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
if (RecentsDebugFlags.Static.EnableStackActionButton) {
// Layout the stack action button such that its drawable is start-aligned with the
@@ -916,12 +938,12 @@
// Calculate the absolute alpha to animate from
final int fromAlpha = mBackgroundScrim.getAlpha();
final int toAlpha = (int) (alpha * 255);
- mBackgroundScrimAnimator = ObjectAnimator.ofInt(mBackgroundScrim, Utilities.DRAWABLE_ALPHA,
- fromAlpha, toAlpha);
+ mBackgroundScrimAnimator = ValueAnimator.ofInt(fromAlpha, toAlpha);
mBackgroundScrimAnimator.setDuration(duration);
mBackgroundScrimAnimator.setInterpolator(toAlpha > fromAlpha
? Interpolators.ALPHA_IN
: Interpolators.ALPHA_OUT);
+ mBackgroundScrimAnimator.addUpdateListener(mUpdateBackgroundScrimAlpha);
mBackgroundScrimAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index d810ea4..eaa32ee 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -354,7 +354,6 @@
TaskViewTransform mFrontOfStackTransform = new TaskViewTransform();
public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
- Resources res = context.getResources();
mContext = context;
mCb = cb;
mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
@@ -519,7 +518,7 @@
* Computes the minimum and maximum scroll progress values and the progress values for each task
* in the stack.
*/
- void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
+ public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
RecentsActivityLaunchState launchState) {
SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 04698827..694c72f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -714,6 +714,7 @@
&& (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
&& !mZenPanel.isEditing();
+ TransitionManager.endTransitions(mDialogView);
TransitionManager.beginDelayedTransition(mDialogView, getTransition());
if (wasVisible != visible && !visible) {
prepareForCollapse();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 32d3445..5c5a6b7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8045,7 +8045,7 @@
// Activity supports picture-in-picture, now check that we can enter PiP at this
// point, if it is
if (!r.checkEnterPictureInPictureState("enterPictureInPictureMode",
- false /* noThrow */, false /* beforeStopping */)) {
+ false /* beforeStopping */)) {
return false;
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e5985c5..874bd1e 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1186,10 +1186,9 @@
* @param beforeStopping Whether this check is for an auto-enter-pip operation, that is to say
* the activity has requested to enter PiP when it would otherwise be stopped.
*
- * @return whether this activity is currently allowed to enter PIP, throwing an exception if
- * the activity is not currently visible and {@param noThrow} is not set.
+ * @return whether this activity is currently allowed to enter PIP.
*/
- boolean checkEnterPictureInPictureState(String caller, boolean noThrow, boolean beforeStopping) {
+ boolean checkEnterPictureInPictureState(String caller, boolean beforeStopping) {
if (!supportsPictureInPicture()) {
return false;
}
@@ -1237,13 +1236,7 @@
return isNotLockedOrOnKeyguard && !hasPinnedStack;
}
default:
- if (noThrow) {
- return false;
- } else {
- throw new IllegalStateException(caller
- + ": Current activity is not visible (state=" + state.name() + ") "
- + "r=" + this);
- }
+ return false;
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index c2656a1..eb3177a 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2076,7 +2076,7 @@
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r + " " + r.state);
try {
final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState(
- "makeInvisible", true /* noThrow */, true /* beforeStopping */);
+ "makeInvisible", true /* beforeStopping */);
// Defer telling the client it is hidden if it can enter Pip and isn't current stopped
// or stopping. This gives it a chance to enter Pip in onPause().
final boolean deferHidingClient = canEnterPictureInPicture
@@ -2390,7 +2390,7 @@
// represent the last resumed activity. However, the last focus stack does if it isn't null.
final ActivityRecord lastResumed = lastFocusedStack.mResumedActivity;
lastResumedCanPip = lastResumed != null && lastResumed.checkEnterPictureInPictureState(
- "resumeTopActivity", true /* noThrow */, userLeaving /* beforeStopping */);
+ "resumeTopActivity", userLeaving /* beforeStopping */);
}
// If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous activity
// to be paused, while at the same time resuming the new resume activity only if the
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 2b4f4e6..a985b4f 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -49,6 +49,7 @@
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.IpPrefix;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -1196,6 +1197,7 @@
// to tear itself down.
private final ArrayList<TetherInterfaceStateMachine> mNotifyList;
private final IPv6TetheringCoordinator mIPv6TetheringCoordinator;
+ private final OffloadWrapper mOffload;
private static final int UPSTREAM_SETTLE_TIME_MS = 10000;
@@ -1220,33 +1222,11 @@
mNotifyList = new ArrayList<>();
mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList, mLog);
+ mOffload = new OffloadWrapper();
setInitialState(mInitialState);
}
- private void startOffloadController() {
- mOffloadController.start();
- sendOffloadExemptPrefixes();
- }
-
- private void sendOffloadExemptPrefixes() {
- sendOffloadExemptPrefixes(mUpstreamNetworkMonitor.getLocalPrefixes());
- }
-
- private void sendOffloadExemptPrefixes(Set<IpPrefix> localPrefixes) {
- // Add in well-known minimum set.
- PrefixUtils.addNonForwardablePrefixes(localPrefixes);
- // Add tragically hardcoded prefixes.
- localPrefixes.add(PrefixUtils.DEFAULT_WIFI_P2P_PREFIX);
-
- // Add prefixes for all downstreams, regardless of IP serving mode.
- for (TetherInterfaceStateMachine tism : mNotifyList) {
- localPrefixes.addAll(PrefixUtils.localPrefixesFrom(tism.linkProperties()));
- }
-
- mOffloadController.setLocalPrefixes(localPrefixes);
- }
-
class InitialState extends State {
@Override
public boolean processMessage(Message message) {
@@ -1404,7 +1384,7 @@
protected void handleNewUpstreamNetworkState(NetworkState ns) {
mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns);
- mOffloadController.setUpstreamLinkProperties((ns != null) ? ns.linkProperties : null);
+ mOffload.updateUpstreamNetworkState(ns);
}
private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) {
@@ -1414,9 +1394,12 @@
}
if (mode == IControlsTethering.STATE_TETHERED) {
+ // No need to notify OffloadController just yet as there are no
+ // "offload-able" prefixes to pass along. This will handled
+ // when the TISM informs Tethering of its LinkProperties.
mForwardedDownstreams.add(who);
} else {
- mOffloadController.removeDownstreamInterface(who.interfaceName());
+ mOffload.excludeDownstreamInterface(who.interfaceName());
mForwardedDownstreams.remove(who);
}
@@ -1441,7 +1424,7 @@
private void handleInterfaceServingStateInactive(TetherInterfaceStateMachine who) {
mNotifyList.remove(who);
mIPv6TetheringCoordinator.removeActiveDownstream(who);
- mOffloadController.removeDownstreamInterface(who.interfaceName());
+ mOffload.excludeDownstreamInterface(who.interfaceName());
mForwardedDownstreams.remove(who);
// If this is a Wi-Fi interface, tell WifiManager of any errors.
@@ -1455,7 +1438,7 @@
private void handleUpstreamNetworkMonitorCallback(int arg1, Object o) {
if (arg1 == UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES) {
- sendOffloadExemptPrefixes((Set<IpPrefix>) o);
+ mOffload.sendOffloadExemptPrefixes((Set<IpPrefix>) o);
return;
}
@@ -1525,7 +1508,7 @@
// TODO: De-duplicate with updateUpstreamWanted() below.
if (upstreamWanted()) {
mUpstreamWanted = true;
- startOffloadController();
+ mOffload.start();
chooseUpstreamType(true);
mTryCell = false;
}
@@ -1533,7 +1516,7 @@
@Override
public void exit() {
- mOffloadController.stop();
+ mOffload.stop();
mUpstreamNetworkMonitor.stop();
mSimChange.stopListening();
notifyDownstreamsOfNewUpstreamIface(null);
@@ -1545,9 +1528,9 @@
mUpstreamWanted = upstreamWanted();
if (mUpstreamWanted != previousUpstreamWanted) {
if (mUpstreamWanted) {
- startOffloadController();
+ mOffload.start();
} else {
- mOffloadController.stop();
+ mOffload.stop();
}
}
return previousUpstreamWanted;
@@ -1602,12 +1585,9 @@
case EVENT_IFACE_UPDATE_LINKPROPERTIES: {
final LinkProperties newLp = (LinkProperties) message.obj;
if (message.arg1 == IControlsTethering.STATE_TETHERED) {
- mOffloadController.notifyDownstreamLinkProperties(newLp);
+ mOffload.updateDownstreamLinkProperties(newLp);
} else {
- mOffloadController.removeDownstreamInterface(newLp.getInterfaceName());
- // Another interface might be in local-only hotspot mode;
- // resend all local prefixes to the OffloadController.
- sendOffloadExemptPrefixes();
+ mOffload.excludeDownstreamInterface(newLp.getInterfaceName());
}
break;
}
@@ -1722,6 +1702,82 @@
} catch (Exception e) {}
}
}
+
+ // A wrapper class to handle multiple situations where several calls to
+ // the OffloadController need to happen together.
+ //
+ // TODO: This suggests that the interface between OffloadController and
+ // Tethering is in need of improvement. Refactor these calls into the
+ // OffloadController implementation.
+ class OffloadWrapper {
+ public void start() {
+ mOffloadController.start();
+ sendOffloadExemptPrefixes();
+ }
+
+ public void stop() {
+ mOffloadController.stop();
+ }
+
+ public void updateUpstreamNetworkState(NetworkState ns) {
+ mOffloadController.setUpstreamLinkProperties(
+ (ns != null) ? ns.linkProperties : null);
+ }
+
+ public void updateDownstreamLinkProperties(LinkProperties newLp) {
+ // Update the list of offload-exempt prefixes before adding
+ // new prefixes on downstream interfaces to the offload HAL.
+ sendOffloadExemptPrefixes();
+ mOffloadController.notifyDownstreamLinkProperties(newLp);
+ }
+
+ public void excludeDownstreamInterface(String ifname) {
+ // This and other interfaces may be in local-only hotspot mode;
+ // resend all local prefixes to the OffloadController.
+ sendOffloadExemptPrefixes();
+ mOffloadController.removeDownstreamInterface(ifname);
+ }
+
+ public void sendOffloadExemptPrefixes() {
+ sendOffloadExemptPrefixes(mUpstreamNetworkMonitor.getLocalPrefixes());
+ }
+
+ public void sendOffloadExemptPrefixes(final Set<IpPrefix> localPrefixes) {
+ // Add in well-known minimum set.
+ PrefixUtils.addNonForwardablePrefixes(localPrefixes);
+ // Add tragically hardcoded prefixes.
+ localPrefixes.add(PrefixUtils.DEFAULT_WIFI_P2P_PREFIX);
+
+ // Maybe add prefixes or addresses for downstreams, depending on
+ // the IP serving mode of each.
+ for (TetherInterfaceStateMachine tism : mNotifyList) {
+ final LinkProperties lp = tism.linkProperties();
+
+ switch (tism.servingMode()) {
+ case IControlsTethering.STATE_UNAVAILABLE:
+ case IControlsTethering.STATE_AVAILABLE:
+ // No usable LinkProperties in these states.
+ continue;
+ case IControlsTethering.STATE_TETHERED:
+ // Only add IPv4 /32 and IPv6 /128 prefixes. The
+ // directly-connected prefixes will be sent as
+ // downstream "offload-able" prefixes.
+ for (LinkAddress addr : lp.getAllLinkAddresses()) {
+ final InetAddress ip = addr.getAddress();
+ if (ip.isLinkLocalAddress()) continue;
+ localPrefixes.add(PrefixUtils.ipAddressAsPrefix(ip));
+ }
+ break;
+ case IControlsTethering.STATE_LOCAL_ONLY:
+ // Add prefixes covering all local IPs.
+ localPrefixes.addAll(PrefixUtils.localPrefixesFrom(lp));
+ break;
+ }
+ }
+
+ mOffloadController.setLocalPrefixes(localPrefixes);
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index ef18e4e..6d5c428 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -48,6 +48,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -69,6 +70,7 @@
private final INetworkManagementService mNms;
private final ITetheringStatsProvider mStatsProvider;
private final SharedLog mLog;
+ private final HashMap<String, LinkProperties> mDownstreams;
private boolean mConfigInitialized;
private boolean mControlInitialized;
private LinkProperties mUpstreamLinkProperties;
@@ -100,6 +102,7 @@
mNms = nms;
mStatsProvider = new OffloadTetheringStatsProvider();
mLog = log.forSubComponent(TAG);
+ mDownstreams = new HashMap<>();
mExemptPrefixes = new HashSet<>();
mLastLocalPrefixStrs = new HashSet<>();
@@ -257,6 +260,11 @@
}
}
+ private String currentUpstreamInterface() {
+ return (mUpstreamLinkProperties != null)
+ ? mUpstreamLinkProperties.getInterfaceName() : null;
+ }
+
private void maybeUpdateStats(String iface) {
if (TextUtils.isEmpty(iface)) {
return;
@@ -281,9 +289,7 @@
private boolean maybeUpdateDataLimit(String iface) {
// setDataLimit may only be called while offload is occuring on this upstream.
- if (!started() ||
- mUpstreamLinkProperties == null ||
- !TextUtils.equals(iface, mUpstreamLinkProperties.getInterfaceName())) {
+ if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) {
return true;
}
@@ -296,9 +302,7 @@
}
private void updateStatsForCurrentUpstream() {
- if (mUpstreamLinkProperties != null) {
- maybeUpdateStats(mUpstreamLinkProperties.getInterfaceName());
- }
+ maybeUpdateStats(currentUpstreamInterface());
}
public void setUpstreamLinkProperties(LinkProperties lp) {
@@ -325,17 +329,42 @@
}
public void notifyDownstreamLinkProperties(LinkProperties lp) {
+ final String ifname = lp.getInterfaceName();
+ final LinkProperties oldLp = mDownstreams.put(ifname, new LinkProperties(lp));
+ if (Objects.equals(oldLp, lp)) return;
+
if (!started()) return;
- // TODO: Cache LinkProperties on a per-ifname basis and compute the
- // deltas, calling addDownstream()/removeDownstream() accordingly.
+ final List<RouteInfo> oldRoutes = (oldLp != null) ? oldLp.getRoutes() : new ArrayList<>();
+ final List<RouteInfo> newRoutes = lp.getRoutes();
+
+ // For each old route, if not in new routes: remove.
+ for (RouteInfo oldRoute : oldRoutes) {
+ if (shouldIgnoreDownstreamRoute(oldRoute)) continue;
+ if (!newRoutes.contains(oldRoute)) {
+ mHwInterface.removeDownstreamPrefix(ifname, oldRoute.getDestination().toString());
+ }
+ }
+
+ // For each new route, if not in old routes: add.
+ for (RouteInfo newRoute : newRoutes) {
+ if (shouldIgnoreDownstreamRoute(newRoute)) continue;
+ if (!oldRoutes.contains(newRoute)) {
+ mHwInterface.addDownstreamPrefix(ifname, newRoute.getDestination().toString());
+ }
+ }
}
public void removeDownstreamInterface(String ifname) {
+ final LinkProperties lp = mDownstreams.remove(ifname);
+ if (lp == null) return;
+
if (!started()) return;
- // TODO: Check cache for LinkProperties of ifname and, if present,
- // call removeDownstream() accordingly.
+ for (RouteInfo route : lp.getRoutes()) {
+ if (shouldIgnoreDownstreamRoute(route)) continue;
+ mHwInterface.removeDownstreamPrefix(ifname, route.getDestination().toString());
+ }
}
private boolean isOffloadDisabled() {
@@ -442,6 +471,13 @@
return localPrefixStrs;
}
+ private static boolean shouldIgnoreDownstreamRoute(RouteInfo route) {
+ // Ignore any link-local routes.
+ if (!route.getDestinationLinkAddress().isGlobalPreferred()) return true;
+
+ return false;
+ }
+
public void dump(IndentingPrintWriter pw) {
if (isOffloadDisabled()) {
pw.println("Offload disabled");
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 86ff0a6..865a989 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -236,6 +236,44 @@
return results.success;
}
+ public boolean addDownstreamPrefix(String ifname, String prefix) {
+ final String logmsg = String.format("addDownstreamPrefix(%s, %s)", ifname, prefix);
+
+ final CbResults results = new CbResults();
+ try {
+ mOffloadControl.addDownstream(ifname, prefix,
+ (boolean success, String errMsg) -> {
+ results.success = success;
+ results.errMsg = errMsg;
+ });
+ } catch (RemoteException e) {
+ record(logmsg, e);
+ return false;
+ }
+
+ record(logmsg, results);
+ return results.success;
+ }
+
+ public boolean removeDownstreamPrefix(String ifname, String prefix) {
+ final String logmsg = String.format("removeDownstreamPrefix(%s, %s)", ifname, prefix);
+
+ final CbResults results = new CbResults();
+ try {
+ mOffloadControl.removeDownstream(ifname, prefix,
+ (boolean success, String errMsg) -> {
+ results.success = success;
+ results.errMsg = errMsg;
+ });
+ } catch (RemoteException e) {
+ record(logmsg, e);
+ return false;
+ }
+
+ record(logmsg, results);
+ return results.success;
+ }
+
private void record(String msg, Throwable t) {
mLog.e(msg + YIELDS + "exception: " + t);
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 69678df..57d2502 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -115,6 +115,7 @@
private final LinkProperties mLinkProperties;
private int mLastError;
+ private int mServingMode;
private String mMyUpstreamIfaceName; // may change over time
private NetworkInterface mNetworkInterface;
private byte[] mHwAddr;
@@ -142,6 +143,7 @@
mLinkProperties = new LinkProperties();
resetLinkProperties();
mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ mServingMode = IControlsTethering.STATE_AVAILABLE;
mInitialState = new InitialState();
mLocalHotspotState = new LocalHotspotState();
@@ -161,6 +163,8 @@
public int lastError() { return mLastError; }
+ public int servingMode() { return mServingMode; }
+
public LinkProperties linkProperties() { return new LinkProperties(mLinkProperties); }
public void stop() { sendMessage(CMD_INTERFACE_DOWN); }
@@ -448,6 +452,7 @@
}
private void sendInterfaceState(int newInterfaceState) {
+ mServingMode = newInterfaceState;
mTetherController.updateInterfaceState(
TetherInterfaceStateMachine.this, newInterfaceState, mLastError);
sendLinkProperties();
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index 9b3bc3f..6065268 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -87,6 +87,7 @@
public static final int IPV4_PROTOCOL_OFFSET = 9;
public static final int IPV4_SRC_ADDR_OFFSET = 12;
public static final int IPV4_DST_ADDR_OFFSET = 16;
+ public static final int IPV4_ADDR_BITS = 32;
public static final int IPV4_ADDR_LEN = 4;
/**
@@ -99,6 +100,7 @@
public static final int IPV6_PROTOCOL_OFFSET = 6;
public static final int IPV6_SRC_ADDR_OFFSET = 8;
public static final int IPV6_DST_ADDR_OFFSET = 24;
+ public static final int IPV6_ADDR_BITS = 128;
public static final int IPV6_ADDR_LEN = 16;
public static final int IPV6_MIN_MTU = 1280;
public static final int RFC7421_PREFIX_LENGTH = 64;
diff --git a/services/net/java/android/net/util/PrefixUtils.java b/services/net/java/android/net/util/PrefixUtils.java
index 962aab4..f60694a 100644
--- a/services/net/java/android/net/util/PrefixUtils.java
+++ b/services/net/java/android/net/util/PrefixUtils.java
@@ -20,6 +20,8 @@
import android.net.LinkAddress;
import android.net.LinkProperties;
+import java.net.Inet4Address;
+import java.net.InetAddress;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -68,6 +70,13 @@
return new IpPrefix(addr.getAddress(), addr.getPrefixLength());
}
+ public static IpPrefix ipAddressAsPrefix(InetAddress ip) {
+ final int bitLength = (ip instanceof Inet4Address)
+ ? NetworkConstants.IPV4_ADDR_BITS
+ : NetworkConstants.IPV6_ADDR_BITS;
+ return new IpPrefix(ip, bitLength);
+ }
+
private static IpPrefix pfx(String prefixStr) {
return new IpPrefix(prefixStr);
}
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index d5bbed7..622a7be 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -79,6 +79,15 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class OffloadControllerTest {
+ private static final String RNDIS0 = "test_rndis0";
+ private static final String RMNET0 = "test_rmnet_data0";
+ private static final String WLAN0 = "test_wlan0";
+
+ private static final String IPV6_LINKLOCAL = "fe80::/64";
+ private static final String IPV6_DOC_PREFIX = "2001:db8::/64";
+ private static final String IPV6_DISCARD_PREFIX = "100::/64";
+ private static final String USB_PREFIX = "192.168.42.0/24";
+ private static final String WIFI_PREFIX = "192.168.43.0/24";
@Mock private OffloadHardwareInterface mHardware;
@Mock private ApplicationInfo mApplicationInfo;
@@ -234,10 +243,8 @@
inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
ArrayList<String> localPrefixes = mStringArrayCaptor.getValue();
assertEquals(4, localPrefixes.size());
- assertTrue(localPrefixes.contains("127.0.0.0/8"));
- assertTrue(localPrefixes.contains("192.0.2.0/24"));
- assertTrue(localPrefixes.contains("fe80::/64"));
- assertTrue(localPrefixes.contains("2001:db8::/64"));
+ assertArrayListContains(localPrefixes,
+ "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64");
inOrder.verifyNoMoreInteractions();
offload.setUpstreamLinkProperties(null);
@@ -352,12 +359,9 @@
inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
localPrefixes = mStringArrayCaptor.getValue();
assertEquals(6, localPrefixes.size());
- assertTrue(localPrefixes.contains("127.0.0.0/8"));
- assertTrue(localPrefixes.contains("192.0.2.0/24"));
- assertTrue(localPrefixes.contains("fe80::/64"));
- assertTrue(localPrefixes.contains("2001:db8::/64"));
- assertTrue(localPrefixes.contains("2001:db8::6173:7369:676e:6564/128"));
- assertTrue(localPrefixes.contains("2001:db8::7261:6e64:6f6d/128"));
+ assertArrayListContains(localPrefixes,
+ "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64",
+ "2001:db8::6173:7369:676e:6564/128", "2001:db8::7261:6e64:6f6d/128");
// The relevant parts of the LinkProperties have not changed, but at the
// moment we do not de-dup upstream LinkProperties this carefully.
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
@@ -441,6 +445,8 @@
waitForIdle();
// There is no current upstream, so no stats are fetched.
inOrder.verify(mHardware, never()).getForwardedStats(eq(ethernetIface));
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(null), eq(null), eq(null), eq(null));
inOrder.verifyNoMoreInteractions();
assertEquals(2, stats.size());
@@ -545,4 +551,79 @@
callback.onStoppedLimitReached();
verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
}
+
+ @Test
+ public void testAddRemoveDownstreams() throws Exception {
+ setupFunctioningHardwareInterface();
+ enableOffload();
+
+ final OffloadController offload = makeOffloadController();
+ offload.start();
+
+ final InOrder inOrder = inOrder(mHardware);
+ inOrder.verify(mHardware, times(1)).initOffloadConfig();
+ inOrder.verify(mHardware, times(1)).initOffloadControl(
+ any(OffloadHardwareInterface.ControlCallback.class));
+ inOrder.verifyNoMoreInteractions();
+
+ // Tethering makes several calls to setLocalPrefixes() before add/remove
+ // downstream calls are made. This is not tested here; only the behavior
+ // of notifyDownstreamLinkProperties() and removeDownstreamInterface()
+ // are tested.
+
+ // [1] USB tethering is started.
+ final LinkProperties usbLinkProperties = new LinkProperties();
+ usbLinkProperties.setInterfaceName(RNDIS0);
+ usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24"));
+ usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX)));
+ offload.notifyDownstreamLinkProperties(usbLinkProperties);
+ inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, USB_PREFIX);
+ inOrder.verifyNoMoreInteractions();
+
+ // [2] Routes for IPv6 link-local prefixes should never be added.
+ usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL)));
+ offload.notifyDownstreamLinkProperties(usbLinkProperties);
+ inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
+ inOrder.verifyNoMoreInteractions();
+
+ // [3] Add an IPv6 prefix for good measure. Only new offload-able
+ // prefixes should be passed to the HAL.
+ usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+ usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX)));
+ offload.notifyDownstreamLinkProperties(usbLinkProperties);
+ inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
+ inOrder.verifyNoMoreInteractions();
+
+ // [4] Adding addresses doesn't affect notifyDownstreamLinkProperties().
+ // The address is passed in by a separate setLocalPrefixes() invocation.
+ usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::2/64"));
+ offload.notifyDownstreamLinkProperties(usbLinkProperties);
+ inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
+
+ // [5] Differences in local routes are converted into addDownstream()
+ // and removeDownstream() invocations accordingly.
+ usbLinkProperties.removeRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0));
+ usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX)));
+ offload.notifyDownstreamLinkProperties(usbLinkProperties);
+ inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
+ inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
+ inOrder.verifyNoMoreInteractions();
+
+ // [6] Removing a downstream interface which was never added causes no
+ // interactions with the HAL.
+ offload.removeDownstreamInterface(WLAN0);
+ inOrder.verifyNoMoreInteractions();
+
+ // [7] Removing an active downstream removes all remaining prefixes.
+ offload.removeDownstreamInterface(RNDIS0);
+ inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, USB_PREFIX);
+ inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ private static void assertArrayListContains(ArrayList<String> list, String... elems) {
+ for (String element : elems) {
+ assertTrue(list.contains(element));
+ }
+ }
}