Build slice from indexed data in SliceProvider
Connect the SliceIndexing data to the SliceProvider, such that a query to SliceProvider can build a Slice via the indexed data from SlicesIndexingManager. We take the key from the Uri supplied to the SettingSliceProvider and find a potential matching row in the indexed data. The matched data is then used to Build a slice for the caller. Bug: 67996923 Test: robotests Change-Id: If51bfd1a05c3f3817ae720554f95a98fc7b002e1
This commit is contained in:
@@ -28,6 +28,7 @@ import android.widget.Toolbar;
|
||||
|
||||
import com.android.settings.core.FeatureFlags;
|
||||
import com.android.settings.dashboard.SiteMapManager;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -185,6 +186,9 @@ public interface SearchFeatureProvider {
|
||||
} else {
|
||||
intent = new Intent(activity, SearchActivity.class);
|
||||
}
|
||||
FeatureFactory.getFactory(
|
||||
activity.getApplicationContext()).getSlicesFeatureProvider()
|
||||
.indexSliceDataAsync(activity.getApplicationContext());
|
||||
activity.startActivityForResult(intent, 0 /* requestCode */);
|
||||
});
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ import android.content.Intent;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
@@ -32,13 +33,25 @@ import androidx.app.slice.SliceProvider;
|
||||
import androidx.app.slice.builders.ListBuilder;
|
||||
|
||||
public class SettingsSliceProvider extends SliceProvider {
|
||||
|
||||
private static final String TAG = "SettingsSliceProvider";
|
||||
|
||||
public static final String SLICE_AUTHORITY = "com.android.settings.slices";
|
||||
|
||||
public static final String PATH_WIFI = "wifi";
|
||||
public static final String ACTION_WIFI_CHANGED =
|
||||
"com.android.settings.slice.action.WIFI_CHANGED";
|
||||
|
||||
public static final String ACTION_TOGGLE_CHANGED =
|
||||
"com.android.settings.slice.action.TOGGLE_CHANGED";
|
||||
|
||||
public static final String EXTRA_SLICE_KEY = "com.android.settings.slice.extra.key";
|
||||
|
||||
// TODO -- Associate slice URI with search result instead of separate hardcoded thing
|
||||
|
||||
@VisibleForTesting
|
||||
SlicesDatabaseAccessor mSlicesDatabaseAccessor;
|
||||
|
||||
public static Uri getUri(String path) {
|
||||
return new Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||
@@ -48,19 +61,26 @@ public class SettingsSliceProvider extends SliceProvider {
|
||||
|
||||
@Override
|
||||
public boolean onCreateSliceProvider() {
|
||||
mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(getContext());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice onBindSlice(Uri sliceUri) {
|
||||
String path = sliceUri.getPath();
|
||||
// If adding a new Slice, do not directly match Slice URIs.
|
||||
// Use {@link SlicesDatabaseAccessor}.
|
||||
switch (path) {
|
||||
case "/" + PATH_WIFI:
|
||||
return createWifiSlice(sliceUri);
|
||||
}
|
||||
throw new IllegalArgumentException("Unrecognized slice uri: " + sliceUri);
|
||||
|
||||
return getHoldingSlice(sliceUri);
|
||||
}
|
||||
|
||||
private Slice getHoldingSlice(Uri uri) {
|
||||
return new ListBuilder(uri).build();
|
||||
}
|
||||
|
||||
// TODO (b/70622039) remove this when the proper wifi slice is enabled.
|
||||
private Slice createWifiSlice(Uri sliceUri) {
|
||||
|
@@ -16,7 +16,9 @@
|
||||
|
||||
package com.android.settings.slices;
|
||||
|
||||
import static com.android.settings.slices.SettingsSliceProvider.ACTION_TOGGLE_CHANGED;
|
||||
import static com.android.settings.slices.SettingsSliceProvider.ACTION_WIFI_CHANGED;
|
||||
import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY;
|
||||
|
||||
import android.app.slice.Slice;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -25,19 +27,34 @@ import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.core.TogglePreferenceController;
|
||||
|
||||
/**
|
||||
* Responds to actions performed on slices and notifies slices of updates in state changes.
|
||||
*/
|
||||
public class SliceBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
private static String TAG = "SettSliceBroadcastRec";
|
||||
|
||||
/**
|
||||
* TODO (b/) move wifi action into generalized case.
|
||||
*/
|
||||
@Override
|
||||
public void onReceive(Context context, Intent i) {
|
||||
String action = i.getAction();
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
String key = intent.getStringExtra(EXTRA_SLICE_KEY);
|
||||
|
||||
switch (action) {
|
||||
case ACTION_TOGGLE_CHANGED:
|
||||
handleToggleAction(context, key);
|
||||
break;
|
||||
case ACTION_WIFI_CHANGED:
|
||||
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
boolean newState = i.getBooleanExtra(Slice.EXTRA_TOGGLE_STATE, wm.isWifiEnabled());
|
||||
boolean newState = intent.getBooleanExtra(Slice.EXTRA_TOGGLE_STATE,
|
||||
wm.isWifiEnabled());
|
||||
wm.setWifiEnabled(newState);
|
||||
// Wait a bit for wifi to update (TODO: is there a better way to do this?)
|
||||
Handler h = new Handler();
|
||||
@@ -48,4 +65,28 @@ public class SliceBroadcastReceiver extends BroadcastReceiver {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleToggleAction(Context context, String key) {
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
throw new IllegalStateException("No key passed to Intent for toggle controller");
|
||||
}
|
||||
|
||||
BasePreferenceController controller = getBasePreferenceController(context, key);
|
||||
|
||||
if (!(controller instanceof TogglePreferenceController)) {
|
||||
throw new IllegalStateException("Toggle action passed for a non-toggle key: " + key);
|
||||
}
|
||||
|
||||
// TODO post context.getContentResolver().notifyChanged(uri, null) in the Toggle controller
|
||||
// so that it's automatically broadcast to any slice.
|
||||
TogglePreferenceController toggleController = (TogglePreferenceController) controller;
|
||||
boolean currentValue = toggleController.isChecked();
|
||||
toggleController.setChecked(!currentValue);
|
||||
}
|
||||
|
||||
private BasePreferenceController getBasePreferenceController(Context context, String key) {
|
||||
final SlicesDatabaseAccessor accessor = new SlicesDatabaseAccessor(context);
|
||||
final SliceData sliceData = accessor.getSliceDataFromKey(key);
|
||||
return SliceBuilderUtils.getPreferenceController(context, sliceData);
|
||||
}
|
||||
}
|
||||
|
125
src/com/android/settings/slices/SliceBuilderUtils.java
Normal file
125
src/com/android/settings/slices/SliceBuilderUtils.java
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.settings.SubSettings;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.core.TogglePreferenceController;
|
||||
import com.android.settings.search.DatabaseIndexingUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import androidx.app.slice.Slice;
|
||||
import androidx.app.slice.builders.ListBuilder;
|
||||
import androidx.app.slice.builders.ListBuilder.RowBuilder;
|
||||
|
||||
/**
|
||||
* Utility class to build Slices objects and Preference Controllers based on the Database managed
|
||||
* by {@link SlicesDatabaseHelper}
|
||||
*/
|
||||
public class SliceBuilderUtils {
|
||||
|
||||
private static final String TAG = "SliceBuilder";
|
||||
|
||||
/**
|
||||
* Build a Slice from {@link SliceData}.
|
||||
*
|
||||
* @return a {@link Slice} based on the data provided by {@param sliceData}.
|
||||
* Will build an {@link Intent} based Slice unless the Preference Controller name in
|
||||
* {@param sliceData} is an inline controller.
|
||||
*/
|
||||
public static Slice buildSlice(Context context, SliceData sliceData) {
|
||||
final PendingIntent contentIntent = getContentIntent(context, sliceData);
|
||||
final Icon icon = Icon.createWithResource(context, sliceData.getIconResource());
|
||||
String summaryText = sliceData.getSummary();
|
||||
String subtitleText = TextUtils.isEmpty(summaryText)
|
||||
? sliceData.getScreenTitle()
|
||||
: summaryText;
|
||||
|
||||
RowBuilder builder = new RowBuilder(sliceData.getUri())
|
||||
.setTitle(sliceData.getTitle())
|
||||
.setTitleItem(icon)
|
||||
.setSubtitle(subtitleText)
|
||||
.setContentIntent(contentIntent);
|
||||
|
||||
BasePreferenceController controller = getPreferenceController(context, sliceData);
|
||||
|
||||
// TODO (b/71640747) Respect setting availability.
|
||||
// TODO (b/71640678) Add dynamic summary text.
|
||||
|
||||
if (controller instanceof TogglePreferenceController) {
|
||||
addToggleAction(context, builder, ((TogglePreferenceController) controller).isChecked(),
|
||||
sliceData.getKey());
|
||||
}
|
||||
|
||||
return new ListBuilder(sliceData.getUri())
|
||||
.addRow(builder)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at the {@link SliceData#preferenceController} from {@param sliceData} and attempts to
|
||||
* build a {@link BasePreferenceController}.
|
||||
*/
|
||||
public static BasePreferenceController getPreferenceController(Context context,
|
||||
SliceData sliceData) {
|
||||
// TODO check for context-only controller first.
|
||||
try {
|
||||
Class<?> clazz = Class.forName(sliceData.getPreferenceController());
|
||||
Constructor<?> preferenceConstructor = clazz.getConstructor(Context.class,
|
||||
String.class);
|
||||
return (BasePreferenceController) preferenceConstructor.newInstance(
|
||||
new Object[]{context, sliceData.getKey()});
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
|
||||
IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
|
||||
throw new IllegalStateException(
|
||||
"Invalid preference controller: " + sliceData.getPreferenceController());
|
||||
}
|
||||
}
|
||||
|
||||
private static void addToggleAction(Context context, RowBuilder builder, boolean isChecked,
|
||||
String key) {
|
||||
PendingIntent actionIntent = getActionIntent(context,
|
||||
SettingsSliceProvider.ACTION_TOGGLE_CHANGED, key);
|
||||
builder.addToggle(actionIntent, isChecked);
|
||||
}
|
||||
|
||||
private static PendingIntent getActionIntent(Context context, String action, String key) {
|
||||
Intent intent = new Intent(action);
|
||||
intent.setClass(context, SliceBroadcastReceiver.class);
|
||||
intent.putExtra(EXTRA_SLICE_KEY, key);
|
||||
return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
private static PendingIntent getContentIntent(Context context, SliceData sliceData) {
|
||||
Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
|
||||
sliceData.getFragmentClassName(), sliceData.getKey(), sliceData.getScreenTitle(),
|
||||
0 /* TODO */);
|
||||
intent.setClassName("com.android.settings", SubSettings.class.getName());
|
||||
return PendingIntent.getActivity(context, 0 /* requestCode */, intent, 0 /* flags */);
|
||||
}
|
||||
}
|
@@ -18,7 +18,6 @@ package com.android.settings.slices;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* Data class representing a slice stored by {@link SlicesIndexer}.
|
||||
* Note that {@link #key} is treated as a primary key for this class and determines equality.
|
||||
@@ -179,5 +178,4 @@ public class SliceData {
|
||||
return mKey;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
141
src/com/android/settings/slices/SlicesDatabaseAccessor.java
Normal file
141
src/com/android/settings/slices/SlicesDatabaseAccessor.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 static com.android.settings.slices.SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.slices.SlicesDatabaseHelper.IndexColumns;
|
||||
|
||||
import androidx.app.slice.Slice;
|
||||
|
||||
/**
|
||||
* Class used to map a {@link Uri} from {@link SettingsSliceProvider} to a Slice.
|
||||
*/
|
||||
public class SlicesDatabaseAccessor {
|
||||
|
||||
public static final String[] SELECT_COLUMNS = {
|
||||
IndexColumns.KEY,
|
||||
IndexColumns.TITLE,
|
||||
IndexColumns.SUMMARY,
|
||||
IndexColumns.SCREENTITLE,
|
||||
IndexColumns.ICON_RESOURCE,
|
||||
IndexColumns.FRAGMENT,
|
||||
IndexColumns.CONTROLLER,
|
||||
};
|
||||
|
||||
Context mContext;
|
||||
|
||||
public SlicesDatabaseAccessor(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the slices database and return a {@link SliceData} object corresponding to the row
|
||||
* matching the key provided by the {@param uri}. Additionally adds the {@param uri} to the
|
||||
* {@link SliceData} object so the {@link Slice} can bind to the {@link Uri}.
|
||||
* Used when building a {@link Slice}.
|
||||
*/
|
||||
public SliceData getSliceDataFromUri(Uri uri) {
|
||||
String key = uri.getLastPathSegment();
|
||||
Cursor cursor = getIndexedSliceData(key);
|
||||
return buildSliceData(cursor, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the slices database and return a {@link SliceData} object corresponding to the row
|
||||
* matching the {@param key}.
|
||||
* Used when handling the action of the {@link Slice}.
|
||||
*/
|
||||
public SliceData getSliceDataFromKey(String key) {
|
||||
Cursor cursor = getIndexedSliceData(key);
|
||||
return buildSliceData(cursor, null /* uri */);
|
||||
}
|
||||
|
||||
private Cursor getIndexedSliceData(String path) {
|
||||
verifyIndexing();
|
||||
|
||||
final String whereClause = buildWhereClause();
|
||||
final SlicesDatabaseHelper helper = SlicesDatabaseHelper.getInstance(mContext);
|
||||
final SQLiteDatabase database = helper.getReadableDatabase();
|
||||
final String[] selection = new String[]{path};
|
||||
|
||||
Cursor resultCursor = database.query(TABLE_SLICES_INDEX, SELECT_COLUMNS, whereClause,
|
||||
selection, null /* groupBy */, null /* having */, null /* orderBy */);
|
||||
|
||||
int numResults = resultCursor.getCount();
|
||||
|
||||
if (numResults == 0) {
|
||||
throw new IllegalStateException("Invalid Slices key from path: " + path);
|
||||
}
|
||||
|
||||
if (numResults > 1) {
|
||||
throw new IllegalStateException(
|
||||
"Should not match more than 1 slice with path: " + path);
|
||||
}
|
||||
|
||||
resultCursor.moveToFirst();
|
||||
return resultCursor;
|
||||
}
|
||||
|
||||
private String buildWhereClause() {
|
||||
return new StringBuilder(IndexColumns.KEY)
|
||||
.append(" = ?")
|
||||
.toString();
|
||||
}
|
||||
|
||||
private SliceData buildSliceData(Cursor cursor, Uri uri) {
|
||||
final String key = cursor.getString(cursor.getColumnIndex(IndexColumns.KEY));
|
||||
final String title = cursor.getString(cursor.getColumnIndex(IndexColumns.TITLE));
|
||||
final String summary = cursor.getString(cursor.getColumnIndex(IndexColumns.SUMMARY));
|
||||
final String screenTitle = cursor.getString(
|
||||
cursor.getColumnIndex(IndexColumns.SCREENTITLE));
|
||||
final int iconResource = cursor.getInt(cursor.getColumnIndex(IndexColumns.ICON_RESOURCE));
|
||||
final String fragmentClassName = cursor.getString(
|
||||
cursor.getColumnIndex(IndexColumns.FRAGMENT));
|
||||
final String controllerClassName = cursor.getString(
|
||||
cursor.getColumnIndex(IndexColumns.CONTROLLER));
|
||||
|
||||
return new SliceData.Builder()
|
||||
.setKey(key)
|
||||
.setTitle(title)
|
||||
.setSummary(summary)
|
||||
.setScreenTitle(screenTitle)
|
||||
.setIcon(iconResource)
|
||||
.setFragmentName(fragmentClassName)
|
||||
.setPreferenceControllerClassName(controllerClassName)
|
||||
.setUri(uri)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void verifyIndexing() {
|
||||
final long uidToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
FeatureFactory.getFactory(
|
||||
mContext).getSlicesFeatureProvider().indexSliceData(mContext);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(uidToken);
|
||||
}
|
||||
}
|
||||
}
|
@@ -104,7 +104,7 @@ public class SlicesDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public static synchronized SlicesDatabaseHelper getInstance(Context context) {
|
||||
if (sSingleton == null) {
|
||||
sSingleton = new SlicesDatabaseHelper(context);
|
||||
sSingleton = new SlicesDatabaseHelper(context.getApplicationContext());
|
||||
}
|
||||
return sSingleton;
|
||||
}
|
||||
|
@@ -13,5 +13,15 @@ public interface SlicesFeatureProvider {
|
||||
|
||||
SliceDataConverter getSliceDataConverter(Context context);
|
||||
|
||||
/**
|
||||
* Asynchronous call to index the data used to build Slices.
|
||||
* If the data is already indexed, the data will not change.
|
||||
*/
|
||||
void indexSliceDataAsync(Context context);
|
||||
|
||||
/**
|
||||
* Indexes the data used to build Slices.
|
||||
* If the data is already indexed, the data will not change.
|
||||
*/
|
||||
void indexSliceData(Context context);
|
||||
}
|
@@ -15,7 +15,7 @@ public class SlicesFeatureProviderImpl implements SlicesFeatureProvider {
|
||||
@Override
|
||||
public SlicesIndexer getSliceIndexer(Context context) {
|
||||
if (mSlicesIndexer == null) {
|
||||
mSlicesIndexer = new SlicesIndexer(context.getApplicationContext());
|
||||
mSlicesIndexer = new SlicesIndexer(context);
|
||||
}
|
||||
return mSlicesIndexer;
|
||||
}
|
||||
@@ -29,9 +29,14 @@ public class SlicesFeatureProviderImpl implements SlicesFeatureProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void indexSliceData(Context context) {
|
||||
// TODO (b/67996923) add indexing time log
|
||||
public void indexSliceDataAsync(Context context) {
|
||||
SlicesIndexer indexer = getSliceIndexer(context);
|
||||
ThreadUtils.postOnBackgroundThread(indexer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void indexSliceData(Context context) {
|
||||
SlicesIndexer indexer = getSliceIndexer(context);
|
||||
indexer.indexSliceData();
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
|
||||
@@ -36,7 +37,7 @@ import java.util.List;
|
||||
*/
|
||||
class SlicesIndexer implements Runnable {
|
||||
|
||||
private static final String TAG = "SlicesIndexingManager";
|
||||
private static final String TAG = "SlicesIndexer";
|
||||
|
||||
private Context mContext;
|
||||
|
||||
@@ -48,18 +49,27 @@ class SlicesIndexer implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously takes data obtained from {@link SliceDataConverter} and indexes it into a
|
||||
* SQLite database.
|
||||
* Asynchronously index slice data from {@link #indexSliceData()}.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
indexSliceData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously takes data obtained from {@link SliceDataConverter} and indexes it into a
|
||||
* SQLite database
|
||||
*/
|
||||
protected void indexSliceData() {
|
||||
if (mHelper.isSliceDataIndexed()) {
|
||||
Log.d(TAG, "Slices already indexed - returning.");
|
||||
return;
|
||||
}
|
||||
|
||||
SQLiteDatabase database = mHelper.getWritableDatabase();
|
||||
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
database.beginTransaction();
|
||||
|
||||
mHelper.reconstruct(mHelper.getWritableDatabase());
|
||||
@@ -67,6 +77,10 @@ class SlicesIndexer implements Runnable {
|
||||
insertSliceData(database, indexData);
|
||||
|
||||
mHelper.setIndexedState();
|
||||
|
||||
// TODO (b/71503044) Log indexing time.
|
||||
Log.d(TAG,
|
||||
"Indexing slices database took: " + (System.currentTimeMillis() - startTime));
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.provider.Settings;
|
||||
|
||||
import com.android.settings.core.TogglePreferenceController;
|
||||
|
||||
public class FakeToggleController extends TogglePreferenceController {
|
||||
|
||||
private String settingKey = "toggle_key";
|
||||
|
||||
private final int ON = 1;
|
||||
private final int OFF = 0;
|
||||
|
||||
public FakeToggleController(Context context, String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
return Settings.System.getInt(mContext.getContentResolver(),
|
||||
settingKey, OFF) == ON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setChecked(boolean isChecked) {
|
||||
return Settings.System.putInt(mContext.getContentResolver(), settingKey,
|
||||
isChecked ? ON : OFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.ContentResolver;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.android.settings.TestConfig;
|
||||
import com.android.settings.testutils.DatabaseTestUtils;
|
||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import androidx.app.slice.Slice;
|
||||
|
||||
@RunWith(SettingsRobolectricTestRunner.class)
|
||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||
public class SettingsSliceProviderTest {
|
||||
|
||||
private final String fakeTitle = "title";
|
||||
private final String KEY = "key";
|
||||
|
||||
private Context mContext;
|
||||
private SettingsSliceProvider mProvider;
|
||||
private SQLiteDatabase mDb;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
mProvider = spy(new SettingsSliceProvider());
|
||||
mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
||||
SlicesDatabaseHelper.getInstance(mContext).setIndexedState();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
DatabaseTestUtils.clearDb(mContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitialSliceReturned_emmptySlice() {
|
||||
Uri uri = SettingsSliceProvider.getUri(KEY);
|
||||
Slice slice = mProvider.onBindSlice(uri);
|
||||
|
||||
assertThat(slice.getUri()).isEqualTo(uri);
|
||||
assertThat(slice.getItems()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUriBuilder_returnsValidSliceUri() {
|
||||
Uri uri = SettingsSliceProvider.getUri(KEY);
|
||||
|
||||
assertThat(uri.getScheme()).isEqualTo(ContentResolver.SCHEME_CONTENT);
|
||||
assertThat(uri.getAuthority()).isEqualTo(SettingsSliceProvider.SLICE_AUTHORITY);
|
||||
assertThat(uri.getLastPathSegment()).isEqualTo(KEY);
|
||||
}
|
||||
|
||||
private void insertSpecialCase(String key) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.KEY, key);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.TITLE, fakeTitle);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.SUMMARY, "s");
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.SCREENTITLE, "s");
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.ICON_RESOURCE, 1234);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.FRAGMENT, "test");
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.CONTROLLER, "test");
|
||||
|
||||
mDb.replaceOrThrow(SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX, null, values);
|
||||
}
|
||||
}
|
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import com.android.settings.TestConfig;
|
||||
import com.android.settings.search.FakeIndexProvider;
|
||||
import com.android.settings.search.SearchIndexableResources;
|
||||
import com.android.settings.testutils.DatabaseTestUtils;
|
||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@RunWith(SettingsRobolectricTestRunner.class)
|
||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||
public class SliceBroadcastReceiverTest {
|
||||
|
||||
private final String fakeTitle = "title";
|
||||
private final String fakeSummary = "summary";
|
||||
private final String fakeScreenTitle = "screen_title";
|
||||
private final int fakeIcon = 1234;
|
||||
private final String fakeFragmentClassName = FakeIndexProvider.class.getName();
|
||||
private final String fakeControllerName = FakeToggleController.class.getName();
|
||||
|
||||
private Context mContext;
|
||||
private SQLiteDatabase mDb;
|
||||
private SliceBroadcastReceiver mReceiver;
|
||||
|
||||
private Set<Class> mProviderClassesCopy;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
||||
mReceiver = new SliceBroadcastReceiver();
|
||||
mProviderClassesCopy = new HashSet<>(SearchIndexableResources.providerValues());
|
||||
SlicesDatabaseHelper helper = SlicesDatabaseHelper.getInstance(mContext);
|
||||
helper.setIndexedState();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
DatabaseTestUtils.clearDb(mContext);
|
||||
SearchIndexableResources.providerValues().clear();
|
||||
SearchIndexableResources.providerValues().addAll(mProviderClassesCopy);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnReceive_toggleChanged() {
|
||||
String key = "key";
|
||||
SearchIndexableResources.providerValues().clear();
|
||||
insertSpecialCase(key);
|
||||
// Turn on toggle setting
|
||||
FakeToggleController fakeToggleController = new FakeToggleController(mContext, key);
|
||||
fakeToggleController.setChecked(true);
|
||||
Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED);
|
||||
intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key);
|
||||
|
||||
assertThat(fakeToggleController.isChecked()).isTrue();
|
||||
|
||||
// Toggle setting
|
||||
mReceiver.onReceive(mContext, intent);
|
||||
|
||||
assertThat(fakeToggleController.isChecked()).isFalse();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testOnReceive_noExtra_illegalSatetException() {
|
||||
Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED);
|
||||
mReceiver.onReceive(mContext, intent);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testOnReceive_emptyKey_throwsIllegalStateException() {
|
||||
Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED);
|
||||
intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, (String) null);
|
||||
mReceiver.onReceive(mContext, intent);
|
||||
}
|
||||
|
||||
private void insertSpecialCase(String key) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.KEY, key);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.TITLE, fakeTitle);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.SUMMARY, fakeSummary);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.SCREENTITLE, fakeScreenTitle);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.ICON_RESOURCE, fakeIcon);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.FRAGMENT, fakeFragmentClassName);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.CONTROLLER, fakeControllerName);
|
||||
|
||||
mDb.replaceOrThrow(SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX, null, values);
|
||||
}
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.android.settings.TestConfig;
|
||||
import com.android.settings.search.FakeIndexProvider;
|
||||
import com.android.settings.testutils.DatabaseTestUtils;
|
||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(SettingsRobolectricTestRunner.class)
|
||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||
public class SlicesDatabaseAccessorTest {
|
||||
|
||||
private final String fakeTitle = "title";
|
||||
private final String fakeSummary = "summary";
|
||||
private final String fakeScreenTitle = "screen_title";
|
||||
private final int fakeIcon = 1234;
|
||||
private final String fakeFragmentClassName = FakeIndexProvider.class.getName();
|
||||
private final String fakeControllerName = FakePreferenceController.class.getName();
|
||||
|
||||
private Context mContext;
|
||||
private SQLiteDatabase mDb;
|
||||
private SlicesDatabaseAccessor mAccessor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mAccessor = spy(new SlicesDatabaseAccessor(mContext));
|
||||
mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
||||
SlicesDatabaseHelper.getInstance(mContext).setIndexedState();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
DatabaseTestUtils.clearDb(mContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSliceDataFromKey_validKey_validSliceReturned() {
|
||||
String key = "key";
|
||||
insertSpecialCase(key);
|
||||
|
||||
SliceData data = mAccessor.getSliceDataFromKey(key);
|
||||
|
||||
assertThat(data.getKey()).isEqualTo(key);
|
||||
assertThat(data.getTitle()).isEqualTo(fakeTitle);
|
||||
assertThat(data.getSummary()).isEqualTo(fakeSummary);
|
||||
assertThat(data.getScreenTitle()).isEqualTo(fakeScreenTitle);
|
||||
assertThat(data.getIconResource()).isEqualTo(fakeIcon);
|
||||
assertThat(data.getFragmentClassName()).isEqualTo(fakeFragmentClassName);
|
||||
assertThat(data.getUri()).isNull();
|
||||
assertThat(data.getPreferenceController()).isEqualTo(fakeControllerName);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testGetSliceDataFromKey_invalidKey_errorThrown() {
|
||||
String key = "key";
|
||||
|
||||
mAccessor.getSliceDataFromKey(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSliceFromUri_validUri_validSliceReturned() {
|
||||
String key = "key";
|
||||
insertSpecialCase(key);
|
||||
Uri uri = SettingsSliceProvider.getUri(key);
|
||||
|
||||
SliceData data = mAccessor.getSliceDataFromUri(uri);
|
||||
|
||||
assertThat(data.getKey()).isEqualTo(key);
|
||||
assertThat(data.getTitle()).isEqualTo(fakeTitle);
|
||||
assertThat(data.getSummary()).isEqualTo(fakeSummary);
|
||||
assertThat(data.getScreenTitle()).isEqualTo(fakeScreenTitle);
|
||||
assertThat(data.getIconResource()).isEqualTo(fakeIcon);
|
||||
assertThat(data.getFragmentClassName()).isEqualTo(fakeFragmentClassName);
|
||||
assertThat(data.getUri()).isEqualTo(uri);
|
||||
assertThat(data.getPreferenceController()).isEqualTo(fakeControllerName);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testGetSliceFromUri_invalidUri_errorThrown() {
|
||||
Uri uri = SettingsSliceProvider.getUri("durr");
|
||||
|
||||
mAccessor.getSliceDataFromUri(uri);
|
||||
}
|
||||
|
||||
private void insertSpecialCase(String key) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.KEY, key);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.TITLE, fakeTitle);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.SUMMARY, fakeSummary);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.SCREENTITLE, fakeScreenTitle);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.ICON_RESOURCE, fakeIcon);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.FRAGMENT, fakeFragmentClassName);
|
||||
values.put(SlicesDatabaseHelper.IndexColumns.CONTROLLER, fakeControllerName);
|
||||
|
||||
mDb.replaceOrThrow(SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX, null, values);
|
||||
}
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 static com.android.settings.TestConfig.SDK_VERSION;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.android.settings.TestConfig;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import androidx.app.slice.Slice;
|
||||
|
||||
@RunWith(SettingsRobolectricTestRunner.class)
|
||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = SDK_VERSION)
|
||||
public class SlicesDatabaseUtilsTest {
|
||||
|
||||
private final String KEY = "KEY";
|
||||
private final String TITLE = "title";
|
||||
private final String SUMMARY = "summary";
|
||||
private final String SCREEN_TITLE = "screen title";
|
||||
private final String FRAGMENT_NAME = "fragment name";
|
||||
private final int ICON = 1234; // I declare a thumb war
|
||||
private final Uri URI = Uri.parse("content://com.android.settings.slices/test");
|
||||
private final String PREF_CONTROLLER = FakeToggleController.class.getName();
|
||||
;
|
||||
|
||||
private Context mContext;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildSlice_returnsMatchingSlice() {
|
||||
Slice slice = SliceBuilderUtils.buildSlice(mContext, getDummyData());
|
||||
|
||||
assertThat(slice).isNotNull(); // TODO improve test for Slice content
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPreferenceController_buildsMatchingController() {
|
||||
BasePreferenceController controller = SliceBuilderUtils.getPreferenceController(mContext,
|
||||
getDummyData());
|
||||
|
||||
assertThat(controller).isInstanceOf(FakeToggleController.class);
|
||||
}
|
||||
|
||||
private SliceData getDummyData() {
|
||||
return new SliceData.Builder()
|
||||
.setKey(KEY)
|
||||
.setTitle(TITLE)
|
||||
.setSummary(SUMMARY)
|
||||
.setScreenTitle(SCREEN_TITLE)
|
||||
.setIcon(ICON)
|
||||
.setFragmentName(FRAGMENT_NAME)
|
||||
.setUri(URI)
|
||||
.setPreferenceControllerClassName(PREF_CONTROLLER)
|
||||
.build();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user