Initial API for providing suggestions.
This is a WIP system api that will be
- Implemented by one unbundled app (SettingsIntelligence) using system-sdk,
- and consumed by Settings app.
Note: The bundled app does not have permission to read/write any
user settings. Nor does it have permission to call any private API
to do anything unsafe.
Test: builds
Test: instrumentation test
Bug: 65065268
Change-Id: Ib190c0e4c167deb3c6197b8a5b39d442d804770b
This commit is contained in:
@@ -305,6 +305,7 @@ LOCAL_SRC_FILES += \
|
||||
core/java/android/service/notification/IStatusBarNotificationHolder.aidl \
|
||||
core/java/android/service/notification/IConditionListener.aidl \
|
||||
core/java/android/service/notification/IConditionProvider.aidl \
|
||||
core/java/android/service/settings/suggestions/ISuggestionService.aidl \
|
||||
core/java/android/service/vr/IPersistentVrStateCallbacks.aidl \
|
||||
core/java/android/service/vr/IVrListener.aidl \
|
||||
core/java/android/service/vr/IVrManager.aidl \
|
||||
|
||||
@@ -57,6 +57,7 @@ package android {
|
||||
field public static final java.lang.String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
|
||||
field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE";
|
||||
field public static final java.lang.String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
|
||||
field public static final java.lang.String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE";
|
||||
field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
|
||||
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
|
||||
field public static final java.lang.String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
|
||||
@@ -40819,6 +40820,35 @@ package android.service.restrictions {
|
||||
|
||||
}
|
||||
|
||||
package android.service.settings.suggestions {
|
||||
|
||||
public final class Suggestion implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public java.lang.String getId();
|
||||
method public android.app.PendingIntent getPendingIntent();
|
||||
method public java.lang.CharSequence getSummary();
|
||||
method public java.lang.CharSequence getTitle();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator<android.service.settings.suggestions.Suggestion> CREATOR;
|
||||
}
|
||||
|
||||
public static class Suggestion.Builder {
|
||||
ctor public Suggestion.Builder(java.lang.String);
|
||||
method public android.service.settings.suggestions.Suggestion build();
|
||||
method public android.service.settings.suggestions.Suggestion.Builder setPendingIntent(android.app.PendingIntent);
|
||||
method public android.service.settings.suggestions.Suggestion.Builder setSummary(java.lang.CharSequence);
|
||||
method public android.service.settings.suggestions.Suggestion.Builder setTitle(java.lang.CharSequence);
|
||||
}
|
||||
|
||||
public abstract class SuggestionService extends android.app.Service {
|
||||
ctor public SuggestionService();
|
||||
method public android.os.IBinder onBind(android.content.Intent);
|
||||
method public abstract java.util.List<android.service.settings.suggestions.Suggestion> onGetSuggestions();
|
||||
method public abstract void onSuggestionDismissed(android.service.settings.suggestions.Suggestion);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.service.textservice {
|
||||
|
||||
public abstract class SpellCheckerService extends android.app.Service {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package android.service.settings.suggestions;
|
||||
|
||||
import android.service.settings.suggestions.Suggestion;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** @hide */
|
||||
interface ISuggestionService {
|
||||
|
||||
/**
|
||||
* Return all available suggestions.
|
||||
*/
|
||||
List<Suggestion> getSuggestions() = 1;
|
||||
|
||||
/**
|
||||
* Dismiss a suggestion. The suggestion will not be included in future {@link #getSuggestions)
|
||||
* calls.
|
||||
*/
|
||||
void dismissSuggestion(in Suggestion suggestion) = 2;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** @hide */
|
||||
package android.service.settings.suggestions;
|
||||
|
||||
parcelable Suggestion;
|
||||
152
core/java/android/service/settings/suggestions/Suggestion.java
Normal file
152
core/java/android/service/settings/suggestions/Suggestion.java
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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 android.service.settings.suggestions;
|
||||
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* Data object that has information about a device suggestion.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class Suggestion implements Parcelable {
|
||||
|
||||
private final String mId;
|
||||
private final CharSequence mTitle;
|
||||
private final CharSequence mSummary;
|
||||
private final PendingIntent mPendingIntent;
|
||||
|
||||
/**
|
||||
* Gets the id for the suggestion object.
|
||||
*/
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Title of the suggestion that is shown to the user.
|
||||
*/
|
||||
public CharSequence getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional summary describing what this suggestion controls.
|
||||
*/
|
||||
public CharSequence getSummary() {
|
||||
return mSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Intent to launch when the suggestion is activated.
|
||||
*/
|
||||
public PendingIntent getPendingIntent() {
|
||||
return mPendingIntent;
|
||||
}
|
||||
|
||||
private Suggestion(Builder builder) {
|
||||
mId = builder.mId;
|
||||
mTitle = builder.mTitle;
|
||||
mSummary = builder.mSummary;
|
||||
mPendingIntent = builder.mPendingIntent;
|
||||
}
|
||||
|
||||
private Suggestion(Parcel in) {
|
||||
mId = in.readString();
|
||||
mTitle = in.readCharSequence();
|
||||
mSummary = in.readCharSequence();
|
||||
mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
|
||||
}
|
||||
|
||||
public static final Creator<Suggestion> CREATOR = new Creator<Suggestion>() {
|
||||
@Override
|
||||
public Suggestion createFromParcel(Parcel in) {
|
||||
return new Suggestion(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Suggestion[] newArray(int size) {
|
||||
return new Suggestion[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mId);
|
||||
dest.writeCharSequence(mTitle);
|
||||
dest.writeCharSequence(mSummary);
|
||||
dest.writeParcelable(mPendingIntent, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for {@link Suggestion}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private final String mId;
|
||||
private CharSequence mTitle;
|
||||
private CharSequence mSummary;
|
||||
private PendingIntent mPendingIntent;
|
||||
|
||||
public Builder(String id) {
|
||||
if (TextUtils.isEmpty(id)) {
|
||||
throw new IllegalArgumentException("Suggestion id cannot be empty");
|
||||
}
|
||||
mId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets suggestion title
|
||||
*/
|
||||
public Builder setTitle(CharSequence title) {
|
||||
mTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets suggestion summary
|
||||
*/
|
||||
public Builder setSummary(CharSequence summary) {
|
||||
mSummary = summary;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets suggestion intent
|
||||
*/
|
||||
public Builder setPendingIntent(PendingIntent pendingIntent) {
|
||||
mPendingIntent = pendingIntent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an immutable {@link Suggestion} object.
|
||||
*/
|
||||
public Suggestion build() {
|
||||
return new Suggestion(this /* builder */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 android.service.settings.suggestions;
|
||||
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is the base class for implementing suggestion service. A suggestion service is responsible
|
||||
* to provide a collection of {@link Suggestion}s for the current user when queried.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public abstract class SuggestionService extends Service {
|
||||
|
||||
private static final String TAG = "SuggestionService";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return new ISuggestionService.Stub() {
|
||||
@Override
|
||||
public List<Suggestion> getSuggestions() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "getSuggestions() " + getPackageName());
|
||||
}
|
||||
return onGetSuggestions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismissSuggestion(Suggestion suggestion) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "dismissSuggestion() " + getPackageName());
|
||||
}
|
||||
onSuggestionDismissed(suggestion);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all available suggestions.
|
||||
*/
|
||||
public abstract List<Suggestion> onGetSuggestions();
|
||||
|
||||
/**
|
||||
* Dismiss a suggestion. The suggestion will not be included in future
|
||||
* {@link #onGetSuggestions()} calls.
|
||||
* @param suggestion
|
||||
*/
|
||||
public abstract void onSuggestionDismissed(Suggestion suggestion);
|
||||
}
|
||||
@@ -3144,6 +3144,11 @@
|
||||
<permission android:name="android.permission.READ_SEARCH_INDEXABLES"
|
||||
android:protectionLevel="signature|privileged" />
|
||||
|
||||
<!-- @SystemApi Internal permission to allows an application to bind to suggestion service.
|
||||
@hide -->
|
||||
<permission android:name="android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<!-- @SystemApi Allows applications to set a live wallpaper.
|
||||
@hide XXX Change to signature once the picker is moved to its
|
||||
own apk as Ghod Intended. -->
|
||||
|
||||
@@ -1264,6 +1264,11 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="android.service.settings.suggestions.MockSuggestionService"
|
||||
android:permission="android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"
|
||||
android:exported="true"/>
|
||||
|
||||
<provider android:name="android.app.activity.LocalProvider"
|
||||
android:authorities="com.android.frameworks.coretests.LocalProvider">
|
||||
<meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 android.service.settings.suggestions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MockSuggestionService extends SuggestionService {
|
||||
|
||||
@Override
|
||||
public List<Suggestion> onGetSuggestions() {
|
||||
final List<Suggestion> data = new ArrayList<>();
|
||||
|
||||
data.add(new Suggestion.Builder("test")
|
||||
.setTitle("title")
|
||||
.setSummary("summary")
|
||||
.build());
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuggestionDismissed(Suggestion suggestion) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 android.service.settings.suggestions;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.rule.ServiceTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class SuggestionServiceTest {
|
||||
|
||||
@Rule
|
||||
public ServiceTestRule mServiceTestRule;
|
||||
private Intent mMockServiceIntent;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mServiceTestRule = new ServiceTestRule();
|
||||
mMockServiceIntent = new Intent(
|
||||
InstrumentationRegistry.getTargetContext(),
|
||||
MockSuggestionService.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canStartService() throws TimeoutException {
|
||||
mServiceTestRule.startService(mMockServiceIntent);
|
||||
// Do nothing after starting service.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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 android.service.settings.suggestions;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class SuggestionTest {
|
||||
private static final String TEST_ID = "id";
|
||||
private static final String TEST_TITLE = "title";
|
||||
private static final String TEST_SUMMARY = "summary";
|
||||
private PendingIntent mTestIntent;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
final Context context = InstrumentationRegistry.getContext();
|
||||
mTestIntent = PendingIntent.getActivity(context, 0 /* requestCode */,
|
||||
new Intent(), 0 /* flags */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildSuggestion_allFieldsShouldBeSet() {
|
||||
final Suggestion suggestion = new Suggestion.Builder(TEST_ID)
|
||||
.setTitle(TEST_TITLE)
|
||||
.setSummary(TEST_SUMMARY)
|
||||
.setPendingIntent(mTestIntent)
|
||||
.build();
|
||||
|
||||
assertThat(suggestion.getId()).isEqualTo(TEST_ID);
|
||||
assertThat(suggestion.getTitle()).isEqualTo(TEST_TITLE);
|
||||
assertThat(suggestion.getSummary()).isEqualTo(TEST_SUMMARY);
|
||||
assertThat(suggestion.getPendingIntent()).isEqualTo(mTestIntent);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void buildSuggestion_emptyKey_shouldCrash() {
|
||||
new Suggestion.Builder(null)
|
||||
.setTitle(TEST_TITLE)
|
||||
.setSummary(TEST_SUMMARY)
|
||||
.setPendingIntent(mTestIntent)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildSuggestion_fromParcelable() {
|
||||
final Parcel parcel = Parcel.obtain();
|
||||
final Suggestion oldSuggestion = new Suggestion.Builder(TEST_ID)
|
||||
.setTitle(TEST_TITLE)
|
||||
.setSummary(TEST_SUMMARY)
|
||||
.setPendingIntent(mTestIntent)
|
||||
.build();
|
||||
|
||||
oldSuggestion.writeToParcel(parcel, 0 /* flags */);
|
||||
parcel.setDataPosition(0);
|
||||
final Suggestion newSuggestion = Suggestion.CREATOR.createFromParcel(parcel);
|
||||
|
||||
assertThat(newSuggestion.getId()).isEqualTo(TEST_ID);
|
||||
assertThat(newSuggestion.getTitle()).isEqualTo(TEST_TITLE);
|
||||
assertThat(newSuggestion.getSummary()).isEqualTo(TEST_SUMMARY);
|
||||
assertThat(newSuggestion.getPendingIntent()).isEqualTo(mTestIntent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user