diff --git a/res/layout/settings_panel.xml b/res/layout/settings_panel.xml
index aec898c99c6..3405ef0d844 100644
--- a/res/layout/settings_panel.xml
+++ b/res/layout/settings_panel.xml
@@ -16,4 +16,5 @@
\ No newline at end of file
+ android:layout_width="match_parent"
+ android:animateLayoutChanges="true"/>
\ No newline at end of file
diff --git a/src/com/android/settings/nfc/NfcPreferenceController.java b/src/com/android/settings/nfc/NfcPreferenceController.java
index 04f288d721b..2ca3b23daea 100644
--- a/src/com/android/settings/nfc/NfcPreferenceController.java
+++ b/src/com/android/settings/nfc/NfcPreferenceController.java
@@ -15,20 +15,26 @@
*/
package com.android.settings.nfc;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
import android.content.IntentFilter;
+import android.net.Uri;
import android.nfc.NfcAdapter;
import android.provider.Settings;
-
+import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
+import java.io.IOException;
+
public class NfcPreferenceController extends TogglePreferenceController
implements LifecycleObserver, OnResume, OnPause {
@@ -51,8 +57,7 @@ public class NfcPreferenceController extends TogglePreferenceController
return;
}
- final SwitchPreference switchPreference =
- (SwitchPreference) screen.findPreference(getPreferenceKey());
+ final SwitchPreference switchPreference = screen.findPreference(getPreferenceKey());
mNfcEnabler = new NfcEnabler(mContext, switchPreference);
@@ -86,14 +91,6 @@ public class NfcPreferenceController extends TogglePreferenceController
: UNSUPPORTED_ON_DEVICE;
}
- @Override
- public IntentFilter getIntentFilter() {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
- filter.addAction(NfcAdapter.EXTRA_ADAPTER_STATE);
- return filter;
- }
-
@Override
public boolean hasAsyncUpdate() {
return true;
@@ -104,6 +101,11 @@ public class NfcPreferenceController extends TogglePreferenceController
return true;
}
+ @Override
+ public Class extends SliceBackgroundWorker> getBackgroundWorkerClass() {
+ return NfcSliceWorker.class;
+ }
+
@Override
public void onResume() {
if (mAirplaneModeObserver != null) {
@@ -135,4 +137,77 @@ public class NfcPreferenceController extends TogglePreferenceController
Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
return toggleable != null && toggleable.contains(Settings.Global.RADIO_NFC);
}
+
+ /**
+ * Listener for background changes to NFC.
+ *
+ *
+ * Listen to broadcasts from {@link NfcAdapter}. The worker will call notify changed on the
+ * NFC Slice only when the following extras are present in the broadcast:
+ *
{
+
+ private static final String TAG = "NfcSliceWorker";
+
+ private static final IntentFilter NFC_FILTER =
+ new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
+
+ private NfcUpdateReceiver mUpdateObserver;
+
+ public NfcSliceWorker(Context context, Uri uri) {
+ super(context, uri);
+ mUpdateObserver = new NfcUpdateReceiver(this);
+ }
+
+ @Override
+ protected void onSlicePinned() {
+ getContext().registerReceiver(mUpdateObserver, NFC_FILTER);
+ }
+
+ @Override
+ protected void onSliceUnpinned() {
+ getContext().unregisterReceiver(mUpdateObserver);
+ }
+
+ @Override
+ public void close() throws IOException {
+ mUpdateObserver = null;
+ }
+
+ public void updateSlice() {
+ notifySliceChange();
+ }
+
+ public class NfcUpdateReceiver extends BroadcastReceiver {
+
+ private final int NO_EXTRA = -1;
+
+ private final NfcSliceWorker mSliceBackgroundWorker;
+
+ public NfcUpdateReceiver(NfcSliceWorker sliceWorker) {
+ mSliceBackgroundWorker = sliceWorker;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int nfcStateExtra = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
+ NO_EXTRA);
+
+ // Do nothing if state change is empty, or an intermediate step.
+ if ( (nfcStateExtra == NO_EXTRA)
+ || (nfcStateExtra == NfcAdapter.STATE_TURNING_ON)
+ || (nfcStateExtra == NfcAdapter.STATE_TURNING_OFF)) {
+ Log.d(TAG, "Transitional update, dropping broadcast");
+ return;
+ }
+
+ Log.d(TAG, "Nfc broadcast received, updating Slice.");
+ mSliceBackgroundWorker.updateSlice();
+ }
+ }
+ }
}
diff --git a/src/com/android/settings/slices/CustomSliceable.java b/src/com/android/settings/slices/CustomSliceable.java
index b48f22a5237..0b97bedc54f 100644
--- a/src/com/android/settings/slices/CustomSliceable.java
+++ b/src/com/android/settings/slices/CustomSliceable.java
@@ -83,18 +83,6 @@ public interface CustomSliceable extends Sliceable {
*/
Intent getIntent();
- /**
- * Settings Slices which require background work, such as updating lists should implement a
- * {@link SliceBackgroundWorker} and return it here. An example of background work is updating
- * a list of Wifi networks available in the area.
- *
- * @return a {@link Class extends SliceBackgroundWorker>} to perform background work for the
- * slice.
- */
- default Class extends SliceBackgroundWorker> getBackgroundWorkerClass() {
- return null;
- }
-
/**
* Standardize the intents returned to indicate actions by the Slice.
*
diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java
index 5c662e5c7e0..397b2fc631c 100644
--- a/src/com/android/settings/slices/SettingsSliceProvider.java
+++ b/src/com/android/settings/slices/SettingsSliceProvider.java
@@ -153,7 +153,7 @@ public class SettingsSliceProvider extends SliceProvider {
if (filter != null) {
registerIntentToUri(filter, sliceUri);
}
- ThreadUtils.postOnMainThread(() -> startBackgroundWorker(sliceable));
+ ThreadUtils.postOnMainThread(() -> startBackgroundWorker(sliceable, sliceUri));
return;
}
@@ -326,20 +326,19 @@ public class SettingsSliceProvider extends SliceProvider {
}
}
- private void startBackgroundWorker(CustomSliceable sliceable) {
+ private void startBackgroundWorker(Sliceable sliceable, Uri uri) {
final Class workerClass = sliceable.getBackgroundWorkerClass();
if (workerClass == null) {
return;
}
- final Uri uri = sliceable.getUri();
if (mPinnedWorkers.containsKey(uri)) {
return;
}
Log.d(TAG, "Starting background worker for: " + uri);
final SliceBackgroundWorker worker = SliceBackgroundWorker.getInstance(
- getContext(), sliceable);
+ getContext(), sliceable, uri);
mPinnedWorkers.put(uri, worker);
worker.onSlicePinned();
}
@@ -397,6 +396,8 @@ public class SettingsSliceProvider extends SliceProvider {
registerIntentToUri(filter, uri);
}
+ ThreadUtils.postOnMainThread(() -> startBackgroundWorker(controller, uri));
+
final List pinnedSlices = getContext().getSystemService(
SliceManager.class).getPinnedSlices();
if (pinnedSlices.contains(uri)) {
diff --git a/src/com/android/settings/slices/SliceBackgroundWorker.java b/src/com/android/settings/slices/SliceBackgroundWorker.java
index 995394e7d41..559aa711e2a 100644
--- a/src/com/android/settings/slices/SliceBackgroundWorker.java
+++ b/src/com/android/settings/slices/SliceBackgroundWorker.java
@@ -80,13 +80,12 @@ public abstract class SliceBackgroundWorker implements Closeable {
* Returns the singleton instance of the {@link SliceBackgroundWorker} for specified {@link
* CustomSliceable}
*/
- static SliceBackgroundWorker getInstance(Context context, CustomSliceable sliceable) {
- final Uri uri = sliceable.getUri();
+ static SliceBackgroundWorker getInstance(Context context, Sliceable sliceable, Uri uri) {
SliceBackgroundWorker worker = getInstance(uri);
if (worker == null) {
final Class extends SliceBackgroundWorker> workerClass =
sliceable.getBackgroundWorkerClass();
- worker = createInstance(context, uri, workerClass);
+ worker = createInstance(context.getApplicationContext(), uri, workerClass);
LIVE_WORKERS.put(uri, worker);
}
return worker;
diff --git a/src/com/android/settings/slices/Sliceable.java b/src/com/android/settings/slices/Sliceable.java
index b00ab8207eb..c1661f8392a 100644
--- a/src/com/android/settings/slices/Sliceable.java
+++ b/src/com/android/settings/slices/Sliceable.java
@@ -91,4 +91,16 @@ public interface Sliceable {
final String toast = context.getString(R.string.copyable_slice_toast, messageTitle);
Toast.makeText(context, toast, Toast.LENGTH_SHORT).show();
}
+
+ /**
+ * Settings Slices which require background work, such as updating lists should implement a
+ * {@link SliceBackgroundWorker} and return it here. An example of background work is updating
+ * a list of Wifi networks available in the area.
+ *
+ * @return a {@link Class extends SliceBackgroundWorker>} to perform background work for the
+ * slice.
+ */
+ default Class extends SliceBackgroundWorker> getBackgroundWorkerClass() {
+ return null;
+ }
}
diff --git a/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java
index 8883ddf7e3a..e3672c92dc9 100644
--- a/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java
@@ -19,10 +19,13 @@ package com.android.settings.nfc;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
import android.nfc.NfcAdapter;
import android.nfc.NfcManager;
import android.os.UserManager;
@@ -31,6 +34,10 @@ import android.provider.Settings;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
+import com.android.settings.nfc.NfcPreferenceController.NfcSliceWorker;
+import com.android.settings.nfc.NfcPreferenceController.NfcSliceWorker.NfcUpdateReceiver;
+import com.android.settings.slices.SliceBuilderUtils;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -246,4 +253,67 @@ public class NfcPreferenceControllerTest {
assertThat(mNfcController.mAirplaneModeObserver).isNull();
}
+
+ @Test
+ public void ncfSliceWorker_nfcBroadcast_noExtra_sliceDoesntUpdate() {
+ final NfcSliceWorker worker = spy(new NfcSliceWorker(mContext, getDummyUri()));
+ final NfcUpdateReceiver receiver = worker.new NfcUpdateReceiver(worker);
+ final Intent triggerIntent = new Intent(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
+
+ receiver.onReceive(mContext, triggerIntent);
+
+ verify(worker, times(0)).updateSlice();
+ }
+
+ @Test
+ public void ncfSliceWorker_nfcBroadcast_turningOn_sliceDoesntUpdate() {
+ final NfcSliceWorker worker = spy(new NfcSliceWorker(mContext, getDummyUri()));
+ final NfcUpdateReceiver receiver = worker.new NfcUpdateReceiver(worker);
+ final Intent triggerIntent = new Intent(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
+ triggerIntent.putExtra(NfcAdapter.EXTRA_ADAPTER_STATE, NfcAdapter.STATE_TURNING_ON);
+
+ receiver.onReceive(mContext, triggerIntent);
+
+ verify(worker, times(0)).updateSlice();
+ }
+
+ @Test
+ public void ncfSliceWorker_nfcBroadcast_turningOff_sliceDoesntUpdate() {
+ final NfcSliceWorker worker = spy(new NfcSliceWorker(mContext, getDummyUri()));
+ final NfcUpdateReceiver receiver = worker.new NfcUpdateReceiver(worker);
+ final Intent triggerIntent = new Intent(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
+ triggerIntent.putExtra(NfcAdapter.EXTRA_ADAPTER_STATE, NfcAdapter.STATE_TURNING_OFF);
+
+ receiver.onReceive(mContext, triggerIntent);
+
+ verify(worker, times(0)).updateSlice();
+ }
+
+ @Test
+ public void ncfSliceWorker_nfcBroadcast_nfcOn_sliceUpdates() {
+ final NfcSliceWorker worker = spy(new NfcSliceWorker(mContext, getDummyUri()));
+ final NfcUpdateReceiver receiver = worker.new NfcUpdateReceiver(worker);
+ final Intent triggerIntent = new Intent(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
+ triggerIntent.putExtra(NfcAdapter.EXTRA_ADAPTER_STATE, NfcAdapter.STATE_ON);
+
+ receiver.onReceive(mContext, triggerIntent);
+
+ verify(worker).updateSlice();
+ }
+
+ @Test
+ public void ncfSliceWorker_nfcBroadcast_nfcOff_sliceUpdates() {
+ final NfcSliceWorker worker = spy(new NfcSliceWorker(mContext, getDummyUri()));
+ final NfcUpdateReceiver receiver = worker.new NfcUpdateReceiver(worker);
+ final Intent triggerIntent = new Intent(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
+ triggerIntent.putExtra(NfcAdapter.EXTRA_ADAPTER_STATE, NfcAdapter.STATE_OFF);
+
+ receiver.onReceive(mContext, triggerIntent);
+
+ verify(worker).updateSlice();
+ }
+
+ private Uri getDummyUri() {
+ return SliceBuilderUtils.getUri("action/nfc", false);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
index 726e4bdf300..a693f349894 100644
--- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
+++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
@@ -64,6 +64,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@@ -183,6 +184,20 @@ public class SettingsSliceProviderTest {
verify(mProvider).registerIntentToUri(eq(FakeToggleController.INTENT_FILTER), eq(uri));
}
+ @Test
+ public void loadSlice_registersBackgroundListener() {
+ insertSpecialCase(KEY);
+ final Uri uri = SliceBuilderUtils.getUri(INTENT_PATH, false);
+
+ mProvider.loadSlice(uri);
+
+ Robolectric.flushForegroundThreadScheduler();
+ Robolectric.flushBackgroundThreadScheduler();
+
+ assertThat(mProvider.mPinnedWorkers.get(uri).getClass())
+ .isEqualTo(FakeToggleController.TestWorker.class);
+ }
+
@Test
public void testLoadSlice_doesNotCacheWithoutPin() {
insertSpecialCase(KEY);
diff --git a/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java b/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java
index d1677cd7382..e7854874c54 100644
--- a/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java
+++ b/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java
@@ -19,10 +19,14 @@ package com.android.settings.testutils;
import android.content.Context;
import android.content.IntentFilter;
+import android.net.Uri;
import android.net.wifi.WifiManager;
import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.slices.SliceBackgroundWorker;
+
+import java.io.IOException;
public class FakeToggleController extends TogglePreferenceController {
@@ -70,6 +74,11 @@ public class FakeToggleController extends TogglePreferenceController {
return true;
}
+ @Override
+ public Class extends SliceBackgroundWorker> getBackgroundWorkerClass() {
+ return TestWorker.class;
+ }
+
@Override
public boolean hasAsyncUpdate() {
return mIsAsyncUpdate;
@@ -78,4 +87,23 @@ public class FakeToggleController extends TogglePreferenceController {
public void setAsyncUpdate(boolean isAsyncUpdate) {
mIsAsyncUpdate = isAsyncUpdate;
}
+
+ public static class TestWorker extends SliceBackgroundWorker {
+
+ public TestWorker(Context context, Uri uri) {
+ super(context, uri);
+ }
+
+ @Override
+ protected void onSlicePinned() {
+ }
+
+ @Override
+ protected void onSliceUnpinned() {
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+ }
}