Merge "Remove NFC Slice jank"

This commit is contained in:
TreeHugger Robot
2019-02-26 19:28:58 +00:00
committed by Android (Google) Code Review
9 changed files with 220 additions and 31 deletions

View File

@@ -16,4 +16,5 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
android:layout_width="match_parent"
android:animateLayoutChanges="true"/>

View File

@@ -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.
*
* <p>
* 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:
* <ul>
* <li>{@link NfcAdapter#STATE_ON}</li>
* <li>{@link NfcAdapter#STATE_OFF}</li>
* </ul>
*/
public static class NfcSliceWorker extends SliceBackgroundWorker<Void> {
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();
}
}
}
}

View File

@@ -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.
* <p>

View File

@@ -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<Uri> pinnedSlices = getContext().getSystemService(
SliceManager.class).getPinnedSlices();
if (pinnedSlices.contains(uri)) {

View File

@@ -80,13 +80,12 @@ public abstract class SliceBackgroundWorker<E> 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;

View File

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

View File

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

View File

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

View File

@@ -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<Void> {
public TestWorker(Context context, Uri uri) {
super(context, uri);
}
@Override
protected void onSlicePinned() {
}
@Override
protected void onSliceUnpinned() {
}
@Override
public void close() throws IOException {
}
}
}