Merge "Add activities to support voice settings."

This commit is contained in:
Barnaby James
2015-04-14 00:14:25 +00:00
committed by Android (Google) Code Review
13 changed files with 808 additions and 13 deletions

View File

@@ -158,6 +158,17 @@
android:value="true" />
</activity>
<activity android:name="AirplaneModeVoiceActivity"
android:label="@string/wireless_networks_settings_title"
android:theme="@android:style/Theme.Material.Light.Voice"
android:exported="true">
<intent-filter>
<action android:name="android.settings.VOICE_CONTROL_AIRPLANE_MODE" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.VOICE" />
</intent-filter>
</activity>
<!-- Top-level settings -->
<activity android:name="Settings$WifiSettingsActivity"
@@ -996,6 +1007,16 @@
android:value="com.android.settings.notification.NotificationStation" />
</activity>
<activity android:name=".notification.ZenModeVoiceActivity"
android:theme="@android:style/Theme.Material.Light"
android:label="@string/zen_mode_settings_title">
<intent-filter>
<action android:name="android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.VOICE" />
</intent-filter>
</activity>
<!--
<activity android:name="Settings$AppOpsSummaryActivity"
android:label="@string/app_ops_settings"
@@ -1874,6 +1895,17 @@
android:value="true" />
</activity>
<activity android:name=".fuelguage.BatterySaverModeVoiceActivity"
android:label="@string/power_usage_summary_title"
android:theme="@android:style/Theme.Material.Light.Voice"
android:exported="true">
<intent-filter>
<action android:name="android.settings.VOICE_CONTROL_BATTERY_SAVER_MODE" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.VOICE" />
</intent-filter>
</activity>
<activity android:name="Settings$AccountSettingsActivity"
android:label="@string/account_settings_title"
android:taskAffinity=""

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/blue" />
</shape>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_root"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/row_one"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp">
<TextView
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/voice_item_label" />
<TextView
android:layout_width="100px"
android:layout_height="100px"
android:gravity="center_horizontal|center_vertical"
android:background="@drawable/bg_circle_blue"
android:textAppearance="?android:attr/textAppearanceMediumInverse"
android:textStyle="bold"
android:id="@+id/voice_item_position" />
</LinearLayout>
</LinearLayout>

View File

@@ -17,6 +17,7 @@
<resources>
<color name="black">#000</color>
<color name="red">#F00</color>
<color name="blue">#00F</color>
<color name="material_empty_color_light">#FFCED7DB</color>

View File

@@ -5991,6 +5991,93 @@
<!-- [CHAR LIMIT=60] Zen mode settings: End time option: Summary text value format when end time = next day -->
<string name="zen_mode_end_time_next_day_summary_format"><xliff:g id="formatted_time">%s</xliff:g> next day</string>
<!-- [CHAR LIMIT=NONE] Zen mode voice: Prompt read for interruption type -->
<string name="zen_mode_interruptions_voice_prompt">When would you like to be interrupted?</string>
<!-- [CHAR LIMIT=NONE] Zen mode voice: Prompt read for zen mode duration -->
<string name="zen_mode_duration_voice_prompt">For how long?</string>
<!-- [CHAR LIMIT=NONE] Zen mode voice: Comma delimited synonyms for important interriuptions -->
<string name="zen_mode_option_important_voice_synonyms">important,priority,priority notifications</string>
<!-- [CHAR LIMIT=NONE] Zen mode voice: Comma delimited synonyms for alarm interriuptions -->
<string name="zen_mode_option_alarms_voice_synonyms">alarms</string>
<!-- [CHAR LIMIT=60] Zen mode voice: Off [CHAR LIMIT=60] -->
<string name="zen_mode_option_off">Off</string>
<!-- [CHAR LIMIT=NONE] Zen mode voice: Comma delimited synonyms for off interriuptions -->
<string name="zen_mode_option_off_voice_synonyms">off,all,everything</string>
<!-- [CHAR LIMIT=NONE] Zen mode voice: Comma delimited synonyms for no interriuptions -->
<string name="zen_mode_option_no_interruptions_voice_synonyms">none,nothing,no interruptions</string>
<!-- [CHAR LIMIT=40] Zen mode voice: Label for indefinite mode duration -->
<string name="zen_mode_duration_indefinte_voice_label">Indefinitely</string>
<!-- [CHAR LIMIT=40] Zen mode voice: Label for duration in minutes -->
<plurals name="zen_mode_duration_minutes_voice_label">
<item quantity="one"><xliff:g id="count" example="1">%d</xliff:g> minute</item>
<item quantity="other"><xliff:g id="count" example="10">%d</xliff:g> minutes</item>
</plurals>
<!-- [CHAR LIMIT=40] Zen mode voice: Label for duration in hours -->
<plurals name="zen_mode_duration_hours_voice_label">
<item quantity="one"><xliff:g id="count" example="1">%d</xliff:g> hour</item>
<item quantity="other"><xliff:g id="count" example="10">%d</xliff:g> hours</item>
</plurals>
<!-- [CHAR LIMIT=NONE] Zen mode voice - spoken summary: important only duration indefinite. -->
<string name="zen_mode_summary_priority_indefinitely">Change to priority notifications only indefinitely</string>
<!-- [CHAR LIMIT=NONE] Zen mode voice- spoken summary: important only duration minutes. -->
<plurals name="zen_mode_summary_priority_by_minute">
<item quantity="one">Change to priority notifications only for one minute until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g></item>
<item quantity="other">Change to priority notifications only for <xliff:g id="duration" example="2">%1$d</xliff:g> minutes until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g></item>
</plurals>
<!-- [CHAR LIMIT=NONE] Zen mode voice- spoken summary: important only duration hours. -->
<plurals name="zen_mode_summary_priority_by_hour">
<item quantity="one">Change to priority notifications only for one hour until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g></item>
<item quantity="other">Change to priority notifications only for <xliff:g id="duration" example="2">%1$d</xliff:g> hours (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
</plurals>
<!-- [CHAR LIMIT=NONE] Zen mode voice - spoken summary: alarms only duration indefinite. -->
<string name="zen_mode_summary_alarams_only_indefinite">Change to alarms only indefinitely</string>
<!-- [CHAR LIMIT=NONE] Zen mode voice- spoken summary: alarms only duration minutes. -->
<plurals name="zen_mode_summary_alarms_only_by_minute">
<item quantity="one">Change to alarms only for one minute until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g></item>
<item quantity="other">Change to alarms only for <xliff:g id="duration" example="2">%1$d</xliff:g> minutes (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
</plurals>
<!-- [CHAR LIMIT=NONE] Zen mode voice- spoken summary: alarms only duration hours. -->
<plurals name="zen_mode_summary_alarms_only_by_hour">
<item quantity="one">Change to alarms only for one hour until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g></item>
<item quantity="other">Change to alarms only for <xliff:g id="duration" example="2">%1$d</xliff:g> hours until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g></item>
</plurals>
<!-- [CHAR LIMIT=NONE] Zen mode voice - spoken summary: no interruptions duration indefinite. -->
<string name="zen_mode_summary_no_interruptions_indefinite">Change to don\'t interrupt indefinitely</string>
<!-- [CHAR LIMIT=NONE] Zen mode voice- spoken summary: alarms only duration minutes. -->
<plurals name="zen_mode_summary_no_interruptions_by_minute">
<item quantity="one">Change to don\'t interrupt for one minute until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g></item>
<item quantity="other">Change to don\'t interrupt for <xliff:g id="duration" example="2">%1$d</xliff:g> minutes (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
</plurals>
<!-- [CHAR LIMIT=NONE] Zen mode voice- spoken summary: alarms only duration hours. -->
<plurals name="zen_mode_summary_no_interruptions_by_hour">
<item quantity="one">Change to don\'t interrupt for one hour until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g></item>
<item quantity="other">Change to don\'t interrupt for <xliff:g id="duration" example="2">%1$d</xliff:g> hours until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g></item>
</plurals>
<!-- [CHAR LIMIT=NONE] Zen mode voice - spoken summary: off. -->
<string name="zen_mode_summary_always">Change to always interrupt</string>
<!-- [CHAR LIMIT=NONE] Zen mode voice: Comma delimited synonyms for indefinte duration -->
<string name="zen_mode_duration_indefinite_voice_synonyms">forever</string>
<!-- [CHAR LIMIT=20] Notifications settings: Apps section header -->
<string name="notification_settings_apps_title">App notifications</string>

View File

@@ -20,6 +20,8 @@ import android.content.Intent;
import android.provider.Settings;
import android.util.Log;
import com.android.settings.utils.VoiceSettingsActivity;
/**
* Activity for modifying the {@link Settings.Global#AIRPLANE_MODE_ON AIRPLANE_MODE_ON}
* setting using the Voice Interaction API.
@@ -27,14 +29,14 @@ import android.util.Log;
public class AirplaneModeVoiceActivity extends VoiceSettingsActivity {
private static final String TAG = "AirplaneModeVoiceActivity";
protected void onVoiceSettingInteraction(Intent intent) {
protected boolean onVoiceSettingInteraction(Intent intent) {
if (intent.hasExtra(Settings.EXTRA_AIRPLANE_MODE_ENABLED)) {
boolean enabled =
intent.getBooleanExtra(Settings.EXTRA_AIRPLANE_MODE_ENABLED, false);
Settings.Global.putInt(getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, enabled ? 1 : 0);
Settings.Global.AIRPLANE_MODE_ON,
intent.getBooleanExtra(Settings.EXTRA_AIRPLANE_MODE_ENABLED, false) ? 1 : 0);
} else {
Log.v(TAG, "Missing airplane mode extra");
}
return true;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2015 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.fuelguage;
import static android.provider.Settings.EXTRA_BATTERY_SAVER_MODE_ENABLED;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log;
import com.android.settings.utils.VoiceSettingsActivity;
/**
* Activity for modifying the {@link android.os.PowerManager} power save mode
* setting using the Voice Interaction API.
*/
public class BatterySaverModeVoiceActivity extends VoiceSettingsActivity {
private static final String TAG = "BatterySaverModeVoiceActivity";
protected boolean onVoiceSettingInteraction(Intent intent) {
if (intent.hasExtra(EXTRA_BATTERY_SAVER_MODE_ENABLED)) {
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (powerManager.setPowerSaveMode(
intent.getBooleanExtra(EXTRA_BATTERY_SAVER_MODE_ENABLED, false))) {
notifySuccess(null);
} else {
Log.v(TAG, "Unable to set power mode");
notifyFailure(null);
}
} else {
Log.v(TAG, "Missing battery saver mode extra");
}
return true;
}
}

View File

@@ -0,0 +1,241 @@
/*
* Copyright (C) 2015 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.notification;
import static android.provider.Settings.EXTRA_DO_NOT_DISTURB_MODE_MINUTES;
import static android.provider.Settings.EXTRA_DO_NOT_DISTURB_MODE_ENABLED;
import com.android.settings.R;
import com.android.settings.utils.VoiceSelectionAdapter;
import com.android.settings.utils.VoiceSelection;
import com.android.settings.utils.VoiceSelectionFragment;
import com.android.settings.utils.VoiceSettingsActivity;
import android.app.Fragment;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.text.format.DateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Activity for modifying the Zen mode (Do not disturb) by voice
* using the Voice Interaction API.
*/
public class ZenModeVoiceActivity extends VoiceSettingsActivity {
private static final String TAG = "ZenModeVoiceActivity";
private static final int MINUTES_MS = 60 * 1000;
@Override
protected boolean onVoiceSettingInteraction(Intent intent) {
setContentView(R.layout.voice_interaction);
pickNotificationMode(intent);
return false;
}
/**
* Start a voice interaction to ask what kind of interruptions should
* be permitted. The intent can optionally include extra information about the type
* of interruptions desired or how long interruptions should be limited to that are
* used as hints.
*/
private void pickNotificationMode(final Intent intent) {
boolean enabled = intent.getBooleanExtra(EXTRA_DO_NOT_DISTURB_MODE_ENABLED, false);
boolean specified = intent.hasExtra(EXTRA_DO_NOT_DISTURB_MODE_ENABLED);
List<VoiceSelection> states = new ArrayList<VoiceSelection>();
if (!specified || enabled) {
states.add(new ModeSelection(this, Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
R.string.zen_mode_option_important_interruptions,
R.string.zen_mode_option_important_voice_synonyms));
states.add(new ModeSelection(this, Global.ZEN_MODE_ALARMS,
R.string.zen_mode_option_alarms,
R.string.zen_mode_option_alarms_voice_synonyms));
states.add(new ModeSelection(this, Global.ZEN_MODE_NO_INTERRUPTIONS,
R.string.zen_mode_option_no_interruptions,
R.string.zen_mode_option_no_interruptions_voice_synonyms));
}
if (!specified || !enabled) {
states.add(new ModeSelection(this, Global.ZEN_MODE_OFF,
R.string.zen_mode_option_off,
R.string.zen_mode_option_off_voice_synonyms));
}
VoiceSelectionFragment fragment = new VoiceSelectionFragment();
fragment.setArguments(VoiceSelectionFragment.createArguments(
getString(R.string.zen_mode_interruptions_voice_prompt)));
fragment.setListAdapter(
new VoiceSelectionAdapter(this, R.layout.voice_item_row, states));
fragment.setOnItemSelectedHandler(new VoiceSelection.OnItemSelectedListener() {
@Override
public void onItemSelected(int index, VoiceSelection selection) {
int mode = ((ModeSelection) selection).mMode;
ConditionSelection conditionSelection = getConditionSelection(
intent.getIntExtra(EXTRA_DO_NOT_DISTURB_MODE_MINUTES, 0));
if (mode != Global.ZEN_MODE_OFF) {
if (conditionSelection == null) {
pickDuration(selection.getLabel(), mode);
return;
}
}
setZenModeConfig(mode, conditionSelection.mCondition);
notifySuccess(getChangeSummary(mode, conditionSelection));
finish();
}
});
showFragment(fragment, "pick_mode_fragment");
}
/**
* Start a voice interaction to ask for the zen mode duration.
*/
private void pickDuration(CharSequence label, final int mode) {
setTitle(label.toString());
List<VoiceSelection> states = new ArrayList<VoiceSelection>();
states.add(new ConditionSelection(null, -1,
getString(R.string.zen_mode_duration_indefinte_voice_label),
getString(R.string.zen_mode_duration_indefinite_voice_synonyms)));
for (int i = ZenModeConfig.MINUTE_BUCKETS.length - 1; i >= 0; --i) {
states.add(getConditionSelection(ZenModeConfig.MINUTE_BUCKETS[i]));
}
VoiceSelectionFragment fragment = new VoiceSelectionFragment();
fragment.setArguments(VoiceSelectionFragment.createArguments(
getString(R.string.zen_mode_duration_voice_prompt)));
fragment.setListAdapter(
new VoiceSelectionAdapter(this, R.layout.voice_item_row, states));
fragment.setOnItemSelectedHandler(new VoiceSelection.OnItemSelectedListener() {
@Override
public void onItemSelected(int index, VoiceSelection item) {
ConditionSelection selection = ((ConditionSelection) item);
setZenModeConfig(mode, selection.mCondition);
notifySuccess(getChangeSummary(mode, selection));
finish();
}
});
showFragment(fragment, "pick_duration_fragment");
}
private void showFragment(Fragment fragment, String tag) {
getFragmentManager()
.beginTransaction()
.replace(R.id.fragment_root, fragment, tag)
.commit();
}
private void setZenModeConfig(int mode, Condition condition) {
if (condition != null) {
NotificationManager.from(this).setZenMode(mode, condition.id, TAG);
} else {
NotificationManager.from(this).setZenMode(mode, null, TAG);
}
}
/**
* Produce a summary of the Zen mode change to be read aloud as TTS.
*/
private CharSequence getChangeSummary(int mode, ConditionSelection duration) {
int indefinite = -1;
int byMinute = -1;
int byHour = -1;
switch (mode) {
case Global.ZEN_MODE_ALARMS:
indefinite = R.string.zen_mode_summary_alarams_only_indefinite;
byMinute = R.plurals.zen_mode_summary_alarms_only_by_minute;
byHour = R.plurals.zen_mode_summary_alarms_only_by_hour;
break;
case Global.ZEN_MODE_NO_INTERRUPTIONS:
indefinite = R.string.zen_mode_summary_no_interruptions_indefinite;
byMinute = R.plurals.zen_mode_summary_no_interruptions_by_minute;
byHour = R.plurals.zen_mode_summary_no_interruptions_by_hour;
break;
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
indefinite = R.string.zen_mode_summary_priority_indefinitely;
byMinute = R.plurals.zen_mode_summary_priority_by_minute;
byHour = R.plurals.zen_mode_summary_priority_by_hour;
break;
default:
case Global.ZEN_MODE_OFF:
indefinite = R.string.zen_mode_summary_always;
break;
};
if (duration == null || duration.mCondition == null) {
return getString(indefinite);
}
long time = System.currentTimeMillis() + duration.mMinutes * MINUTES_MS;
String skeleton = DateFormat.is24HourFormat(this, UserHandle.myUserId()) ? "Hm" : "hma";
String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
CharSequence formattedTime = DateFormat.format(pattern, time);
Resources res = getResources();
if (duration.mMinutes < 60) {
return res.getQuantityString(byMinute,
duration.mMinutes, duration.mMinutes, formattedTime);
} else {
int hours = duration.mMinutes / 60;
return res.getQuantityString(byHour, hours, hours, formattedTime);
}
}
private ConditionSelection getConditionSelection(int minutes) {
Condition condition = ZenModeConfig.toTimeCondition(this, minutes, UserHandle.myUserId());
Resources res = getResources();
if (minutes <= 0) {
return null;
} else if (minutes < 60) {
String label = res.getQuantityString(R.plurals.zen_mode_duration_minutes_voice_label,
minutes, minutes);
return new ConditionSelection(condition, minutes, label, Integer.toString(minutes));
} else {
int hours = minutes / 60;
String label = res.getQuantityString(R.plurals.zen_mode_duration_hours_voice_label,
hours, hours);
return new ConditionSelection(condition, minutes, label, Integer.toString(hours));
}
}
private static class ConditionSelection extends VoiceSelection {
Condition mCondition;
int mMinutes;
public ConditionSelection(Condition condition, int minutes, CharSequence label,
CharSequence synonyms) {
super(label, synonyms);
mMinutes = minutes;
mCondition = condition;
}
}
private static class ModeSelection extends VoiceSelection {
int mMode;
public ModeSelection(Context context, int mode, int label, int synonyms) {
super(context.getString(label), context.getString(synonyms));
mMode = mode;
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2015 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.utils;
import android.app.VoiceInteractor.PickOptionRequest.Option;
import android.os.Bundle;
import android.text.TextUtils;
/**
* Model for a single item that can be selected by a {@link VoiceSelectionFragment}.
* Each item consists of a visual label and several alternative synonyms for the item
* that can be used to identify the item by voice.
*/
public class VoiceSelection {
final CharSequence mLabel;
final CharSequence[] mSynonyms;
/**
* Created a new selectable item with a visual label and a set of synonyms.
*/
public VoiceSelection(CharSequence label, CharSequence synonyms) {
mLabel = label;
mSynonyms = TextUtils.split(synonyms.toString(), ",");
}
/**
* Created a new selectable item with a visual label and no synonyms.
*/
public VoiceSelection(CharSequence label) {
mLabel = label;
mSynonyms = null;
}
public CharSequence getLabel() {
return mLabel;
}
public CharSequence[] getSynonyms() {
return mSynonyms;
}
Option toOption(int index) {
Option result = new Option(mLabel);
Bundle extras = new Bundle();
extras.putInt("index", index);
result.setExtras(extras);
for (CharSequence synonym : mSynonyms) {
result.addSynonym(synonym);
}
return result;
}
/**
* Listener interface for when an item is selected.
*/
public interface OnItemSelectedListener {
abstract void onItemSelected(int position, VoiceSelection selection);
};
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2015 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.utils;
import android.content.Context;
import android.widget.ArrayAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.app.Activity;
import com.android.settings.R;
import java.util.List;
import android.util.Log;
/**
* Array adapter for selecting an item by voice interaction. Each row includes a visual
* indication of the 1-indexed position of the item so that a user can easily say
* "number 4" to select it.
*/
public class VoiceSelectionAdapter extends ArrayAdapter<VoiceSelection> {
public VoiceSelectionAdapter(Context context, int resource, List<VoiceSelection> objects) {
super(context, resource, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
VoiceSelection item = getItem(position);
View row = convertView;
if (row == null) {
LayoutInflater inflater = ((Activity) getContext()).getLayoutInflater();
row = inflater.inflate(R.layout.voice_item_row, parent, false);
}
TextView label = (TextView) row.findViewById(R.id.voice_item_label);
if (label != null) {
label.setText(item.getLabel());
}
TextView positionLabel = (TextView) row.findViewById(R.id.voice_item_position);
if (positionLabel != null) {
positionLabel.setText(Integer.toString(position + 1));
}
return row;
}
};

View File

@@ -0,0 +1,133 @@
/*
* Copyright (C) 2015 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.utils;
import android.app.ListFragment;
import android.app.VoiceInteractor;
import android.app.VoiceInteractor.PickOptionRequest;
import android.app.VoiceInteractor.PickOptionRequest.Option;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import java.util.List;
/**
* An Activity fragment that presents a set of options as a visual list and also allows
* items to be selected by the users voice.
*/
public class VoiceSelectionFragment extends ListFragment {
private static final String EXTRA_SELECTION_PROMPT = "selection_prompt";
private CharSequence mPrompt = null;
private VoiceInteractor.Request mRequest = null;
private VoiceInteractor mVoiceInteractor = null;
private VoiceSelection.OnItemSelectedListener mOnItemSelectedListener = null;
/**
* No-args ctor required for fragment.
*/
public VoiceSelectionFragment() {}
@Override
public void onCreate(Bundle args) {
super.onCreate(args);
mPrompt = getArguments().getCharSequence(EXTRA_SELECTION_PROMPT);
}
/**
* Set the prompt spoken when the fragment is presented.
*/
static public Bundle createArguments(CharSequence prompt) {
Bundle args = new Bundle();
args.putCharSequence(EXTRA_SELECTION_PROMPT, prompt);
return args;
}
private VoiceSelection getSelectionAt(int position) {
return ((ArrayAdapter<VoiceSelection>) getListAdapter()).getItem(position);
}
@Override
public void onStart() {
super.onStart();
final int numItems = getListAdapter().getCount();
if (numItems <= 0) {
return;
}
Option[] options = new Option[numItems];
for (int idx = 0; idx < numItems; idx++) {
options[idx] = getSelectionAt(idx).toOption(idx);
}
mRequest = new PickOptionRequest(mPrompt, options, null) {
@Override
public void onPickOptionResult(boolean isComplete, Option[] options, Bundle args) {
if (!isComplete || options == null) {
return;
}
if (options.length == 1 && mOnItemSelectedListener != null) {
int idx = options[0].getExtras().getInt("index", -1);
mOnItemSelectedListener.onItemSelected(idx, getSelectionAt(idx));
} else {
onCancel();
}
}
};
mVoiceInteractor = getActivity().getVoiceInteractor();
if (mVoiceInteractor != null) {
mVoiceInteractor.submitRequest(mRequest);
}
}
@Override
public void onDetach() {
super.onDetach();
mVoiceInteractor = null;
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
if (mRequest != null) {
mRequest.cancel();
mRequest = null;
}
if (mOnItemSelectedListener != null) {
mOnItemSelectedListener.onItemSelected(position, getSelectionAt(position));
}
}
/**
* Sets the selection handler for an item either by voice or by touch.
*/
public void setOnItemSelectedHandler(VoiceSelection.OnItemSelectedListener listener) {
mOnItemSelectedListener = listener;
}
/**
* Called when the user cancels the interaction. The default implementation is to
* finish the activity.
*/
public void onCancel() {
getActivity().finish();
}
};

View File

@@ -14,17 +14,20 @@
* limitations under the License.
*/
package com.android.settings;
package com.android.settings.utils;
import android.app.Activity;
import android.app.VoiceInteractor;
import android.app.VoiceInteractor.CompleteVoiceRequest;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
/**
* Activity for modifying a setting using the Voice Interaction API. This activity
* MUST only modify the setting if the intent was sent using
* {@link android.service.voice.VoiceInteractionSession#startVoiceActivity startVoiceActivity}.
* will only allow modifying the setting if the intent was sent using
* {@link android.service.voice.VoiceInteractionSession#startVoiceActivity startVoiceActivity}
* by the current Voice Interaction Service.
*/
abstract public class VoiceSettingsActivity extends Activity {
@@ -34,18 +37,38 @@ abstract public class VoiceSettingsActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (isVoiceInteraction()) {
if (isVoiceInteraction() || savedInstanceState == null) {
// Only permit if this is a voice interaction.
onVoiceSettingInteraction(getIntent());
if (onVoiceSettingInteraction(getIntent())) {
// If it's complete, finish.
finish();
}
} else {
Log.v(TAG, "Cannot modify settings without voice interaction");
finish();
}
finish();
}
/**
* Modify the setting as a voice interaction. The activity will finish
* after this method is called.
* Modify the setting as a voice interaction. Should return true if the
* voice interaction is complete or false if more interaction is required.
*/
abstract protected void onVoiceSettingInteraction(Intent intent);
abstract protected boolean onVoiceSettingInteraction(Intent intent);
/**
* Send a notification that the interaction was successful. If {@link prompt} is
* not null, then it will be read to the user.
*/
protected void notifySuccess(CharSequence prompt) {
if (getVoiceInteractor() != null) {
getVoiceInteractor().submitRequest(new CompleteVoiceRequest(prompt, null));
}
}
/**
* Indicates when the setting could not be changed.
*/
protected void notifyFailure(String reason) {
getVoiceInteractor().submitRequest(new VoiceInteractor.AbortVoiceRequest(reason, null));
}
}