Merge "Tie ContextualWifiSlice to UI instead of garbage collector"
This commit is contained in:
committed by
Android (Google) Code Review
commit
a63fd33c0b
@@ -67,7 +67,8 @@ public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>
|
||||
|
||||
@VisibleForTesting
|
||||
Uri mNotifyUri;
|
||||
private Context mContext;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
ContextualCardLoader(Context context) {
|
||||
super(context);
|
||||
|
@@ -109,13 +109,13 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
|
||||
}
|
||||
}
|
||||
|
||||
void loadContextualCards(ContextualCardsFragment fragment) {
|
||||
void loadContextualCards(LoaderManager loaderManager) {
|
||||
mStartTime = System.currentTimeMillis();
|
||||
final CardContentLoaderCallbacks cardContentLoaderCallbacks =
|
||||
new CardContentLoaderCallbacks(mContext);
|
||||
cardContentLoaderCallbacks.setListener(this);
|
||||
// Use the cached data when navigating back to the first page and upon screen rotation.
|
||||
LoaderManager.getInstance(fragment).initLoader(CARD_CONTENT_LOADER_ID, null /* bundle */,
|
||||
loaderManager.initLoader(CARD_CONTENT_LOADER_ID, null /* bundle */,
|
||||
cardContentLoaderCallbacks);
|
||||
}
|
||||
|
||||
|
@@ -19,16 +19,19 @@ package com.android.settings.homepage.contextualcards;
|
||||
import static com.android.settings.homepage.contextualcards.ContextualCardsAdapter.SPAN_COUNT;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
|
||||
public class ContextualCardsFragment extends InstrumentedFragment {
|
||||
|
||||
@@ -42,14 +45,19 @@ public class ContextualCardsFragment extends InstrumentedFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mContextualCardManager = new ContextualCardManager(getContext(), getSettingsLifecycle(),
|
||||
final Context context = getContext();
|
||||
if (savedInstanceState == null) {
|
||||
FeatureFactory.getFactory(context).getSlicesFeatureProvider().newUiSession();
|
||||
}
|
||||
mContextualCardManager = new ContextualCardManager(context, getSettingsLifecycle(),
|
||||
savedInstanceState);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mContextualCardManager.loadContextualCards(this);
|
||||
mContextualCardManager.loadContextualCards(LoaderManager.getInstance(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.slices;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Manages custom {@link androidx.slice.Slice Slices}, which are all Slices not backed by
|
||||
* preferences.
|
||||
*/
|
||||
public class CustomSliceManager {
|
||||
|
||||
private final Context mContext;
|
||||
private final Map<Uri, CustomSliceable> mSliceableCache;
|
||||
|
||||
public CustomSliceManager(Context context) {
|
||||
mContext = context.getApplicationContext();
|
||||
mSliceableCache = new WeakHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link CustomSliceable} associated to the Uri.
|
||||
* <p>
|
||||
* Do not change this method signature to accommodate for a special-case slicable - a context is
|
||||
* the only thing that should be needed to create the object.
|
||||
*/
|
||||
public CustomSliceable getSliceableFromUri(Uri uri) {
|
||||
final Uri newUri = CustomSliceRegistry.removeParameterFromUri(uri);
|
||||
if (mSliceableCache.containsKey(newUri)) {
|
||||
return mSliceableCache.get(newUri);
|
||||
}
|
||||
|
||||
final Class clazz = CustomSliceRegistry.getSliceClassByUri(newUri);
|
||||
if (clazz == null) {
|
||||
throw new IllegalArgumentException("No Slice found for uri: " + uri);
|
||||
}
|
||||
|
||||
final CustomSliceable sliceable = CustomSliceable.createInstance(mContext, clazz);
|
||||
mSliceableCache.put(newUri, sliceable);
|
||||
return sliceable;
|
||||
}
|
||||
}
|
@@ -331,7 +331,7 @@ public class CustomSliceRegistry {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if {@param uri} is a valid Slice Uri handled by
|
||||
* {@link CustomSliceManager}.
|
||||
* {@link CustomSliceRegistry}.
|
||||
*/
|
||||
public static boolean isValidUri(Uri uri) {
|
||||
return sUriToSlice.containsKey(removeParameterFromUri(uri));
|
||||
@@ -339,7 +339,7 @@ public class CustomSliceRegistry {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if {@param action} is a valid intent action handled by
|
||||
* {@link CustomSliceManager}.
|
||||
* {@link CustomSliceRegistry}.
|
||||
*/
|
||||
public static boolean isValidAction(String action) {
|
||||
return isValidUri(Uri.parse(action));
|
||||
|
@@ -110,7 +110,7 @@ public interface CustomSliceable extends Sliceable {
|
||||
try {
|
||||
final Constructor<? extends CustomSliceable> constructor =
|
||||
sliceable.getConstructor(Context.class);
|
||||
final Object[] params = new Object[]{context};
|
||||
final Object[] params = new Object[]{context.getApplicationContext()};
|
||||
return constructor.newInstance(params);
|
||||
} catch (NoSuchMethodException | InstantiationException |
|
||||
IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
|
||||
|
@@ -117,9 +117,6 @@ public class SettingsSliceProvider extends SliceProvider {
|
||||
|
||||
private static final KeyValueListParser KEY_VALUE_LIST_PARSER = new KeyValueListParser(',');
|
||||
|
||||
@VisibleForTesting
|
||||
CustomSliceManager mCustomSliceManager;
|
||||
|
||||
@VisibleForTesting
|
||||
SlicesDatabaseAccessor mSlicesDatabaseAccessor;
|
||||
|
||||
@@ -140,15 +137,15 @@ public class SettingsSliceProvider extends SliceProvider {
|
||||
mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(getContext());
|
||||
mSliceDataCache = new ConcurrentHashMap<>();
|
||||
mSliceWeakDataCache = new WeakHashMap<>();
|
||||
mCustomSliceManager = FeatureFactory.getFactory(
|
||||
getContext()).getSlicesFeatureProvider().getCustomSliceManager(getContext());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlicePinned(Uri sliceUri) {
|
||||
if (CustomSliceRegistry.isValidUri(sliceUri)) {
|
||||
final CustomSliceable sliceable = mCustomSliceManager.getSliceableFromUri(sliceUri);
|
||||
final Context context = getContext();
|
||||
final CustomSliceable sliceable = FeatureFactory.getFactory(context)
|
||||
.getSlicesFeatureProvider().getSliceableFromUri(context, sliceUri);
|
||||
final IntentFilter filter = sliceable.getIntentFilter();
|
||||
if (filter != null) {
|
||||
registerIntentToUri(filter, sliceUri);
|
||||
@@ -195,9 +192,10 @@ public class SettingsSliceProvider extends SliceProvider {
|
||||
// Before adding a slice to {@link CustomSliceManager}, please get approval
|
||||
// from the Settings team.
|
||||
if (CustomSliceRegistry.isValidUri(sliceUri)) {
|
||||
final CustomSliceable sliceable = mCustomSliceManager.getSliceableFromUri(
|
||||
sliceUri);
|
||||
return sliceable.getSlice();
|
||||
final Context context = getContext();
|
||||
return FeatureFactory.getFactory(context)
|
||||
.getSlicesFeatureProvider().getSliceableFromUri(context, sliceUri)
|
||||
.getSlice();
|
||||
}
|
||||
|
||||
if (CustomSliceRegistry.WIFI_CALLING_URI.equals(sliceUri)) {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package com.android.settings.slices;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.android.settings.network.telephony.Enhanced4gLteSliceHelper;
|
||||
import com.android.settings.wifi.calling.WifiCallingSliceHelper;
|
||||
@@ -14,6 +15,20 @@ public interface SlicesFeatureProvider {
|
||||
|
||||
SliceDataConverter getSliceDataConverter(Context context);
|
||||
|
||||
/**
|
||||
* Starts a new UI session for the purpose of using Slices.
|
||||
*
|
||||
* A UI session is defined as an duration of time when user stays in a UI screen. Screen
|
||||
* rotation does not break the continuation of session, going to a sub-page and coming out does
|
||||
* not break the continuation either. Leaving the page and coming back breaks it.
|
||||
*/
|
||||
void newUiSession();
|
||||
|
||||
/**
|
||||
* Returns the token created in {@link #newUiSession}.
|
||||
*/
|
||||
long getUiSessionToken();
|
||||
|
||||
/**
|
||||
* Asynchronous call to index the data used to build Slices.
|
||||
* If the data is already indexed, the data will not change.
|
||||
@@ -26,7 +41,14 @@ public interface SlicesFeatureProvider {
|
||||
*/
|
||||
void indexSliceData(Context context);
|
||||
|
||||
CustomSliceManager getCustomSliceManager(Context context);
|
||||
|
||||
/**
|
||||
* Return a {@link CustomSliceable} associated to the Uri.
|
||||
* <p>
|
||||
* Do not change this method signature to accommodate for a special-case sliceable - a context
|
||||
* is the only thing that should be needed to create the object.
|
||||
*/
|
||||
CustomSliceable getSliceableFromUri(Context context, Uri uri);
|
||||
|
||||
/**
|
||||
* Gets new WifiCallingSliceHelper object
|
||||
|
@@ -17,19 +17,23 @@
|
||||
package com.android.settings.slices;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.android.settings.network.telephony.Enhanced4gLteSliceHelper;
|
||||
import com.android.settings.wifi.calling.WifiCallingSliceHelper;
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Manages Slices in Settings.
|
||||
*/
|
||||
public class SlicesFeatureProviderImpl implements SlicesFeatureProvider {
|
||||
|
||||
private long mUiSessionToken;
|
||||
private SlicesIndexer mSlicesIndexer;
|
||||
private SliceDataConverter mSliceDataConverter;
|
||||
private CustomSliceManager mCustomSliceManager;
|
||||
|
||||
@Override
|
||||
public SliceDataConverter getSliceDataConverter(Context context) {
|
||||
@@ -40,11 +44,13 @@ public class SlicesFeatureProviderImpl implements SlicesFeatureProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomSliceManager getCustomSliceManager(Context context) {
|
||||
if (mCustomSliceManager == null) {
|
||||
mCustomSliceManager = new CustomSliceManager(context.getApplicationContext());
|
||||
public void newUiSession() {
|
||||
mUiSessionToken = SystemClock.elapsedRealtime();
|
||||
}
|
||||
return mCustomSliceManager;
|
||||
|
||||
@Override
|
||||
public long getUiSessionToken() {
|
||||
return mUiSessionToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,6 +75,18 @@ public class SlicesFeatureProviderImpl implements SlicesFeatureProvider {
|
||||
return new Enhanced4gLteSliceHelper(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomSliceable getSliceableFromUri(Context context, Uri uri) {
|
||||
final Uri newUri = CustomSliceRegistry.removeParameterFromUri(uri);
|
||||
final Class clazz = CustomSliceRegistry.getSliceClassByUri(newUri);
|
||||
if (clazz == null) {
|
||||
throw new IllegalArgumentException("No Slice found for uri: " + uri);
|
||||
}
|
||||
|
||||
final CustomSliceable sliceable = CustomSliceable.createInstance(context, clazz);
|
||||
return sliceable;
|
||||
}
|
||||
|
||||
private SlicesIndexer getSliceIndexer(Context context) {
|
||||
if (mSlicesIndexer == null) {
|
||||
mSlicesIndexer = new SlicesIndexer(context.getApplicationContext());
|
||||
|
@@ -25,6 +25,7 @@ import android.util.Log;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.slice.Slice;
|
||||
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.slices.CustomSliceRegistry;
|
||||
import com.android.settings.slices.CustomSliceable;
|
||||
|
||||
@@ -35,7 +36,9 @@ public class ContextualWifiSlice extends WifiSlice {
|
||||
|
||||
private static final String TAG = "ContextualWifiSlice";
|
||||
@VisibleForTesting
|
||||
boolean mPreviouslyDisplayed;
|
||||
static long sActiveUiSession = -1000;
|
||||
@VisibleForTesting
|
||||
static boolean sPreviouslyDisplayed;
|
||||
|
||||
public ContextualWifiSlice(Context context) {
|
||||
super(context);
|
||||
@@ -48,13 +51,19 @@ public class ContextualWifiSlice extends WifiSlice {
|
||||
|
||||
@Override
|
||||
public Slice getSlice() {
|
||||
if (!mPreviouslyDisplayed && !TextUtils.equals(getActiveSSID(), WifiSsid.NONE)) {
|
||||
final long currentUiSession = FeatureFactory.getFactory(mContext)
|
||||
.getSlicesFeatureProvider().getUiSessionToken();
|
||||
if (currentUiSession != sActiveUiSession) {
|
||||
sActiveUiSession = currentUiSession;
|
||||
sPreviouslyDisplayed = false;
|
||||
}
|
||||
if (!sPreviouslyDisplayed && !TextUtils.equals(getActiveSSID(), WifiSsid.NONE)) {
|
||||
Log.d(TAG, "Wifi is connected, no point showing any suggestion.");
|
||||
return null;
|
||||
}
|
||||
// Set mPreviouslyDisplayed to true - we will show *something* on the screen. So we should
|
||||
// Set sPreviouslyDisplayed to true - we will show *something* on the screen. So we should
|
||||
// keep showing this card to keep UI stable, even if wifi connects to a network later.
|
||||
mPreviouslyDisplayed = true;
|
||||
sPreviouslyDisplayed = true;
|
||||
|
||||
return super.getSlice();
|
||||
}
|
||||
|
@@ -135,7 +135,6 @@ public class SettingsSliceProviderTest {
|
||||
mProvider.mSliceWeakDataCache = new HashMap<>();
|
||||
mProvider.mSliceDataCache = new HashMap<>();
|
||||
mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext);
|
||||
mProvider.mCustomSliceManager = new CustomSliceManager(mContext);
|
||||
when(mProvider.getContext()).thenReturn(mContext);
|
||||
|
||||
SlicesDatabaseHelper.getInstance(mContext).setIndexedState();
|
||||
|
@@ -55,6 +55,7 @@ public class SlicesDatabaseHelperTest {
|
||||
@After
|
||||
public void cleanUp() {
|
||||
DatabaseTestUtils.clearDb(mContext);
|
||||
mDatabase.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@@ -36,6 +36,8 @@ import androidx.slice.widget.SliceLiveData;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.slices.CustomSliceRegistry;
|
||||
import com.android.settings.slices.SlicesFeatureProviderImpl;
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -52,11 +54,15 @@ public class ContextualWifiSliceTest {
|
||||
private ContentResolver mResolver;
|
||||
private WifiManager mWifiManager;
|
||||
private ContextualWifiSlice mWifiSlice;
|
||||
private FakeFeatureFactory mFeatureFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
mResolver = mock(ContentResolver.class);
|
||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
mFeatureFactory.slicesFeatureProvider = new SlicesFeatureProviderImpl();
|
||||
mFeatureFactory.slicesFeatureProvider.newUiSession();
|
||||
doReturn(mResolver).when(mContext).getContentResolver();
|
||||
mWifiManager = mContext.getSystemService(WifiManager.class);
|
||||
|
||||
@@ -65,10 +71,28 @@ public class ContextualWifiSliceTest {
|
||||
mWifiManager.setWifiEnabled(true);
|
||||
|
||||
mWifiSlice = new ContextualWifiSlice(mContext);
|
||||
mWifiSlice.sPreviouslyDisplayed = false;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWifiSlice_hasActiveConnection_shouldReturnNull() {
|
||||
mWifiSlice.sPreviouslyDisplayed = false;
|
||||
final WifiConfiguration config = new WifiConfiguration();
|
||||
config.SSID = "123";
|
||||
mWifiManager.connect(config, null /* listener */);
|
||||
|
||||
final Slice wifiSlice = mWifiSlice.getSlice();
|
||||
|
||||
assertThat(wifiSlice).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWifiSlice_newSession_hasActiveConnection_shouldReturnNull() {
|
||||
// Session: use a non-active value
|
||||
// previous displayed: yes
|
||||
mWifiSlice.sPreviouslyDisplayed = true;
|
||||
mWifiSlice.sActiveUiSession = ~mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
|
||||
|
||||
final WifiConfiguration config = new WifiConfiguration();
|
||||
config.SSID = "123";
|
||||
mWifiManager.connect(config, null /* listener */);
|
||||
@@ -80,7 +104,8 @@ public class ContextualWifiSliceTest {
|
||||
|
||||
@Test
|
||||
public void getWifiSlice_previousDisplayed_hasActiveConnection_shouldHaveTitleAndToggle() {
|
||||
mWifiSlice.mPreviouslyDisplayed = true;
|
||||
mWifiSlice.sActiveUiSession = mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
|
||||
mWifiSlice.sPreviouslyDisplayed = true;
|
||||
final WifiConfiguration config = new WifiConfiguration();
|
||||
config.SSID = "123";
|
||||
mWifiManager.connect(config, null /* listener */);
|
||||
@@ -101,7 +126,8 @@ public class ContextualWifiSliceTest {
|
||||
|
||||
@Test
|
||||
public void getWifiSlice_contextualWifiSlice_shouldReturnContextualWifiSliceUri() {
|
||||
mWifiSlice.mPreviouslyDisplayed = true;
|
||||
mWifiSlice.sActiveUiSession = mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
|
||||
mWifiSlice.sPreviouslyDisplayed = true;
|
||||
|
||||
final Slice wifiSlice = mWifiSlice.getSlice();
|
||||
|
||||
|
Reference in New Issue
Block a user