Merge "Slice background worker with Wi-Fi Slice"

This commit is contained in:
Jason Chiu
2018-10-19 20:23:05 +00:00
committed by Android (Google) Code Review
6 changed files with 313 additions and 10 deletions

View File

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

View File

@@ -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;
@@ -132,6 +133,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);
}
@@ -166,6 +169,7 @@ public class SettingsSliceProvider extends SliceProvider {
if (filter != null) {
registerIntentToUri(filter, sliceUri);
}
startBackgroundWorker(sliceable);
return;
}
@@ -194,6 +198,7 @@ public class SettingsSliceProvider extends SliceProvider {
SliceBroadcastRelay.unregisterReceivers(getContext(), sliceUri);
mRegisteredUris.remove(sliceUri);
}
stopBackgroundWorker(sliceUri);
mSliceDataCache.remove(sliceUri);
}
@@ -353,6 +358,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<>();

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

View File

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

View File

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

View File

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