Merge "Tie ContextualWifiSlice to UI instead of garbage collector"

This commit is contained in:
TreeHugger Robot
2019-03-15 21:41:29 +00:00
committed by Android (Google) Code Review
13 changed files with 113 additions and 91 deletions

View File

@@ -67,7 +67,8 @@ public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>
@VisibleForTesting @VisibleForTesting
Uri mNotifyUri; Uri mNotifyUri;
private Context mContext;
private final Context mContext;
ContextualCardLoader(Context context) { ContextualCardLoader(Context context) {
super(context); super(context);

View File

@@ -109,13 +109,13 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
} }
} }
void loadContextualCards(ContextualCardsFragment fragment) { void loadContextualCards(LoaderManager loaderManager) {
mStartTime = System.currentTimeMillis(); mStartTime = System.currentTimeMillis();
final CardContentLoaderCallbacks cardContentLoaderCallbacks = final CardContentLoaderCallbacks cardContentLoaderCallbacks =
new CardContentLoaderCallbacks(mContext); new CardContentLoaderCallbacks(mContext);
cardContentLoaderCallbacks.setListener(this); cardContentLoaderCallbacks.setListener(this);
// Use the cached data when navigating back to the first page and upon screen rotation. // 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); cardContentLoaderCallbacks);
} }

View File

@@ -19,16 +19,19 @@ package com.android.settings.homepage.contextualcards;
import static com.android.settings.homepage.contextualcards.ContextualCardsAdapter.SPAN_COUNT; import static com.android.settings.homepage.contextualcards.ContextualCardsAdapter.SPAN_COUNT;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.loader.app.LoaderManager;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment; import com.android.settings.core.InstrumentedFragment;
import com.android.settings.overlay.FeatureFactory;
public class ContextualCardsFragment extends InstrumentedFragment { public class ContextualCardsFragment extends InstrumentedFragment {
@@ -42,14 +45,19 @@ public class ContextualCardsFragment extends InstrumentedFragment {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(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); savedInstanceState);
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
mContextualCardManager.loadContextualCards(this); mContextualCardManager.loadContextualCards(LoaderManager.getInstance(this));
} }
@Override @Override

View File

@@ -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;
}
}

View File

@@ -331,7 +331,7 @@ public class CustomSliceRegistry {
/** /**
* Returns {@code true} if {@param uri} is a valid Slice Uri handled by * Returns {@code true} if {@param uri} is a valid Slice Uri handled by
* {@link CustomSliceManager}. * {@link CustomSliceRegistry}.
*/ */
public static boolean isValidUri(Uri uri) { public static boolean isValidUri(Uri uri) {
return sUriToSlice.containsKey(removeParameterFromUri(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 * Returns {@code true} if {@param action} is a valid intent action handled by
* {@link CustomSliceManager}. * {@link CustomSliceRegistry}.
*/ */
public static boolean isValidAction(String action) { public static boolean isValidAction(String action) {
return isValidUri(Uri.parse(action)); return isValidUri(Uri.parse(action));

View File

@@ -110,7 +110,7 @@ public interface CustomSliceable extends Sliceable {
try { try {
final Constructor<? extends CustomSliceable> constructor = final Constructor<? extends CustomSliceable> constructor =
sliceable.getConstructor(Context.class); sliceable.getConstructor(Context.class);
final Object[] params = new Object[]{context}; final Object[] params = new Object[]{context.getApplicationContext()};
return constructor.newInstance(params); return constructor.newInstance(params);
} catch (NoSuchMethodException | InstantiationException | } catch (NoSuchMethodException | InstantiationException |
IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {

View File

@@ -117,9 +117,6 @@ public class SettingsSliceProvider extends SliceProvider {
private static final KeyValueListParser KEY_VALUE_LIST_PARSER = new KeyValueListParser(','); private static final KeyValueListParser KEY_VALUE_LIST_PARSER = new KeyValueListParser(',');
@VisibleForTesting
CustomSliceManager mCustomSliceManager;
@VisibleForTesting @VisibleForTesting
SlicesDatabaseAccessor mSlicesDatabaseAccessor; SlicesDatabaseAccessor mSlicesDatabaseAccessor;
@@ -140,15 +137,15 @@ public class SettingsSliceProvider extends SliceProvider {
mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(getContext()); mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(getContext());
mSliceDataCache = new ConcurrentHashMap<>(); mSliceDataCache = new ConcurrentHashMap<>();
mSliceWeakDataCache = new WeakHashMap<>(); mSliceWeakDataCache = new WeakHashMap<>();
mCustomSliceManager = FeatureFactory.getFactory(
getContext()).getSlicesFeatureProvider().getCustomSliceManager(getContext());
return true; return true;
} }
@Override @Override
public void onSlicePinned(Uri sliceUri) { public void onSlicePinned(Uri sliceUri) {
if (CustomSliceRegistry.isValidUri(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(); final IntentFilter filter = sliceable.getIntentFilter();
if (filter != null) { if (filter != null) {
registerIntentToUri(filter, sliceUri); registerIntentToUri(filter, sliceUri);
@@ -195,9 +192,10 @@ public class SettingsSliceProvider extends SliceProvider {
// Before adding a slice to {@link CustomSliceManager}, please get approval // Before adding a slice to {@link CustomSliceManager}, please get approval
// from the Settings team. // from the Settings team.
if (CustomSliceRegistry.isValidUri(sliceUri)) { if (CustomSliceRegistry.isValidUri(sliceUri)) {
final CustomSliceable sliceable = mCustomSliceManager.getSliceableFromUri( final Context context = getContext();
sliceUri); return FeatureFactory.getFactory(context)
return sliceable.getSlice(); .getSlicesFeatureProvider().getSliceableFromUri(context, sliceUri)
.getSlice();
} }
if (CustomSliceRegistry.WIFI_CALLING_URI.equals(sliceUri)) { if (CustomSliceRegistry.WIFI_CALLING_URI.equals(sliceUri)) {

View File

@@ -1,6 +1,7 @@
package com.android.settings.slices; package com.android.settings.slices;
import android.content.Context; import android.content.Context;
import android.net.Uri;
import com.android.settings.network.telephony.Enhanced4gLteSliceHelper; import com.android.settings.network.telephony.Enhanced4gLteSliceHelper;
import com.android.settings.wifi.calling.WifiCallingSliceHelper; import com.android.settings.wifi.calling.WifiCallingSliceHelper;
@@ -14,6 +15,20 @@ public interface SlicesFeatureProvider {
SliceDataConverter getSliceDataConverter(Context context); 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. * Asynchronous call to index the data used to build Slices.
* If the data is already indexed, the data will not change. * If the data is already indexed, the data will not change.
@@ -26,7 +41,14 @@ public interface SlicesFeatureProvider {
*/ */
void indexSliceData(Context context); 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 * Gets new WifiCallingSliceHelper object

View File

@@ -17,19 +17,23 @@
package com.android.settings.slices; package com.android.settings.slices;
import android.content.Context; import android.content.Context;
import android.net.Uri;
import android.os.SystemClock;
import com.android.settings.network.telephony.Enhanced4gLteSliceHelper; import com.android.settings.network.telephony.Enhanced4gLteSliceHelper;
import com.android.settings.wifi.calling.WifiCallingSliceHelper; import com.android.settings.wifi.calling.WifiCallingSliceHelper;
import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.utils.ThreadUtils;
import java.util.Random;
/** /**
* Manages Slices in Settings. * Manages Slices in Settings.
*/ */
public class SlicesFeatureProviderImpl implements SlicesFeatureProvider { public class SlicesFeatureProviderImpl implements SlicesFeatureProvider {
private long mUiSessionToken;
private SlicesIndexer mSlicesIndexer; private SlicesIndexer mSlicesIndexer;
private SliceDataConverter mSliceDataConverter; private SliceDataConverter mSliceDataConverter;
private CustomSliceManager mCustomSliceManager;
@Override @Override
public SliceDataConverter getSliceDataConverter(Context context) { public SliceDataConverter getSliceDataConverter(Context context) {
@@ -40,11 +44,13 @@ public class SlicesFeatureProviderImpl implements SlicesFeatureProvider {
} }
@Override @Override
public CustomSliceManager getCustomSliceManager(Context context) { public void newUiSession() {
if (mCustomSliceManager == null) { mUiSessionToken = SystemClock.elapsedRealtime();
mCustomSliceManager = new CustomSliceManager(context.getApplicationContext());
} }
return mCustomSliceManager;
@Override
public long getUiSessionToken() {
return mUiSessionToken;
} }
@Override @Override
@@ -69,6 +75,18 @@ public class SlicesFeatureProviderImpl implements SlicesFeatureProvider {
return new Enhanced4gLteSliceHelper(context); 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) { private SlicesIndexer getSliceIndexer(Context context) {
if (mSlicesIndexer == null) { if (mSlicesIndexer == null) {
mSlicesIndexer = new SlicesIndexer(context.getApplicationContext()); mSlicesIndexer = new SlicesIndexer(context.getApplicationContext());

View File

@@ -25,6 +25,7 @@ import android.util.Log;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.slice.Slice; import androidx.slice.Slice;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.slices.CustomSliceRegistry; import com.android.settings.slices.CustomSliceRegistry;
import com.android.settings.slices.CustomSliceable; import com.android.settings.slices.CustomSliceable;
@@ -35,7 +36,9 @@ public class ContextualWifiSlice extends WifiSlice {
private static final String TAG = "ContextualWifiSlice"; private static final String TAG = "ContextualWifiSlice";
@VisibleForTesting @VisibleForTesting
boolean mPreviouslyDisplayed; static long sActiveUiSession = -1000;
@VisibleForTesting
static boolean sPreviouslyDisplayed;
public ContextualWifiSlice(Context context) { public ContextualWifiSlice(Context context) {
super(context); super(context);
@@ -48,13 +51,19 @@ public class ContextualWifiSlice extends WifiSlice {
@Override @Override
public Slice getSlice() { 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."); Log.d(TAG, "Wifi is connected, no point showing any suggestion.");
return null; 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. // keep showing this card to keep UI stable, even if wifi connects to a network later.
mPreviouslyDisplayed = true; sPreviouslyDisplayed = true;
return super.getSlice(); return super.getSlice();
} }

View File

@@ -135,7 +135,6 @@ public class SettingsSliceProviderTest {
mProvider.mSliceWeakDataCache = new HashMap<>(); mProvider.mSliceWeakDataCache = new HashMap<>();
mProvider.mSliceDataCache = new HashMap<>(); mProvider.mSliceDataCache = new HashMap<>();
mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext); mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext);
mProvider.mCustomSliceManager = new CustomSliceManager(mContext);
when(mProvider.getContext()).thenReturn(mContext); when(mProvider.getContext()).thenReturn(mContext);
SlicesDatabaseHelper.getInstance(mContext).setIndexedState(); SlicesDatabaseHelper.getInstance(mContext).setIndexedState();

View File

@@ -55,6 +55,7 @@ public class SlicesDatabaseHelperTest {
@After @After
public void cleanUp() { public void cleanUp() {
DatabaseTestUtils.clearDb(mContext); DatabaseTestUtils.clearDb(mContext);
mDatabase.close();
} }
@Test @Test

View File

@@ -36,6 +36,8 @@ import androidx.slice.widget.SliceLiveData;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.slices.CustomSliceRegistry; 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.Before;
import org.junit.Test; import org.junit.Test;
@@ -52,11 +54,15 @@ public class ContextualWifiSliceTest {
private ContentResolver mResolver; private ContentResolver mResolver;
private WifiManager mWifiManager; private WifiManager mWifiManager;
private ContextualWifiSlice mWifiSlice; private ContextualWifiSlice mWifiSlice;
private FakeFeatureFactory mFeatureFactory;
@Before @Before
public void setUp() { public void setUp() {
mContext = spy(RuntimeEnvironment.application); mContext = spy(RuntimeEnvironment.application);
mResolver = mock(ContentResolver.class); mResolver = mock(ContentResolver.class);
mFeatureFactory = FakeFeatureFactory.setupForTest();
mFeatureFactory.slicesFeatureProvider = new SlicesFeatureProviderImpl();
mFeatureFactory.slicesFeatureProvider.newUiSession();
doReturn(mResolver).when(mContext).getContentResolver(); doReturn(mResolver).when(mContext).getContentResolver();
mWifiManager = mContext.getSystemService(WifiManager.class); mWifiManager = mContext.getSystemService(WifiManager.class);
@@ -65,10 +71,28 @@ public class ContextualWifiSliceTest {
mWifiManager.setWifiEnabled(true); mWifiManager.setWifiEnabled(true);
mWifiSlice = new ContextualWifiSlice(mContext); mWifiSlice = new ContextualWifiSlice(mContext);
mWifiSlice.sPreviouslyDisplayed = false;
} }
@Test @Test
public void getWifiSlice_hasActiveConnection_shouldReturnNull() { 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(); final WifiConfiguration config = new WifiConfiguration();
config.SSID = "123"; config.SSID = "123";
mWifiManager.connect(config, null /* listener */); mWifiManager.connect(config, null /* listener */);
@@ -80,7 +104,8 @@ public class ContextualWifiSliceTest {
@Test @Test
public void getWifiSlice_previousDisplayed_hasActiveConnection_shouldHaveTitleAndToggle() { public void getWifiSlice_previousDisplayed_hasActiveConnection_shouldHaveTitleAndToggle() {
mWifiSlice.mPreviouslyDisplayed = true; mWifiSlice.sActiveUiSession = mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
mWifiSlice.sPreviouslyDisplayed = true;
final WifiConfiguration config = new WifiConfiguration(); final WifiConfiguration config = new WifiConfiguration();
config.SSID = "123"; config.SSID = "123";
mWifiManager.connect(config, null /* listener */); mWifiManager.connect(config, null /* listener */);
@@ -101,7 +126,8 @@ public class ContextualWifiSliceTest {
@Test @Test
public void getWifiSlice_contextualWifiSlice_shouldReturnContextualWifiSliceUri() { public void getWifiSlice_contextualWifiSlice_shouldReturnContextualWifiSliceUri() {
mWifiSlice.mPreviouslyDisplayed = true; mWifiSlice.sActiveUiSession = mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
mWifiSlice.sPreviouslyDisplayed = true;
final Slice wifiSlice = mWifiSlice.getSlice(); final Slice wifiSlice = mWifiSlice.getSlice();