Snap for 5262136 from e56df8877a to qt-release

Change-Id: Icdd5c6facb754c84367b2188739fd4a053ec9c4e
This commit is contained in:
android-build-team Robot
2019-01-27 04:12:42 +00:00
143 changed files with 3427 additions and 1285 deletions

View File

@@ -1249,12 +1249,11 @@
android:label="@string/local_backup_password_title"
android:exported="false" />
<activity android:name="CredentialStorage"
<activity android:name=".security.CredentialStorage"
android:theme="@style/Transparent"
android:launchMode="singleTop"
android:configChanges="orientation|keyboardHidden|screenSize">
<intent-filter android:priority="1">
<action android:name="com.android.credentials.UNLOCK" />
<action android:name="com.android.credentials.INSTALL" />
<action android:name="com.android.credentials.RESET" />
<category android:name="android.intent.category.DEFAULT" />
@@ -2978,6 +2977,10 @@
<action android:name="android.settings.panel.action.VOLUME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.settings.panel.action.NFC" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<provider android:name=".slices.SettingsSliceProvider"

View File

@@ -51,6 +51,18 @@
android:background="?android:attr/selectableItemBackground"
android:textColor="?android:attr/colorAccent"/>
<TextView
style="@style/device_info_dialog_label"
android:id="@+id/module_version_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/module_version"/>
<TextView
style="@style/device_info_dialog_value"
android:id="@+id/module_version_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
style="@style/device_info_dialog_label"
android:id="@+id/baseband_version_label"

View File

@@ -85,12 +85,6 @@
android:layout_height="wrap_content"
FaceEnrollAccessibilitySwitch:messageText="@string/security_settings_face_enroll_introduction_accessibility_diversity"/>
<com.android.settings.biometrics.face.FaceEnrollAccessibilityToggle
android:id="@+id/toggle_vision"
android:layout_width="match_parent"
android:layout_height="wrap_content"
FaceEnrollAccessibilitySwitch:messageText="@string/security_settings_face_enroll_introduction_accessibility_vision"/>
</LinearLayout>
</FrameLayout>

View File

@@ -34,7 +34,6 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/homepage_card_dismissal_margin_bottom"
android:gravity="bottom|end">
<Button
@@ -49,8 +48,8 @@
style="@style/ContextualCardDismissalButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/homepage_card_dismissal_button_side_margin"
android:layout_marginEnd="@dimen/homepage_card_dismissal_button_side_margin"
android:layout_marginStart="@dimen/homepage_card_dismissal_button_margin_start"
android:layout_marginEnd="@dimen/homepage_card_dismissal_button_margin_end"
android:text="@string/contextual_card_dismiss_keep"/>
</LinearLayout>

View File

@@ -31,6 +31,8 @@
android:layout_weight="1"
android:textSize="18sp"
android:gravity="center_vertical"
android:maxLines="1"
android:ellipsize="end"
style="@style/info_label"/>
<ProgressBar

View File

@@ -335,10 +335,10 @@
<dimen name="homepage_half_card_title_margin_top">12dp</dimen>
<!-- Homepage dismissal cards size and padding -->
<dimen name="homepage_card_dismissal_margin_top">16dp</dimen>
<dimen name="homepage_card_dismissal_margin_bottom">2dp</dimen>
<dimen name="homepage_card_dismissal_margin_top">12dp</dimen>
<dimen name="homepage_card_dismissal_side_margin">16dp</dimen>
<dimen name="homepage_card_dismissal_button_side_margin">6dp</dimen>
<dimen name="homepage_card_dismissal_button_margin_start">4dp</dimen>
<dimen name="homepage_card_dismissal_button_margin_end">6dp</dimen>
<!-- Horizontal divider size and margin -->
<dimen name="horizontal_divider_margin_top">4dp</dimen>

View File

@@ -1430,6 +1430,16 @@
<!-- Title shown on security settings to allow the user to change their lockscreen password [CHAR LIMIT=22]-->
<string name="unlock_change_lock_password_title">Change unlock password</string>
<!-- Footer preference text in the screen lock type picker to indicate which app is requesting a new screen lock and that it requests for a strong PIN or password [CHAR LIMIT=NONE] -->
<string name="unlock_footer_high_complexity_requested"><xliff:g id="app_name" example="Gmail">%1$s</xliff:g> requests a strong PIN or password.</string>
<!-- Footer preference text in the screen lock type picker to indicate which app is requesting a new screen lock and that it requests for a medium strength PIN or password [CHAR LIMIT=NONE] -->
<string name="unlock_footer_medium_complexity_requested"><xliff:g id="app_name" example="Gmail">%1$s</xliff:g> requests a new PIN or password.</string>
<!-- Footer preference text in the screen lock type picker to indicate which app is requesting a new screen lock and it requests for any screen lock [CHAR LIMIT=NONE] -->
<string name="unlock_footer_low_complexity_requested"><xliff:g id="app_name" example="Gmail">%1$s</xliff:g> requests a new pattern, PIN or password.</string>
<!-- Footer preference text in the screen lock type picker to indicate which app is requesting a new screen lock [CHAR LIMIT=NONE] -->
<string name="unlock_footer_none_complexity_requested"><xliff:g id="app_name" example="Gmail">%1$s</xliff:g> requests a new screen lock.</string>
<!-- Message shown on the lock screen when the user incorrectly enters their lock and it counts towards the max attempts before their data on the device is wiped. [CHAR LIMIT=NONE] -->
<string name="lock_failed_attempts_before_wipe">Try again. Attempt <xliff:g id="current_attempts">%1$d</xliff:g> of <xliff:g id="total_attempts">%2$d</xliff:g>.</string>
@@ -2902,6 +2912,8 @@
<string name="kernel_version">Kernel version</string>
<!-- About phone screen, setting option name [CHAR LIMIT=40] -->
<string name="build_number">Build number</string>
<!-- About phone screen, mainline module versions [CHAR LIMIT=40] -->
<string name="module_version">Mainline module versions</string>
<!-- About phone screen, show when a value of some status item is unavailable. -->
<string name="device_info_not_available">Not available</string>
@@ -4444,6 +4456,10 @@
<string name="keyboard_shortcuts_helper">Keyboard shortcuts helper</string>
<!-- Summary text for the 'keyboard shortcuts helper' dialog. [CHAR LIMIT=100] -->
<string name="keyboard_shortcuts_helper_summary">Display available shortcuts</string>
<!-- Title for the 'Work profile' preference category inside Languages and inputs'. [CHAR LIMIT=45] -->
<string name="language_and_input_for_work_category_title">Work profile</string>
<!-- Title for the 'Virtual keyboards for work' preference. [CHAR LIMIT=45] -->
<string name="virtual_keyboards_for_work_title">Virtual keyboard for work</string>
<!-- Summary text for keyboards when no layout has been selected. [CHAR LIMIT=35] -->
<string name="default_keyboard_layout">Default</string>
@@ -4475,6 +4491,8 @@
<!-- User dictionary settings --><skip />
<!-- User dictionary settings. The title of the list item to go into the Personal dictionary settings screen. [CHAR LIMIT=35] -->
<string name="user_dict_settings_title">Personal dictionary</string>
<!-- Title for the 'Spell checker for work' preference. [CHAR LIMIT=45] -->
<string name="user_dict_settings_for_work_title">Personal dictionary for work</string>
<!-- User dictionary settings. The summary of the list item to go into the Personal dictionary settings screen. -->
<string name="user_dict_settings_summary">""</string>
<!-- User dictionary settings. The title of the menu item to add a new word to the user dictionary. -->
@@ -5731,10 +5749,6 @@
<string name="credentials_erased">Credential storage is erased.</string>
<!-- Toast message [CHAR LIMIT=30] when credential storage containing private keys and certificates could not be erased (opposite of string credentials_erased) -->
<string name="credentials_not_erased">Credential storage couldn\u2019t be erased.</string>
<!-- This string is in a dialog, and the dialog shows up on a device that's managed by a user's company. It lets the user know that they need to have a secure lock screen (PIN, password, or pattern) before they can use credential storage [CHAR LIMIT=NONE] -->
<string name="credentials_configure_lock_screen_hint">Before you can use credential storage, your device need to have a secure lock screen</string>
<!-- This string is for the content of the button that leads user to lock screen settings [CHAR LIMIT=20] -->
<string name="credentials_configure_lock_screen_button">SET LOCK</string>
<!-- Title of Usage Access preference item [CHAR LIMIT=30] -->
<string name="usage_access_title">Apps with usage access</string>
@@ -6368,6 +6382,8 @@
<!-- Title for spell checker settings -->
<string name="spellcheckers_settings_title">Spell checker</string>
<!-- Title for spell checker settings for work [CHAR LIMIT=45]-->
<string name="spellcheckers_settings_for_work_title">Spell checker for work</string>
<!-- Prompt for the user to enter their current full-backup password -->
<string name="current_backup_pw_prompt">Type your current full backup password here</string>
@@ -10363,6 +10379,17 @@
<string name="mobile_data_settings_title">Mobile data</string>
<!-- Mobile network settings screen, title of Mobile data switch preference [CHAR LIMIT=NONE] -->
<string name="mobile_data_settings_summary">Access data using mobile network</string>
<!-- Mobile network settings screen, title of item showing the name of the default subscription
that will be used for calls. This only appears in multi-SIM mode. [CHAR LIMIT=NONE] -->
<string name="calls_preference">Calls preference</string>
<!-- Mobile network settings screen, title of item showing the name of the default subscription
that will be used for SMS messages. This only appears in multi-SIM mode. [CHAR LIMIT=NONE] -->
<string name="sms_preference">SMS preference</string>
<!-- Mobile network settings screen, a label in a chooser dialog that appears when choosing the
default subscription to use for either calls or SMS when in multi-SIM mode. This label means
that the user will be asked which mobile network subscription to use every time they place a
call or send an SMS, instead of defaulting to one particular subscription. [CHAR LIMIT=40]-->
<string name="calls_and_sms_ask_every_time">Ask every time</string>
<!-- Summary of the 'Mobile network' item on the Network & internet page when there is no mobile
service setup yet (eg no SIM card inserted and no eSIM configured). Tapping it leads to a
@@ -10450,8 +10477,8 @@
<!-- See less items in contextual homepage [CHAR LIMIT=30]-->
<string name="see_less">See less</string>
<!-- Title for Network connection request Dialog [CHAR LIMIT=30] -->
<string name="network_connection_request_dialog_title">Choose a device</string>
<!-- Title for Network connection request Dialog [CHAR LIMIT=60] -->
<string name="network_connection_request_dialog_title">Device to use with <xliff:g id="appName" example="ThirdPartyAppName">%1$s</xliff:g></string>
<!-- Message for Network connection timeout Dialog [CHAR LIMIT=NONE] -->
<string name="network_connection_timeout_dialog_message">No devices found. Make sure devices are turned on and available to connect.</string>
<!-- OK button for Network connection timeout Dialog [CHAR LIMIT=30] -->

View File

@@ -460,8 +460,7 @@
<style name="ContextualCardDismissalButton"
parent="android:Widget.DeviceDefault.Button.Borderless.Colored">
<item name="android:minWidth">10dp</item>
<item name="android:minHeight">16dp</item>
<item name="android:minWidth">24dp</item>
<item name="android:textAllCaps">false</item>
</style>

View File

@@ -92,4 +92,27 @@
android:summary="@string/vibrate_input_devices_summary"
settings:controller="com.android.settings.inputmethod.GameControllerPreferenceController" />
<com.android.settings.widget.WorkOnlyCategory
android:key="language_and_input_for_work_category"
android:title="@string/language_and_input_for_work_category_title">
<Preference
android:key="virtual_keyboards_for_work_pref"
android:title="@string/virtual_keyboards_for_work_title"
android:fragment="com.android.settings.inputmethod.VirtualKeyboardFragment"
settings:controller="com.android.settings.inputmethod.VirtualKeyboardForWorkPreferenceController" />
<Preference
android:key="spellcheckers_settings_for_work_pref"
android:title="@string/spellcheckers_settings_for_work_title"
android:fragment="com.android.settings.inputmethod.SpellCheckersSettings"
settings:controller="com.android.settings.language.UserDictionaryForWorkPreferenceController" />
<Preference
android:key="user_dictionary_settings_for_work_pref"
android:title="@string/user_dict_settings_for_work_title"
android:fragment="com.android.settings.inputmethod.UserDictionaryList"
settings:controller="com.android.settings.inputmethod.SpellCheckerForWorkPreferenceController" />
</com.android.settings.widget.WorkOnlyCategory>
</PreferenceScreen>

View File

@@ -25,8 +25,7 @@
<com.android.settings.widget.GearPreference
android:key="default_assist"
android:title="@string/default_assist_title"
android:summary="@string/summary_placeholder"
android:fragment="com.android.settings.applications.assist.DefaultAssistPicker" />
android:summary="@string/summary_placeholder" />
<Preference
android:key="gesture_assist_application"

View File

@@ -17,24 +17,37 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="mobile_network_pref_screen"
settings:initialExpandedChildrenCount="5">
settings:initialExpandedChildrenCount="7">
<com.android.settings.datausage.DataUsageSummaryPreference
android:key="status_header"
android:visibility="gone"
android:selectable="false" />
<ListPreference
android:key="calls_preference"
android:title="@string/calls_preference"
settings:controller="com.android.settings.network.telephony.CallsDefaultSubscriptionController"
settings:allowDividerAbove="true" />
<ListPreference
android:key="sms_preference"
android:title="@string/sms_preference"
settings:controller="com.android.settings.network.telephony.SmsDefaultSubscriptionController" />
<Preference
android:key="cdma_lte_data_service_key"
android:title="@string/cdma_lte_data_service"
settings:controller="com.android.settings.network.telephony.DataServiceSetupPreferenceController">
</Preference>
settings:controller="com.android.settings.network.telephony.DataServiceSetupPreferenceController"
settings:allowDividerAbove="true"
settings:allowDividerBelow="false" />
<SwitchPreference
android:key="mobile_data_enable"
android:title="@string/mobile_data_settings_title"
android:summary="@string/mobile_data_settings_summary"
settings:controller="com.android.settings.network.telephony.MobileDataPreferenceController"/>
settings:controller="com.android.settings.network.telephony.MobileDataPreferenceController"
settings:allowDividerAbove="true"/>
<com.android.settingslib.RestrictedSwitchPreference
android:key="button_roaming_key"

View File

@@ -40,8 +40,8 @@ import com.android.settings.core.InstrumentedFragment;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
import java.util.List;
@@ -150,8 +150,8 @@ public class EncryptionInterstitial extends SettingsActivity {
GlifLayout layout = (GlifLayout) view;
layout.setHeaderText(getActivity().getTitle());
final ButtonFooterMixin buttonFooterMixin = layout.getMixin(ButtonFooterMixin.class);
buttonFooterMixin.setSecondaryButton(
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
mixin.setSecondaryButton(
new FooterButton.Builder(getContext())
.setText(R.string.encryption_interstitial_no)
.setListener(this::onNoButtonClicked)
@@ -160,7 +160,7 @@ public class EncryptionInterstitial extends SettingsActivity {
.build()
);
buttonFooterMixin.setPrimaryButton(
mixin.setPrimaryButton(
new FooterButton.Builder(getContext())
.setText(R.string.encryption_interstitial_yes)
.setListener(this::onYesButtonClicked)

View File

@@ -66,9 +66,9 @@ import com.android.settings.password.ConfirmLockPattern;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.google.android.setupcompat.TemplateLayout;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.item.FooterButton.ButtonType;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupcompat.template.FooterButton.ButtonType;
import java.util.List;
@@ -417,8 +417,8 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
}
final TemplateLayout layout = mContentView.findViewById(R.id.setup_wizard_layout);
final ButtonFooterMixin buttonFooterMixin = layout.getMixin(ButtonFooterMixin.class);
buttonFooterMixin.setPrimaryButton(
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
mixin.setPrimaryButton(
new FooterButton.Builder(getActivity())
.setText(R.string.master_clear_button_text)
.setListener(mInitiateListener)
@@ -426,7 +426,7 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
.setTheme(R.style.SudGlifButton_Primary)
.build()
);
mInitiateButton = buttonFooterMixin.getPrimaryButton();
mInitiateButton = mixin.getPrimaryButton();
}
private void getContentDescription(View v, StringBuffer description) {

View File

@@ -45,9 +45,9 @@ import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.google.android.setupcompat.TemplateLayout;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.item.FooterButton.ButtonType;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupcompat.template.FooterButton.ButtonType;
/**
* Confirm and execute a reset of the device to a clean "just out of the box"
@@ -154,8 +154,8 @@ public class MasterClearConfirm extends InstrumentedFragment {
private void establishFinalConfirmationState() {
final TemplateLayout layout = mContentView.findViewById(R.id.setup_wizard_layout);
final ButtonFooterMixin buttonFooterMixin = layout.getMixin(ButtonFooterMixin.class);
buttonFooterMixin.setPrimaryButton(
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
mixin.setPrimaryButton(
new FooterButton.Builder(getActivity())
.setText(R.string.master_clear_button_text)
.setListener(mFinalClickListener)

View File

@@ -32,7 +32,6 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.transition.TransitionManager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -103,8 +102,6 @@ public class SettingsActivity extends SettingsBaseActivity
*/
public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
public static final String BACK_STACK_PREFS = ":settings:prefs";
// extras that allow any preference activity to be launched as part of a wizard
// show Back and Next buttons? takes boolean parameter
@@ -374,14 +371,13 @@ public class SettingsActivity extends SettingsBaseActivity
setTitleFromIntent(intent);
Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
switchToFragment(initialFragmentName, initialArguments, true, false,
mInitialTitleResId, mInitialTitle, false);
switchToFragment(initialFragmentName, initialArguments, true,
mInitialTitleResId, mInitialTitle);
} else {
// Show search icon as up affordance if we are displaying the main Dashboard
mInitialTitleResId = R.string.dashboard_title;
switchToFragment(TopLevelSettings.class.getName(), null /* args */, false, false,
mInitialTitleResId, mInitialTitle, false);
switchToFragment(TopLevelSettings.class.getName(), null /* args */, false,
mInitialTitleResId, mInitialTitle);
}
}
@@ -567,7 +563,7 @@ public class SettingsActivity extends SettingsBaseActivity
* Switch to a specific Fragment with taking care of validation, Title and BackStack
*/
private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {
int titleResId, CharSequence title) {
Log.d(LOG_TAG, "Switching to fragment " + fragmentName);
if (validate && !isValidFragment(fragmentName)) {
throw new IllegalArgumentException("Invalid fragment for this activity: "
@@ -576,12 +572,6 @@ public class SettingsActivity extends SettingsBaseActivity
Fragment f = Fragment.instantiate(this, fragmentName, args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.main_content, f);
if (withTransition) {
TransitionManager.beginDelayedTransition(mContent);
}
if (addToBackStack) {
transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
}
if (titleResId > 0) {
transaction.setBreadCrumbTitle(titleResId);
} else if (title != null) {

View File

@@ -314,6 +314,9 @@ public class RecentAppsPreferenceController extends AbstractPreferenceController
Log.d(TAG, "System package, skipping " + pkgName);
return false;
}
if (AppUtils.isHiddenSystemModule(mContext, pkgName)) {
return false;
}
final Intent launchIntent = new Intent().addCategory(Intent.CATEGORY_LAUNCHER)
.setPackage(pkgName);

View File

@@ -20,9 +20,11 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserManager;
import android.permission.PermissionControllerManager;
import android.text.TextUtils;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.internal.util.CollectionUtils;
import com.android.settings.R;
@@ -43,6 +45,10 @@ public abstract class DefaultAppShortcutPreferenceControllerBase extends BasePre
private final RoleManager mRoleManager;
private boolean mAppQualified;
private PreferenceScreen mPreferenceScreen;
public DefaultAppShortcutPreferenceControllerBase(Context context, String preferenceKey,
String roleName, String packageName) {
super(context, preferenceKey);
@@ -51,6 +57,17 @@ public abstract class DefaultAppShortcutPreferenceControllerBase extends BasePre
mPackageName = packageName;
mRoleManager = context.getSystemService(RoleManager.class);
// TODO: STOPSHIP(b/110557011): Remove this check once we have all default apps migrated.
if (mRoleName != null) {
final PermissionControllerManager permissionControllerManager =
mContext.getSystemService(PermissionControllerManager.class);
permissionControllerManager.isApplicationQualifiedForRole(mRoleName, mPackageName,
mContext.getMainExecutor(), qualified -> {
mAppQualified = qualified;
refreshAvailability();
});
}
}
// TODO: STOPSHIP(b/110557011): Remove this once we have all default apps migrated.
@@ -59,6 +76,23 @@ public abstract class DefaultAppShortcutPreferenceControllerBase extends BasePre
this(context, preferenceKey, null /* roleName */, packageName);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreferenceScreen = screen;
}
private void refreshAvailability() {
if (mPreferenceScreen != null) {
final Preference preference = mPreferenceScreen.findPreference(getPreferenceKey());
if (preference != null) {
preference.setVisible(isAvailable());
updateState(preference);
}
}
}
@Override
public int getAvailabilityStatus() {
if (mContext.getSystemService(UserManager.class).isManagedProfile()) {
@@ -104,7 +138,7 @@ public abstract class DefaultAppShortcutPreferenceControllerBase extends BasePre
protected boolean hasAppCapability() {
// TODO: STOPSHIP(b/110557011): Remove this check once we have all default apps migrated.
if (mRoleName != null) {
return mRoleManager.isRoleAvailable(mRoleName);
return mAppQualified;
}
return false;
}

View File

@@ -1,246 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.applications.assist;
import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.provider.Settings;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionServiceInfo;
import android.speech.RecognitionService;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.internal.app.AssistUtils;
import com.android.settings.R;
import com.android.settings.applications.defaultapps.DefaultAppPickerFragment;
import com.android.settingslib.applications.DefaultAppInfo;
import com.android.settingslib.widget.CandidateInfo;
import java.util.ArrayList;
import java.util.List;
public class DefaultAssistPicker extends DefaultAppPickerFragment {
private static final String TAG = "DefaultAssistPicker";
private static final Intent ASSIST_SERVICE_PROBE =
new Intent(VoiceInteractionService.SERVICE_INTERFACE);
private static final Intent ASSIST_ACTIVITY_PROBE =
new Intent(Intent.ACTION_ASSIST);
@VisibleForTesting
final List<Info> mAvailableAssistants = new ArrayList<>();
private AssistUtils mAssistUtils;
private ActivityManager mActivityManager;
@Override
public int getMetricsCategory() {
return SettingsEnums.DEFAULT_ASSIST_PICKER;
}
@Override
protected boolean shouldShowItemNone() {
return true;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mAssistUtils = new AssistUtils(context);
mActivityManager = context.getSystemService(ActivityManager.class);
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.default_assist_settings;
}
@Override
protected List<DefaultAppInfo> getCandidates() {
mAvailableAssistants.clear();
addAssistServices();
addAssistActivities();
final List<String> packages = new ArrayList<>();
final List<DefaultAppInfo> candidates = new ArrayList<>();
for (Info info : mAvailableAssistants) {
final String packageName = info.component.getPackageName();
if (packages.contains(packageName)) {
// A service appears before an activity thus overrides it if from the same package.
continue;
}
packages.add(packageName);
candidates.add(new DefaultAppInfo(getContext(), mPm, mUserId, info.component));
}
return candidates;
}
@Override
protected String getDefaultKey() {
final ComponentName cn = getCurrentAssist();
if (cn != null) {
return new DefaultAppInfo(getContext(), mPm, mUserId, cn).getKey();
}
return null;
}
@Override
protected String getConfirmationMessage(CandidateInfo appInfo) {
if (appInfo == null) {
return null;
}
return getContext().getString(R.string.assistant_security_warning, appInfo.loadLabel());
}
@Override
protected boolean setDefaultKey(String key) {
if (TextUtils.isEmpty(key)) {
setAssistNone();
return true;
}
ComponentName cn = ComponentName.unflattenFromString(key);
final Info info = findAssistantByPackageName(cn.getPackageName());
if (info == null) {
setAssistNone();
return true;
}
if (info.isVoiceInteractionService()) {
setAssistService(info);
} else {
setAssistActivity(info);
}
return true;
}
public ComponentName getCurrentAssist() {
return mAssistUtils.getAssistComponentForUser(mUserId);
}
@VisibleForTesting
void addAssistServices() {
if (mActivityManager.isLowRamDevice()) {
return;
}
final List<ResolveInfo> services = mPm.queryIntentServices(
ASSIST_SERVICE_PROBE, PackageManager.GET_META_DATA);
for (ResolveInfo resolveInfo : services) {
VoiceInteractionServiceInfo voiceInteractionServiceInfo =
new VoiceInteractionServiceInfo(mPm, resolveInfo.serviceInfo);
if (!voiceInteractionServiceInfo.getSupportsAssist()) {
continue;
}
mAvailableAssistants.add(new Info(
new ComponentName(resolveInfo.serviceInfo.packageName,
resolveInfo.serviceInfo.name),
voiceInteractionServiceInfo));
}
}
private void addAssistActivities() {
final List<ResolveInfo> activities = mPm.queryIntentActivities(
ASSIST_ACTIVITY_PROBE, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : activities) {
mAvailableAssistants.add(new Info(
new ComponentName(resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name)));
}
}
private Info findAssistantByPackageName(String packageName) {
for (Info info : mAvailableAssistants) {
if (TextUtils.equals(info.component.getPackageName(), packageName)) {
return info;
}
}
return null;
}
private void setAssistNone() {
Settings.Secure.putString(getContext().getContentResolver(),
Settings.Secure.ASSISTANT, "");
Settings.Secure.putString(getContext().getContentResolver(),
Settings.Secure.VOICE_INTERACTION_SERVICE, "");
Settings.Secure.putString(getContext().getContentResolver(),
Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer());
}
private void setAssistService(Info serviceInfo) {
final String serviceComponentName = serviceInfo.component.
flattenToShortString();
final String serviceRecognizerName = new ComponentName(
serviceInfo.component.getPackageName(),
serviceInfo.voiceInteractionServiceInfo.getRecognitionService())
.flattenToShortString();
Settings.Secure.putString(getContext().getContentResolver(),
Settings.Secure.ASSISTANT, serviceComponentName);
Settings.Secure.putString(getContext().getContentResolver(),
Settings.Secure.VOICE_INTERACTION_SERVICE, serviceComponentName);
Settings.Secure.putString(getContext().getContentResolver(),
Settings.Secure.VOICE_RECOGNITION_SERVICE, serviceRecognizerName);
}
private void setAssistActivity(Info activityInfo) {
Settings.Secure.putString(getContext().getContentResolver(),
Settings.Secure.ASSISTANT, activityInfo.component.flattenToShortString());
Settings.Secure.putString(getContext().getContentResolver(),
Settings.Secure.VOICE_INTERACTION_SERVICE, "");
Settings.Secure.putString(getContext().getContentResolver(),
Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer());
}
private String getDefaultRecognizer() {
final ResolveInfo resolveInfo = mPm.resolveService(
new Intent(RecognitionService.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
if (resolveInfo == null || resolveInfo.serviceInfo == null) {
Log.w(TAG, "Unable to resolve default voice recognition service.");
return "";
}
return new ComponentName(resolveInfo.serviceInfo.packageName,
resolveInfo.serviceInfo.name).flattenToShortString();
}
static class Info {
public final ComponentName component;
public final VoiceInteractionServiceInfo voiceInteractionServiceInfo;
Info(ComponentName component) {
this.component = component;
this.voiceInteractionServiceInfo = null;
}
Info(ComponentName component, VoiceInteractionServiceInfo voiceInteractionServiceInfo) {
this.component = component;
this.voiceInteractionServiceInfo = voiceInteractionServiceInfo;
}
public boolean isVoiceInteractionService() {
return voiceInteractionServiceInfo != null;
}
}
}

View File

@@ -16,15 +16,19 @@
package com.android.settings.applications.assist;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.provider.Settings;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionServiceInfo;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.internal.app.AssistUtils;
import com.android.settings.R;
@@ -38,6 +42,7 @@ public class DefaultAssistPreferenceController extends DefaultAppPreferenceContr
private final AssistUtils mAssistUtils;
private final boolean mShowSetting;
private final String mPrefKey;
private final Intent mIntent;
public DefaultAssistPreferenceController(Context context, String prefKey,
boolean showSetting) {
@@ -45,6 +50,15 @@ public class DefaultAssistPreferenceController extends DefaultAppPreferenceContr
mPrefKey = prefKey;
mShowSetting = showSetting;
mAssistUtils = new AssistUtils(context);
final String packageName = mPackageManager.getPermissionControllerPackageName();
if (packageName != null) {
mIntent = new Intent(Intent.ACTION_MANAGE_DEFAULT_APP)
.setPackage(packageName)
.putExtra(Intent.EXTRA_ROLE_NAME, RoleManager.ROLE_ASSISTANT);
} else {
mIntent = null;
}
}
@Override
@@ -72,6 +86,17 @@ public class DefaultAssistPreferenceController extends DefaultAppPreferenceContr
.setComponent(new ComponentName(cn.getPackageName(), activity));
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (TextUtils.equals(preference.getKey(), "default_assist")) {
if (mIntent != null) {
mContext.startActivity(mIntent);
}
return true;
}
return false;
}
@Override
public boolean isAvailable() {
return mContext.getResources().getBoolean(R.bool.config_show_assist_and_voice_input);

View File

@@ -32,8 +32,8 @@ import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
import com.android.settings.core.InstrumentedActivity;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
/**
@@ -78,7 +78,7 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
protected boolean mLaunchedConfirmLock;
protected byte[] mToken;
protected int mUserId;
protected ButtonFooterMixin mButtonFooterMixin;
protected FooterBarMixin mFooterBarMixin;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -137,8 +137,8 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
}
protected FooterButton getNextButton() {
if (mButtonFooterMixin != null) {
return mButtonFooterMixin.getPrimaryButton();
if (mFooterBarMixin != null) {
return mFooterBarMixin.getPrimaryButton();
}
return null;
}

View File

@@ -28,7 +28,7 @@ import com.android.settings.R;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.span.LinkSpan;
/**

View File

@@ -33,8 +33,8 @@ import com.android.settings.biometrics.BiometricEnrollSidecar;
import com.android.settings.biometrics.BiometricErrorDialog;
import com.android.settings.biometrics.BiometricsEnrollEnrolling;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import java.util.ArrayList;
@@ -91,8 +91,8 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling {
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
this, android.R.interpolator.linear_out_slow_in);
mButtonFooterMixin = getLayout().getMixin(ButtonFooterMixin.class);
mButtonFooterMixin.setSecondaryButton(
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_enrolling_skip)
.setListener(this::onSkipButtonClick)

View File

@@ -23,8 +23,8 @@ import android.view.View;
import com.android.settings.R;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
/**
* Activity which concludes face enrollment.
@@ -37,8 +37,8 @@ public class FaceEnrollFinish extends BiometricEnrollBase {
setContentView(R.layout.face_enroll_finish);
setHeaderText(R.string.security_settings_face_enroll_finish_title);
mButtonFooterMixin = getLayout().getMixin(ButtonFooterMixin.class);
mButtonFooterMixin.setPrimaryButton(
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
mFooterBarMixin.setPrimaryButton(
new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_done)
.setListener(this::onNextButtonClick)

View File

@@ -34,8 +34,8 @@ import com.android.settings.biometrics.BiometricEnrollIntroduction;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.span.LinkSpan;
public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
@@ -43,7 +43,6 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
private static final String TAG = "FaceIntro";
private FaceManager mFaceManager;
private FaceEnrollAccessibilityToggle mSwitchVision;
private FaceEnrollAccessibilityToggle mSwitchDiversity;
@Override
@@ -57,11 +56,10 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
accessibilityLayout.setVisibility(View.VISIBLE);
});
mSwitchVision = findViewById(R.id.toggle_vision);
mSwitchDiversity = findViewById(R.id.toggle_diversity);
mButtonFooterMixin = getLayout().getMixin(ButtonFooterMixin.class);
mButtonFooterMixin.setSecondaryButton(
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_introduction_cancel)
.setListener(this::onCancelButtonClick)
@@ -70,7 +68,7 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
.build()
);
mButtonFooterMixin.setPrimaryButton(
mFooterBarMixin.setPrimaryButton(
new FooterButton.Builder(this)
.setText(R.string.wizard_next)
.setListener(this::onNextButtonClick)
@@ -108,16 +106,16 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
@Override
protected FooterButton getCancelButton() {
if (mButtonFooterMixin != null) {
return mButtonFooterMixin.getSecondaryButton();
if (mFooterBarMixin != null) {
return mFooterBarMixin.getSecondaryButton();
}
return null;
}
@Override
protected FooterButton getNextButton() {
if (mButtonFooterMixin != null) {
return mButtonFooterMixin.getPrimaryButton();
if (mFooterBarMixin != null) {
return mFooterBarMixin.getPrimaryButton();
}
return null;
}
@@ -167,7 +165,6 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
} else {
intent.setClass(this, FaceEnrollEnrolling.class);
}
intent.putExtra(EXTRA_KEY_REQUIRE_VISION, mSwitchVision.isChecked());
intent.putExtra(EXTRA_KEY_REQUIRE_DIVERSITY, mSwitchDiversity.isChecked());
return intent;
}

View File

@@ -47,8 +47,8 @@ import com.android.settings.biometrics.BiometricErrorDialog;
import com.android.settings.biometrics.BiometricsEnrollEnrolling;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
/**
* Activity which handles the actual enrolling for fingerprint.
@@ -137,8 +137,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
mProgressBar = (ProgressBar) findViewById(R.id.fingerprint_progress_bar);
mVibrator = getSystemService(Vibrator.class);
mButtonFooterMixin = getLayout().getMixin(ButtonFooterMixin.class);
mButtonFooterMixin.setSecondaryButton(
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
.setListener(this::onSkipButtonClick)

View File

@@ -30,8 +30,8 @@ import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricEnrollSidecar.Listener;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
/**
* Activity explaining the fingerprint sensor location for fingerprint enrollment.
@@ -48,8 +48,8 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getContentView());
mButtonFooterMixin = getLayout().getMixin(ButtonFooterMixin.class);
mButtonFooterMixin.setSecondaryButton(
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.skip_label)
.setListener(this::onSkipButtonClick)

View File

@@ -26,8 +26,8 @@ import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
/**
* Activity which concludes fingerprint enrollment.
@@ -42,8 +42,8 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
setContentView(R.layout.fingerprint_enroll_finish);
setHeaderText(R.string.security_settings_fingerprint_enroll_finish_title);
mButtonFooterMixin = getLayout().getMixin(ButtonFooterMixin.class);
mButtonFooterMixin.setSecondaryButton(
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.fingerprint_enroll_button_add)
.setButtonType(FooterButton.ButtonType.SKIP)
@@ -51,7 +51,7 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
.build()
);
mButtonFooterMixin.setPrimaryButton(
mFooterBarMixin.setPrimaryButton(
new FooterButton.Builder(this)
.setText(R.string.security_settings_fingerprint_enroll_done)
.setListener(this::onNextButtonClick)
@@ -65,7 +65,7 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
protected void onResume() {
super.onResume();
FooterButton addButton = mButtonFooterMixin.getSecondaryButton();
FooterButton addButton = mFooterBarMixin.getSecondaryButton();
final FingerprintManager fpm = Utils.getFingerprintManagerOrNull(this);
boolean hideAddAnother = false;

View File

@@ -32,8 +32,8 @@ import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.span.LinkSpan;
public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@@ -47,8 +47,8 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
super.onCreate(savedInstanceState);
mFingerprintManager = Utils.getFingerprintManagerOrNull(this);
mButtonFooterMixin = getLayout().getMixin(ButtonFooterMixin.class);
mButtonFooterMixin.setSecondaryButton(
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_introduction_cancel)
.setListener(this::onCancelButtonClick)
@@ -57,7 +57,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
.build()
);
mButtonFooterMixin.setPrimaryButton(
mFooterBarMixin.setPrimaryButton(
new FooterButton.Builder(this)
.setText(R.string.wizard_next)
.setListener(this::onNextButtonClick)
@@ -95,16 +95,16 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@Override
protected FooterButton getCancelButton() {
if (mButtonFooterMixin != null) {
return mButtonFooterMixin.getSecondaryButton();
if (mFooterBarMixin != null) {
return mFooterBarMixin.getSecondaryButton();
}
return null;
}
@Override
protected FooterButton getNextButton() {
if (mButtonFooterMixin != null) {
return mButtonFooterMixin.getPrimaryButton();
if (mFooterBarMixin != null) {
return mFooterBarMixin.getPrimaryButton();
}
return null;
}

View File

@@ -23,7 +23,7 @@ import android.hardware.fingerprint.FingerprintManager;
import com.android.settings.R;
import com.android.settings.Utils;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.FooterButton;
public class FingerprintSuggestionActivity extends SetupFingerprintEnrollIntroduction {

View File

@@ -24,7 +24,7 @@ import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.FooterButton;
public class SetupFingerprintEnrollFinish extends FingerprintEnrollFinish {

View File

@@ -34,7 +34,7 @@ import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment
import com.android.settings.password.SetupChooseLockGeneric;
import com.android.settings.password.SetupSkipDialog;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.FooterButton;
public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntroduction {
private static final String KEY_LOCK_SCREEN_PRESENT = "wasLockScreenPresent";

View File

@@ -64,7 +64,6 @@ public class SettingsBaseActivity extends FragmentActivity {
final TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme);
if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
getWindow().addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
super.setContentView(R.layout.settings_base_layout);

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2019 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.core;
import android.content.Context;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.settings.Utils;
/**
* Abstract class to provide additional logic to deal with optional {@link Preference} entries that
* are used only when work profile is enabled.
*
* <p>TODO(b/123376083): Consider merging this into {@link BasePreferenceController}.</p>
*/
public abstract class WorkProfilePreferenceController extends BasePreferenceController {
@Nullable
private final UserHandle mWorkProfileUser;
/**
* Constructor of {@link WorkProfilePreferenceController}. Called by
* {@link BasePreferenceController#createInstance(Context, String)} through reflection.
*
* @param context {@link Context} to instantiate this controller.
* @param preferenceKey Preference key to be associated with the {@link Preference}.
*/
public WorkProfilePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mWorkProfileUser = Utils.getManagedProfile(UserManager.get(context));
}
/**
* @return Non-{@code null} {@link UserHandle} when a work profile is enabled.
* Otherwise {@code null}.
*/
@Nullable
protected UserHandle getWorkProfileUser() {
return mWorkProfileUser;
}
/**
* Called back from {@link #handlePreferenceTreeClick(Preference)} to associate source metrics
* category.
*
* @return One of {@link android.app.settings.SettingsEnums}.
*/
protected abstract int getSourceMetricsCategory();
/**
* {@inheritDoc}
*
* <p>When you override this method, do not forget to check {@link #getWorkProfileUser()} to
* see if work profile user actually exists or not.</p>
*/
@AvailabilityStatus
@Override
public int getAvailabilityStatus() {
return mWorkProfileUser != null ? AVAILABLE : DISABLED_FOR_USER;
}
/**
* Launches the specified fragment for the work profile user if the associated
* {@link Preference} is clicked. Otherwise just forward it to the super class.
*
* @param preference the preference being clicked.
* @return {@code true} if handled.
*/
public boolean handlePreferenceTreeClick(Preference preference) {
if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
return super.handlePreferenceTreeClick(preference);
}
new SubSettingLauncher(preference.getContext())
.setDestination(preference.getFragment())
.setSourceMetricsCategory(getSourceMetricsCategory())
.setArguments(preference.getExtras())
.setUserHandle(mWorkProfileUser)
.launch();
return true;
}
}

View File

@@ -135,8 +135,8 @@ public class DataUsageList extends DataUsageBaseFragment {
mUidDetailProvider = new UidDetailProvider(activity);
mTelephonyManager = activity.getSystemService(TelephonyManager.class);
mUsageAmount = findPreference(KEY_USAGE_AMOUNT);
mChart = (ChartDataUsagePreference) findPreference(KEY_CHART_DATA);
mApps = (PreferenceGroup) findPreference(KEY_APPS_GROUP);
mChart = findPreference(KEY_CHART_DATA);
mApps = findPreference(KEY_APPS_GROUP);
processArgument();
}
@@ -306,7 +306,7 @@ public class DataUsageList extends DataUsageBaseFragment {
getLoaderManager().restartLoader(LOADER_SUMMARY, null /* args */,
mNetworkStatsDetailCallbacks);
final long totalBytes = mCycleData != null
final long totalBytes = mCycleData != null && !mCycleData.isEmpty()
? mCycleData.get(mCycleSpinner.getSelectedItemPosition()).getTotalUsage() : 0;
final CharSequence totalPhrase = DataUsageUtils.formatDataUsage(getActivity(), totalBytes);
mUsageAmount.setTitle(getString(R.string.data_used_template, totalPhrase));

View File

@@ -87,7 +87,7 @@ public class DataUsageSummary extends DataUsageBaseFragment implements DataUsage
boolean hasMobileData = DataUsageUtils.hasMobileData(context);
int defaultSubId = DataUsageUtils.getDefaultSubscriptionId(context);
final int defaultSubId = SubscriptionManager.getDefaultDataSubscriptionId();
if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
hasMobileData = false;
}

View File

@@ -44,8 +44,8 @@ import androidx.fragment.app.FragmentActivity;
import com.android.settings.R;
import com.android.settingslib.Utils;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
import java.text.NumberFormat;
@@ -63,7 +63,7 @@ public abstract class StorageWizardBase extends FragmentActivity {
protected VolumeInfo mVolume;
protected DiskInfo mDisk;
private ButtonFooterMixin mButtonFooterMixin;
private FooterBarMixin mFooterBarMixin;
private FooterButton mBack;
private FooterButton mNext;
@@ -94,8 +94,8 @@ public abstract class StorageWizardBase extends FragmentActivity {
public void setContentView(@LayoutRes int layoutResID) {
super.setContentView(layoutResID);
mButtonFooterMixin = getGlifLayout().getMixin(ButtonFooterMixin.class);
mButtonFooterMixin.setSecondaryButton(
mFooterBarMixin = getGlifLayout().getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.wizard_back)
.setListener(this::onNavigateBack)
@@ -103,7 +103,7 @@ public abstract class StorageWizardBase extends FragmentActivity {
.setTheme(R.style.SudGlifButton_Secondary)
.build()
);
mButtonFooterMixin.setPrimaryButton(
mFooterBarMixin.setPrimaryButton(
new FooterButton.Builder(this)
.setText(R.string.wizard_next)
.setListener(this::onNavigateNext)
@@ -111,8 +111,8 @@ public abstract class StorageWizardBase extends FragmentActivity {
.setTheme(R.style.SudGlifButton_Primary)
.build()
);
mBack = mButtonFooterMixin.getSecondaryButton();
mNext = mButtonFooterMixin.getPrimaryButton();
mBack = mFooterBarMixin.getSecondaryButton();
mNext = mFooterBarMixin.getPrimaryButton();
setIcon(com.android.internal.R.drawable.ic_sd_card_48dp);
}

View File

@@ -90,5 +90,6 @@ public class FirmwareVersionDialogFragment extends InstrumentedDialogFragment {
new BasebandVersionDialogController(this).initialize();
new KernelVersionDialogController(this).initialize();
new BuildNumberDialogController(this).initialize();
new ModuleVersionDialogController(this).initialize();
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2019 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.deviceinfo.firmwareversion;
import android.content.Context;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import android.util.Log;
import com.android.settings.R;
import androidx.annotation.VisibleForTesting;
public class ModuleVersionDialogController {
private static final String TAG = "MainlineModuleControl";
@VisibleForTesting
static final int MODULE_VERSION_LABEL_ID = R.id.module_version_label;
@VisibleForTesting
static final int MODULE_VERSION_VALUE_ID = R.id.module_version_value;
private final FirmwareVersionDialogFragment mDialog;
private final Context mContext;
private final PackageManager mPackageManager;
public ModuleVersionDialogController(FirmwareVersionDialogFragment dialog) {
mDialog = dialog;
mContext = mDialog.getContext();
mPackageManager = mContext.getPackageManager();
}
/**
* Updates the mainline module version field of the dialog.
*/
public void initialize() {
final String moduleProvider = mContext.getString(
com.android.internal.R.string.config_defaultModuleMetadataProvider);
if (!TextUtils.isEmpty(moduleProvider)) {
try {
mDialog.setText(MODULE_VERSION_VALUE_ID,
mPackageManager.getPackageInfo(moduleProvider, 0 /* flags */).versionName);
return;
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Failed to get mainline version.", e);
}
}
mDialog.removeSettingFromScreen(MODULE_VERSION_LABEL_ID);
mDialog.removeSettingFromScreen(MODULE_VERSION_VALUE_ID);
}
}

View File

@@ -18,14 +18,12 @@ import android.hardware.display.ColorDisplayManager;
import androidx.annotation.VisibleForTesting;
import com.android.internal.app.ColorDisplayController;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
public class ColorModePreferenceController extends BasePreferenceController {
private static final String TAG = "ColorModePreference";
private ColorDisplayController mColorDisplayController;
private ColorDisplayManager mColorDisplayManager;
public ColorModePreferenceController(Context context, String key) {
super(context, key);
@@ -35,30 +33,30 @@ public class ColorModePreferenceController extends BasePreferenceController {
public int getAvailabilityStatus() {
return mContext.getSystemService(ColorDisplayManager.class)
.isDeviceColorManaged()
&& !getColorDisplayController().getAccessibilityTransformActivated() ?
&& !ColorDisplayManager.areAccessibilityTransformsEnabled(mContext) ?
AVAILABLE_UNSEARCHABLE : DISABLED_FOR_USER;
}
@Override
public CharSequence getSummary() {
final int colorMode = getColorDisplayController().getColorMode();
if (colorMode == ColorDisplayController.COLOR_MODE_AUTOMATIC) {
final int colorMode = getColorDisplayManager().getColorMode();
if (colorMode == ColorDisplayManager.COLOR_MODE_AUTOMATIC) {
return mContext.getText(R.string.color_mode_option_automatic);
}
if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
if (colorMode == ColorDisplayManager.COLOR_MODE_SATURATED) {
return mContext.getText(R.string.color_mode_option_saturated);
}
if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
if (colorMode == ColorDisplayManager.COLOR_MODE_BOOSTED) {
return mContext.getText(R.string.color_mode_option_boosted);
}
return mContext.getText(R.string.color_mode_option_natural);
}
@VisibleForTesting
ColorDisplayController getColorDisplayController() {
if (mColorDisplayController == null) {
mColorDisplayController = new ColorDisplayController(mContext);
ColorDisplayManager getColorDisplayManager() {
if (mColorDisplayManager == null) {
mColorDisplayManager = mContext.getSystemService(ColorDisplayManager.class);
}
return mColorDisplayController;
return mColorDisplayManager;
}
}

View File

@@ -14,14 +14,20 @@
package com.android.settings.display;
import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
import android.hardware.display.ColorDisplayManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.provider.SearchIndexableResource;
import android.provider.Settings.Secure;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import com.android.internal.app.ColorDisplayController;
import com.android.settings.R;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
@@ -36,8 +42,7 @@ import java.util.List;
@SuppressWarnings("WeakerAccess")
@SearchIndexable
public class ColorModePreferenceFragment extends RadioButtonPickerFragment
implements ColorDisplayController.Callback {
public class ColorModePreferenceFragment extends RadioButtonPickerFragment {
@VisibleForTesting
static final String KEY_COLOR_MODE_NATURAL = "color_mode_natural";
@@ -48,21 +53,41 @@ public class ColorModePreferenceFragment extends RadioButtonPickerFragment
@VisibleForTesting
static final String KEY_COLOR_MODE_AUTOMATIC = "color_mode_automatic";
private ColorDisplayController mController;
private ContentObserver mContentObserver;
private ColorDisplayManager mColorDisplayManager;
@Override
public void onAttach(Context context) {
super.onAttach(context);
mController = new ColorDisplayController(context);
mController.setListener(this);
mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
final ContentResolver cr = context.getContentResolver();
mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
if (ColorDisplayManager.areAccessibilityTransformsEnabled(getContext())) {
// Color modes are not configurable when Accessibility transforms are enabled.
// Close this fragment in that case.
getActivity().finish();
}
}
};
cr.registerContentObserver(
Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED),
false /* notifyForDescendants */, mContentObserver, mUserId);
cr.registerContentObserver(
Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED),
false /* notifyForDescendants */, mContentObserver, mUserId);
}
@Override
public void onDetach() {
super.onDetach();
if (mController != null) {
mController.setListener(null);
mController = null;
if (mContentObserver != null) {
getContext().getContentResolver().unregisterContentObserver(mContentObserver);
mContentObserver = null;
}
}
@@ -90,22 +115,22 @@ public class ColorModePreferenceFragment extends RadioButtonPickerFragment
final int[] availableColorModes = c.getResources().getIntArray(
com.android.internal.R.array.config_availableColorModes);
List<ColorModeCandidateInfo> candidates = new ArrayList<ColorModeCandidateInfo>();
List<ColorModeCandidateInfo> candidates = new ArrayList<>();
if (availableColorModes != null) {
for (int colorMode : availableColorModes) {
if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) {
if (colorMode == ColorDisplayManager.COLOR_MODE_NATURAL) {
candidates.add(new ColorModeCandidateInfo(
c.getText(R.string.color_mode_option_natural),
KEY_COLOR_MODE_NATURAL, true /* enabled */));
} else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
} else if (colorMode == ColorDisplayManager.COLOR_MODE_BOOSTED) {
candidates.add(new ColorModeCandidateInfo(
c.getText(R.string.color_mode_option_boosted),
KEY_COLOR_MODE_BOOSTED, true /* enabled */));
} else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
} else if (colorMode == ColorDisplayManager.COLOR_MODE_SATURATED) {
candidates.add(new ColorModeCandidateInfo(
c.getText(R.string.color_mode_option_saturated),
KEY_COLOR_MODE_SATURATED, true /* enabled */));
} else if (colorMode == ColorDisplayController.COLOR_MODE_AUTOMATIC) {
} else if (colorMode == ColorDisplayManager.COLOR_MODE_AUTOMATIC) {
candidates.add(new ColorModeCandidateInfo(
c.getText(R.string.color_mode_option_automatic),
KEY_COLOR_MODE_AUTOMATIC, true /* enabled */));
@@ -117,12 +142,12 @@ public class ColorModePreferenceFragment extends RadioButtonPickerFragment
@Override
protected String getDefaultKey() {
final int colorMode = mController.getColorMode();
if (colorMode == ColorDisplayController.COLOR_MODE_AUTOMATIC) {
final int colorMode = mColorDisplayManager.getColorMode();
if (colorMode == ColorDisplayManager.COLOR_MODE_AUTOMATIC) {
return KEY_COLOR_MODE_AUTOMATIC;
} else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
} else if (colorMode == ColorDisplayManager.COLOR_MODE_SATURATED) {
return KEY_COLOR_MODE_SATURATED;
} else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
} else if (colorMode == ColorDisplayManager.COLOR_MODE_BOOSTED) {
return KEY_COLOR_MODE_BOOSTED;
}
return KEY_COLOR_MODE_NATURAL;
@@ -132,16 +157,16 @@ public class ColorModePreferenceFragment extends RadioButtonPickerFragment
protected boolean setDefaultKey(String key) {
switch (key) {
case KEY_COLOR_MODE_NATURAL:
mController.setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
mColorDisplayManager.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
break;
case KEY_COLOR_MODE_BOOSTED:
mController.setColorMode(ColorDisplayController.COLOR_MODE_BOOSTED);
mColorDisplayManager.setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED);
break;
case KEY_COLOR_MODE_SATURATED:
mController.setColorMode(ColorDisplayController.COLOR_MODE_SATURATED);
mColorDisplayManager.setColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
break;
case KEY_COLOR_MODE_AUTOMATIC:
mController.setColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC);
mColorDisplayManager.setColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC);
break;
}
return true;
@@ -179,15 +204,6 @@ public class ColorModePreferenceFragment extends RadioButtonPickerFragment
}
}
@Override
public void onAccessibilityTransformChanged(boolean state) {
// Color modes are no not configurable when Accessibility transforms are enabled. Close
// this fragment in that case.
if (state) {
getActivity().onBackPressed();
}
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override

View File

@@ -17,12 +17,13 @@ import android.content.Context;
import android.hardware.display.ColorDisplayManager;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import androidx.annotation.VisibleForTesting;
import com.android.internal.app.ColorDisplayController;
import com.android.settings.core.TogglePreferenceController;
public class DisplayWhiteBalancePreferenceController extends TogglePreferenceController {
private ColorDisplayController mColorDisplayController;
private ColorDisplayManager mColorDisplayManager;
public DisplayWhiteBalancePreferenceController(Context context, String key) {
super(context, key);
@@ -33,8 +34,8 @@ public class DisplayWhiteBalancePreferenceController extends TogglePreferenceCon
// Display white balance is only valid in linear light space. COLOR_MODE_SATURATED implies
// unmanaged color mode, and hence unknown color processing conditions.
return ColorDisplayManager.isDisplayWhiteBalanceAvailable(mContext) &&
getColorDisplayController().getColorMode() !=
ColorDisplayController.COLOR_MODE_SATURATED ?
getColorDisplayManager().getColorMode() !=
ColorDisplayManager.COLOR_MODE_SATURATED ?
AVAILABLE : DISABLED_FOR_USER;
}
@@ -51,10 +52,11 @@ public class DisplayWhiteBalancePreferenceController extends TogglePreferenceCon
return true;
}
ColorDisplayController getColorDisplayController() {
if (mColorDisplayController == null) {
mColorDisplayController = new ColorDisplayController(mContext);
@VisibleForTesting
ColorDisplayManager getColorDisplayManager() {
if (mColorDisplayManager == null) {
mColorDisplayManager = mContext.getSystemService(ColorDisplayManager.class);
}
return mColorDisplayController;
return mColorDisplayManager;
}
}

View File

@@ -67,8 +67,7 @@ public class NightDisplayActivationPreferenceController extends TogglePreference
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final LayoutPreference preference = (LayoutPreference) screen.findPreference(
getPreferenceKey());
final LayoutPreference preference = screen.findPreference(getPreferenceKey());
mTurnOnButton = preference.findViewById(R.id.night_display_turn_on_button);
mTurnOnButton.setOnClickListener(mListener);
mTurnOffButton = preference.findViewById(R.id.night_display_turn_off_button);
@@ -106,14 +105,14 @@ public class NightDisplayActivationPreferenceController extends TogglePreference
final int autoMode = mController.getAutoMode();
String buttonText;
if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) {
buttonText = mContext.getString(isActivated
? R.string.night_display_activation_off_custom
: R.string.night_display_activation_on_custom,
mTimeFormatter.getFormattedTimeString(isActivated
? mController.getCustomStartTime()
: mController.getCustomEndTime()));
} else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
} else if (autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
buttonText = mContext.getString(isActivated
? R.string.night_display_activation_off_twilight
: R.string.night_display_activation_on_twilight);

View File

@@ -23,7 +23,6 @@ import androidx.preference.DropDownPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.internal.app.ColorDisplayController;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
@@ -31,11 +30,11 @@ public class NightDisplayAutoModePreferenceController extends BasePreferenceCont
implements Preference.OnPreferenceChangeListener {
private DropDownPreference mPreference;
private ColorDisplayController mController;
private ColorDisplayManager mManager;
public NightDisplayAutoModePreferenceController(Context context, String key) {
super(context, key);
mController = new ColorDisplayController(context);
mManager = context.getSystemService(ColorDisplayManager.class);
}
@Override
@@ -48,7 +47,7 @@ public class NightDisplayAutoModePreferenceController extends BasePreferenceCont
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = (DropDownPreference) screen.findPreference(getPreferenceKey());
mPreference = screen.findPreference(getPreferenceKey());
mPreference.setEntries(new CharSequence[]{
mContext.getString(R.string.night_display_auto_mode_never),
@@ -56,19 +55,19 @@ public class NightDisplayAutoModePreferenceController extends BasePreferenceCont
mContext.getString(R.string.night_display_auto_mode_twilight)
});
mPreference.setEntryValues(new CharSequence[]{
String.valueOf(ColorDisplayController.AUTO_MODE_DISABLED),
String.valueOf(ColorDisplayController.AUTO_MODE_CUSTOM),
String.valueOf(ColorDisplayController.AUTO_MODE_TWILIGHT)
String.valueOf(ColorDisplayManager.AUTO_MODE_DISABLED),
String.valueOf(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME),
String.valueOf(ColorDisplayManager.AUTO_MODE_TWILIGHT)
});
}
@Override
public final void updateState(Preference preference) {
mPreference.setValue(String.valueOf(mController.getAutoMode()));
mPreference.setValue(String.valueOf(mManager.getNightDisplayAutoMode()));
}
@Override
public final boolean onPreferenceChange(Preference preference, Object newValue) {
return mController.setAutoMode(Integer.parseInt((String) newValue));
return mManager.setNightDisplayAutoMode(Integer.parseInt((String) newValue));
}
}

View File

@@ -18,9 +18,7 @@ package com.android.settings.display;
import android.content.Context;
import android.hardware.display.ColorDisplayManager;
import androidx.preference.Preference;
import com.android.internal.app.ColorDisplayController;
import com.android.settings.core.BasePreferenceController;
@@ -44,7 +42,8 @@ public class NightDisplayCustomEndTimePreferenceController extends BasePreferenc
@Override
public final void updateState(Preference preference) {
preference.setVisible(mController.getAutoMode() == ColorDisplayController.AUTO_MODE_CUSTOM);
preference
.setVisible(mController.getAutoMode() == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
preference.setSummary(mTimeFormatter.getFormattedTimeString(
mController.getCustomEndTime()));
}

View File

@@ -18,9 +18,7 @@ package com.android.settings.display;
import android.content.Context;
import android.hardware.display.ColorDisplayManager;
import androidx.preference.Preference;
import com.android.internal.app.ColorDisplayController;
import com.android.settings.core.BasePreferenceController;
@@ -44,7 +42,8 @@ public class NightDisplayCustomStartTimePreferenceController extends BasePrefere
@Override
public final void updateState(Preference preference) {
preference.setVisible(mController.getAutoMode() == ColorDisplayController.AUTO_MODE_CUSTOM);
preference
.setVisible(mController.getAutoMode() == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
preference.setSummary(mTimeFormatter.getFormattedTimeString(
mController.getCustomStartTime()));
}

View File

@@ -22,6 +22,7 @@ import android.text.TextUtils;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.annotation.VisibleForTesting;
import com.android.internal.app.ColorDisplayController;
import com.android.settings.core.SliderPreferenceController;
@@ -54,8 +55,7 @@ public class NightDisplayIntensityPreferenceController extends SliderPreferenceC
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final SeekBarPreference preference = (SeekBarPreference) screen.findPreference(
getPreferenceKey());
final SeekBarPreference preference = screen.findPreference(getPreferenceKey());
preference.setContinuousUpdates(true);
preference.setMax(getMaxSteps());
}

View File

@@ -38,7 +38,7 @@ public class NightDisplayPreferenceController extends AbstractPreferenceControll
return true;
}
final ColorDisplayController controller = new ColorDisplayController(context);
return controller.getAutoMode() != ColorDisplayController.AUTO_MODE_DISABLED;
return controller.getAutoMode() != ColorDisplayManager.AUTO_MODE_DISABLED;
}
@Override

View File

@@ -18,6 +18,7 @@ package com.android.settings.display;
import android.content.Context;
import android.hardware.display.ColorDisplayManager;
import com.android.internal.app.ColorDisplayController;
import com.android.settings.R;
@@ -54,7 +55,7 @@ public class NightDisplayTimeFormatter {
private String getAutoModeSummary(Context context, ColorDisplayController controller) {
final boolean isActivated = controller.isActivated();
final int autoMode = controller.getAutoMode();
if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) {
if (isActivated) {
return context.getString(R.string.night_display_summary_on_auto_mode_custom,
getFormattedTimeString(controller.getCustomEndTime()));
@@ -62,7 +63,7 @@ public class NightDisplayTimeFormatter {
return context.getString(R.string.night_display_summary_off_auto_mode_custom,
getFormattedTimeString(controller.getCustomStartTime()));
}
} else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
} else if (autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
return context.getString(isActivated
? R.string.night_display_summary_on_auto_mode_twilight
: R.string.night_display_summary_off_auto_mode_twilight);

View File

@@ -46,6 +46,7 @@ import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -351,6 +352,12 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro
@VisibleForTesting
boolean shouldHideSipper(BatterySipper sipper) {
// Don't show hidden system module
final String packageName = mBatteryUtils.getPackageName(sipper.getUid());
if (!TextUtils.isEmpty(packageName)
&& AppUtils.isHiddenSystemModule(mContext, packageName)) {
return true;
}
// Don't show over-counted and unaccounted in any condition
return sipper.drainType == BatterySipper.DrainType.OVERCOUNTED
|| sipper.drainType == BatterySipper.DrainType.UNACCOUNTED;

View File

@@ -53,7 +53,7 @@ public class SettingsHomepageActivity extends SettingsBaseActivity {
final Toolbar toolbar = findViewById(R.id.search_action_bar);
FeatureFactory.getFactory(this).getSearchFeatureProvider()
.initSearchToolbar(this, toolbar);
.initSearchToolbar(this /* activity */, toolbar);
final ImageView avatarView = findViewById(R.id.account_avatar);
final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(this, avatarView);

View File

@@ -30,16 +30,11 @@ public class BluetoothUpdateWorker extends SliceBackgroundWorker implements Blue
private static final String TAG = "BluetoothUpdateWorker";
private final Context mContext;
private final Uri mUri;
private final LocalBluetoothManager mLocalBluetoothManager;
public BluetoothUpdateWorker(Context context, Uri uri) {
super(context, uri);
mContext = context;
mUri = uri;
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
mLocalBluetoothManager = Utils.getLocalBtManager(context);
}
@Override
@@ -89,8 +84,4 @@ public class BluetoothUpdateWorker extends SliceBackgroundWorker implements Blue
int bluetoothProfile) {
notifySliceChange();
}
private void notifySliceChange() {
mContext.getContentResolver().notifyChange(mUri, null);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2019 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.inputmethod;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.view.inputmethod.InputMethodSystemProperty;
import com.android.settings.R;
import com.android.settings.core.WorkProfilePreferenceController;
/**
* Preference controller for "Spell checker for work".
*
* @see SpellCheckerPreferenceController
*/
public final class SpellCheckerForWorkPreferenceController extends WorkProfilePreferenceController {
public SpellCheckerForWorkPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
protected int getSourceMetricsCategory() {
return SettingsEnums.SETTINGS_LANGUAGE_CATEGORY;
}
@AvailabilityStatus
@Override
public int getAvailabilityStatus() {
if (!mContext.getResources().getBoolean(R.bool.config_show_spellcheckers_settings)
|| !InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
return UNSUPPORTED_ON_DEVICE;
}
return super.getAvailabilityStatus();
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2019 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.inputmethod;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.view.inputmethod.InputMethodSystemProperty;
import com.android.settings.R;
import com.android.settings.core.WorkProfilePreferenceController;
public final class VirtualKeyboardForWorkPreferenceController
extends WorkProfilePreferenceController {
public VirtualKeyboardForWorkPreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
}
@Override
protected int getSourceMetricsCategory() {
return SettingsEnums.SETTINGS_LANGUAGE_CATEGORY;
}
@AvailabilityStatus
@Override
public int getAvailabilityStatus() {
if (!mContext.getResources().getBoolean(R.bool.config_show_virtual_keyboard_pref)
|| !InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
return UNSUPPORTED_ON_DEVICE;
}
return super.getAvailabilityStatus();
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2019 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.language;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.view.inputmethod.InputMethodSystemProperty;
import com.android.settings.R;
import com.android.settings.core.WorkProfilePreferenceController;
/**
* Preference controller for "UserDictionary for work".
*
* @see UserDictionaryPreferenceController
*/
public final class UserDictionaryForWorkPreferenceController
extends WorkProfilePreferenceController {
public UserDictionaryForWorkPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
protected int getSourceMetricsCategory() {
return SettingsEnums.SETTINGS_LANGUAGE_CATEGORY;
}
@AvailabilityStatus
@Override
public int getAvailabilityStatus() {
if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
return UNSUPPORTED_ON_DEVICE;
}
return super.getAvailabilityStatus();
}
}

View File

@@ -13,6 +13,8 @@
*/
package com.android.settings.location;
import static java.util.concurrent.TimeUnit.DAYS;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
@@ -74,6 +76,7 @@ public class RecentLocationAccessPreferenceController extends AbstractPreference
final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE);
intent.putExtra(Intent.EXTRA_PERMISSION_NAME,
Manifest.permission.ACCESS_FINE_LOCATION);
intent.putExtra(Intent.EXTRA_DURATION_MILLIS, DAYS.toMillis(1));
mContext.startActivity(intent);
});
}

View File

@@ -30,7 +30,7 @@ public class SubscriptionUtil {
private static List<SubscriptionInfo> sResultsForTesting;
@VisibleForTesting
static void setAvailableSubscriptionsForTesting(List<SubscriptionInfo> results) {
public static void setAvailableSubscriptionsForTesting(List<SubscriptionInfo> results) {
sResultsForTesting = results;
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2019 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.network.telephony;
import android.content.Context;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
public class CallsDefaultSubscriptionController extends DefaultSubscriptionController {
public CallsDefaultSubscriptionController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
protected SubscriptionInfo getDefaultSubscriptionInfo() {
return mManager.getDefaultVoiceSubscriptionInfo();
}
@Override
protected int getDefaultSubscriptionId() {
return SubscriptionManager.getDefaultVoiceSubscriptionId();
}
@Override
protected void setDefaultSubscription(int subscriptionId) {
mManager.setDefaultVoiceSubId(subscriptionId);
}
}

View File

@@ -41,7 +41,6 @@ public class CarrierPreferenceController extends BasePreferenceController {
public CarrierPreferenceController(Context context, String key) {
super(context, key);
mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
mCarrierConfigManager = new CarrierConfigManager(context);
mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
@@ -88,6 +87,7 @@ public class CarrierPreferenceController extends BasePreferenceController {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setComponent(cn);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
final PackageManager pm = mContext.getPackageManager();
final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0 /* flags */);

View File

@@ -0,0 +1,175 @@
/*
* Copyright (C) 2019 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.network.telephony;
import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
import android.content.Context;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.SubscriptionsChangeListener;
import java.util.List;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
/**
* This implements common controller functionality for a Preference letting the user see/change
* what mobile network subscription is used by default for some service controlled by the
* SubscriptionManager. This can be used for services such as Calls or SMS.
*/
public abstract class DefaultSubscriptionController extends BasePreferenceController implements
LifecycleObserver, Preference.OnPreferenceChangeListener,
SubscriptionsChangeListener.SubscriptionsChangeListenerClient {
private static final String TAG = "DefaultSubController";
protected SubscriptionsChangeListener mChangeListener;
protected ListPreference mPreference;
protected SubscriptionManager mManager;
public DefaultSubscriptionController(Context context, String preferenceKey) {
super(context, preferenceKey);
mManager = context.getSystemService(SubscriptionManager.class);
mChangeListener = new SubscriptionsChangeListener(context, this);
}
public void init(Lifecycle lifecycle) {
lifecycle.addObserver(this);
}
/** @return SubscriptionInfo for the default subscription for the service, or null if there
* isn't one. */
protected abstract SubscriptionInfo getDefaultSubscriptionInfo();
/** @return the id of the default subscription for the service, or
* SubscriptionManager.INVALID_SUBSCRIPTION_ID if there isn't one. */
protected abstract int getDefaultSubscriptionId();
/** Called to change the default subscription for the service. */
protected abstract void setDefaultSubscription(int subscriptionId);
@Override
public int getAvailabilityStatus() {
final List<SubscriptionInfo> subs = SubscriptionUtil.getAvailableSubscriptions(mManager);
if (subs.size() > 1) {
return AVAILABLE;
} else {
return CONDITIONALLY_UNAVAILABLE;
}
}
@OnLifecycleEvent(ON_RESUME)
public void onResume() {
mChangeListener.start();
updateEntries();
}
@OnLifecycleEvent(ON_PAUSE)
public void onPause() {
mChangeListener.stop();
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
updateEntries();
}
@Override
public CharSequence getSummary() {
final SubscriptionInfo info = getDefaultSubscriptionInfo();
if (info != null) {
return info.getDisplayName();
} else {
return mContext.getString(R.string.calls_and_sms_ask_every_time);
}
}
private void updateEntries() {
if (mPreference == null) {
return;
}
if (!isAvailable()) {
mPreference.setVisible(false);
return;
}
mPreference.setVisible(true);
final List<SubscriptionInfo> subs = SubscriptionUtil.getAvailableSubscriptions(mManager);
// We'll have one entry for each available subscription, plus one for a "ask me every
// time" entry at the end.
final CharSequence[] displayNames = new CharSequence[subs.size() + 1];
final CharSequence[] subscriptionIds = new CharSequence[subs.size() + 1];
final int serviceDefaultSubId = getDefaultSubscriptionId();
boolean subIsAvailable = false;
int i = 0;
for (; i < subs.size(); i++) {
displayNames[i] = subs.get(i).getDisplayName();
final int subId = subs.get(i).getSubscriptionId();
subscriptionIds[i] = Integer.toString(subId);
if (subId == serviceDefaultSubId) {
subIsAvailable = true;
}
}
// Add the extra "Ask every time" value at the end.
displayNames[i] = mContext.getString(R.string.calls_and_sms_ask_every_time);
subscriptionIds[i] = Integer.toString(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mPreference.setEntries(displayNames);
mPreference.setEntryValues(subscriptionIds);
if (subIsAvailable) {
mPreference.setValue(Integer.toString(serviceDefaultSubId));
} else {
mPreference.setValue(Integer.toString(SubscriptionManager.INVALID_SUBSCRIPTION_ID));
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final int subscriptionId = Integer.parseInt((String) newValue);
setDefaultSubscription(subscriptionId);
refreshSummary(mPreference);
return true;
}
@Override
public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
}
@Override
public void onSubscriptionsChanged() {
if (mPreference != null) {
updateEntries();
refreshSummary(mPreference);
}
}
}

View File

@@ -126,6 +126,10 @@ public class MobileNetworkSettings extends RestrictedDashboardFragment {
public void onAttach(Context context) {
super.onAttach(context);
if (FeatureFlagPersistent.isEnabled(getContext(), FeatureFlags.NETWORK_INTERNET_V2)) {
use(CallsDefaultSubscriptionController.class).init(getLifecycle());
use(SmsDefaultSubscriptionController.class).init(getLifecycle());
}
use(MobileDataPreferenceController.class).init(getFragmentManager(), mSubId);
use(RoamingPreferenceController.class).init(getFragmentManager(), mSubId);
use(ApnPreferenceController.class).init(mSubId);

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2019 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.network.telephony;
import android.content.Context;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
public class SmsDefaultSubscriptionController extends DefaultSubscriptionController {
public SmsDefaultSubscriptionController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
protected SubscriptionInfo getDefaultSubscriptionInfo() {
return mManager.getDefaultSmsSubscriptionInfo();
}
@Override
protected int getDefaultSubscriptionId() {
return SubscriptionManager.getDefaultSmsSubscriptionId();
}
@Override
protected void setDefaultSubscription(int subscriptionId) {
mManager.setDefaultSmsSubId(subscriptionId);
}
}

View File

@@ -47,8 +47,8 @@ import com.android.settings.SetupWizardUtils;
import com.android.settings.Utils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
public class RedactionInterstitial extends SettingsActivity {
@@ -133,8 +133,8 @@ public class RedactionInterstitial extends SettingsActivity {
}
final GlifLayout layout = view.findViewById(R.id.setup_wizard_layout);
final ButtonFooterMixin buttonFooterMixin = layout.getMixin(ButtonFooterMixin.class);
buttonFooterMixin.setPrimaryButton(
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
mixin.setPrimaryButton(
new FooterButton.Builder(getContext())
.setText(R.string.app_notifications_dialog_done)
.setListener(this::onDoneButtonClicked)

View File

@@ -0,0 +1,53 @@
package com.android.settings.panel;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import com.android.settings.R;
import com.android.settings.SubSettings;
import com.android.settings.connecteddevice.AdvancedConnectedDeviceDashboardFragment;
import com.android.settings.slices.CustomSliceRegistry;
import com.android.settings.slices.SliceBuilderUtils;
import java.util.ArrayList;
import java.util.List;
public class NfcPanel implements PanelContent {
private final Context mContext;
public static NfcPanel create(Context context) {
return new NfcPanel(context);
}
private NfcPanel(Context context) {
mContext = context.getApplicationContext();
}
@Override
public CharSequence getTitle() {
return mContext.getText(R.string.nfc_quick_toggle_title);
}
@Override
public List<Uri> getSlices() {
final List<Uri> uris = new ArrayList<>();
uris.add(CustomSliceRegistry.NFC_SLICE_URI);
return uris;
}
@Override
public Intent getSeeMoreIntent() {
final String screenTitle =
mContext.getText(R.string.connected_device_connections_title).toString();
Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext,
AdvancedConnectedDeviceDashboardFragment.class.getName(),
null /* key */,
screenTitle,
SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY);
intent.setClassName(mContext.getPackageName(), SubSettings.class.getName());
return intent;
}
}

View File

@@ -28,6 +28,8 @@ public class PanelFeatureProviderImpl implements PanelFeatureProvider {
return InternetConnectivityPanel.create(context);
case Settings.Panel.ACTION_VOLUME:
return VolumePanel.create(context);
case Settings.Panel.ACTION_NFC:
return NfcPanel.create(context);
}
throw new IllegalStateException("No matching panel for: " + panelType);

View File

@@ -30,6 +30,7 @@ import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import java.util.concurrent.Executor;
@@ -39,11 +40,6 @@ import java.util.concurrent.Executor;
*/
public class BiometricFragment extends InstrumentedFragment {
private static final String KEY_TITLE = "title";
private static final String KEY_SUBTITLE = "subtitle";
private static final String KEY_DESCRIPTION = "description";
private static final String KEY_NEGATIVE_TEXT = "negative_text";
// Re-set by the application. Should be done upon orientation changes, etc
private Executor mClientExecutor;
private AuthenticationCallback mClientCallback;
@@ -53,7 +49,7 @@ public class BiometricFragment extends InstrumentedFragment {
// Created/Initialized once and retained
private final Handler mHandler = new Handler(Looper.getMainLooper());
private PromptInfo mPromptInfo;
private Bundle mBundle;
private BiometricPrompt mBiometricPrompt;
private CancellationSignal mCancellationSignal;
@@ -82,13 +78,17 @@ public class BiometricFragment extends InstrumentedFragment {
public void onClick(DialogInterface dialog, int which) {
mAuthenticationCallback.onAuthenticationError(
BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON,
mPromptInfo.getNegativeButtonText());
mBundle.getString(BiometricPrompt.KEY_NEGATIVE_TEXT));
}
};
public static BiometricFragment newInstance(PromptInfo info) {
/**
* @param bundle Bundle passed from {@link BiometricPrompt.Builder#buildIntent()}
* @return
*/
public static BiometricFragment newInstance(Bundle bundle) {
BiometricFragment biometricFragment = new BiometricFragment();
biometricFragment.setArguments(info.getBundle());
biometricFragment.setArguments(bundle);
return biometricFragment;
}
@@ -119,14 +119,16 @@ public class BiometricFragment extends InstrumentedFragment {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mPromptInfo = new PromptInfo(getArguments());
mBundle = getArguments();
mBiometricPrompt = new BiometricPrompt.Builder(getContext())
.setTitle(mPromptInfo.getTitle())
.setTitle(mBundle.getString(BiometricPrompt.KEY_TITLE))
.setUseDefaultTitle() // use default title if title is null/empty
.setSubtitle(mPromptInfo.getSubtitle())
.setDescription(mPromptInfo.getDescription())
.setNegativeButton(mPromptInfo.getNegativeButtonText(), mClientExecutor,
mNegativeButtonListener)
.setSubtitle(mBundle.getString(BiometricPrompt.KEY_SUBTITLE))
.setDescription(mBundle.getString(BiometricPrompt.KEY_DESCRIPTION))
.setRequireConfirmation(mBundle.getBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION))
.setNegativeButton(getResources().getString(
R.string.confirm_device_credential_use_alternate_method),
mClientExecutor, mNegativeButtonListener)
.build();
mCancellationSignal = new CancellationSignal();
@@ -139,65 +141,5 @@ public class BiometricFragment extends InstrumentedFragment {
public int getMetricsCategory() {
return SettingsEnums.BIOMETRIC_FRAGMENT;
}
/**
* A simple wrapper for BiometricPrompt.PromptInfo. Since we want to manage the lifecycle
* of BiometricPrompt correctly, the information needs to be stored in here.
*/
static class PromptInfo {
private final Bundle mBundle;
private PromptInfo(Bundle bundle) {
mBundle = bundle;
}
Bundle getBundle() {
return mBundle;
}
public CharSequence getTitle() {
return mBundle.getCharSequence(KEY_TITLE);
}
public CharSequence getSubtitle() {
return mBundle.getCharSequence(KEY_SUBTITLE);
}
public CharSequence getDescription() {
return mBundle.getCharSequence(KEY_DESCRIPTION);
}
public CharSequence getNegativeButtonText() {
return mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
}
public static class Builder {
private final Bundle mBundle = new Bundle();
public Builder setTitle(@NonNull CharSequence title) {
mBundle.putCharSequence(KEY_TITLE, title);
return this;
}
public Builder setSubtitle(@Nullable CharSequence subtitle) {
mBundle.putCharSequence(KEY_SUBTITLE, subtitle);
return this;
}
public Builder setDescription(@Nullable CharSequence description) {
mBundle.putCharSequence(KEY_DESCRIPTION, description);
return this;
}
public Builder setNegativeButtonText(@NonNull CharSequence text) {
mBundle.putCharSequence(KEY_NEGATIVE_TEXT, text);
return this;
}
public PromptInfo build() {
return new PromptInfo(mBundle);
}
}
}
}

View File

@@ -18,13 +18,20 @@ package com.android.settings.password;
import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PARENT_PROFILE_PASSWORD;
import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static com.android.settings.password.ChooseLockPassword.ChooseLockPasswordFragment.RESULT_FINISHED;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
@@ -66,6 +73,8 @@ import com.android.settings.search.SearchFeatureProvider;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.FooterPreferenceMixinCompat;
import java.util.List;
@@ -152,6 +161,14 @@ public class ChooseLockGeneric extends SettingsActivity {
private UserManager mUserManager;
private ChooseLockGenericController mController;
/**
* From intent extra {@link ChooseLockSettingsHelper#EXTRA_KEY_REQUESTED_MIN_COMPLEXITY}.
*/
@PasswordComplexity private int mRequestedMinComplexity;
/** From intent extra {@link ChooseLockSettingsHelper#EXTRA_KEY_CALLER_APP_NAME}. */
private String mCallerAppName = null;
protected boolean mForFingerprint = false;
protected boolean mForFace = false;
@@ -195,6 +212,10 @@ public class ChooseLockGeneric extends SettingsActivity {
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
mForFace = getActivity().getIntent().getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
mRequestedMinComplexity = getActivity().getIntent()
.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
mCallerAppName =
getActivity().getIntent().getStringExtra(EXTRA_KEY_CALLER_APP_NAME);
mForChangeCredRequiredForBoot = getArguments() != null && getArguments().getBoolean(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT);
mUserManager = UserManager.get(getActivity());
@@ -217,7 +238,8 @@ public class ChooseLockGeneric extends SettingsActivity {
UserManager.get(getActivity()),
getArguments(),
getActivity().getIntent().getExtras()).getIdentifier();
mController = new ChooseLockGenericController(getContext(), mUserId);
mController =
new ChooseLockGenericController(getContext(), mUserId, mRequestedMinComplexity);
if (ACTION_SET_NEW_PASSWORD.equals(chooseLockAction)
&& UserManager.get(getActivity()).isManagedProfile(mUserId)
&& mLockPatternUtils.isSeparateProfileChallengeEnabled(mUserId)) {
@@ -291,6 +313,9 @@ public class ChooseLockGeneric extends SettingsActivity {
// Forward the target user id to ChooseLockGeneric.
chooseLockGenericIntent.putExtra(Intent.EXTRA_USER_ID, mUserId);
chooseLockGenericIntent.putExtra(CONFIRM_CREDENTIALS, !mPasswordConfirmed);
chooseLockGenericIntent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY,
mRequestedMinComplexity);
chooseLockGenericIntent.putExtra(EXTRA_KEY_CALLER_APP_NAME, mCallerAppName);
if (mUserPassword != null) {
chooseLockGenericIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
mUserPassword);
@@ -461,6 +486,13 @@ public class ChooseLockGeneric extends SettingsActivity {
protected void addPreferences() {
addPreferencesFromResource(R.xml.security_settings_picker);
if (!TextUtils.isEmpty(mCallerAppName)) {
FooterPreferenceMixinCompat footerMixin =
new FooterPreferenceMixinCompat(this, getSettingsLifecycle());
FooterPreference footer = footerMixin.createFooterPreference();
footer.setTitle(getFooterString());
}
// Used for testing purposes
findPreference(ScreenLockType.NONE.preferenceKey).setViewId(R.id.lock_none);
findPreference(KEY_SKIP_FINGERPRINT).setViewId(R.id.lock_none);
@@ -469,6 +501,27 @@ public class ChooseLockGeneric extends SettingsActivity {
findPreference(ScreenLockType.PASSWORD.preferenceKey).setViewId(R.id.lock_password);
}
private String getFooterString() {
@StringRes int stringId;
switch (mRequestedMinComplexity) {
case PASSWORD_COMPLEXITY_HIGH:
stringId = R.string.unlock_footer_high_complexity_requested;
break;
case PASSWORD_COMPLEXITY_MEDIUM:
stringId = R.string.unlock_footer_medium_complexity_requested;
break;
case PASSWORD_COMPLEXITY_LOW:
stringId = R.string.unlock_footer_low_complexity_requested;
break;
case PASSWORD_COMPLEXITY_NONE:
default:
stringId = R.string.unlock_footer_none_complexity_requested;
break;
}
return getResources().getString(stringId, mCallerAppName);
}
private void updatePreferenceText() {
if (mForFingerprint) {
setPreferenceTitle(ScreenLockType.PATTERN,
@@ -624,6 +677,7 @@ public class ChooseLockGeneric extends SettingsActivity {
ChooseLockPassword.IntentBuilder builder =
new ChooseLockPassword.IntentBuilder(getContext())
.setPasswordQuality(quality)
.setRequestedMinComplexity(mRequestedMinComplexity)
.setForFingerprint(mForFingerprint)
.setForFace(mForFace)
.setUserId(mUserId);

View File

@@ -16,7 +16,11 @@
package com.android.settings.password;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.PasswordMetrics;
import android.content.Context;
import android.os.UserHandle;
@@ -36,6 +40,7 @@ public class ChooseLockGenericController {
private final Context mContext;
private final int mUserId;
@PasswordComplexity private final int mRequestedMinComplexity;
private ManagedLockPasswordProvider mManagedPasswordProvider;
private DevicePolicyManager mDpm;
@@ -43,6 +48,19 @@ public class ChooseLockGenericController {
this(
context,
userId,
PASSWORD_COMPLEXITY_NONE);
}
/**
* @param requestedMinComplexity specifies the min password complexity to be taken into account
* when determining the available screen lock types
*/
public ChooseLockGenericController(Context context, int userId,
@PasswordComplexity int requestedMinComplexity) {
this(
context,
userId,
requestedMinComplexity,
context.getSystemService(DevicePolicyManager.class),
ManagedLockPasswordProvider.get(context, userId));
}
@@ -51,21 +69,26 @@ public class ChooseLockGenericController {
ChooseLockGenericController(
Context context,
int userId,
@PasswordComplexity int requestedMinComplexity,
DevicePolicyManager dpm,
ManagedLockPasswordProvider managedLockPasswordProvider) {
mContext = context;
mUserId = userId;
mRequestedMinComplexity = requestedMinComplexity;
mManagedPasswordProvider = managedLockPasswordProvider;
mDpm = dpm;
}
/**
* @return The higher quality of either the specified {@code quality} or the quality required
* by {@link DevicePolicyManager#getPasswordQuality}.
* Returns the highest quality among the specified {@code quality}, the quality required by
* {@link DevicePolicyManager#getPasswordQuality}, and the quality required by min password
* complexity.
*/
public int upgradeQuality(int quality) {
// Compare min allowed password quality
return Math.max(quality, mDpm.getPasswordQuality(null, mUserId));
// Compare specified quality and dpm quality
int dpmUpgradedQuality = Math.max(quality, mDpm.getPasswordQuality(null, mUserId));
return Math.max(dpmUpgradedQuality,
PasswordMetrics.complexityLevelToMinQuality(mRequestedMinComplexity));
}
/**

View File

@@ -16,14 +16,18 @@
package com.android.settings.password;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.PasswordMetrics;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -56,6 +60,7 @@ import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
import com.android.internal.widget.TextViewInputDisabler;
@@ -68,8 +73,8 @@ import com.android.settings.core.InstrumentedFragment;
import com.android.settings.notification.RedactionInterstitial;
import com.android.settings.widget.ImeAwareEditText;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
import java.util.ArrayList;
@@ -133,6 +138,11 @@ public class ChooseLockPassword extends SettingsActivity {
return this;
}
public IntentBuilder setRequestedMinComplexity(@PasswordComplexity int level) {
mIntent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, level);
return this;
}
public Intent build() {
return mIntent;
}
@@ -190,12 +200,10 @@ public class ChooseLockPassword extends SettingsActivity {
private int mPasswordMinNumeric = 0;
private int mPasswordMinNonLetter = 0;
private int mPasswordMinLengthToFulfillAllPolicies = 0;
private boolean mPasswordNumSequenceAllowed = true;
@PasswordComplexity private int mRequestedMinComplexity = PASSWORD_COMPLEXITY_NONE;
protected int mUserId;
private byte[] mPasswordHistoryHashFactor;
/**
* Password requirements that we need to verify.
*/
private int[] mPasswordRequirements;
private LockPatternUtils mLockPatternUtils;
private SaveAndFinishWorker mSaveAndFinishWorker;
@@ -372,7 +380,13 @@ public class ChooseLockPassword extends SettingsActivity {
mForFingerprint = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
mForFace = intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
processPasswordRequirements(intent);
mRequestedMinComplexity = intent.getIntExtra(
EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
mRequestedQuality = Math.max(
intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, mRequestedQuality),
mLockPatternUtils.getRequestedPasswordQuality(mUserId));
loadDpmPasswordRequirements();
mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
if (intent.getBooleanExtra(
@@ -407,7 +421,7 @@ public class ChooseLockPassword extends SettingsActivity {
ViewGroup container = view.findViewById(R.id.password_container);
container.setOpticalInsets(Insets.NONE);
final ButtonFooterMixin mixin = mLayout.getMixin(ButtonFooterMixin.class);
final FooterBarMixin mixin = mLayout.getMixin(FooterBarMixin.class);
mixin.setSecondaryButton(
new FooterButton.Builder(getActivity())
.setText(R.string.lockpassword_clear_label)
@@ -504,31 +518,6 @@ public class ChooseLockPassword extends SettingsActivity {
}
private void setupPasswordRequirementsView(View view) {
final List<Integer> passwordRequirements = new ArrayList<>();
if (mPasswordMinUpperCase > 0) {
passwordRequirements.add(MIN_UPPER_LETTERS_IN_PASSWORD);
}
if (mPasswordMinLowerCase > 0) {
passwordRequirements.add(MIN_LOWER_LETTERS_IN_PASSWORD);
}
if (mPasswordMinLetters > 0) {
if (mPasswordMinLetters > mPasswordMinUpperCase + mPasswordMinLowerCase) {
passwordRequirements.add(MIN_LETTER_IN_PASSWORD);
}
}
if (mPasswordMinNumeric > 0) {
passwordRequirements.add(MIN_NUMBER_IN_PASSWORD);
}
if (mPasswordMinSymbols > 0) {
passwordRequirements.add(MIN_SYMBOLS_IN_PASSWORD);
}
if (mPasswordMinNonLetter > 0) {
if (mPasswordMinNonLetter > mPasswordMinNumeric + mPasswordMinSymbols) {
passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD);
}
}
// Convert list to array.
mPasswordRequirements = passwordRequirements.stream().mapToInt(i -> i).toArray();
mPasswordRestrictionView = view.findViewById(R.id.password_requirements_view);
mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
mPasswordRequirementAdapter = new PasswordRequirementAdapter();
@@ -603,13 +592,12 @@ public class ChooseLockPassword extends SettingsActivity {
/**
* Read the requirements from {@link DevicePolicyManager} and intent and aggregate them.
*
* @param intent the incoming intent
*/
private void processPasswordRequirements(Intent intent) {
private void loadDpmPasswordRequirements() {
final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId);
mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
mRequestedQuality), dpmPasswordQuality);
if (dpmPasswordQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
mPasswordNumSequenceAllowed = false;
}
mPasswordMinLength = Math.max(LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId));
mPasswordMaxLength = mLockPatternUtils.getMaximumPasswordLength(mRequestedQuality);
@@ -620,7 +608,7 @@ public class ChooseLockPassword extends SettingsActivity {
mPasswordMinSymbols = mLockPatternUtils.getRequestedPasswordMinimumSymbols(mUserId);
mPasswordMinNonLetter = mLockPatternUtils.getRequestedPasswordMinimumNonLetter(mUserId);
// Modify the value based on dpm policy.
// Modify the value based on dpm policy
switch (dpmPasswordQuality) {
case PASSWORD_QUALITY_ALPHABETIC:
if (mPasswordMinLetters == 0) {
@@ -646,19 +634,88 @@ public class ChooseLockPassword extends SettingsActivity {
mPasswordMinSymbols = 0;
mPasswordMinNonLetter = 0;
}
mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies();
}
/**
* Merges the dpm requirements and the min complexity requirements.
*
* <p>Since there are more than one set of metrics to meet the min complexity requirement,
* and we are not hard-coding any one of them to be the requirements the user must fulfil,
* we are taking what the user has already entered into account when compiling the list of
* requirements from min complexity. Then we merge this list with the DPM requirements, and
* present the merged set as validation results to the user on the UI.
*
* <p>For example, suppose min complexity requires either ALPHABETIC(8+), or
* ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI
* would be 8. Then the user appends "1" to make it "a1". We now know the user is entering
* an alphanumeric password so we would update the min complexity required min length to 6.
* This might result in a little confusion for the user but the UI does not support showing
* multiple sets of requirements / validation results as options to users, this is the best
* we can do now.
*/
private void mergeMinComplexityAndDpmRequirements(int userEnteredPasswordQuality) {
if (mRequestedMinComplexity == PASSWORD_COMPLEXITY_NONE) {
// dpm requirements are dominant if min complexity is none
return;
}
// reset dpm requirements
loadDpmPasswordRequirements();
PasswordMetrics minMetrics = PasswordMetrics.getMinimumMetrics(
mRequestedMinComplexity, userEnteredPasswordQuality, mRequestedQuality,
requiresNumeric(), requiresLettersOrSymbols());
mPasswordNumSequenceAllowed = mPasswordNumSequenceAllowed
&& minMetrics.quality != PASSWORD_QUALITY_NUMERIC_COMPLEX;
mPasswordMinLength = Math.max(mPasswordMinLength, minMetrics.length);
mPasswordMinLetters = Math.max(mPasswordMinLetters, minMetrics.letters);
mPasswordMinUpperCase = Math.max(mPasswordMinUpperCase, minMetrics.upperCase);
mPasswordMinLowerCase = Math.max(mPasswordMinLowerCase, minMetrics.lowerCase);
mPasswordMinNumeric = Math.max(mPasswordMinNumeric, minMetrics.numeric);
mPasswordMinSymbols = Math.max(mPasswordMinSymbols, minMetrics.symbols);
mPasswordMinNonLetter = Math.max(mPasswordMinNonLetter, minMetrics.nonLetter);
if (minMetrics.quality == PASSWORD_QUALITY_ALPHABETIC) {
if (!requiresLettersOrSymbols()) {
mPasswordMinLetters = 1;
}
}
if (minMetrics.quality == PASSWORD_QUALITY_ALPHANUMERIC) {
if (!requiresLettersOrSymbols()) {
mPasswordMinLetters = 1;
}
if (!requiresNumeric()) {
mPasswordMinNumeric = 1;
}
}
mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies();
}
private boolean requiresLettersOrSymbols() {
// This is the condition for the password to be considered ALPHABETIC according to
// PasswordMetrics.computeForPassword()
return mPasswordMinLetters + mPasswordMinUpperCase
+ mPasswordMinLowerCase + mPasswordMinSymbols + mPasswordMinNonLetter > 0;
}
private boolean requiresNumeric() {
return mPasswordMinNumeric > 0;
}
/**
* Validates PIN/Password and returns the validation result.
*
* @param password the raw password the user typed in
* @return the validation result.
*/
private int validatePassword(String password) {
@VisibleForTesting
int validatePassword(String password) {
int errorCode = NO_ERROR;
final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password);
mergeMinComplexityAndDpmRequirements(metrics.quality);
if (password.length() < mPasswordMinLength) {
if (mPasswordMinLength > mPasswordMinLengthToFulfillAllPolicies) {
@@ -668,14 +725,25 @@ public class ChooseLockPassword extends SettingsActivity {
errorCode |= TOO_LONG;
} else {
// The length requirements are fulfilled.
final int dpmQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId);
if (dpmQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX &&
metrics.numeric == password.length()) {
if (!mPasswordNumSequenceAllowed
&& !requiresLettersOrSymbols()
&& metrics.numeric == password.length()) {
// Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
// if DevicePolicyManager requires a complex numeric password. There can be
// two cases in the UI: 1. User chooses to enroll a PIN, 2. User chooses to
// enroll a password but enters a numeric-only pin. We should carry out the
// sequence check in both cases.
// if DevicePolicyManager or min password complexity requires a complex numeric
// password. There can be two cases in the UI: 1. User chooses to enroll a
// PIN, 2. User chooses to enroll a password but enters a numeric-only pin. We
// should carry out the sequence check in both cases.
//
// Conditions for the !requiresLettersOrSymbols() to be necessary:
// - DPM requires NUMERIC_COMPLEX
// - min complexity not NONE, user picks PASSWORD type so ALPHABETIC or
// ALPHANUMERIC is required
// Imagine user has entered "12345678", if we don't skip the sequence check, the
// validation result would show both "requires a letter" and "sequence not
// allowed", while the only requirement the user needs to know is "requires a
// letter" because once the user has fulfilled the alphabetic requirement, the
// password would not be containing only digits so this check would not be
// performed anyway.
final int sequence = PasswordMetrics.maxLengthSequence(password);
if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) {
errorCode |= CONTAIN_SEQUENTIAL_DIGITS;
@@ -706,43 +774,24 @@ public class ChooseLockPassword extends SettingsActivity {
}
}
// Check the requirements one by one.
for (int i = 0; i < mPasswordRequirements.length; i++) {
int passwordRestriction = mPasswordRequirements[i];
switch (passwordRestriction) {
case MIN_LETTER_IN_PASSWORD:
if (metrics.letters < mPasswordMinLetters) {
errorCode |= NOT_ENOUGH_LETTER;
}
break;
case MIN_UPPER_LETTERS_IN_PASSWORD:
if (metrics.upperCase < mPasswordMinUpperCase) {
errorCode |= NOT_ENOUGH_UPPER_CASE;
}
break;
case MIN_LOWER_LETTERS_IN_PASSWORD:
if (metrics.lowerCase < mPasswordMinLowerCase) {
errorCode |= NOT_ENOUGH_LOWER_CASE;
}
break;
case MIN_SYMBOLS_IN_PASSWORD:
if (metrics.symbols < mPasswordMinSymbols) {
errorCode |= NOT_ENOUGH_SYMBOLS;
}
break;
case MIN_NUMBER_IN_PASSWORD:
if (metrics.numeric < mPasswordMinNumeric) {
errorCode |= NOT_ENOUGH_DIGITS;
}
break;
case MIN_NON_LETTER_IN_PASSWORD:
if (metrics.nonLetter < mPasswordMinNonLetter) {
errorCode |= NOT_ENOUGH_NON_LETTER;
}
break;
}
if (metrics.letters < mPasswordMinLetters) {
errorCode |= NOT_ENOUGH_LETTER;
}
if (metrics.upperCase < mPasswordMinUpperCase) {
errorCode |= NOT_ENOUGH_UPPER_CASE;
}
if (metrics.lowerCase < mPasswordMinLowerCase) {
errorCode |= NOT_ENOUGH_LOWER_CASE;
}
if (metrics.symbols < mPasswordMinSymbols) {
errorCode |= NOT_ENOUGH_SYMBOLS;
}
if (metrics.numeric < mPasswordMinNumeric) {
errorCode |= NOT_ENOUGH_DIGITS;
}
if (metrics.nonLetter < mPasswordMinNonLetter) {
errorCode |= NOT_ENOUGH_NON_LETTER;
}
return errorCode;
}

View File

@@ -50,8 +50,8 @@ import com.android.settings.core.InstrumentedFragment;
import com.android.settings.notification.RedactionInterstitial;
import com.google.android.collect.Lists;
import com.google.android.setupcompat.item.FooterButton;
import com.google.android.setupcompat.template.ButtonFooterMixin;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
import java.util.ArrayList;
@@ -489,7 +489,7 @@ public class ChooseLockPattern extends SettingsActivity {
}
}
final ButtonFooterMixin mixin = layout.getMixin(ButtonFooterMixin.class);
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
mixin.setSecondaryButton(
new FooterButton.Builder(getActivity())
.setText(R.string.lockpattern_tutorial_cancel_label)
@@ -512,7 +512,6 @@ public class ChooseLockPattern extends SettingsActivity {
return layout;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);

View File

@@ -45,6 +45,18 @@ public final class ChooseLockSettingsHelper {
public static final String EXTRA_KEY_FOR_FACE = "for_face";
public static final String EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT = "for_cred_req_boot";
/**
* Intent extra for passing the requested min password complexity to later steps in the set new
* screen lock flow.
*/
public static final String EXTRA_KEY_REQUESTED_MIN_COMPLEXITY = "requested_min_complexity";
/**
* Intent extra for passing the label of the calling app to later steps in the set new screen
* lock flow.
*/
public static final String EXTRA_KEY_CALLER_APP_NAME = "caller_app_name";
/**
* When invoked via {@link ConfirmLockPassword.InternalActivity}, this flag
* controls if we relax the enforcement of

View File

@@ -23,6 +23,7 @@ import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.Context;
import android.content.Intent;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
@@ -87,6 +88,8 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
private TrustManager mTrustManager;
private ChooseLockSettingsHelper mChooseLockSettingsHelper;
private Handler mHandler = new Handler(Looper.getMainLooper());
private boolean mIsFallback; // BiometricPrompt fallback
private boolean mCCLaunched;
private String mTitle;
private String mDetails;
@@ -102,6 +105,13 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
if (!mGoingToBackground) {
if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED) {
if (mIsFallback) {
mBiometricManager.onConfirmDeviceCredentialError(
BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
getString(
com.android.internal.R.string
.biometric_error_user_canceled));
}
finish();
} else {
// All other errors go to some version of CC
@@ -118,6 +128,10 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
ConfirmDeviceCredentialUtils.checkForPendingIntent(
ConfirmDeviceCredentialActivity.this);
if (mIsFallback) {
mBiometricManager.onConfirmDeviceCredentialSuccess();
}
setResult(Activity.RESULT_OK);
finish();
}
@@ -158,6 +172,19 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this);
final LockPatternUtils lockPatternUtils = new LockPatternUtils(this);
Bundle bpBundle =
intent.getBundleExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE);
if (bpBundle != null) {
mIsFallback = true;
// TODO: CDC maybe should show description as well.
mTitle = bpBundle.getString(BiometricPrompt.KEY_TITLE);
mDetails = bpBundle.getString(BiometricPrompt.KEY_SUBTITLE);
} else {
bpBundle = new Bundle();
bpBundle.putString(BiometricPrompt.KEY_TITLE, mTitle);
bpBundle.putString(BiometricPrompt.KEY_SUBTITLE, mDetails);
}
boolean launchedBiometric = false;
boolean launchedCDC = false;
// If the target is a managed user and user key not unlocked yet, we will force unlock
@@ -170,7 +197,7 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
&& !lockPatternUtils.isSeparateProfileChallengeEnabled(mUserId)) {
mCredentialMode = CREDENTIAL_MANAGED;
if (isBiometricAllowed(effectiveUserId)) {
showBiometricPrompt();
showBiometricPrompt(bpBundle);
launchedBiometric = true;
} else {
showConfirmCredentials();
@@ -181,7 +208,7 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
if (isBiometricAllowed(effectiveUserId)) {
// Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
// onAuthenticationError and do the right thing automatically.
showBiometricPrompt();
showBiometricPrompt(bpBundle);
launchedBiometric = true;
} else {
showConfirmCredentials();
@@ -216,6 +243,13 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
if (mBiometricFragment != null) {
mBiometricFragment.cancel();
}
if (mIsFallback && !mCCLaunched) {
mBiometricManager.onConfirmDeviceCredentialError(
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
getString(com.android.internal.R.string.biometric_error_user_canceled));
}
finish();
} else {
mGoingToBackground = false;
@@ -242,7 +276,7 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
&& !isBiometricDisabledByAdmin(effectiveUserId);
}
private void showBiometricPrompt() {
private void showBiometricPrompt(Bundle bundle) {
mBiometricManager.setActiveUser(mUserId);
mBiometricFragment = (BiometricFragment) getSupportFragmentManager()
@@ -250,13 +284,7 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
boolean newFragment = false;
if (mBiometricFragment == null) {
final BiometricFragment.PromptInfo info = new BiometricFragment.PromptInfo.Builder()
.setTitle(mTitle)
.setSubtitle(mDetails)
.setNegativeButtonText(getResources()
.getString(R.string.confirm_device_credential_use_alternate_method))
.build();
mBiometricFragment = BiometricFragment.newInstance(info);
mBiometricFragment = BiometricFragment.newInstance(bundle);
newFragment = true;
}
mBiometricFragment.setCallbacks(mExecutor, mAuthenticationCallback);
@@ -272,6 +300,7 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
* Shows ConfirmDeviceCredentials for normal apps.
*/
private void showConfirmCredentials() {
mCCLaunched = true;
boolean launched = false;
if (mCredentialMode == CREDENTIAL_MANAGED) {
// We set the challenge as 0L, so it will force to unlock managed profile when it

View File

@@ -17,6 +17,8 @@
package com.android.settings.password;
import android.app.KeyguardManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.os.Bundle;
import android.os.UserManager;
import android.view.MenuItem;
@@ -45,6 +47,7 @@ public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivi
private boolean mFirstTimeVisible = true;
private boolean mIsKeyguardLocked = false;
private ConfirmCredentialTheme mConfirmCredentialTheme;
private BiometricManager mBiometricManager;
private boolean isInternalActivity() {
return (this instanceof ConfirmLockPassword.InternalActivity)
@@ -68,6 +71,8 @@ public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivi
}
super.onCreate(savedState);
mBiometricManager = getSystemService(BiometricManager.class);
if (mConfirmCredentialTheme == ConfirmCredentialTheme.NORMAL) {
// Prevent the content parent from consuming the window insets because GlifLayout uses
// it to show the status bar background.
@@ -140,6 +145,17 @@ public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivi
}
}
@Override
public void onStop() {
super.onStop();
// TODO(b/123378871): Remove when moved.
if (!isChangingConfigurations()) {
mBiometricManager.onConfirmDeviceCredentialError(
BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
getString(com.android.internal.R.string.biometric_error_user_canceled));
}
}
@Override
public void finish() {
super.finish();

View File

@@ -29,6 +29,7 @@ import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserManager;
@@ -84,6 +85,7 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
protected final Handler mHandler = new Handler();
protected boolean mFrp;
private CharSequence mFrpAlternateButtonText;
protected BiometricManager mBiometricManager;
private boolean isInternalActivity() {
return (getActivity() instanceof ConfirmLockPassword.InternalActivity)
@@ -107,6 +109,7 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
mLockPatternUtils = new LockPatternUtils(getActivity());
mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService(
Context.DEVICE_POLICY_SERVICE);
mBiometricManager = getActivity().getSystemService(BiometricManager.class);
}
@Override

View File

@@ -437,6 +437,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils,
mUserManager, mEffectiveUserId);
}
mBiometricManager.onConfirmDeviceCredentialSuccess();
startDisappearAnimation(intent);
ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity());
} else {

View File

@@ -490,6 +490,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils,
mUserManager, mEffectiveUserId);
}
mBiometricManager.onConfirmDeviceCredentialSuccess();
startDisappearAnimation(intent);
ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity());
} else {

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2019 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.password;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import com.android.settings.Utils;
public final class PasswordUtils extends com.android.settingslib.Utils {
private static final String TAG = "Settings";
private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
/**
* Returns whether the uid which the activity with {@code activityToken} is launched from has
* been granted the {@code permission}.
*/
public static boolean isCallingAppPermitted(Context context, IBinder activityToken,
String permission) {
try {
return context.checkPermission(permission, /* pid= */ -1,
ActivityManager.getService().getLaunchedFromUid(activityToken))
== PackageManager.PERMISSION_GRANTED;
} catch (RemoteException e) {
Log.v(TAG, "Could not talk to activity manager.", e);
return false;
}
}
/**
* Returns the label of the package which the activity with {@code activityToken} is launched
* from or {@code null} if it is launched from the settings app itself.
*/
@Nullable
public static CharSequence getCallingAppLabel(Context context, IBinder activityToken) {
String pkg = getCallingAppPackageName(activityToken);
if (pkg == null || pkg.equals(SETTINGS_PACKAGE_NAME)) {
return null;
}
return Utils.getApplicationLabel(context, pkg);
}
/**
* Returns the package name which the activity with {@code activityToken} is launched from.
*/
@Nullable
private static String getCallingAppPackageName(IBinder activityToken) {
String pkg = null;
try {
pkg = ActivityManager.getService().getLaunchedFromPackage(activityToken);
} catch (RemoteException e) {
Log.v(TAG, "Could not talk to activity manager.", e);
}
return pkg;
}
/** Crashes the calling application and provides it with {@code message}. */
public static void crashCallingApplication(IBinder activityToken, String message) {
IActivityManager am = ActivityManager.getService();
try {
int uid = am.getLaunchedFromUid(activityToken);
int userId = UserHandle.getUserId(uid);
am.crashApplication(
uid,
/* initialPid= */ -1,
getCallingAppPackageName(activityToken),
userId,
message);
} catch (RemoteException e) {
Log.v(TAG, "Could not talk to activity manager.", e);
}
}
}

View File

@@ -16,13 +16,22 @@
package com.android.settings.password;
import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PARENT_PROFILE_PASSWORD;
import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD;
import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.PasswordMetrics;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import com.android.settings.Utils;
@@ -37,6 +46,21 @@ public class SetNewPasswordActivity extends Activity implements SetNewPasswordCo
private String mNewPasswordAction;
private SetNewPasswordController mSetNewPasswordController;
/**
* From intent extra {@link DevicePolicyManager#EXTRA_PASSWORD_COMPLEXITY}.
*
* <p>This is used only if caller has the required permission and activity is launched by
* {@link DevicePolicyManager#ACTION_SET_NEW_PASSWORD}.
*/
private @PasswordComplexity int mRequestedMinComplexity = PASSWORD_COMPLEXITY_NONE;
/**
* Label of the app which launches this activity.
*
* <p>Value would be {@code null} if launched from settings app.
*/
private String mCallerAppName = null;
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
@@ -48,6 +72,25 @@ public class SetNewPasswordActivity extends Activity implements SetNewPasswordCo
finish();
return;
}
IBinder activityToken = getActivityToken();
mCallerAppName = (String) PasswordUtils.getCallingAppLabel(this, activityToken);
if (ACTION_SET_NEW_PASSWORD.equals(mNewPasswordAction)
&& getIntent().hasExtra(EXTRA_PASSWORD_COMPLEXITY)) {
boolean hasPermission = PasswordUtils.isCallingAppPermitted(
this, activityToken, GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
if (hasPermission) {
mRequestedMinComplexity = PasswordMetrics.sanitizeComplexityLevel(getIntent()
.getIntExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_NONE));
} else {
PasswordUtils.crashCallingApplication(activityToken,
"Must have permission " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY
+ " to use extra " + EXTRA_PASSWORD_COMPLEXITY);
finish();
return;
}
}
mSetNewPasswordController = SetNewPasswordController.create(
this, this, getIntent(), getActivityToken());
mSetNewPasswordController.dispatchSetNewPasswordIntent();
@@ -60,6 +103,12 @@ public class SetNewPasswordActivity extends Activity implements SetNewPasswordCo
: new Intent(this, ChooseLockGeneric.class);
intent.setAction(mNewPasswordAction);
intent.putExtras(chooseLockFingerprintExtras);
if (mCallerAppName != null) {
intent.putExtra(EXTRA_KEY_CALLER_APP_NAME, mCallerAppName);
}
if (mRequestedMinComplexity != PASSWORD_COMPLEXITY_NONE) {
intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, mRequestedMinComplexity);
}
startActivity(intent);
finish();
}

View File

@@ -16,11 +16,17 @@
package com.android.settings.password;
import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.IBinder;
import android.os.UserHandle;
import android.view.LayoutInflater;
import android.view.View;
@@ -48,6 +54,7 @@ import com.google.android.setupdesign.GlifPreferenceLayout;
* Other changes should be done to ChooseLockGeneric class instead and let this class inherit
* those changes.
*/
// TODO(b/123225425): Restrict SetupChooseLockGeneric to be accessible by SUW only
public class SetupChooseLockGeneric extends ChooseLockGeneric {
private static final String KEY_UNLOCK_SET_DO_LATER = "unlock_set_do_later";
@@ -71,6 +78,20 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric {
@Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
if(getIntent().hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)) {
IBinder activityToken = getActivityToken();
boolean hasPermission = PasswordUtils.isCallingAppPermitted(
this, activityToken, GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
if (!hasPermission) {
PasswordUtils.crashCallingApplication(activityToken,
"Must have permission " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY
+ " to use extra " + EXTRA_PASSWORD_COMPLEXITY);
finish();
return;
}
}
LinearLayout layout = (LinearLayout) findViewById(R.id.content_parent);
layout.setFitsSystemWindows(false);
}

View File

@@ -76,6 +76,7 @@ public class PermissionBarChartPreferenceController extends BasePreferenceContro
.setEmptyText(R.string.permission_bar_chart_empty_text)
.setDetailsOnClickListener((View v) -> {
final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE);
intent.putExtra(Intent.EXTRA_DURATION_MILLIS, DAYS.toMillis(1));
mContext.startActivity(intent);
})
.build();
@@ -102,7 +103,7 @@ public class PermissionBarChartPreferenceController extends BasePreferenceContro
private void retrievePermissionUsageData() {
mContext.getSystemService(PermissionControllerManager.class).getPermissionUsages(
false /* countSystem */, (int) DAYS.toSeconds(1),
false /* countSystem */, (int) DAYS.toMillis(1),
mContext.getMainExecutor() /* executor */, this /* callback */);
}
@@ -111,6 +112,9 @@ public class PermissionBarChartPreferenceController extends BasePreferenceContro
return null;
}
// STOPSHIP: Ignore the STORAGE group since it's going away.
usageInfos.removeIf(usage -> usage.getName().equals("android.permission-group.STORAGE"));
final BarViewInfo[] barViewInfos = new BarViewInfo[
Math.min(BarChartPreference.MAXIMUM_BAR_VIEWS, usageInfos.size())];

View File

@@ -1,86 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security;
import android.app.Activity;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.CredentialStorage;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.password.ChooseLockGeneric;
/**
* Prompt for key guard configuration confirmation.
*/
public class ConfigureKeyGuardDialog extends InstrumentedDialogFragment
implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
public static final String TAG = "ConfigureKeyGuardDialog";
private boolean mConfigureConfirmed;
@Override
public int getMetricsCategory() {
return SettingsEnums.CONFIGURE_KEYGUARD_DIALOG;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(android.R.string.dialog_alert_title)
.setMessage(R.string.credentials_configure_lock_screen_hint)
.setPositiveButton(R.string.credentials_configure_lock_screen_button, this)
.setNegativeButton(android.R.string.cancel, this)
.create();
}
@Override
public void onClick(DialogInterface dialog, int button) {
mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
}
@Override
public void onDismiss(DialogInterface dialog) {
if (mConfigureConfirmed) {
mConfigureConfirmed = false;
startPasswordSetup();
return;
} else {
final Activity activity = getActivity();
if (activity != null) {
activity.finish();
}
}
}
@VisibleForTesting
void startPasswordSetup() {
Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
CredentialStorage.MIN_PASSWORD_QUALITY);
startActivity(intent);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.settings;
package com.android.settings.security;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
@@ -44,8 +44,8 @@ import androidx.fragment.app.FragmentActivity;
import com.android.internal.widget.LockPatternUtils;
import com.android.org.bouncycastle.asn1.ASN1InputStream;
import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import com.android.settings.R;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.security.ConfigureKeyGuardDialog;
import com.android.settings.vpn2.VpnUtils;
import java.io.ByteArrayInputStream;
@@ -55,46 +55,12 @@ import sun.security.util.ObjectIdentifier;
import sun.security.x509.AlgorithmId;
/**
* CredentialStorage handles KeyStore reset, unlock, and install.
*
* CredentialStorage has a pretty convoluted state machine to migrate
* from the old style separate keystore password to a new key guard
* based password, as well as to deal with setting up the key guard if
* necessary.
*
* KeyStore: UNINITALIZED
* KeyGuard: OFF
* Action: set up key guard
* Notes: factory state
*
* KeyStore: UNINITALIZED
* KeyGuard: ON
* Action: confirm key guard
* Notes: user had key guard but no keystore and upgraded from pre-ICS
* OR user had key guard and pre-ICS keystore password which was then reset
*
* KeyStore: LOCKED
* KeyGuard: OFF/ON
* Action: confirm key guard
* Notes: request normal unlock to unlock the keystore.
* if unlock, ensure key guard before install.
* if reset, treat as UNINITALIZED/OFF
*
* KeyStore: UNLOCKED
* KeyGuard: OFF
* Action: set up key guard
* Notes: ensure key guard, then proceed
*
* KeyStore: UNLOCKED
* keyguard: ON
* Action: normal unlock/install
* Notes: this is the common case
* CredentialStorage handles resetting and installing keys into KeyStore.
*/
public final class CredentialStorage extends FragmentActivity {
private static final String TAG = "CredentialStorage";
public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK";
public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
public static final String ACTION_RESET = "com.android.credentials.RESET";
@@ -102,8 +68,7 @@ public final class CredentialStorage extends FragmentActivity {
// lower than this, keystore should not be activated.
public static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
private static final int CONFIRM_KEY_GUARD_REQUEST = 1;
private static final int CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST = 2;
private static final int CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST = 1;
private final KeyStore mKeyStore = KeyStore.getInstance();
private LockPatternUtils mUtils;
@@ -133,75 +98,26 @@ public final class CredentialStorage extends FragmentActivity {
if (ACTION_INSTALL.equals(action) && checkCallerIsCertInstallerOrSelfInProfile()) {
mInstallBundle = intent.getExtras();
}
// ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
handleUnlockOrInstall();
handleInstall();
}
} else {
// Users can set a screen lock if there is none even if they can't modify the
// credentials store.
if (ACTION_UNLOCK.equals(action) && mKeyStore.state() == KeyStore.State.UNINITIALIZED) {
ensureKeyGuard();
} else {
finish();
}
finish();
}
}
/**
* Based on the current state of the KeyStore and key guard, try to
* make progress on unlocking or installing to the keystore.
* Install credentials from mInstallBundle into Keystore.
*/
private void handleUnlockOrInstall() {
private void handleInstall() {
// something already decided we are done, do not proceed
if (isFinishing()) {
return;
}
switch (mKeyStore.state()) {
case UNINITIALIZED: {
ensureKeyGuard();
return;
}
case LOCKED: {
// Force key guard confirmation
confirmKeyGuard(CONFIRM_KEY_GUARD_REQUEST);
return;
}
case UNLOCKED: {
if (!mUtils.isSecure(UserHandle.myUserId())) {
final ConfigureKeyGuardDialog dialog = new ConfigureKeyGuardDialog();
dialog.show(getSupportFragmentManager(), ConfigureKeyGuardDialog.TAG);
return;
}
if (installIfAvailable()) {
finish();
}
return;
}
if (installIfAvailable()) {
finish();
}
}
/**
* Make sure the user enters the key guard to set or change the
* keystore password. This can be used in UNINITIALIZED to set the
* keystore password or UNLOCKED to change the password (as is the
* case after unlocking with an old-style password).
*/
private void ensureKeyGuard() {
if (!mUtils.isSecure(UserHandle.myUserId())) {
// key guard not setup, doing so will initialize keystore
final ConfigureKeyGuardDialog dialog = new ConfigureKeyGuardDialog();
dialog.show(getSupportFragmentManager(), ConfigureKeyGuardDialog.TAG);
// will return to onResume after Activity
return;
}
// force key guard confirmation
if (confirmKeyGuard(CONFIRM_KEY_GUARD_REQUEST)) {
// will return password value via onActivityResult
return;
}
finish();
}
private boolean isHardwareBackedKey(byte[] keyData) {
try {
final ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData));
@@ -254,15 +170,7 @@ public final class CredentialStorage extends FragmentActivity {
final String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME);
final byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
int flags = KeyStore.FLAG_ENCRYPTED;
if (uid == Process.WIFI_UID && isHardwareBackedKey(value)) {
// Hardware backed keystore is secure enough to allow for WIFI stack
// to enable access to secure networks without user intervention
Log.d(TAG, "Saving private key with FLAG_NONE for WIFI_UID");
flags = KeyStore.FLAG_NONE;
}
if (!mKeyStore.importKey(key, value, uid, flags)) {
if (!mKeyStore.importKey(key, value, uid, KeyStore.FLAG_NONE)) {
Log.e(TAG, "Failed to install " + key + " as uid " + uid);
return true;
}
@@ -475,20 +383,7 @@ public final class CredentialStorage extends FragmentActivity {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Receive key guard password initiated by confirmKeyGuard.
if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
if (resultCode == Activity.RESULT_OK) {
final String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
if (!TextUtils.isEmpty(password)) {
// success
mKeyStore.unlock(password);
// return to onResume
return;
}
}
// failed confirmation, bail
finish();
} else if (requestCode == CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST) {
if (requestCode == CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST) {
if (resultCode == Activity.RESULT_OK) {
new ResetKeyStoreAndKeyChain().execute();
return;

View File

@@ -0,0 +1,7 @@
# Enterprise security reviewers
eranm@google.com
pgrafov@google.com
rubinxu@google.com
# Emergency
sandness@google.com

View File

@@ -154,6 +154,15 @@ public class CustomSliceRegistry {
.appendEncodedPath(SettingsSlicesContract.PATH_SETTING_INTENT)
.appendPath("low_storage")
.build();
/**
* Backing Uri for NFC Slice
*/
public static final Uri NFC_SLICE_URI = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(SettingsSliceProvider.SLICE_AUTHORITY)
.appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
.appendPath("toggle_nfc")
.build();
/**
* Backing Uri for Notification channel Slice.
*/

View File

@@ -59,10 +59,6 @@ public abstract class SliceBackgroundWorker<E> implements Closeable {
mUri = uri;
}
protected Uri getUri() {
return mUri;
}
/**
* Returns the singleton instance of the {@link SliceBackgroundWorker} for specified {@link Uri}
* if exists
@@ -148,7 +144,14 @@ public abstract class SliceBackgroundWorker<E> implements Closeable {
if (needNotify) {
mCachedResults = results;
mContext.getContentResolver().notifyChange(mUri, null);
notifySliceChange();
}
}
}
/**
* Notify that data was updated and attempt to sync changes to the Slice.
*/
protected void notifySliceChange() {
mContext.getContentResolver().notifyChange(mUri, null);
}
}

View File

@@ -56,8 +56,6 @@ public class ConfigDialogFragment extends InstrumentedDialogFragment implements
ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
private Context mContext;
private boolean mUnlocking = false;
@Override
public int getMetricsCategory() {
@@ -84,27 +82,6 @@ public class ConfigDialogFragment extends InstrumentedDialogFragment implements
mContext = context;
}
@Override
public void onResume() {
super.onResume();
// Check KeyStore here, so others do not need to deal with it.
if (!KeyStore.getInstance().isUnlocked()) {
if (!mUnlocking) {
// Let us unlock KeyStore. See you later!
Credentials.getInstance().unlock(mContext);
} else {
// We already tried, but it is still not working!
dismiss();
}
mUnlocking = !mUnlocking;
return;
}
// Now KeyStore is always unlocked. Reset the flag.
mUnlocking = false;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();

View File

@@ -29,6 +29,8 @@ import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.google.android.setupcompat.util.WizardManagerHelper;
public class WallpaperSuggestionActivity extends Activity {
@Override
@@ -39,6 +41,10 @@ public class WallpaperSuggestionActivity extends Activity {
.setClassName(getString(R.string.config_wallpaper_picker_package),
getString(R.string.config_wallpaper_picker_class))
.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
// passing the necessary extra to next page
WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
if (pm.resolveActivity(intent, 0) != null) {
startActivity(intent);
} else {

View File

@@ -21,6 +21,7 @@ import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
@@ -74,6 +75,9 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp
/** Delayed time to stop scanning wifi. */
private static final int DELAY_TIME_STOP_SCAN_MS = 30 * 1000;
@VisibleForTesting
final static String EXTRA_APP_NAME = "com.android.settings.wifi.extra.APP_NAME";
private List<AccessPoint> mAccessPointList;
private FilterWifiTracker mFilterWifiTracker;
private AccessPointAdapter mDialogAdapter;
@@ -93,7 +97,8 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp
final View customTitle = inflater.inflate(R.layout.network_request_dialog_title, null);
final TextView title = customTitle.findViewById(R.id.network_request_title_text);
title.setText(R.string.network_connection_request_dialog_title);
title.setText(getTitle());
final ProgressBar progressBar = customTitle.findViewById(
R.id.network_request_title_progress);
progressBar.setVisibility(View.VISIBLE);
@@ -115,6 +120,16 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp
return dialog;
}
private String getTitle() {
final Intent intent = getActivity().getIntent();
String appName = "";
if (intent != null) {
appName = intent.getStringExtra(EXTRA_APP_NAME);
}
return getString(R.string.network_connection_request_dialog_title, appName);
}
@NonNull
List<AccessPoint> getAccessPointList() {
// Initials list for adapter, in case of display crashing.

View File

@@ -63,6 +63,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.settings.ProxySelector;
import com.android.settings.R;
import com.android.settings.wifi.details.WifiPrivacyPreferenceController;
import com.android.settingslib.Utils;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.wifi.AccessPoint;
@@ -251,8 +252,6 @@ public class WifiConfigController implements TextWatcher,
com.android.settings.core.FeatureFlags.WIFI_MAC_RANDOMIZATION)) {
View privacySettingsLayout = mView.findViewById(R.id.privacy_settings_fields);
privacySettingsLayout.setVisibility(View.VISIBLE);
// Set default value
mPrivacySettingsSpinner.setSelection(WifiConfiguration.RANDOMIZATION_PERSISTENT);
}
mHiddenSettingsSpinner.setOnItemSelectedListener(this);
mHiddenWarningView = mView.findViewById(R.id.hidden_settings_warning);
@@ -281,7 +280,12 @@ public class WifiConfigController implements TextWatcher,
mHiddenSettingsSpinner.setSelection(config.hiddenSSID
? HIDDEN_NETWORK
: NOT_HIDDEN_NETWORK);
mPrivacySettingsSpinner.setSelection(config.macRandomizationSetting);
final int prefMacValue =
WifiPrivacyPreferenceController.translateMacRandomizedValueToPrefValue(
config.macRandomizationSetting);
mPrivacySettingsSpinner.setSelection(prefMacValue);
if (config.getIpAssignment() == IpAssignment.STATIC) {
mIpSettingsSpinner.setSelection(STATIC_IP);
showAdvancedFields = true;
@@ -766,7 +770,10 @@ public class WifiConfigController implements TextWatcher,
}
if (mPrivacySettingsSpinner != null) {
config.macRandomizationSetting = mPrivacySettingsSpinner.getSelectedItemPosition();
final int macValue =
WifiPrivacyPreferenceController.translatePrefValueToMacRandomizedValue(
mPrivacySettingsSpinner.getSelectedItemPosition());
config.macRandomizationSetting = macValue;
}
return config;

View File

@@ -290,7 +290,8 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
.setButton2OnClickListener(view -> signIntoNetwork())
.setButton3Text(R.string.share)
.setButton3Icon(R.drawable.ic_qrcode_24dp)
.setButton3OnClickListener(view -> shareNetwork());
.setButton3OnClickListener(view -> shareNetwork())
.setButton3Visible(WifiDppUtils.isSuportConfigurator(mContext, mAccessPoint));
mSignalStrengthPref = screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
mTxLinkSpeedPref = screen.findPreference(KEY_TX_LINK_SPEED);
@@ -558,10 +559,15 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
/**
* Show QR code to share the network represented by this preference.
*/
public void launchQRCodeGenerator() {
Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntent(mContext, mWifiManager,
public void launchWifiDppConfiguratorActivity() {
final Intent intent = WifiDppUtils.getConfiguratorIntentOrNull(mContext, mWifiManager,
mAccessPoint);
mContext.startActivity(intent);
if (intent == null) {
Log.e(TAG, "Launch Wi-Fi DPP configurator with a wrong Wi-Fi network!");
} else {
mContext.startActivity(intent);
}
}
/**
@@ -584,7 +590,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
WifiNetworkDetailsFragment.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
}
} else {
launchQRCodeGenerator();
launchWifiDppConfiguratorActivity();
}
}

View File

@@ -152,7 +152,7 @@ public class WifiNetworkDetailsFragment extends DashboardFragment {
if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS
&& resultCode == Activity.RESULT_OK) {
mWifiDetailPreferenceController.launchQRCodeGenerator();
mWifiDetailPreferenceController.launchWifiDppConfiguratorActivity();
}
}
}

View File

@@ -85,15 +85,29 @@ public class WifiPrivacyPreferenceController extends BasePreferenceController im
return WifiConfiguration.RANDOMIZATION_PERSISTENT;
}
private final int PREF_RANDOMIZATION_PERSISTENT = 0;
private final int PREF_RANDOMIZATION_NONE = 1;
@VisibleForTesting
protected int translateMacRandomizedValueToPrefValue(int macRandomized) {
if (macRandomized == WifiConfiguration.RANDOMIZATION_PERSISTENT) {
return PREF_RANDOMIZATION_PERSISTENT;
} else {
return PREF_RANDOMIZATION_NONE;
}
private static final int PREF_RANDOMIZATION_PERSISTENT = 0;
private static final int PREF_RANDOMIZATION_NONE = 1;
/**
* Returns preference index value.
*
* @param macRandomized is mac randomized value
* @return index value of preference
*/
public static int translateMacRandomizedValueToPrefValue(int macRandomized) {
return (macRandomized == WifiConfiguration.RANDOMIZATION_PERSISTENT)
? PREF_RANDOMIZATION_PERSISTENT : PREF_RANDOMIZATION_NONE;
}
/**
* Returns mac randomized value.
*
* @param prefMacRandomized is preference index value
* @return mac randomized value
*/
public static int translatePrefValueToMacRandomizedValue(int prefMacRandomized) {
return (prefMacRandomized == PREF_RANDOMIZATION_PERSISTENT)
? WifiConfiguration.RANDOMIZATION_PERSISTENT : WifiConfiguration.RANDOMIZATION_NONE;
}
private void updateSummary(DropDownPreference preference, int macRandomized) {

View File

@@ -84,9 +84,18 @@ public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
MenuItem item = menu.add(0, Menu.FIRST, 0, R.string.next_label);
item.setIcon(R.drawable.ic_scan_24dp);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
final WifiNetworkConfig wifiNetworkConfig = getWifiNetworkConfigFromHostActivity();
MenuItem menuItem;
if (wifiNetworkConfig.isSupportConfiguratorQrCodeScanner(getActivity())) {
menuItem = menu.add(0, Menu.FIRST, 0, R.string.next_label);
menuItem.setIcon(R.drawable.ic_scan_24dp);
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
} else {
menuItem = menu.findItem(Menu.FIRST);
if (menuItem != null) {
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
}
super.onCreateOptionsMenu(menu, inflater);
}
@@ -116,11 +125,7 @@ public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment {
mQrCodeView = view.findViewById(R.id.qrcode_view);
mHeaderIcon.setImageResource(R.drawable.ic_qrcode_24dp);
WifiNetworkConfig wifiNetworkConfig = ((WifiNetworkConfig.Retriever) getActivity())
.getWifiNetworkConfig();
if (!WifiNetworkConfig.isValidConfig(wifiNetworkConfig)) {
throw new IllegalStateException("Invalid Wi-Fi network for configuring");
}
final WifiNetworkConfig wifiNetworkConfig = getWifiNetworkConfigFromHostActivity();
mTitle.setText(R.string.wifi_dpp_share_wifi);
mSummary.setText(getString(R.string.wifi_dpp_scan_qr_code_with_another_device,
wifiNetworkConfig.getSsid()));
@@ -139,4 +144,14 @@ public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment {
Log.e(TAG, "Error generatting QR code bitmap " + e);
}
}
WifiNetworkConfig getWifiNetworkConfigFromHostActivity() {
final WifiNetworkConfig wifiNetworkConfig = ((WifiNetworkConfig.Retriever) getActivity())
.getWifiNetworkConfig();
if (!WifiNetworkConfig.isValidConfig(wifiNetworkConfig)) {
throw new IllegalStateException("Invalid Wi-Fi network for configuring");
}
return wifiNetworkConfig;
}
}

View File

@@ -134,26 +134,36 @@ public class WifiDppUtils {
private static String getSecurityString(AccessPoint accessPoint) {
switch(accessPoint.getSecurity()) {
case AccessPoint.SECURITY_WEP:
return "WEP";
return WifiQrCode.SECURITY_WEP;
case AccessPoint.SECURITY_PSK:
return "WPA";
return WifiQrCode.SECURITY_WPA;
case AccessPoint.SECURITY_SAE:
return WifiQrCode.SECURITY_SAE;
default:
return "nopass";
return WifiQrCode.SECURITY_NO_PASSWORD;
}
}
/**
* Returns an intent to launch QR code generator.
* Returns an intent to launch QR code generator or scanner according to the Wi-Fi network
* security. It may return null if the security is not supported by QR code generator nor
* scanner.
*
* @param context The context to use for the content resolver
* @param wifiManager An instance of {@link WifiManager}
* @param accessPoint An instance of {@link AccessPoint}
* @return Intent for launching QR code generator
*/
public static Intent getConfiguratorQrCodeGeneratorIntent(Context context,
public static Intent getConfiguratorIntentOrNull(Context context,
WifiManager wifiManager, AccessPoint accessPoint) {
final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class);
intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR);
if (isSupportConfiguratorQrCodeGenerator(accessPoint)) {
intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR);
} else if (isSupportConfiguratorQrCodeScanner(context, accessPoint)) {
intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_SCANNER);
} else {
return null;
}
final WifiConfiguration wifiConfig = accessPoint.getConfig();
final String ssid = removeFirstAndLastDoubleQuotes(wifiConfig.SSID);
@@ -183,4 +193,43 @@ public class WifiDppUtils {
return intent;
}
/**
* Android Q supports Wi-Fi configurator by:
*
* 1. QR code generator of ZXing's Wi-Fi network config format.
* and
* 2. QR code scanner of Wi-Fi DPP QR code format.
*/
public static boolean isSuportConfigurator(Context context, AccessPoint accessPoint) {
return isSupportConfiguratorQrCodeScanner(context, accessPoint) ||
isSupportConfiguratorQrCodeGenerator(accessPoint);
}
private static boolean isSupportConfiguratorQrCodeScanner(Context context,
AccessPoint accessPoint) {
if (!isWifiDppEnabled(context)) {
return false;
}
// DPP 1.0 only supports SAE and PSK.
final int security = accessPoint.getSecurity();
if (security == AccessPoint.SECURITY_SAE || security == AccessPoint.SECURITY_PSK) {
return true;
}
return false;
}
private static boolean isSupportConfiguratorQrCodeGenerator(AccessPoint accessPoint) {
// QR code generator produces QR code with ZXing's Wi-Fi network config format,
// it supports PSK and WEP and non security
final int security = accessPoint.getSecurity();
if (security == AccessPoint.SECURITY_PSK || security == AccessPoint.SECURITY_WEP ||
security == AccessPoint.SECURITY_NONE) {
return true;
}
return false;
}
}

View File

@@ -17,6 +17,7 @@
package com.android.settings.wifi.dpp;
import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_NO_PASSWORD;
import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_SAE;
import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_WEP;
import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_WPA;
@@ -208,6 +209,19 @@ public class WifiNetworkConfig {
wifiManager.connect(wifiConfiguration, listener);
}
public boolean isSupportConfiguratorQrCodeScanner(Context context) {
if (!WifiDppUtils.isWifiDppEnabled(context)) {
return false;
}
// DPP 1.0 only supports SAE and PSK.
if (SECURITY_SAE.equals(mSecurity) || SECURITY_WPA.equals(mSecurity)) {
return true;
}
return false;
}
/**
* This is a simplified method from {@code WifiConfigController.getConfig()}
*/

View File

@@ -48,7 +48,6 @@ public class WifiNetworkListFragment extends SettingsPreferenceFragment implemen
private static final String TAG = "WifiNetworkListFragment";
private static final String WIFI_CONFIG_KEY = "wifi_config_key";
private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list";
private static final String PREF_KEY_ACCESS_POINTS = "access_points";
static final int ADD_NETWORK_REQUEST = 1;
@@ -277,7 +276,6 @@ public class WifiNetworkListFragment extends SettingsPreferenceFragment implemen
// AccessPoints are sorted by the WifiTracker
final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
boolean hasAvailableAccessPoints = false;
mAccessPointsPreferenceCategory.setVisible(true);
cacheRemoveAllPrefs(mAccessPointsPreferenceCategory);
@@ -299,7 +297,6 @@ public class WifiNetworkListFragment extends SettingsPreferenceFragment implemen
return;
}
hasAvailableAccessPoints = true;
final AccessPointPreference pref = (AccessPointPreference) getCachedPreference(key);
if (pref != null) {
pref.setOrder(index);
@@ -317,15 +314,6 @@ public class WifiNetworkListFragment extends SettingsPreferenceFragment implemen
removeCachedPrefs(mAccessPointsPreferenceCategory);
mAddPreference.setOrder(index);
mAccessPointsPreferenceCategory.addPreference(mAddPreference);
if (!hasAvailableAccessPoints) {
final Preference pref = new Preference(getPrefContext());
pref.setSelectable(false);
pref.setSummary(R.string.wifi_empty_list_wifi_on);
pref.setOrder(index++);
pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
mAccessPointsPreferenceCategory.addPreference(pref);
}
}
private AccessPointPreference createAccessPointPreference(AccessPoint accessPoint) {

View File

@@ -67,6 +67,7 @@ public class WifiQrCode {
public static final String SECURITY_NO_PASSWORD = "nopass";
public static final String SECURITY_WEP = "WEP";
public static final String SECURITY_WPA = "WPA";
public static final String SECURITY_SAE = "WPA3";
private String mQrCode;

View File

@@ -292,11 +292,12 @@ public class WifiSlice implements CustomSliceable {
@Override
public void onWifiStateChanged(int state) {
mContext.getContentResolver().notifyChange(getUri(), null);
notifySliceChange();
}
@Override
public void onConnectedChanged() {
notifySliceChange();
}
@Override

View File

@@ -32,6 +32,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
import com.android.settingslib.RestrictedSwitchPreference;
@@ -45,6 +46,9 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.shadows.ShadowDevicePolicyManager;
import java.util.Arrays;
import java.util.Collections;
@RunWith(RobolectricTestRunner.class)
public class CrossProfileCalendarPreferenceControllerTest {
@@ -123,7 +127,17 @@ public class CrossProfileCalendarPreferenceControllerTest {
@Test
public void updateState_somePackagesAllowed_preferenceShouldNotBeDisabled() throws Exception {
dpm.setProfileOwner(TEST_COMPONENT_NAME);
dpm.addCrossProfileCalendarPackage(TEST_COMPONENT_NAME, TEST_PACKAGE_NAME);
dpm.setCrossProfileCalendarPackages(TEST_COMPONENT_NAME,
Collections.singleton(TEST_PACKAGE_NAME));
mController.updateState(mPreference);
verify(mPreference).setDisabledByAdmin(null);
}
@Test
public void updateState_allPackagesAllowed_preferenceShouldNotBeDisabled() throws Exception {
dpm.setProfileOwner(TEST_COMPONENT_NAME);
dpm.setCrossProfileCalendarPackages(TEST_COMPONENT_NAME, null);
mController.updateState(mPreference);
verify(mPreference).setDisabledByAdmin(null);

View File

@@ -38,6 +38,7 @@ import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.PowerManager;
@@ -102,10 +103,13 @@ public class RecentAppsPreferenceControllerTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
when(mContext.getApplicationContext()).thenReturn(mContext);
ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", mAppState);
doReturn(mUsageStatsManager).when(mContext).getSystemService(Context.USAGE_STATS_SERVICE);
doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE);
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[] {});
mController = new RecentAppsPreferenceController(mContext, mAppState, null);
when(mScreen.findPreference(anyString())).thenReturn(mCategory);
@@ -350,6 +354,57 @@ public class RecentAppsPreferenceControllerTest {
verify(mCategory).addPreference(argThat(summaryMatches("0 minutes ago")));
}
@Test
public void displayPreference_shouldNotShowHiddenSystemModule() {
final List<UsageStats> stats = new ArrayList<>();
// Regular app.
final UsageStats stat1 = new UsageStats();
stat1.mLastTimeUsed = System.currentTimeMillis();
stat1.mPackageName = "com.foo.bar";
stats.add(stat1);
// Hidden system module.
final UsageStats stat2 = new UsageStats();
stat2.mLastTimeUsed = System.currentTimeMillis() + 200;
stat2.mPackageName = "com.foo.hidden";
stats.add(stat2);
ApplicationsState.AppEntry stat1Entry = mock(ApplicationsState.AppEntry.class);
ApplicationsState.AppEntry stat2Entry = mock(ApplicationsState.AppEntry.class);
stat1Entry.info = mApplicationInfo;
stat2Entry.info = mApplicationInfo;
when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())).thenReturn(stat1Entry);
when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())).thenReturn(stat2Entry);
final ModuleInfo moduleInfo1 = new ModuleInfo();
moduleInfo1.setPackageName(stat1.mPackageName);
moduleInfo1.setHidden(false);
final ModuleInfo moduleInfo2 = new ModuleInfo();
moduleInfo2.setPackageName(stat2.mPackageName);
moduleInfo2.setHidden(true);
ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", null);
final List<ModuleInfo> modules = new ArrayList<>();
modules.add(moduleInfo2);
when(mPackageManager.getInstalledModules(anyInt() /* flags */))
.thenReturn(modules);
when(mPackageManager.resolveActivity(any(Intent.class), anyInt()))
.thenReturn(new ResolveInfo());
when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong()))
.thenReturn(stats);
mController.displayPreference(mScreen);
// Only add stat1. stat2 is skipped because it is hidden module.
final ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
verify(mCategory).addPreference(prefCaptor.capture());
final Preference pref = prefCaptor.getValue();
assertThat(pref.getKey()).isEqualTo(stat1.mPackageName);
}
private static ArgumentMatcher<Preference> summaryMatches(String expected) {
return preference -> TextUtils.equals(expected, preference.getSummary());
}

View File

@@ -20,11 +20,16 @@ import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEA
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -55,6 +60,8 @@ public class SpecialAppAccessPreferenceControllerTest {
private ApplicationsState.Session mSession;
@Mock
private PreferenceScreen mScreen;
@Mock
private PackageManager mPackageManager;
private SpecialAppAccessPreferenceController mController;
private Preference mPreference;
@@ -62,8 +69,11 @@ public class SpecialAppAccessPreferenceControllerTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mContext = spy(RuntimeEnvironment.application);
when(mContext.getApplicationContext()).thenReturn(mContext);
ShadowUserManager.getShadow().setProfileIdsWithDisabled(new int[]{0});
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(new ArrayList<ModuleInfo>()).when(mPackageManager).getInstalledModules(anyInt());
mController = new SpecialAppAccessPreferenceController(mContext, "test_key");
mPreference = new Preference(mContext);
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);

Some files were not shown because too many files have changed in this diff Show More