Slice background worker with Wi-Fi Slice
Test: manual Change-Id: Ic4fdc5713f511ff80f03728c99c68fda3d0cab02
This commit is contained in:
@@ -89,6 +89,16 @@ public interface CustomSliceable {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings Slices which can represent component lists that are updatable by the
|
||||
* {@link SliceBackgroundWorker} returned here.
|
||||
*
|
||||
* @return a {@link SliceBackgroundWorker} for fetching the list of results in the background.
|
||||
*/
|
||||
default SliceBackgroundWorker getBackgroundWorker() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standardize the intents returned to indicate actions by the Slice.
|
||||
* <p>
|
||||
|
@@ -28,6 +28,7 @@ import android.os.StrictMode;
|
||||
import android.provider.Settings;
|
||||
import android.provider.SettingsSlicesContract;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.KeyValueListParser;
|
||||
import android.util.Log;
|
||||
@@ -131,6 +132,8 @@ public class SettingsSliceProvider extends SliceProvider {
|
||||
|
||||
final Set<Uri> mRegisteredUris = new ArraySet<>();
|
||||
|
||||
final Map<Uri, SliceBackgroundWorker> mWorkerMap = new ArrayMap<>();
|
||||
|
||||
public SettingsSliceProvider() {
|
||||
super(READ_SEARCH_INDEXABLES);
|
||||
}
|
||||
@@ -165,6 +168,7 @@ public class SettingsSliceProvider extends SliceProvider {
|
||||
if (filter != null) {
|
||||
registerIntentToUri(filter, sliceUri);
|
||||
}
|
||||
startBackgroundWorker(sliceable);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -191,6 +195,7 @@ public class SettingsSliceProvider extends SliceProvider {
|
||||
SliceBroadcastRelay.unregisterReceivers(getContext(), sliceUri);
|
||||
mRegisteredUris.remove(sliceUri);
|
||||
}
|
||||
stopBackgroundWorker(sliceUri);
|
||||
mSliceDataCache.remove(sliceUri);
|
||||
}
|
||||
|
||||
@@ -348,6 +353,31 @@ public class SettingsSliceProvider extends SliceProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private void startBackgroundWorker(CustomSliceable sliceable) {
|
||||
final SliceBackgroundWorker worker = sliceable.getBackgroundWorker();
|
||||
if (worker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Uri uri = sliceable.getUri();
|
||||
Log.d(TAG, "Starting background worker for: " + uri);
|
||||
if (mWorkerMap.containsKey(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWorkerMap.put(uri, worker);
|
||||
worker.onSlicePinned();
|
||||
}
|
||||
|
||||
private void stopBackgroundWorker(Uri uri) {
|
||||
final SliceBackgroundWorker worker = mWorkerMap.get(uri);
|
||||
if (worker != null) {
|
||||
Log.d(TAG, "Stopping background worker for: " + uri);
|
||||
worker.onSliceUnpinned();
|
||||
mWorkerMap.remove(uri);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Uri> buildUrisFromKeys(List<String> keys, String authority) {
|
||||
final List<Uri> descendants = new ArrayList<>();
|
||||
|
||||
|
85
src/com/android/settings/slices/SliceBackgroundWorker.java
Normal file
85
src/com/android/settings/slices/SliceBackgroundWorker.java
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.ContentResolver;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The Slice background worker is used to make Settings Slices be able to work with data that is
|
||||
* changing continuously, e.g. available Wi-Fi networks.
|
||||
*
|
||||
* The background worker will be started at {@link SettingsSliceProvider#onSlicePinned(Uri)}, and be
|
||||
* stopped at {@link SettingsSliceProvider#onSliceUnpinned(Uri)}.
|
||||
*
|
||||
* {@link SliceBackgroundWorker} caches the results, uses the cache to compare if there is any data
|
||||
* changed, and then notifies the Slice {@link Uri} to update.
|
||||
*/
|
||||
public abstract class SliceBackgroundWorker<E> {
|
||||
|
||||
private final ContentResolver mContentResolver;
|
||||
private final Uri mUri;
|
||||
|
||||
private List<E> mCachedResults;
|
||||
|
||||
protected SliceBackgroundWorker(ContentResolver cr, Uri uri) {
|
||||
mContentResolver = cr;
|
||||
mUri = uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the Slice is pinned. This is the place to register callbacks or initialize scan
|
||||
* tasks.
|
||||
*/
|
||||
protected abstract void onSlicePinned();
|
||||
|
||||
/**
|
||||
* Called when the Slice is unpinned. This is the place to unregister callbacks or perform any
|
||||
* final cleanup.
|
||||
*/
|
||||
protected abstract void onSliceUnpinned();
|
||||
|
||||
/**
|
||||
* @return a {@link List} of cached results
|
||||
*/
|
||||
public final List<E> getResults() {
|
||||
return mCachedResults == null ? null : new ArrayList<>(mCachedResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the results when data changes
|
||||
*/
|
||||
protected final void updateResults(List<E> results) {
|
||||
boolean needNotify = false;
|
||||
|
||||
if (results == null) {
|
||||
if (mCachedResults != null) {
|
||||
needNotify = true;
|
||||
}
|
||||
} else {
|
||||
needNotify = !results.equals(mCachedResults);
|
||||
}
|
||||
|
||||
if (needNotify) {
|
||||
mCachedResults = results;
|
||||
mContentResolver.notifyChange(mUri, null);
|
||||
}
|
||||
}
|
||||
}
|
@@ -37,12 +37,6 @@ public class WifiDialogActivity extends Activity implements WifiDialog.WifiDialo
|
||||
|
||||
private static final String TAG = "WifiDialogActivity";
|
||||
|
||||
private static final int RESULT_CONNECTED = RESULT_FIRST_USER;
|
||||
private static final int RESULT_FORGET = RESULT_FIRST_USER + 1;
|
||||
|
||||
private static final String KEY_ACCESS_POINT_STATE = "access_point_state";
|
||||
private static final String KEY_WIFI_CONFIGURATION = "wifi_configuration";
|
||||
|
||||
/**
|
||||
* Boolean extra indicating whether this activity should connect to an access point on the
|
||||
* caller's behalf. If this is set to false, the caller should check
|
||||
@@ -51,6 +45,11 @@ public class WifiDialogActivity extends Activity implements WifiDialog.WifiDialo
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final String KEY_CONNECT_FOR_CALLER = "connect_for_caller";
|
||||
static final String KEY_ACCESS_POINT_STATE = "access_point_state";
|
||||
private static final String KEY_WIFI_CONFIGURATION = "wifi_configuration";
|
||||
|
||||
private static final int RESULT_CONNECTED = RESULT_FIRST_USER;
|
||||
private static final int RESULT_FORGET = RESULT_FIRST_USER + 1;
|
||||
|
||||
private WifiDialog mDialog;
|
||||
|
||||
|
@@ -29,6 +29,9 @@ import android.net.Uri;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.net.wifi.WifiSsid;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.SettingsSlicesContract;
|
||||
import android.text.TextUtils;
|
||||
|
||||
@@ -42,8 +45,16 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SubSettings;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.slices.CustomSliceable;
|
||||
import com.android.settings.slices.SliceBackgroundWorker;
|
||||
import com.android.settings.slices.SliceBuilderUtils;
|
||||
import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
|
||||
import com.android.settingslib.wifi.AccessPoint;
|
||||
import com.android.settingslib.wifi.WifiTracker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class to build a Wifi Slice, and handle all associated actions.
|
||||
@@ -96,14 +107,73 @@ public class WifiSlice implements CustomSliceable {
|
||||
final SliceAction toggleSliceAction = new SliceAction(toggleAction, null /* actionTitle */,
|
||||
isWifiEnabled);
|
||||
|
||||
return new ListBuilder(mContext, WIFI_URI, ListBuilder.INFINITY)
|
||||
final ListBuilder listBuilder = new ListBuilder(mContext, WIFI_URI, ListBuilder.INFINITY)
|
||||
.setAccentColor(color)
|
||||
.addRow(new RowBuilder()
|
||||
.setTitle(title)
|
||||
.setSubtitle(summary)
|
||||
.addEndItem(toggleSliceAction)
|
||||
.setPrimaryAction(primarySliceAction))
|
||||
.build();
|
||||
.setPrimaryAction(primarySliceAction));
|
||||
|
||||
if (isWifiEnabled) {
|
||||
final List<AccessPoint> result = getBackgroundWorker().getResults();
|
||||
if (result != null && !result.isEmpty()) {
|
||||
for (AccessPoint ap : result) {
|
||||
listBuilder.addRow(getAccessPointRow(ap));
|
||||
}
|
||||
listBuilder.setSeeMoreAction(primaryAction);
|
||||
}
|
||||
}
|
||||
return listBuilder.build();
|
||||
}
|
||||
|
||||
private RowBuilder getAccessPointRow(AccessPoint accessPoint) {
|
||||
final String title = accessPoint.getConfigName();
|
||||
final IconCompat levelIcon = IconCompat.createWithResource(mContext,
|
||||
com.android.settingslib.Utils.getWifiIconResource(accessPoint.getLevel()));
|
||||
final RowBuilder rowBuilder = new RowBuilder()
|
||||
.setTitleItem(levelIcon, ListBuilder.ICON_IMAGE)
|
||||
.setTitle(title)
|
||||
.setSubtitle(accessPoint.getSettingsSummary())
|
||||
.setPrimaryAction(new SliceAction(
|
||||
getAccessPointAction(accessPoint), levelIcon, title));
|
||||
|
||||
final IconCompat endIcon = getEndIcon(accessPoint);
|
||||
if (endIcon != null) {
|
||||
rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE);
|
||||
}
|
||||
return rowBuilder;
|
||||
}
|
||||
|
||||
private IconCompat getEndIcon(AccessPoint accessPoint) {
|
||||
if (accessPoint.isActive()) {
|
||||
return IconCompat.createWithResource(mContext, R.drawable.ic_settings);
|
||||
} else if (accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
|
||||
return IconCompat.createWithResource(mContext, R.drawable.ic_friction_lock_closed);
|
||||
} else if (accessPoint.isMetered()) {
|
||||
return IconCompat.createWithResource(mContext, R.drawable.ic_friction_money);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PendingIntent getAccessPointAction(AccessPoint accessPoint) {
|
||||
final Bundle extras = new Bundle();
|
||||
accessPoint.saveWifiState(extras);
|
||||
|
||||
Intent intent;
|
||||
if (accessPoint.isActive()) {
|
||||
intent = new SubSettingLauncher(mContext)
|
||||
.setTitleRes(R.string.pref_title_network_details)
|
||||
.setDestination(WifiNetworkDetailsFragment.class.getName())
|
||||
.setArguments(extras)
|
||||
.setSourceMetricsCategory(MetricsEvent.WIFI)
|
||||
.toIntent();
|
||||
} else {
|
||||
intent = new Intent(mContext, WifiDialogActivity.class);
|
||||
intent.putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras);
|
||||
}
|
||||
return PendingIntent.getActivity(mContext, accessPoint.hashCode() /* requestCode */,
|
||||
intent, 0 /* flags */);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,4 +246,75 @@ public class WifiSlice implements CustomSliceable {
|
||||
return PendingIntent.getActivity(mContext, 0 /* requestCode */,
|
||||
intent, 0 /* flags */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SliceBackgroundWorker getBackgroundWorker() {
|
||||
return WifiScanWorker.getInstance(mContext, WIFI_URI);
|
||||
}
|
||||
|
||||
private static class WifiScanWorker extends SliceBackgroundWorker<AccessPoint>
|
||||
implements WifiTracker.WifiListener {
|
||||
|
||||
private static WifiScanWorker mWifiScanWorker;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private WifiTracker mWifiTracker;
|
||||
private WifiManager mWifiManager;
|
||||
|
||||
private WifiScanWorker(Context context, Uri uri) {
|
||||
super(context.getContentResolver(), uri);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static WifiScanWorker getInstance(Context context, Uri uri) {
|
||||
if (mWifiScanWorker == null) {
|
||||
mWifiScanWorker = new WifiScanWorker(context, uri);
|
||||
}
|
||||
return mWifiScanWorker;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSlicePinned() {
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
mWifiTracker = new WifiTracker(mContext, this, true, true);
|
||||
mWifiManager = mWifiTracker.getManager();
|
||||
mWifiTracker.onStart();
|
||||
onAccessPointsChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSliceUnpinned() {
|
||||
mWifiTracker.onStop();
|
||||
mWifiTracker.onDestroy();
|
||||
mWifiScanWorker = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWifiStateChanged(int state) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectedChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccessPointsChanged() {
|
||||
// in case state has changed
|
||||
if (!mWifiManager.isWifiEnabled()) {
|
||||
updateResults(null);
|
||||
return;
|
||||
}
|
||||
// AccessPoints are sorted by the WifiTracker
|
||||
final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
|
||||
final List<AccessPoint> resultList = new ArrayList<>();
|
||||
for (AccessPoint ap : accessPoints) {
|
||||
if (ap.isReachable()) {
|
||||
resultList.add(ap);
|
||||
}
|
||||
}
|
||||
updateResults(resultList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -118,7 +118,7 @@ public class SettingsSliceProviderTest {
|
||||
mProvider.mSliceWeakDataCache = new HashMap<>();
|
||||
mProvider.mSliceDataCache = new HashMap<>();
|
||||
mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext);
|
||||
mProvider.mCustomSliceManager = new CustomSliceManager(mContext);
|
||||
mProvider.mCustomSliceManager = spy(new CustomSliceManager(mContext));
|
||||
when(mProvider.getContext()).thenReturn(mContext);
|
||||
|
||||
mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
||||
@@ -481,6 +481,44 @@ public class SettingsSliceProviderTest {
|
||||
mProvider.onSlicePinned(uri);
|
||||
}
|
||||
|
||||
private SliceBackgroundWorker initBackgroundWorker(Uri uri) {
|
||||
final SliceBackgroundWorker worker = spy(new SliceBackgroundWorker(
|
||||
mContext.getContentResolver(), uri) {
|
||||
@Override
|
||||
public void onSlicePinned() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSliceUnpinned() {
|
||||
}
|
||||
});
|
||||
final WifiSlice wifiSlice = spy(new WifiSlice(mContext));
|
||||
when(wifiSlice.getBackgroundWorker()).thenReturn(worker);
|
||||
when(mProvider.mCustomSliceManager.getSliceableFromUri(uri)).thenReturn(wifiSlice);
|
||||
return worker;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSlicePinned_backgroundWorker_started() {
|
||||
final Uri uri = WifiSlice.WIFI_URI;
|
||||
final SliceBackgroundWorker worker = initBackgroundWorker(uri);
|
||||
|
||||
mProvider.onSlicePinned(uri);
|
||||
|
||||
verify(worker).onSlicePinned();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSlicePinned_backgroundWorker_stopped() {
|
||||
final Uri uri = WifiSlice.WIFI_URI;
|
||||
final SliceBackgroundWorker worker = initBackgroundWorker(uri);
|
||||
|
||||
mProvider.onSlicePinned(uri);
|
||||
mProvider.onSliceUnpinned(uri);
|
||||
|
||||
verify(worker).onSliceUnpinned();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantWhitelistedPackagePermissions_noWhitelist_shouldNotGrant() {
|
||||
final List<Uri> uris = new ArrayList<>();
|
||||
|
Reference in New Issue
Block a user