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
diff --git a/src/com/android/settings/search/SearchFeatureProvider.java b/src/com/android/settings/search/SearchFeatureProvider.java
index 437fc86..b9d5045 100644
--- a/src/com/android/settings/search/SearchFeatureProvider.java
+++ b/src/com/android/settings/search/SearchFeatureProvider.java
@@ -28,6 +28,7 @@
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 @@
} else {
intent = new Intent(activity, SearchActivity.class);
}
+ FeatureFactory.getFactory(
+ activity.getApplicationContext()).getSlicesFeatureProvider()
+ .indexSliceDataAsync(activity.getApplicationContext());
activity.startActivityForResult(intent, 0 /* requestCode */);
});
}
diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java
index 08ea7c6..7136182 100644
--- a/src/com/android/settings/slices/SettingsSliceProvider.java
+++ b/src/com/android/settings/slices/SettingsSliceProvider.java
@@ -24,6 +24,7 @@
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.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 @@
@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) {
diff --git a/src/com/android/settings/slices/SliceBroadcastReceiver.java b/src/com/android/settings/slices/SliceBroadcastReceiver.java
index b6f2ab9..970b72a 100644
--- a/src/com/android/settings/slices/SliceBroadcastReceiver.java
+++ b/src/com/android/settings/slices/SliceBroadcastReceiver.java
@@ -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.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 @@
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);
+ }
}
diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java
new file mode 100644
index 0000000..3663e89
--- /dev/null
+++ b/src/com/android/settings/slices/SliceBuilderUtils.java
@@ -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 */);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/slices/SliceData.java b/src/com/android/settings/slices/SliceData.java
index f83676a..f72add7 100644
--- a/src/com/android/settings/slices/SliceData.java
+++ b/src/com/android/settings/slices/SliceData.java
@@ -18,7 +18,6 @@
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 @@
return mKey;
}
}
-
}
\ No newline at end of file
diff --git a/src/com/android/settings/slices/SlicesDatabaseAccessor.java b/src/com/android/settings/slices/SlicesDatabaseAccessor.java
new file mode 100644
index 0000000..4fca63a
--- /dev/null
+++ b/src/com/android/settings/slices/SlicesDatabaseAccessor.java
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/slices/SlicesDatabaseHelper.java b/src/com/android/settings/slices/SlicesDatabaseHelper.java
index 18f8cc9..448d8f1 100644
--- a/src/com/android/settings/slices/SlicesDatabaseHelper.java
+++ b/src/com/android/settings/slices/SlicesDatabaseHelper.java
@@ -104,7 +104,7 @@
public static synchronized SlicesDatabaseHelper getInstance(Context context) {
if (sSingleton == null) {
- sSingleton = new SlicesDatabaseHelper(context);
+ sSingleton = new SlicesDatabaseHelper(context.getApplicationContext());
}
return sSingleton;
}
diff --git a/src/com/android/settings/slices/SlicesFeatureProvider.java b/src/com/android/settings/slices/SlicesFeatureProvider.java
index cbf1b75..e5bba61 100644
--- a/src/com/android/settings/slices/SlicesFeatureProvider.java
+++ b/src/com/android/settings/slices/SlicesFeatureProvider.java
@@ -13,5 +13,15 @@
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);
}
\ No newline at end of file
diff --git a/src/com/android/settings/slices/SlicesFeatureProviderImpl.java b/src/com/android/settings/slices/SlicesFeatureProviderImpl.java
index 34ef884..8e5bc06 100644
--- a/src/com/android/settings/slices/SlicesFeatureProviderImpl.java
+++ b/src/com/android/settings/slices/SlicesFeatureProviderImpl.java
@@ -15,7 +15,7 @@
@Override
public SlicesIndexer getSliceIndexer(Context context) {
if (mSlicesIndexer == null) {
- mSlicesIndexer = new SlicesIndexer(context.getApplicationContext());
+ mSlicesIndexer = new SlicesIndexer(context);
}
return mSlicesIndexer;
}
@@ -29,9 +29,14 @@
}
@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();
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/slices/SlicesIndexer.java b/src/com/android/settings/slices/SlicesIndexer.java
index 0297f3f..a92388a 100644
--- a/src/com/android/settings/slices/SlicesIndexer.java
+++ b/src/com/android/settings/slices/SlicesIndexer.java
@@ -20,6 +20,7 @@
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 @@
*/
class SlicesIndexer implements Runnable {
- private static final String TAG = "SlicesIndexingManager";
+ private static final String TAG = "SlicesIndexer";
private Context mContext;
@@ -48,18 +49,27 @@
}
/**
- * 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 @@
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();
diff --git a/tests/robotests/src/com/android/settings/slices/FakeToggleController.java b/tests/robotests/src/com/android/settings/slices/FakeToggleController.java
new file mode 100644
index 0000000..1b08e35
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/slices/FakeToggleController.java
@@ -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;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
new file mode 100644
index 0000000..2af15e2
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java
new file mode 100644
index 0000000..efd1cc5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/slices/SlicesDatabaseAccessorTest.java b/tests/robotests/src/com/android/settings/slices/SlicesDatabaseAccessorTest.java
new file mode 100644
index 0000000..106e6fe
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/slices/SlicesDatabaseAccessorTest.java
@@ -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);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/slices/SlicesDatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SlicesDatabaseUtilsTest.java
new file mode 100644
index 0000000..f22e85f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/slices/SlicesDatabaseUtilsTest.java
@@ -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();
+ }
+}