Add Slices Data object and DB Contract

Add in a Data object used to represent one row
in a new SQLite database used for building Slices.

The database has the following schema:
- Key
- Title
- Subtitle
- Screentitle
- Icon
- Fragment
- Controller

The key is the preference key.
Title, subtitle and Icon are for UI info.
Screentitle and fragment are for the intent.
Controller is used to get dynamic ui info (like summary),
and to take actions on the slice (like a toggle).

The actual indexing and a Slice will be handled in a subsquent CL,
but a prototype can be found here: ag/3324435

Test: robotests
Bug: 67996923
Change-Id: Id91deb58a3ab89ce1dab5a3f34cdb9ade6263aa8
This commit is contained in:
Matthew Fritze
2017-12-11 15:01:11 -08:00
parent 84e8c795b1
commit 7fddfebf6c
9 changed files with 701 additions and 44 deletions

View File

@@ -0,0 +1,112 @@
/*
* 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 android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.net.wifi.WifiManager;
import com.android.settings.R;
import androidx.app.slice.Slice;
import androidx.app.slice.SliceProvider;
import androidx.app.slice.builders.ListBuilder;
public class SettingsSliceProvider extends SliceProvider {
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";
// TODO -- Associate slice URI with search result instead of separate hardcoded thing
public static Uri getUri(String path) {
return new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(SLICE_AUTHORITY)
.appendPath(path).build();
}
@Override
public boolean onCreateSliceProvider() {
return true;
}
@Override
public Slice onBindSlice(Uri sliceUri) {
String path = sliceUri.getPath();
switch (path) {
case "/" + PATH_WIFI:
return createWifiSlice(sliceUri);
}
throw new IllegalArgumentException("Unrecognized slice uri: " + sliceUri);
}
// TODO (b/70622039) remove this when the proper wifi slice is enabled.
private Slice createWifiSlice(Uri sliceUri) {
// Get wifi state
WifiManager wifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
int wifiState = wifiManager.getWifiState();
boolean wifiEnabled = false;
String state;
switch (wifiState) {
case WifiManager.WIFI_STATE_DISABLED:
case WifiManager.WIFI_STATE_DISABLING:
state = getContext().getString(R.string.disconnected);
break;
case WifiManager.WIFI_STATE_ENABLED:
case WifiManager.WIFI_STATE_ENABLING:
state = wifiManager.getConnectionInfo().getSSID();
wifiEnabled = true;
break;
case WifiManager.WIFI_STATE_UNKNOWN:
default:
state = ""; // just don't show anything?
break;
}
boolean finalWifiEnabled = wifiEnabled;
return new ListBuilder(sliceUri)
.setColor(R.color.material_blue_500)
.add(b -> b
.setTitle(getContext().getString(R.string.wifi_settings))
.setTitleItem(Icon.createWithResource(getContext(), R.drawable.wifi_signal))
.setSubtitle(state)
.addToggle(getBroadcastIntent(ACTION_WIFI_CHANGED), finalWifiEnabled)
.setContentIntent(getIntent(Intent.ACTION_MAIN)))
.build();
}
private PendingIntent getIntent(String action) {
Intent intent = new Intent(action);
PendingIntent pi = PendingIntent.getActivity(getContext(), 0, intent, 0);
return pi;
}
private PendingIntent getBroadcastIntent(String action) {
Intent intent = new Intent(action);
intent.setClass(getContext(), SliceBroadcastReceiver.class);
return PendingIntent.getBroadcast(getContext(), 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.ACTION_WIFI_CHANGED;
import android.app.slice.Slice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.Handler;
/**
* Responds to actions performed on slices and notifies slices of updates in state changes.
*/
public class SliceBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent i) {
String action = i.getAction();
switch (action) {
case ACTION_WIFI_CHANGED:
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
boolean newState = i.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();
h.postDelayed(() -> {
Uri uri = SettingsSliceProvider.getUri(SettingsSliceProvider.PATH_WIFI);
context.getContentResolver().notifyChange(uri, null);
}, 1000);
break;
}
}
}

View File

@@ -0,0 +1,188 @@
/*
* 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 android.net.Uri;
import android.text.TextUtils;
/**
* TODO (b/67996923) Add SlicesIndexingManager
* Data class representing a slice stored by {@link SlicesIndexingManager}.
* Note that {@link #key} is treated as a primary key for this class and determines equality.
*/
public class SliceData {
private final String key;
private final String title;
private final String summary;
private final String screenTitle;
private final int iconResource;
private final String fragmentClassName;
private final Uri uri;
private final String preferenceController;
public String getKey() {
return key;
}
public String getTitle() {
return title;
}
public String getSummary() {
return summary;
}
public String getScreenTitle() {
return screenTitle;
}
public int getIconResource() {
return iconResource;
}
public String getFragmentClassName() {
return fragmentClassName;
}
public Uri getUri() {
return uri;
}
public String getPreferenceController() {
return preferenceController;
}
private SliceData(Builder builder) {
key = builder.mKey;
title = builder.mTitle;
summary = builder.mSummary;
screenTitle = builder.mScreenTitle;
iconResource = builder.mIconResource;
fragmentClassName = builder.mFragmentClassName;
uri = builder.mUri;
preferenceController = builder.mPrefControllerClassName;
}
@Override
public int hashCode() {
return key.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof SliceData)) {
return false;
}
SliceData newObject = (SliceData) obj;
return TextUtils.equals(key, newObject.key);
}
static class Builder {
private String mKey;
private String mTitle;
private String mSummary;
private String mScreenTitle;
private int mIconResource;
private String mFragmentClassName;
private Uri mUri;
private String mPrefControllerClassName;
public Builder setKey(String key) {
mKey = key;
return this;
}
public Builder setTitle(String title) {
mTitle = title;
return this;
}
public Builder setSummary(String summary) {
mSummary = summary;
return this;
}
public Builder setScreenTitle(String screenTitle) {
mScreenTitle = screenTitle;
return this;
}
public Builder setIcon(int iconResource) {
mIconResource = iconResource;
return this;
}
public Builder setPreferenceControllerClassName(String controllerClassName) {
mPrefControllerClassName = controllerClassName;
return this;
}
public Builder setFragmentName(String fragmentClassName) {
mFragmentClassName = fragmentClassName;
return this;
}
public Builder setUri(Uri uri) {
mUri = uri;
return this;
}
public SliceData build() {
if (TextUtils.isEmpty(mKey)) {
throw new IllegalStateException("Key cannot be empty");
}
if (TextUtils.isEmpty(mTitle)) {
throw new IllegalStateException("Title cannot be empty");
}
if (TextUtils.isEmpty(mFragmentClassName)) {
throw new IllegalStateException("Fragment Name cannot be empty");
}
if (TextUtils.isEmpty(mPrefControllerClassName)) {
throw new IllegalStateException("Preference Controller cannot be empty");
}
if (mUri == null) {
throw new IllegalStateException("Uri cannot be null");
}
return new SliceData(this);
}
public String getKey() {
return mKey;
}
}
}

View File

@@ -0,0 +1,122 @@
package com.android.settings.slices;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
/**
* Defines the schema for the Slices database.
*/
public class SlicesDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "SlicesDatabaseHelper";
private static final String DATABASE_NAME = "slices_index.db";
private static final String SHARED_PREFS_TAG = "slices_shared_prefs";
private static final int DATABASE_VERSION = 1;
public interface Tables {
String TABLE_SLICES_INDEX = "slices_index";
}
public interface IndexColumns {
/**
* Primary key of the DB. Preference key from preference controllers.
*/
String KEY = "key";
/**
* Title of the Setting.
*/
String TITLE = "title";
/**
* Summary / Subtitle for the setting.
*/
String SUBTITLE = "subtitle";
/**
* Title of the Setting screen on which the Setting lives.
*/
String SCREENTITLE = "screentitle";
/**
* Resource ID for the icon of the setting. Should be 0 for no icon.
*/
String ICON_RESOURCE = "icon";
/**
* Classname of the fragment name of the page that hosts the setting.
*/
String FRAGMENT = "fragment";
/**
* Class name of the controller backing the setting. Must be a
* {@link com.android.settings.core.BasePreferenceController}.
*/
String CONTROLLER = "controller";
}
private static final String CREATE_SLICES_TABLE =
"CREATE VIRTUAL TABLE " + Tables.TABLE_SLICES_INDEX + " USING fts4" +
"(" +
IndexColumns.KEY +
", " +
IndexColumns.TITLE +
", " +
IndexColumns.SUBTITLE +
", " +
IndexColumns.SCREENTITLE +
", " +
IndexColumns.ICON_RESOURCE +
", " +
IndexColumns.FRAGMENT +
", " +
IndexColumns.CONTROLLER +
");";
private final Context mContext;
public SlicesDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null /* CursorFactor */, DATABASE_VERSION);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
createDatabases(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < DATABASE_VERSION) {
Log.d(TAG, "Reconstructing DB from " + oldVersion + "to " + newVersion);
reconstruct(db);
}
}
@VisibleForTesting
void reconstruct(SQLiteDatabase db) {
mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
.edit()
.clear()
.commit();
dropTables(db);
createDatabases(db);
}
private void createDatabases(SQLiteDatabase db) {
db.execSQL(CREATE_SLICES_TABLE);
Log.d(TAG, "Created databases");
}
private void dropTables(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SLICES_INDEX);
}
}