Merge "Allows user to skip fingerprint during setup"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 637e710..c40328e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1546,7 +1546,7 @@
<activity
android:name=".Settings$FingerprintSuggestionActivity"
android:label="@string/security_settings_fingerprint_preference_title"
- android:icon="@drawable/ic_fingerprint">
+ android:icon="@drawable/ic_suggestion_fingerprint">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.settings.suggested.category.SETTINGS_ONLY" />
@@ -1626,7 +1626,7 @@
</activity>
<activity android:name=".Settings$FingerprintEnrollSuggestionActivity"
- android:icon="@drawable/ic_settings_security">
+ android:icon="@drawable/ic_suggestion_fingerprint">
<intent-filter android:priority="2">
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.settings.suggested.category.LOCK_SCREEN" />
@@ -1638,7 +1638,7 @@
<meta-data android:name="com.android.settings.title"
android:resource="@string/suggested_lock_settings_title" />
<meta-data android:name="com.android.settings.summary"
- android:resource="@string/suggested_lock_settings_summary" />
+ android:resource="@string/suggested_fingerprint_lock_settings_summary" />
</activity>
<activity android:name="ChooseLockGeneric$InternalActivity" android:exported="false"
diff --git a/res/drawable/ic_suggestion_fingerprint.xml b/res/drawable/ic_suggestion_fingerprint.xml
new file mode 100644
index 0000000..feebd94
--- /dev/null
+++ b/res/drawable/ic_suggestion_fingerprint.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="32dp"
+ android:tint="?android:attr/colorAccent"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0">
+ <path
+ android:fillColor="#ffffff"
+ android:pathData="M23.7,5.9c-0.1,0.0 -0.2,0.0 -0.3,-0.1C21.0,4.5 18.6,3.9 16.0,3.9c-2.5,0.0 -4.6,0.6 -6.9,1.9C8.8,6.0 8.3,5.9 8.1,5.5C7.9,5.2 8.0,4.7 8.4,4.5c2.5,-1.4 4.9,-2.1 7.7,-2.1c2.8,0.0 5.4,0.7 8.0,2.1c0.4,0.2 0.5,0.6 0.3,1.0C24.2,5.7 24.0,5.9 23.7,5.9z"/>
+ <path
+ android:fillColor="#ffffff"
+ android:pathData="M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 -0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 -12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z"/>
+ <path
+ android:fillColor="#ffffff"
+ android:pathData="M13.3,29.6c-0.2,0.0 -0.4,-0.1 -0.5,-0.2c-1.1,-1.2 -1.7,-2.0 -2.6,-3.6c-0.9,-1.7 -1.4,-3.7 -1.4,-5.9c0.0,-4.1 3.3,-7.4 7.4,-7.4c4.1,0.0 7.4,3.3 7.4,7.4c0.0,0.4 -0.3,0.7 -0.7,0.7s-0.7,-0.3 -0.7,-0.7c0.0,-3.3 -2.7,-5.9 -5.9,-5.9c-3.3,0.0 -5.9,2.7 -5.9,5.9c0.0,2.0 0.4,3.8 1.2,5.2c0.8,1.6 1.4,2.2 2.4,3.3c0.3,0.3 0.3,0.8 0.0,1.0C13.7,29.5 13.5,29.6 13.3,29.6z"/>
+ <path
+ android:fillColor="#ffffff"
+ android:pathData="M22.6,27.1c-1.6,0.0 -2.9,-0.4 -4.1,-1.2c-1.9,-1.4 -3.1,-3.6 -3.1,-6.0c0.0,-0.4 0.3,-0.7 0.7,-0.7s0.7,0.3 0.7,0.7c0.0,1.9 0.9,3.7 2.5,4.8c0.9,0.6 1.9,1.0 3.2,1.0c0.3,0.0 0.8,0.0 1.3,-0.1c0.4,-0.1 0.8,0.2 0.8,0.6c0.1,0.4 -0.2,0.8 -0.6,0.8C23.4,27.1 22.8,27.1 22.6,27.1z"/>
+ <path
+ android:fillColor="#ffffff"
+ android:pathData="M20.0,29.9c-0.1,0.0 -0.1,0.0 -0.2,0.0c-2.1,-0.6 -3.4,-1.4 -4.8,-2.9c-1.8,-1.9 -2.8,-4.4 -2.8,-7.1c0.0,-2.2 1.8,-4.1 4.1,-4.1c2.2,0.0 4.1,1.8 4.1,4.1c0.0,1.4 1.2,2.6 2.6,2.6c1.4,0.0 2.6,-1.2 2.6,-2.6c0.0,-5.1 -4.2,-9.3 -9.3,-9.3c-3.6,0.0 -6.9,2.1 -8.4,5.4C7.3,17.1 7.0,18.4 7.0,19.8c0.0,1.1 0.1,2.7 0.9,4.9c0.1,0.4 -0.1,0.8 -0.4,0.9c-0.4,0.1 -0.8,-0.1 -0.9,-0.4c-0.6,-1.8 -0.9,-3.6 -0.9,-5.4c0.0,-1.6 0.3,-3.1 0.9,-4.4c1.7,-3.8 5.6,-6.3 9.8,-6.3c5.9,0.0 10.7,4.8 10.7,10.7c0.0,2.2 -1.8,4.1 -4.1,4.1s-4.0,-1.8 -4.0,-4.1c0.0,-1.4 -1.2,-2.6 -2.6,-2.6c-1.4,0.0 -2.6,1.2 -2.6,2.6c0.0,2.3 0.9,4.5 2.4,6.1c1.2,1.3 2.4,2.0 4.2,2.5c0.4,0.1 0.6,0.5 0.5,0.9C20.6,29.7 20.3,29.9 20.0,29.9z"/>
+</vector>
+
diff --git a/res/layout/font_size_activity.xml b/res/layout/font_size_activity.xml
index ec064fa..479a5f5 100644
--- a/res/layout/font_size_activity.xml
+++ b/res/layout/font_size_activity.xml
@@ -70,7 +70,7 @@
android:focusable="true"
android:contentDescription="@string/font_size_make_smaller_desc" />
- <SeekBar
+ <com.android.settings.widget.LabeledSeekBar
android:id="@+id/seek_bar"
android:layout_width="0dp"
android:layout_height="48dp"
diff --git a/res/layout/locale_order_list.xml b/res/layout/locale_order_list.xml
index 97ba9a3..e210c65 100644
--- a/res/layout/locale_order_list.xml
+++ b/res/layout/locale_order_list.xml
@@ -25,20 +25,19 @@
android:id="@+id/dragList"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1"/>
+ android:layout_weight="1"
+ android:scrollbars="vertical"/>
- <TextView
+ <Button
android:id="@+id/add_language"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:clickable="true"
- android:contextClickable="true"
android:drawableStart="@drawable/ic_add_24dp"
android:drawablePadding="12dp"
- android:gravity="center_vertical"
- android:measureWithLargestChild="false"
- android:padding="16dp"
+ android:minHeight="64dp"
+ android:textAlignment="textStart"
android:text="@string/add_a_language"
+ style="@style/Base.Widget.AppCompat.Button.Borderless"
android:textAppearance="?android:attr/textAppearanceListItem"/>
</LinearLayout>
diff --git a/res/layout/printer_dropdown_item.xml b/res/layout/printer_dropdown_item.xml
index 0063477..1209aa6 100644
--- a/res/layout/printer_dropdown_item.xml
+++ b/res/layout/printer_dropdown_item.xml
@@ -15,31 +15,33 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:minHeight="48dip"
- android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:background="?android:attr/selectableItemBackground">
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:orientation="horizontal"
+ android:gravity="start|center_vertical">
<ImageView
android:id="@+id/icon"
- android:layout_width="48dip"
- android:layout_height="48dip"
+ android:layout_width="40dip"
+ android:layout_height="40dip"
android:layout_gravity="center_vertical"
- android:layout_marginEnd="8dip"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
android:duplicateParentState="true"
android:contentDescription="@null"
- android:visibility="gone">
+ android:visibility="invisible">
</ImageView>
- <LinearLayout
- android:layout_width="wrap_content"
+ <RelativeLayout
+ android:layout_width="0dip"
+ android:layout_weight="1"
android:layout_height="wrap_content"
- android:layout_marginTop="6dip"
- android:layout_marginBottom="6dip"
- android:orientation="vertical">
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="16dip"
+ android:duplicateParentState="true">
<TextView
android:id="@+id/title"
@@ -47,24 +49,45 @@
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:singleLine="true"
- android:ellipsize="marquee"
+ android:ellipsize="end"
+ android:textIsSelectable="false"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
android:fadingEdge="horizontal"
+ android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
- android:textIsSelectable="false">
+ android:duplicateParentState="true">
</TextView>
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:layout_alignParentStart="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:singleLine="true"
android:ellipsize="end"
android:textIsSelectable="false"
android:visibility="gone"
- android:textColor="?android:attr/textColorSecondary">
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAlignment="viewStart"
+ android:duplicateParentState="true">
</TextView>
- </LinearLayout>
+ </RelativeLayout>
+
+ <ImageView
+ android:id="@+id/more_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingLeft="16dip"
+ android:contentDescription="@string/printer_info_desc"
+ android:src="@drawable/ic_info"
+ android:tint="?android:attr/colorControlNormal"
+ android:tintMode="src_in"
+ android:visibility="gone">
+ </ImageView>
</LinearLayout>
diff --git a/res/layout/screen_zoom_activity.xml b/res/layout/screen_zoom_activity.xml
index 51fea69..47c6b19 100644
--- a/res/layout/screen_zoom_activity.xml
+++ b/res/layout/screen_zoom_activity.xml
@@ -69,7 +69,7 @@
android:focusable="true"
android:contentDescription="@string/screen_zoom_make_smaller_desc" />
- <SeekBar
+ <com.android.settings.widget.LabeledSeekBar
android:id="@+id/seek_bar"
android:layout_width="0dp"
android:layout_height="48dp"
diff --git a/res/layout/wifi_dialog.xml b/res/layout/wifi_dialog.xml
index c5e554d..05c33d7 100644
--- a/res/layout/wifi_dialog.xml
+++ b/res/layout/wifi_dialog.xml
@@ -157,6 +157,24 @@
android:text="@string/wifi_do_not_validate_eap_server_warning" />
</LinearLayout>
+ <LinearLayout android:id="@+id/l_domain"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/wifi_item" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/wifi_item_label"
+ android:text="@string/wifi_eap_domain" />
+
+ <EditText android:id="@+id/domain"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/wifi_item_edit_content"
+ android:singleLine="true"
+ android:inputType="textNoSuggestions" />
+ </LinearLayout>
+
<LinearLayout android:id="@+id/l_user_cert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 438afa9..58815c6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -966,6 +966,9 @@
<!-- Summary for suggested actions for screen lock -->
<string name="suggested_lock_settings_summary">Protect your device</string>
+ <!-- Summary for suggested actions for settings up a fingerprint lock -->
+ <string name="suggested_fingerprint_lock_settings_summary">Unlock with your fingerprint</string>
+
<!-- Title for security picker to choose the unlock method: None/Pattern/PIN/Password [CHAR LIMIT=22] -->
<string name="lock_settings_picker_title">Choose screen lock</string>
@@ -1613,6 +1616,8 @@
<string name="please_select_phase2">Phase 2 authentication</string>
<!-- Label for the EAP CA certificate of the network -->
<string name="wifi_eap_ca_cert">CA certificate</string>
+ <!-- Label for the domain name that the EAP CA certificate(s) can be used to validate. -->
+ <string name="wifi_eap_domain">Domain</string>
<!-- Label for the EAP user certificate of the network -->
<string name="wifi_eap_user_cert">User certificate</string>
<!-- Label for the EAP identity of the network -->
@@ -3547,7 +3552,7 @@
<!-- Toast that settings for an application is failed to open. -->
<string name="failed_to_open_app_settings_toast">Failed to open settings for <xliff:g id="spell_application_name">%1$s</xliff:g></string>
- <!-- Title for the 'keyboard and input methods' preference category. [CHAR LIMIT=35] -->
+ <!-- Title for the 'keyboard and input methods' preference category. [CHAR LIMIT=45] -->
<string name="keyboard_and_input_methods_category">Keyboard and input methods</string>
<!-- Title for the 'virtual keyboard' preference sub-screen. [CHAR LIMIT=35] -->
<string name="virtual_keyboard_category">Virtual keyboard</string>
@@ -4034,6 +4039,9 @@
<!-- Utterance to announce that the search box is hidden. This is spoken to a blind user. [CHAR LIMIT=none] -->
<string name="print_search_box_hidden_utterance">Search box hidden</string>
+ <!-- Description of printer info icon. [CHAR LIMIT=50] -->
+ <string name="printer_info_desc">More information about this printer</string>
+
<!-- App Fuel Gauge strings -->
<skip />
@@ -6326,7 +6334,9 @@
<!-- App notification summary with notifications enabled [CHAR LIMIT=40] -->
<string name="notifications_enabled">Normal</string>
<!-- App notification summary with notifications disabled [CHAR LIMIT=40] -->
- <string name="notifications_disabled">Block</string>
+ <string name="notifications_disabled">Fully Blocked</string>
+ <!-- App notification summary with notifications disabled [CHAR LIMIT=40] -->
+ <string name="notifications_partially_disabled">Partially Blocked</string>
<!-- App notification summary with 2 items [CHAR LIMIT=15] -->
<string name="notifications_two_items"><xliff:g id="notif_state" example="Priority">%1$s</xliff:g> / <xliff:g id="notif_state" example="Priority">%2$s</xliff:g></string>
<!-- App notification summary with 3 items [CHAR LIMIT=15] -->
diff --git a/res/xml/suggestion_ordering.xml b/res/xml/suggestion_ordering.xml
index 55f7803..1eeafba 100644
--- a/res/xml/suggestion_ordering.xml
+++ b/res/xml/suggestion_ordering.xml
@@ -17,9 +17,9 @@
<optional-steps>
<step category="com.android.settings.suggested.category.LOCK_SCREEN" />
<step category="com.android.settings.suggested.category.EMAIL" />
- <step category="com.android.settings.suggested.category.PAYMENT" />
<step category="com.android.settings.suggested.category.PARTNER_ACCOUNT"
multiple="true" />
+ <step category="com.android.settings.suggested.category.HOTWORD" />
<step category="com.android.settings.suggested.category.DEFAULT"
multiple="true" />
<step category="com.android.settings.suggested.category.SETTINGS_ONLY"
diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java
index 03a3fef..42ae602 100644
--- a/src/com/android/settings/ChooseLockGeneric.java
+++ b/src/com/android/settings/ChooseLockGeneric.java
@@ -240,6 +240,7 @@
if (UserManager.get(getActivity()).isAdminUser()
&& mUserId == UserHandle.myUserId()
&& LockPatternUtils.isDeviceEncryptionEnabled()
+ && !LockPatternUtils.isFileEncryptionEnabled()
&& !dpm.getDoNotAskCredentialsOnBoot()) {
mEncryptionRequestQuality = quality;
mEncryptionRequestDisabled = disabled;
diff --git a/src/com/android/settings/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/ConfirmDeviceCredentialActivity.java
index 28c0515..16d0685 100644
--- a/src/com/android/settings/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/ConfirmDeviceCredentialActivity.java
@@ -19,6 +19,8 @@
import android.app.Activity;
import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
@@ -72,6 +74,11 @@
Log.e(TAG, "Invalid intent extra", se);
}
}
+ // if the client app did not hand in a title and we are about to show the work challenge,
+ // check whether there is a policy setting the organization name and use that as title
+ if ((title == null) && Utils.isManagedProfile(UserManager.get(this), userId)) {
+ title = getTitleFromOrganizationName(userId);
+ }
ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this);
if (!helper.launchConfirmationActivity(0 /* request code */, null /* title */, title,
details, false /* returnCredentials */, true /* isExternal */, userId)) {
@@ -84,4 +91,10 @@
private boolean isInternalActivity() {
return this instanceof ConfirmDeviceCredentialActivity.InternalActivity;
}
+
+ private String getTitleFromOrganizationName(int userId) {
+ DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ return (dpm != null) ? dpm.getOrganizationNameForUser(userId) : null;
+ }
}
diff --git a/src/com/android/settings/PreviewSeekBarPreferenceFragment.java b/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
index ea8b55a..ad32c2d 100644
--- a/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
+++ b/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
@@ -28,6 +28,7 @@
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import com.android.settings.widget.DotsPageIndicator;
+import com.android.settings.widget.LabeledSeekBar;
/**
@@ -97,7 +98,8 @@
// seek bar.
final int max = Math.max(1, mEntries.length - 1);
- final SeekBar seekBar = (SeekBar) content.findViewById(R.id.seek_bar);
+ final LabeledSeekBar seekBar = (LabeledSeekBar) content.findViewById(R.id.seek_bar);
+ seekBar.setLabels(mEntries);
seekBar.setMax(max);
seekBar.setProgress(mInitialIndex);
seekBar.setOnSeekBarChangeListener(new onPreviewSeekBarChangeListener());
diff --git a/src/com/android/settings/TetherService.java b/src/com/android/settings/TetherService.java
index d4944ef..1d68e90 100644
--- a/src/com/android/settings/TetherService.java
+++ b/src/com/android/settings/TetherService.java
@@ -37,6 +37,7 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.TetherUtil;
import java.util.ArrayList;
@@ -46,7 +47,8 @@
private static final String TAG = "TetherService";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final String EXTRA_RESULT = "EntitlementResult";
+ @VisibleForTesting
+ public static final String EXTRA_RESULT = "EntitlementResult";
// Activity results to match the activity provision protocol.
// Default to something not ok.
@@ -295,7 +297,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "Got provision result " + intent);
- String provisionResponse = context.getResources().getString(
+ String provisionResponse = getResources().getString(
com.android.internal.R.string.config_mobile_hotspot_provision_response);
if (provisionResponse.equals(intent.getAction())) {
diff --git a/src/com/android/settings/applications/AppStateNotificationBridge.java b/src/com/android/settings/applications/AppStateNotificationBridge.java
index 6d057b3..c7b78af 100644
--- a/src/com/android/settings/applications/AppStateNotificationBridge.java
+++ b/src/com/android/settings/applications/AppStateNotificationBridge.java
@@ -63,7 +63,11 @@
@Override
public boolean filterApp(AppEntry info) {
- return info.extraInfo != null && ((AppRow) info.extraInfo).banned;
+ if (info == null) {
+ return false;
+ }
+ AppRow row = (AppRow) info.extraInfo;
+ return row.banned || row.bannedTopics;
}
};
}
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index f31fa72..83159e3 100755
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -821,6 +821,8 @@
public static CharSequence getNotificationSummary(AppRow appRow, Context context) {
if (appRow.banned) {
return context.getString(R.string.notifications_disabled);
+ } else if (appRow.bannedTopics) {
+ return context.getString(R.string.notifications_partially_disabled);
}
return context.getString(R.string.notifications_enabled);
}
diff --git a/src/com/android/settings/datausage/DataUsageMeteredSettings.java b/src/com/android/settings/datausage/DataUsageMeteredSettings.java
index 25e1a07..eb43d47 100644
--- a/src/com/android/settings/datausage/DataUsageMeteredSettings.java
+++ b/src/com/android/settings/datausage/DataUsageMeteredSettings.java
@@ -14,6 +14,7 @@
package com.android.settings.datausage;
+import android.app.backup.BackupManager;
import android.content.Context;
import android.content.res.Resources;
import android.net.NetworkPolicy;
@@ -150,6 +151,8 @@
super.notifyChanged();
if (!mBinding) {
mPolicyEditor.setPolicyMetered(mTemplate, isChecked());
+ // Stage the backup of the SettingsProvider package which backs this up
+ BackupManager.dataChanged("com.android.providers.settings");
}
}
}
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index 292d247..8dd6a4c 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -54,6 +54,7 @@
row.appImportance = getImportance(row.pkg, row.uid, null);
row.appBypassDnd = getBypassZenMode(row.pkg, row.uid, null);
row.appSensitive = getSensitive(row.pkg, row.uid, null);
+ row.bannedTopics = hasBannedTopics(row.pkg, row.uid);
return row;
}
@@ -170,6 +171,15 @@
}
}
+ public boolean hasBannedTopics(String pkg, int uid) {
+ try {
+ return sINM.hasBannedTopics(pkg, uid);
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
static class Row {
public String section;
}
@@ -186,6 +196,7 @@
public int appImportance;
public boolean appBypassDnd;
public boolean appSensitive;
+ public boolean bannedTopics;
}
public static class TopicRow extends AppRow {
diff --git a/src/com/android/settings/print/PrintServiceSettingsFragment.java b/src/com/android/settings/print/PrintServiceSettingsFragment.java
index 2adfb17..a9dba1e 100644
--- a/src/com/android/settings/print/PrintServiceSettingsFragment.java
+++ b/src/com/android/settings/print/PrintServiceSettingsFragment.java
@@ -40,11 +40,13 @@
import android.print.PrinterInfo;
import android.text.TextUtils;
import android.util.Log;
+import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.view.View.OnClickListener;
import android.view.accessibility.AccessibilityManager;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
@@ -529,6 +531,17 @@
return position;
}
+ /**
+ * Checks if a printer can be used for printing
+ *
+ * @param position The position of the printer in the list
+ * @return true iff the printer can be used for printing.
+ */
+ public boolean isActionable(int position) {
+ PrinterInfo printer = (PrinterInfo) getItem(position);
+ return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
+ }
+
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
@@ -536,7 +549,9 @@
R.layout.printer_dropdown_item, parent, false);
}
- PrinterInfo printer = (PrinterInfo) getItem(position);
+ convertView.setEnabled(isActionable(position));
+
+ final PrinterInfo printer = (PrinterInfo) getItem(position);
CharSequence title = printer.getName();
CharSequence subtitle = printer.getDescription();
Drawable icon = printer.loadIcon(getActivity());
@@ -553,10 +568,36 @@
subtitleView.setVisibility(View.GONE);
}
+ ImageView moreInfoView = (ImageView) convertView.findViewById(R.id.more_info);
+ if (printer.getInfoIntent() != null) {
+ moreInfoView.setVisibility(View.VISIBLE);
+ moreInfoView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ getActivity().startIntentSender(
+ printer.getInfoIntent().getIntentSender(), null, 0, 0, 0);
+ } catch (SendIntentException e) {
+ Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
+ }
+ }
+ });
+ } else {
+ moreInfoView.setVisibility(View.GONE);
+ }
+
ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
if (icon != null) {
- iconView.setImageDrawable(icon);
iconView.setVisibility(View.VISIBLE);
+ if (!isActionable(position)) {
+ icon.mutate();
+
+ TypedValue value = new TypedValue();
+ getActivity().getTheme().resolveAttribute(android.R.attr.disabledAlpha, value,
+ true);
+ icon.setAlpha((int)(value.getFloat() * 255));
+ }
+ iconView.setImageDrawable(icon);
} else {
iconView.setVisibility(View.GONE);
}
diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java
index cd24be8..4cbb3df 100644
--- a/src/com/android/settings/vpn2/VpnSettings.java
+++ b/src/com/android/settings/vpn2/VpnSettings.java
@@ -223,6 +223,8 @@
final List<LegacyVpnInfo> connectedLegacyVpns = getConnectedLegacyVpns();
final List<AppVpnInfo> connectedAppVpns = getConnectedAppVpns();
+ final Set<Integer> readOnlyUsers = getReadOnlyUserProfiles();
+
// Refresh the PreferenceGroup which lists VPNs
getActivity().runOnUiThread(new Runnable() {
@Override
@@ -233,11 +235,13 @@
for (VpnProfile profile : vpnProfiles) {
ConfigPreference p = findOrCreatePreference(profile);
p.setState(ConfigPreference.STATE_NONE);
+ p.setEnabled(!readOnlyUsers.contains(UserHandle.myUserId()));
updates.add(p);
}
for (AppVpnInfo app : vpnApps) {
AppPreference p = findOrCreatePreference(app);
p.setState(AppPreference.STATE_DISCONNECTED);
+ p.setEnabled(!readOnlyUsers.contains(app.userId));
updates.add(p);
}
@@ -417,6 +421,19 @@
return connections;
}
+ @WorkerThread
+ private Set<Integer> getReadOnlyUserProfiles() {
+ Set<Integer> result = new ArraySet<>();
+ for (UserHandle profile : mUserManager.getUserProfiles()) {
+ final int profileId = profile.getIdentifier();
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, profile)
+ || mConnectivityManager.getAlwaysOnVpnPackageForUser(profileId) != null) {
+ result.add(profileId);
+ }
+ }
+ return result;
+ }
+
static List<AppVpnInfo> getVpnApps(Context context, boolean includeProfiles) {
List<AppVpnInfo> result = Lists.newArrayList();
diff --git a/src/com/android/settings/widget/LabeledSeekBar.java b/src/com/android/settings/widget/LabeledSeekBar.java
new file mode 100644
index 0000000..9463bf7
--- /dev/null
+++ b/src/com/android/settings/widget/LabeledSeekBar.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2016 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.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.SeekBar;
+
+import java.util.List;
+
+/**
+ * LabeledSeekBar represent a seek bar assigned with labeled, discrete values.
+ * It pretends to be a group of radio button for AccessibilityServices, in order to adjust the
+ * behavior of these services to keep the mental model of the visual discrete SeekBar.
+ */
+public class LabeledSeekBar extends SeekBar {
+
+ private class LabeledSeekBarExploreByTouchHelper extends ExploreByTouchHelper {
+
+ public LabeledSeekBarExploreByTouchHelper(View forView) {
+ super(forView);
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ return getVirtualViewIdIndexFromX(x);
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(List<Integer> list) {
+ for (int i = 0, c = LabeledSeekBar.this.getMax(); i <= c; ++i) {
+ list.add(i);
+ }
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
+ Bundle arguments) {
+ if (virtualViewId == ExploreByTouchHelper.HOST_ID) {
+ // Do nothing
+ return false;
+ }
+
+ switch (action) {
+ case AccessibilityNodeInfoCompat.ACTION_CLICK:
+ LabeledSeekBar.this.setProgress(virtualViewId);
+ sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(
+ int virtualViewId, AccessibilityNodeInfoCompat node) {
+ node.setClassName(RadioButton.class.getName());
+ node.setBoundsInParent(getBoundsInParentFromVirtualViewId(virtualViewId));
+ node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+ node.setContentDescription(mLabels[virtualViewId]);
+ node.setClickable(true);
+ node.setCheckable(true);
+ node.setChecked(virtualViewId == LabeledSeekBar.this.getProgress());
+ }
+
+ @Override
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ event.setClassName(RadioButton.class.getName());
+ event.setContentDescription(mLabels[virtualViewId]);
+ event.setChecked(virtualViewId == LabeledSeekBar.this.getProgress());
+ }
+
+ @Override
+ protected void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
+ node.setClassName(RadioGroup.class.getName());
+ }
+
+ @Override
+ protected void onPopulateEventForHost(AccessibilityEvent event) {
+ event.setClassName(RadioGroup.class.getName());
+ }
+
+ private int getHalfVirtualViewWidth() {
+ final int width = LabeledSeekBar.this.getWidth();
+ final int barWidth = width - LabeledSeekBar.this.getPaddingStart()
+ - LabeledSeekBar.this.getPaddingEnd();
+ return Math.max(0, barWidth / (LabeledSeekBar.this.getMax() * 2));
+ }
+
+ private int getVirtualViewIdIndexFromX(float x) {
+ final int posBase = Math.max(0,
+ ((int) x - LabeledSeekBar.this.getPaddingStart()) / getHalfVirtualViewWidth());
+ return (posBase + 1) / 2;
+ }
+
+ private Rect getBoundsInParentFromVirtualViewId(int virtualViewId) {
+ int left = (virtualViewId * 2 - 1) * getHalfVirtualViewWidth()
+ + LabeledSeekBar.this.getPaddingStart();
+ int right = (virtualViewId * 2 + 1) * getHalfVirtualViewWidth()
+ + LabeledSeekBar.this.getPaddingStart();
+
+ // Edge case
+ left = virtualViewId == 0 ? 0 : left;
+ right = virtualViewId == LabeledSeekBar.this.getMax()
+ ? LabeledSeekBar.this.getWidth() : right;
+
+ final Rect r = new Rect();
+ r.set(left, 0, right, LabeledSeekBar.this.getHeight());
+ return r;
+ }
+ }
+
+ private String[] mLabels;
+
+ private ExploreByTouchHelper mAccessHelper;
+
+ public LabeledSeekBar(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.seekBarStyle);
+ }
+
+ public LabeledSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public LabeledSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public synchronized void setProgress(int progress) {
+ if (mAccessHelper != null) {
+ mAccessHelper.invalidateRoot();
+ }
+
+ super.setProgress(progress);
+ }
+
+ public void setLabels(String[] labels) {
+ mLabels = labels;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mAccessHelper = new LabeledSeekBarExploreByTouchHelper(this);
+ ViewCompat.setAccessibilityDelegate(this, mAccessHelper);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ ViewCompat.setAccessibilityDelegate(this, null);
+ mAccessHelper = null;
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ protected boolean dispatchHoverEvent(MotionEvent event) {
+ if (mAccessHelper != null && mAccessHelper.dispatchHoverEvent(event)) {
+ return true;
+ }
+
+ return super.dispatchHoverEvent(event);
+ }
+}
diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java
index 3bb1473..b3b284b 100644
--- a/src/com/android/settings/wifi/WifiConfigController.java
+++ b/src/com/android/settings/wifi/WifiConfigController.java
@@ -127,6 +127,7 @@
private Spinner mSecuritySpinner;
private Spinner mEapMethodSpinner;
private Spinner mEapCaCertSpinner;
+ private TextView mEapDomainView;
private Spinner mPhase2Spinner;
// Associated with mPhase2Spinner, one of mPhase2FullAdapter or mPhase2PeapAdapter
private ArrayAdapter<String> mPhase2Adapter;
@@ -414,7 +415,7 @@
if (mEapCaCertSpinner != null
&& mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE
&& ((String) mEapCaCertSpinner.getSelectedItem())
- .equals(mDoNotValidateEapServerString)) {
+ .equals(mDoNotValidateEapServerString)) {
// Display warning if user chooses not to validate the EAP server with a user-supplied
// CA certificate in an EAP network configuration.
mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.VISIBLE);
@@ -514,17 +515,22 @@
// Note: |caCert| should not be able to take the value |unspecifiedCert|,
// since we prevent such configurations from being saved.
config.enterpriseConfig.setCaCertificateAliases(null);
- } else if (caCert.equals(mMultipleCertSetString)) {
- if (mAccessPoint != null) {
- if (!mAccessPoint.isSaved()) {
- Log.e(TAG, "Multiple certs can only be set when editing saved network");
- }
- config.enterpriseConfig.setCaCertificateAliases(
- mAccessPoint.getConfig().enterpriseConfig
- .getCaCertificateAliases());
- }
} else {
- config.enterpriseConfig.setCaCertificateAliases(new String[] {caCert});
+ config.enterpriseConfig.setDomainSuffixMatch(
+ mEapDomainView.getText().toString());
+ if (caCert.equals(mMultipleCertSetString)) {
+ if (mAccessPoint != null) {
+ if (!mAccessPoint.isSaved()) {
+ Log.e(TAG, "Multiple certs can only be set "
+ + "when editing saved network");
+ }
+ config.enterpriseConfig.setCaCertificateAliases(
+ mAccessPoint.getConfig().enterpriseConfig
+ .getCaCertificateAliases());
+ }
+ } else {
+ config.enterpriseConfig.setCaCertificateAliases(new String[] {caCert});
+ }
}
String clientCert = (String) mEapUserCertSpinner.getSelectedItem();
@@ -738,6 +744,7 @@
mPhase2Spinner = (Spinner) mView.findViewById(R.id.phase2);
mEapCaCertSpinner = (Spinner) mView.findViewById(R.id.ca_cert);
mEapCaCertSpinner.setOnItemSelectedListener(this);
+ mEapDomainView = (TextView) mView.findViewById(R.id.domain);
mEapUserCertSpinner = (Spinner) mView.findViewById(R.id.user_cert);
mEapUserCertSpinner.setOnItemSelectedListener(this);
mEapIdentityView = (TextView) mView.findViewById(R.id.identity);
@@ -787,6 +794,7 @@
Credentials.CA_CERTIFICATE, true, mDoNotValidateEapServerString);
mEapCaCertSpinner.setSelection(MULTIPLE_CERT_SET_INDEX);
}
+ mEapDomainView.setText(enterpriseConfig.getDomainSuffixMatch());
setSelection(mEapUserCertSpinner, enterpriseConfig.getClientCertificateAlias());
mEapIdentityView.setText(enterpriseConfig.getIdentity());
mEapAnonymousView.setText(enterpriseConfig.getAnonymousIdentity());
@@ -811,6 +819,7 @@
* EAP-TLS valid fields include
* user_cert
* ca_cert
+ * domain
* identity
* EAP-TTLS valid fields include
* phase2: PAP, MSCHAP, MSCHAPV2, GTC
@@ -823,6 +832,7 @@
// Common defaults
mView.findViewById(R.id.l_method).setVisibility(View.VISIBLE);
mView.findViewById(R.id.l_identity).setVisibility(View.VISIBLE);
+ mView.findViewById(R.id.l_domain).setVisibility(View.VISIBLE);
// Defaults for most of the EAP methods and over-riden by
// by certain EAP methods
@@ -835,6 +845,7 @@
case WIFI_EAP_METHOD_PWD:
setPhase2Invisible();
setCaCertInvisible();
+ setDomainInvisible();
setAnonymousIdentInvisible();
setUserCertInvisible();
break;
@@ -870,11 +881,22 @@
setPhase2Invisible();
setAnonymousIdentInvisible();
setCaCertInvisible();
+ setDomainInvisible();
setUserCertInvisible();
setPasswordInvisible();
setIdentityInvisible();
break;
}
+
+ if (mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) {
+ String eapCertSelection = (String) mEapCaCertSpinner.getSelectedItem();
+ if (eapCertSelection.equals(mDoNotValidateEapServerString)
+ || eapCertSelection.equals(mUnspecifiedCertString)) {
+ // Domain suffix matching is not relevant if the user hasn't chosen a CA
+ // certificate yet, or chooses not to validate the EAP server.
+ setDomainInvisible();
+ }
+ }
}
private void setIdentityInvisible() {
@@ -892,6 +914,11 @@
mEapCaCertSpinner.setSelection(UNSPECIFIED_CERT_INDEX);
}
+ private void setDomainInvisible() {
+ mView.findViewById(R.id.l_domain).setVisibility(View.GONE);
+ mEapDomainView.setText("");
+ }
+
private void setUserCertInvisible() {
mView.findViewById(R.id.l_user_cert).setVisibility(View.GONE);
mEapUserCertSpinner.setSelection(UNSPECIFIED_CERT_INDEX);
@@ -1120,7 +1147,7 @@
if (parent == mSecuritySpinner) {
mAccessPointSecurity = position;
showSecurityFields();
- } else if (parent == mEapMethodSpinner) {
+ } else if (parent == mEapMethodSpinner || parent == mEapCaCertSpinner) {
showSecurityFields();
} else if (parent == mProxySettingsSpinner) {
showProxyFields();
diff --git a/tests/unit/src/com/android/settings/TetherServiceTest.java b/tests/unit/src/com/android/settings/TetherServiceTest.java
new file mode 100644
index 0000000..09c6119
--- /dev/null
+++ b/tests/unit/src/com/android/settings/TetherServiceTest.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2016 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;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE;
+import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK;
+import static android.net.ConnectivityManager.EXTRA_REM_TETHER_TYPE;
+import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION;
+import static android.net.ConnectivityManager.EXTRA_SET_ALARM;
+import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
+import static android.net.ConnectivityManager.TETHERING_INVALID;
+import static android.net.ConnectivityManager.TETHERING_USB;
+import static android.net.ConnectivityManager.TETHERING_WIFI;
+import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
+
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.test.ServiceTestCase;
+import android.test.mock.MockResources;
+import android.util.Log;
+
+import com.android.settings.TetherService;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.ref.WeakReference;
+
+public class TetherServiceTest extends ServiceTestCase<TetherService> {
+
+ private static final String TAG = "TetherServiceTest";
+ private static final String TEST_RESPONSE_ACTION = "testProvisioningResponseAction";
+ private static final String TEST_NO_UI_ACTION = "testNoUiProvisioningRequestAction";
+ private static final int BOGUS_RECEIVER_RESULT = -5;
+ private static final int TEST_CHECK_PERIOD = 100;
+ private static final int MS_PER_HOUR = 60 * 60 * 1000;
+ private static final int SHORT_TIMEOUT = 100;
+ private static final int PROVISION_TIMEOUT = 1000;
+
+ private TetherService mService;
+ private MockResources mResources;
+ int mLastReceiverResultCode = BOGUS_RECEIVER_RESULT;
+ private int mLastTetherRequestType = TETHERING_INVALID;
+ private int mProvisionResponse = BOGUS_RECEIVER_RESULT;
+ private ProvisionReceiver mProvisionReceiver;
+ private Receiver mResultReceiver;
+
+ @Mock private AlarmManager mAlarmManager;
+ @Mock private ConnectivityManager mConnectivityManager;
+ @Mock private WifiManager mWifiManager;
+ @Mock private SharedPreferences mPrefs;
+ @Mock private Editor mPrefEditor;
+ @Captor private ArgumentCaptor<PendingIntent> mPiCaptor;
+ @Captor private ArgumentCaptor<String> mStoredTypes;
+
+ public TetherServiceTest() {
+ super(TetherService.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+
+ mResources = new MockResources();
+ mContext = new TestContextWrapper(getContext());
+ setContext(mContext);
+
+ mResultReceiver = new Receiver(this);
+ mLastReceiverResultCode = BOGUS_RECEIVER_RESULT;
+ mProvisionResponse = Activity.RESULT_OK;
+ mProvisionReceiver = new ProvisionReceiver();
+ IntentFilter filter = new IntentFilter(TEST_NO_UI_ACTION);
+ filter.addCategory(Intent.CATEGORY_DEFAULT);
+ mContext.registerReceiver(mProvisionReceiver, filter);
+
+ final String CURRENT_TYPES = "currentTethers";
+ when(mPrefs.getString(CURRENT_TYPES, "")).thenReturn("");
+ when(mPrefs.edit()).thenReturn(mPrefEditor);
+ when(mPrefEditor.putString(eq(CURRENT_TYPES), mStoredTypes.capture())).thenReturn(
+ mPrefEditor);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mContext.unregisterReceiver(mProvisionReceiver);
+ super.tearDown();
+ }
+
+ private void cancelAllProvisioning() {
+ int[] types = new int[]{TETHERING_BLUETOOTH, TETHERING_WIFI, TETHERING_USB};
+ for (int type : types) {
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_REM_TETHER_TYPE, type);
+ startService(intent);
+ }
+ }
+
+ public void testStartForProvision() {
+ runProvisioningForType(TETHERING_WIFI);
+
+ assertTrue(waitForProvisionRequest(TETHERING_WIFI));
+ assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
+ }
+
+ public void testScheduleRechecks() {
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_WIFI);
+ intent.putExtra(EXTRA_SET_ALARM, true);
+ startService(intent);
+
+ long period = TEST_CHECK_PERIOD * MS_PER_HOUR;
+ verify(mAlarmManager).setRepeating(eq(AlarmManager.ELAPSED_REALTIME), anyLong(),
+ eq(period), mPiCaptor.capture());
+ PendingIntent pi = mPiCaptor.getValue();
+ assertEquals(TetherService.class.getName(), pi.getIntent().getComponent().getClassName());
+ }
+
+ public void testStartMultiple() {
+ runProvisioningForType(TETHERING_WIFI);
+
+ assertTrue(waitForProvisionRequest(TETHERING_WIFI));
+ assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
+
+ runProvisioningForType(TETHERING_USB);
+
+ assertTrue(waitForProvisionRequest(TETHERING_USB));
+ assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
+
+ runProvisioningForType(TETHERING_BLUETOOTH);
+
+ assertTrue(waitForProvisionRequest(TETHERING_BLUETOOTH));
+ assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
+ }
+
+ public void testPersistTypes() {
+ runProvisioningForType(TETHERING_WIFI);
+
+ waitForProvisionRequest(TETHERING_WIFI);
+ waitForProvisionResponse(TETHER_ERROR_NO_ERROR);
+
+ runProvisioningForType(TETHERING_BLUETOOTH);
+
+ waitForProvisionRequest(TETHERING_BLUETOOTH);
+ waitForProvisionResponse(TETHER_ERROR_NO_ERROR);
+
+ shutdownService();
+ assertEquals(TETHERING_WIFI + "," + TETHERING_BLUETOOTH, mStoredTypes.getValue());
+ }
+
+ public void testFailureStopsTethering_Wifi() {
+ mProvisionResponse = Activity.RESULT_CANCELED;
+
+ runProvisioningForType(TETHERING_WIFI);
+
+ assertTrue(waitForProvisionRequest(TETHERING_WIFI));
+ assertTrue(waitForProvisionResponse(TETHER_ERROR_PROVISION_FAILED));
+
+ verify(mWifiManager).setWifiApEnabled(isNull(WifiConfiguration.class), eq(false));
+ }
+
+ public void testFailureStopsTethering_Usb() {
+ mProvisionResponse = Activity.RESULT_CANCELED;
+
+ runProvisioningForType(TETHERING_USB);
+
+ assertTrue(waitForProvisionRequest(TETHERING_USB));
+ assertTrue(waitForProvisionResponse(TETHER_ERROR_PROVISION_FAILED));
+
+ verify(mConnectivityManager).setUsbTethering(eq(false));
+ }
+
+ public void testCancelAlarm() {
+ runProvisioningForType(TETHERING_WIFI);
+
+ assertTrue(waitForProvisionRequest(TETHERING_WIFI));
+ assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
+
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_REM_TETHER_TYPE, TETHERING_WIFI);
+ startService(intent);
+
+ verify(mAlarmManager).cancel(mPiCaptor.capture());
+ PendingIntent pi = mPiCaptor.getValue();
+ assertEquals(TetherService.class.getName(), pi.getIntent().getComponent().getClassName());
+ }
+
+ private void runProvisioningForType(int type) {
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
+ intent.putExtra(EXTRA_RUN_PROVISION, true);
+ intent.putExtra(EXTRA_PROVISION_CALLBACK, mResultReceiver);
+ startService(intent);
+ }
+
+ private boolean waitForProvisionRequest(int expectedType) {
+ long startTime = SystemClock.uptimeMillis();
+ while (true) {
+ if (mLastTetherRequestType == expectedType) {
+ mLastTetherRequestType = -1;
+ return true;
+ }
+ if ((SystemClock.uptimeMillis() - startTime) > PROVISION_TIMEOUT) {
+ Log.v(TAG, String.format(
+ "waitForProvisionRequest timeout: expected=%d, actual=%d",
+ expectedType, mLastTetherRequestType));
+ return false;
+ }
+ SystemClock.sleep(SHORT_TIMEOUT);
+ }
+ }
+
+ private boolean waitForProvisionResponse(int expectedValue) {
+ long startTime = SystemClock.uptimeMillis();
+ while (true) {
+ if (mLastReceiverResultCode == expectedValue) {
+ mLastReceiverResultCode = BOGUS_RECEIVER_RESULT;
+ return true;
+ }
+ if ((SystemClock.uptimeMillis() - startTime) > PROVISION_TIMEOUT) {
+ Log.v(TAG, String.format(
+ "waitForProvisionResponse timeout: expected=%d, actual=%d",
+ expectedValue, mLastReceiverResultCode));
+ return false;
+ }
+ SystemClock.sleep(SHORT_TIMEOUT);
+ }
+ }
+
+ private static class MockResources extends android.test.mock.MockResources {
+ @Override
+ public int getInteger(int id) {
+ switch(id) {
+ case com.android.internal.R.integer.config_mobile_hotspot_provision_check_period:
+ return TEST_CHECK_PERIOD;
+ default:
+ return 0;
+ }
+ }
+
+ @Override
+ public String getString(int id) {
+ switch(id) {
+ case com.android.internal.R.string.config_mobile_hotspot_provision_response:
+ return TEST_RESPONSE_ACTION;
+ case com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui:
+ return TEST_NO_UI_ACTION;
+ default:
+ return null;
+ }
+ }
+ }
+
+ private class TestContextWrapper extends ContextWrapper {
+
+ public TestContextWrapper(Context base) {
+ super(base);
+ }
+
+ @Override
+ public Resources getResources() {
+ return mResources;
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ // Stub out prefs to control the persisted tether type list.
+ if (name == "tetherPrefs") {
+ return mPrefs;
+ }
+ return super.getSharedPreferences(name, mode);
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (ALARM_SERVICE.equals(name)) {
+ return mAlarmManager;
+ } else if (CONNECTIVITY_SERVICE.equals(name)) {
+ return mConnectivityManager;
+ } else if (WIFI_SERVICE.equals(name)) {
+ return mWifiManager;
+ }
+
+ return super.getSystemService(name);
+ }
+ }
+
+ private static final class Receiver extends ResultReceiver {
+ final WeakReference<TetherServiceTest> mTest;
+
+ Receiver(TetherServiceTest test) {
+ super(null);
+ mTest = new WeakReference<TetherServiceTest>(test);
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ TetherServiceTest test = mTest.get();
+ if (test != null) {
+ test.mLastReceiverResultCode = resultCode;
+ }
+ }
+ };
+
+ /**
+ * Stubs out the provisioning app receiver.
+ */
+ private class ProvisionReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mLastTetherRequestType = intent.getIntExtra("TETHER_TYPE", TETHERING_INVALID);
+ sendResponse(mProvisionResponse, context);
+ }
+
+ private void sendResponse(int response, Context context) {
+ Intent responseIntent = new Intent(TEST_RESPONSE_ACTION);
+ responseIntent.putExtra(TetherService.EXTRA_RESULT, response);
+ context.sendBroadcast(
+ responseIntent, android.Manifest.permission.CONNECTIVITY_INTERNAL);
+ }
+ }
+}