Preparation task for Settings Fragment Migration

Create some compatible files.
These files can be used for Settings Fragment Migration task.

Bug: 110259478
Test: make RunSettingsLibRoboTests -j40
Change-Id: Ib3d52e9a5f5bed5c194d429fdfa4b0d01ed07f01
This commit is contained in:
tmfang
2018-07-04 15:59:21 +08:00
committed by Fan Zhang
parent 16b6a2c772
commit 464da581aa
17 changed files with 2372 additions and 5 deletions

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2018 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.settingslib;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.DialogPreference;
import androidx.preference.PreferenceDialogFragmentCompat;
public class CustomDialogPreferenceCompat extends DialogPreference {
private CustomPreferenceDialogFragment mFragment;
private DialogInterface.OnShowListener mOnShowListener;
public CustomDialogPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public CustomDialogPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomDialogPreferenceCompat(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomDialogPreferenceCompat(Context context) {
super(context);
}
public boolean isDialogOpen() {
return getDialog() != null && getDialog().isShowing();
}
public Dialog getDialog() {
return mFragment != null ? mFragment.getDialog() : null;
}
public void setOnShowListener(DialogInterface.OnShowListener listner) {
mOnShowListener = listner;
}
protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
DialogInterface.OnClickListener listener) {
}
protected void onDialogClosed(boolean positiveResult) {
}
protected void onClick(DialogInterface dialog, int which) {
}
protected void onBindDialogView(View view) {
}
private void setFragment(CustomPreferenceDialogFragment fragment) {
mFragment = fragment;
}
private DialogInterface.OnShowListener getOnShowListener() {
return mOnShowListener;
}
public static class CustomPreferenceDialogFragment extends PreferenceDialogFragmentCompat {
public static CustomPreferenceDialogFragment newInstance(String key) {
final CustomPreferenceDialogFragment fragment = new CustomPreferenceDialogFragment();
final Bundle b = new Bundle(1);
b.putString(ARG_KEY, key);
fragment.setArguments(b);
return fragment;
}
private CustomDialogPreferenceCompat getCustomizablePreference() {
return (CustomDialogPreferenceCompat) getPreference();
}
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
getCustomizablePreference().setFragment(this);
getCustomizablePreference().onPrepareDialogBuilder(builder, this);
}
@Override
public void onDialogClosed(boolean positiveResult) {
getCustomizablePreference().onDialogClosed(positiveResult);
}
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
getCustomizablePreference().onBindDialogView(view);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.setOnShowListener(getCustomizablePreference().getOnShowListener());
return dialog;
}
@Override
public void onClick(DialogInterface dialog, int which) {
super.onClick(dialog, which);
getCustomizablePreference().onClick(dialog, which);
}
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2018 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.settingslib;
import static android.text.InputType.TYPE_CLASS_TEXT;
import static android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import androidx.annotation.CallSuper;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.EditTextPreference;
import androidx.preference.EditTextPreferenceDialogFragmentCompat;
public class CustomEditTextPreferenceCompat extends EditTextPreference {
private CustomPreferenceDialogFragment mFragment;
public CustomEditTextPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public CustomEditTextPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomEditTextPreferenceCompat(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomEditTextPreferenceCompat(Context context) {
super(context);
}
public EditText getEditText() {
if (mFragment != null) {
final Dialog dialog = mFragment.getDialog();
if (dialog != null) {
return (EditText) dialog.findViewById(android.R.id.edit);
}
}
return null;
}
public boolean isDialogOpen() {
return getDialog() != null && getDialog().isShowing();
}
public Dialog getDialog() {
return mFragment != null ? mFragment.getDialog() : null;
}
protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
DialogInterface.OnClickListener listener) {
}
protected void onDialogClosed(boolean positiveResult) {
}
protected void onClick(DialogInterface dialog, int which) {
}
@CallSuper
protected void onBindDialogView(View view) {
final EditText editText = view.findViewById(android.R.id.edit);
if (editText != null) {
editText.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES);
editText.requestFocus();
}
}
private void setFragment(CustomPreferenceDialogFragment fragment) {
mFragment = fragment;
}
public static class CustomPreferenceDialogFragment extends
EditTextPreferenceDialogFragmentCompat {
public static CustomPreferenceDialogFragment newInstance(String key) {
final CustomPreferenceDialogFragment fragment = new CustomPreferenceDialogFragment();
final Bundle b = new Bundle(1);
b.putString(ARG_KEY, key);
fragment.setArguments(b);
return fragment;
}
private CustomEditTextPreferenceCompat getCustomizablePreference() {
return (CustomEditTextPreferenceCompat) getPreference();
}
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
getCustomizablePreference().onBindDialogView(view);
}
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
getCustomizablePreference().setFragment(this);
getCustomizablePreference().onPrepareDialogBuilder(builder, this);
}
@Override
public void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
getCustomizablePreference().onDialogClosed(positiveResult);
}
@Override
public void onClick(DialogInterface dialog, int which) {
super.onClick(dialog, which);
getCustomizablePreference().onClick(dialog, which);
}
}
}

View File

@@ -0,0 +1,265 @@
/*
* Copyright (C) 2018 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.settingslib.inputmethod;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.text.TextUtils;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.android.settingslib.R;
import java.text.Collator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
public class InputMethodAndSubtypeEnablerManagerCompat implements
Preference.OnPreferenceChangeListener {
private final PreferenceFragmentCompat mFragment;
private boolean mHaveHardKeyboard;
private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
new HashMap<>();
private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>();
private InputMethodManager mImm;
// TODO: Change mInputMethodInfoList to Map
private List<InputMethodInfo> mInputMethodInfoList;
private final Collator mCollator = Collator.getInstance();
public InputMethodAndSubtypeEnablerManagerCompat(PreferenceFragmentCompat fragment) {
mFragment = fragment;
mImm = fragment.getContext().getSystemService(InputMethodManager.class);
mInputMethodInfoList = mImm.getInputMethodList();
}
public void init(PreferenceFragmentCompat fragment, String targetImi, PreferenceScreen root) {
final Configuration config = fragment.getResources().getConfiguration();
mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
for (final InputMethodInfo imi : mInputMethodInfoList) {
// Add subtype preferences of this IME when it is specified or no IME is specified.
if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) {
addInputMethodSubtypePreferences(fragment, imi, root);
}
}
}
public void refresh(Context context, PreferenceFragmentCompat fragment) {
// Refresh internal states in mInputMethodSettingValues to keep the latest
// "InputMethodInfo"s and "InputMethodSubtype"s
InputMethodSettingValuesWrapper
.getInstance(context).refreshAllInputMethodAndSubtypes();
InputMethodAndSubtypeUtilCompat.loadInputMethodSubtypeList(fragment,
context.getContentResolver(), mInputMethodInfoList, mInputMethodAndSubtypePrefsMap);
updateAutoSelectionPreferences();
}
public void save(Context context, PreferenceFragmentCompat fragment) {
InputMethodAndSubtypeUtilCompat.saveInputMethodSubtypeList(fragment,
context.getContentResolver(), mInputMethodInfoList, mHaveHardKeyboard);
}
@Override
public boolean onPreferenceChange(final Preference pref, final Object newValue) {
if (!(newValue instanceof Boolean)) {
return true; // Invoke default behavior.
}
final boolean isChecking = (Boolean) newValue;
for (final String imiId : mAutoSelectionPrefsMap.keySet()) {
// An auto select subtype preference is changing.
if (mAutoSelectionPrefsMap.get(imiId) == pref) {
final TwoStatePreference autoSelectionPref = (TwoStatePreference) pref;
autoSelectionPref.setChecked(isChecking);
// Enable or disable subtypes depending on the auto selection preference.
setAutoSelectionSubtypesEnabled(imiId, autoSelectionPref.isChecked());
return false;
}
}
// A subtype preference is changing.
if (pref instanceof InputMethodSubtypePreference) {
final InputMethodSubtypePreference subtypePref = (InputMethodSubtypePreference) pref;
subtypePref.setChecked(isChecking);
if (!subtypePref.isChecked()) {
// It takes care of the case where no subtypes are explicitly enabled then the auto
// selection preference is going to be checked.
updateAutoSelectionPreferences();
}
return false;
}
return true; // Invoke default behavior.
}
private void addInputMethodSubtypePreferences(PreferenceFragmentCompat fragment,
InputMethodInfo imi, final PreferenceScreen root) {
Context prefContext = fragment.getPreferenceManager().getContext();
final int subtypeCount = imi.getSubtypeCount();
if (subtypeCount <= 1) {
return;
}
final String imiId = imi.getId();
final PreferenceCategory keyboardSettingsCategory =
new PreferenceCategory(prefContext);
root.addPreference(keyboardSettingsCategory);
final PackageManager pm = prefContext.getPackageManager();
final CharSequence label = imi.loadLabel(pm);
keyboardSettingsCategory.setTitle(label);
keyboardSettingsCategory.setKey(imiId);
// TODO: Use toggle Preference if images are ready.
final TwoStatePreference autoSelectionPref =
new SwitchWithNoTextPreference(prefContext);
mAutoSelectionPrefsMap.put(imiId, autoSelectionPref);
keyboardSettingsCategory.addPreference(autoSelectionPref);
autoSelectionPref.setOnPreferenceChangeListener(this);
final PreferenceCategory activeInputMethodsCategory =
new PreferenceCategory(prefContext);
activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
root.addPreference(activeInputMethodsCategory);
CharSequence autoSubtypeLabel = null;
final ArrayList<Preference> subtypePreferences = new ArrayList<>();
for (int index = 0; index < subtypeCount; ++index) {
final InputMethodSubtype subtype = imi.getSubtypeAt(index);
if (subtype.overridesImplicitlyEnabledSubtype()) {
if (autoSubtypeLabel == null) {
autoSubtypeLabel = InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(
subtype, prefContext, imi);
}
} else {
final Preference subtypePref = new InputMethodSubtypePreference(
prefContext, subtype, imi);
subtypePreferences.add(subtypePref);
}
}
subtypePreferences.sort((lhs, rhs) -> {
if (lhs instanceof InputMethodSubtypePreference) {
return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator);
}
return lhs.compareTo(rhs);
});
for (final Preference pref : subtypePreferences) {
activeInputMethodsCategory.addPreference(pref);
pref.setOnPreferenceChangeListener(this);
InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
}
mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences);
if (TextUtils.isEmpty(autoSubtypeLabel)) {
autoSelectionPref.setTitle(
R.string.use_system_language_to_select_input_method_subtypes);
} else {
autoSelectionPref.setTitle(autoSubtypeLabel);
}
}
private boolean isNoSubtypesExplicitlySelected(final String imiId) {
final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
for (final Preference pref : subtypePrefs) {
if (pref instanceof TwoStatePreference && ((TwoStatePreference) pref).isChecked()) {
return false;
}
}
return true;
}
private void setAutoSelectionSubtypesEnabled(final String imiId,
final boolean autoSelectionEnabled) {
final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
if (autoSelectionPref == null) {
return;
}
autoSelectionPref.setChecked(autoSelectionEnabled);
final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
for (final Preference pref : subtypePrefs) {
if (pref instanceof TwoStatePreference) {
// When autoSelectionEnabled is true, all subtype prefs need to be disabled with
// implicitly checked subtypes. In case of false, all subtype prefs need to be
// enabled.
pref.setEnabled(!autoSelectionEnabled);
if (autoSelectionEnabled) {
((TwoStatePreference) pref).setChecked(false);
}
}
}
if (autoSelectionEnabled) {
InputMethodAndSubtypeUtilCompat.saveInputMethodSubtypeList(
mFragment, mFragment.getContext().getContentResolver(),
mInputMethodInfoList, mHaveHardKeyboard);
updateImplicitlyEnabledSubtypes(imiId);
}
}
private void updateImplicitlyEnabledSubtypes(final String targetImiId) {
// When targetImiId is null, apply to all subtypes of all IMEs
for (final InputMethodInfo imi : mInputMethodInfoList) {
final String imiId = imi.getId();
final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
// No need to update implicitly enabled subtypes when the user has unchecked the
// "subtype auto selection".
if (autoSelectionPref == null || !autoSelectionPref.isChecked()) {
continue;
}
if (imiId.equals(targetImiId) || targetImiId == null) {
updateImplicitlyEnabledSubtypesOf(imi);
}
}
}
private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi) {
final String imiId = imi.getId();
final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
final List<InputMethodSubtype> implicitlyEnabledSubtypes =
mImm.getEnabledInputMethodSubtypeList(imi, true);
if (subtypePrefs == null || implicitlyEnabledSubtypes == null) {
return;
}
for (final Preference pref : subtypePrefs) {
if (!(pref instanceof TwoStatePreference)) {
continue;
}
final TwoStatePreference subtypePref = (TwoStatePreference) pref;
subtypePref.setChecked(false);
for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) {
final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
subtypePref.setChecked(true);
break;
}
}
}
}
private void updateAutoSelectionPreferences() {
for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) {
setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
}
updateImplicitlyEnabledSubtypes(null /* targetImiId */ /* check */);
}
}

View File

@@ -0,0 +1,436 @@
/*
* Copyright (C) 2018 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.settingslib.inputmethod;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.icu.text.ListFormatter;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.app.LocaleHelper;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
// TODO: Consolidate this with {@link InputMethodSettingValuesWrapper}.
public class InputMethodAndSubtypeUtilCompat {
private static final boolean DEBUG = false;
private static final String TAG = "InputMethdAndSubtypeUtlCompat";
private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
private static final char INPUT_METHOD_SEPARATER = ':';
private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
private static final int NOT_A_SUBTYPE_ID = -1;
private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter
= new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
= new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
// InputMethods and subtypes are saved in the settings as follows:
// ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
public static String buildInputMethodsAndSubtypesString(
final HashMap<String, HashSet<String>> imeToSubtypesMap) {
final StringBuilder builder = new StringBuilder();
for (final String imi : imeToSubtypesMap.keySet()) {
if (builder.length() > 0) {
builder.append(INPUT_METHOD_SEPARATER);
}
final HashSet<String> subtypeIdSet = imeToSubtypesMap.get(imi);
builder.append(imi);
for (final String subtypeId : subtypeIdSet) {
builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
}
}
return builder.toString();
}
private static String buildInputMethodsString(final HashSet<String> imiList) {
final StringBuilder builder = new StringBuilder();
for (final String imi : imiList) {
if (builder.length() > 0) {
builder.append(INPUT_METHOD_SEPARATER);
}
builder.append(imi);
}
return builder.toString();
}
private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
try {
return Settings.Secure.getInt(resolver,
Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
} catch (SettingNotFoundException e) {
return NOT_A_SUBTYPE_ID;
}
}
private static boolean isInputMethodSubtypeSelected(ContentResolver resolver) {
return getInputMethodSubtypeSelected(resolver) != NOT_A_SUBTYPE_ID;
}
private static void putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode) {
Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, hashCode);
}
// Needs to modify InputMethodManageService if you want to change the format of saved string.
static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
ContentResolver resolver) {
final String enabledInputMethodsStr = Settings.Secure.getString(
resolver, Settings.Secure.ENABLED_INPUT_METHODS);
if (DEBUG) {
Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
}
return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
}
public static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
final String inputMethodsAndSubtypesString) {
final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
return subtypesMap;
}
sStringInputMethodSplitter.setString(inputMethodsAndSubtypesString);
while (sStringInputMethodSplitter.hasNext()) {
final String nextImsStr = sStringInputMethodSplitter.next();
sStringInputMethodSubtypeSplitter.setString(nextImsStr);
if (sStringInputMethodSubtypeSplitter.hasNext()) {
final HashSet<String> subtypeIdSet = new HashSet<>();
// The first element is {@link InputMethodInfoId}.
final String imiId = sStringInputMethodSubtypeSplitter.next();
while (sStringInputMethodSubtypeSplitter.hasNext()) {
subtypeIdSet.add(sStringInputMethodSubtypeSplitter.next());
}
subtypesMap.put(imiId, subtypeIdSet);
}
}
return subtypesMap;
}
private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
HashSet<String> set = new HashSet<>();
String disabledIMEsStr = Settings.Secure.getString(
resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
if (TextUtils.isEmpty(disabledIMEsStr)) {
return set;
}
sStringInputMethodSplitter.setString(disabledIMEsStr);
while(sStringInputMethodSplitter.hasNext()) {
set.add(sStringInputMethodSplitter.next());
}
return set;
}
public static void saveInputMethodSubtypeList(PreferenceFragmentCompat context,
ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
boolean hasHardKeyboard) {
String currentInputMethodId = Settings.Secure.getString(resolver,
Settings.Secure.DEFAULT_INPUT_METHOD);
final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
final HashMap<String, HashSet<String>> enabledIMEsAndSubtypesMap =
getEnabledInputMethodsAndSubtypeList(resolver);
final HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
boolean needsToResetSelectedSubtype = false;
for (final InputMethodInfo imi : inputMethodInfos) {
final String imiId = imi.getId();
final Preference pref = context.findPreference(imiId);
if (pref == null) {
continue;
}
// In the choose input method screen or in the subtype enabler screen,
// <code>pref</code> is an instance of TwoStatePreference.
final boolean isImeChecked = (pref instanceof TwoStatePreference) ?
((TwoStatePreference) pref).isChecked()
: enabledIMEsAndSubtypesMap.containsKey(imiId);
final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
final boolean systemIme = imi.isSystem();
if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
context.getActivity()).isAlwaysCheckedIme(imi))
|| isImeChecked) {
if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
// imiId has just been enabled
enabledIMEsAndSubtypesMap.put(imiId, new HashSet<>());
}
final HashSet<String> subtypesSet = enabledIMEsAndSubtypesMap.get(imiId);
boolean subtypePrefFound = false;
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
final TwoStatePreference subtypePref = (TwoStatePreference) context
.findPreference(imiId + subtypeHashCodeStr);
// In the Configure input method screen which does not have subtype preferences.
if (subtypePref == null) {
continue;
}
if (!subtypePrefFound) {
// Once subtype preference is found, subtypeSet needs to be cleared.
// Because of system change, hashCode value could have been changed.
subtypesSet.clear();
// If selected subtype preference is disabled, needs to reset.
needsToResetSelectedSubtype = true;
subtypePrefFound = true;
}
// Checking <code>subtypePref.isEnabled()</code> is insufficient to determine
// whether the user manually enabled this subtype or not. Implicitly-enabled
// subtypes are also checked just as an indicator to users. We also need to
// check <code>subtypePref.isEnabled()</code> so that only manually enabled
// subtypes can be saved here.
if (subtypePref.isEnabled() && subtypePref.isChecked()) {
subtypesSet.add(subtypeHashCodeStr);
if (isCurrentInputMethod) {
if (selectedInputMethodSubtype == subtype.hashCode()) {
// Selected subtype is still enabled, there is no need to reset
// selected subtype.
needsToResetSelectedSubtype = false;
}
}
} else {
subtypesSet.remove(subtypeHashCodeStr);
}
}
} else {
enabledIMEsAndSubtypesMap.remove(imiId);
if (isCurrentInputMethod) {
// We are processing the current input method, but found that it's not enabled.
// This means that the current input method has been uninstalled.
// If currentInputMethod is already uninstalled, InputMethodManagerService will
// find the applicable IME from the history and the system locale.
if (DEBUG) {
Log.d(TAG, "Current IME was uninstalled or disabled.");
}
currentInputMethodId = null;
}
}
// If it's a disabled system ime, add it to the disabled list so that it
// doesn't get enabled automatically on any changes to the package list
if (systemIme && hasHardKeyboard) {
if (disabledSystemIMEs.contains(imiId)) {
if (isImeChecked) {
disabledSystemIMEs.remove(imiId);
}
} else {
if (!isImeChecked) {
disabledSystemIMEs.add(imiId);
}
}
}
}
final String enabledIMEsAndSubtypesString = buildInputMethodsAndSubtypesString(
enabledIMEsAndSubtypesMap);
final String disabledSystemIMEsString = buildInputMethodsString(disabledSystemIMEs);
if (DEBUG) {
Log.d(TAG, "--- Save enabled inputmethod settings. :" + enabledIMEsAndSubtypesString);
Log.d(TAG, "--- Save disabled system inputmethod settings. :"
+ disabledSystemIMEsString);
Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId);
Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype);
Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver));
}
// Redefines SelectedSubtype when all subtypes are unchecked or there is no subtype
// selected. And if the selected subtype of the current input method was disabled,
// We should reset the selected input method's subtype.
if (needsToResetSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) {
if (DEBUG) {
Log.d(TAG, "--- Reset inputmethod subtype because it's not defined.");
}
putSelectedInputMethodSubtype(resolver, NOT_A_SUBTYPE_ID);
}
Settings.Secure.putString(resolver,
Settings.Secure.ENABLED_INPUT_METHODS, enabledIMEsAndSubtypesString);
if (disabledSystemIMEsString.length() > 0) {
Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
disabledSystemIMEsString);
}
// If the current input method is unset, InputMethodManagerService will find the applicable
// IME from the history and the system locale.
Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD,
currentInputMethodId != null ? currentInputMethodId : "");
}
public static void loadInputMethodSubtypeList(final PreferenceFragmentCompat context,
final ContentResolver resolver, final List<InputMethodInfo> inputMethodInfos,
final Map<String, List<Preference>> inputMethodPrefsMap) {
final HashMap<String, HashSet<String>> enabledSubtypes =
getEnabledInputMethodsAndSubtypeList(resolver);
for (final InputMethodInfo imi : inputMethodInfos) {
final String imiId = imi.getId();
final Preference pref = context.findPreference(imiId);
if (pref instanceof TwoStatePreference) {
final TwoStatePreference subtypePref = (TwoStatePreference) pref;
final boolean isEnabled = enabledSubtypes.containsKey(imiId);
subtypePref.setChecked(isEnabled);
if (inputMethodPrefsMap != null) {
for (final Preference childPref: inputMethodPrefsMap.get(imiId)) {
childPref.setEnabled(isEnabled);
}
}
setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled);
}
}
updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
}
private static void setSubtypesPreferenceEnabled(final PreferenceFragmentCompat context,
final List<InputMethodInfo> inputMethodProperties, final String id,
final boolean enabled) {
final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
for (final InputMethodInfo imi : inputMethodProperties) {
if (id.equals(imi.getId())) {
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
.findPreference(id + subtype.hashCode());
if (pref != null) {
pref.setEnabled(enabled);
}
}
}
}
}
private static void updateSubtypesPreferenceChecked(final PreferenceFragmentCompat context,
final List<InputMethodInfo> inputMethodProperties,
final HashMap<String, HashSet<String>> enabledSubtypes) {
final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
for (final InputMethodInfo imi : inputMethodProperties) {
final String id = imi.getId();
if (!enabledSubtypes.containsKey(id)) {
// There is no need to enable/disable subtypes of disabled IMEs.
continue;
}
final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id);
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
final String hashCode = String.valueOf(subtype.hashCode());
if (DEBUG) {
Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", "
+ enabledSubtypesSet.contains(hashCode));
}
final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
.findPreference(id + hashCode);
if (pref != null) {
pref.setChecked(enabledSubtypesSet.contains(hashCode));
}
}
}
}
public static void removeUnnecessaryNonPersistentPreference(final Preference pref) {
final String key = pref.getKey();
if (pref.isPersistent() || key == null) {
return;
}
final SharedPreferences prefs = pref.getSharedPreferences();
if (prefs != null && prefs.contains(key)) {
prefs.edit().remove(key).apply();
}
}
@NonNull
public static String getSubtypeLocaleNameAsSentence(@Nullable InputMethodSubtype subtype,
@NonNull final Context context, @NonNull final InputMethodInfo inputMethodInfo) {
if (subtype == null) {
return "";
}
final Locale locale = getDisplayLocale(context);
final CharSequence subtypeName = subtype.getDisplayName(context,
inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
.applicationInfo);
return LocaleHelper.toSentenceCase(subtypeName.toString(), locale);
}
@NonNull
public static String getSubtypeLocaleNameListAsSentence(
@NonNull final List<InputMethodSubtype> subtypes, @NonNull final Context context,
@NonNull final InputMethodInfo inputMethodInfo) {
if (subtypes.isEmpty()) {
return "";
}
final Locale locale = getDisplayLocale(context);
final int subtypeCount = subtypes.size();
final CharSequence[] subtypeNames = new CharSequence[subtypeCount];
for (int i = 0; i < subtypeCount; i++) {
subtypeNames[i] = subtypes.get(i).getDisplayName(context,
inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
.applicationInfo);
}
return LocaleHelper.toSentenceCase(
ListFormatter.getInstance(locale).format((Object[]) subtypeNames), locale);
}
@NonNull
private static Locale getDisplayLocale(@Nullable final Context context) {
if (context == null) {
return Locale.getDefault();
}
if (context.getResources() == null) {
return Locale.getDefault();
}
final Configuration configuration = context.getResources().getConfiguration();
if (configuration == null) {
return Locale.getDefault();
}
final Locale configurationLocale = configuration.getLocales().get(0);
if (configurationLocale == null) {
return Locale.getDefault();
}
return configurationLocale;
}
public static boolean isValidSystemNonAuxAsciiCapableIme(InputMethodInfo imi) {
if (imi.isAuxiliaryIme() || !imi.isSystem()) {
return false;
}
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
if (SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())
&& subtype.isAsciiCapable()) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2018 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.settingslib.license;
import android.content.Context;
import android.util.Log;
import com.android.settingslib.utils.AsyncLoaderCompat;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.VisibleForTesting;
/**
* LicenseHtmlLoader is a loader which loads a license html file from default license xml files.
*/
public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> {
private static final String TAG = "LicenseHtmlLoaderCompat";
private static final String[] DEFAULT_LICENSE_XML_PATHS = {
"/system/etc/NOTICE.xml.gz",
"/vendor/etc/NOTICE.xml.gz",
"/odm/etc/NOTICE.xml.gz",
"/oem/etc/NOTICE.xml.gz"};
private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html";
private Context mContext;
public LicenseHtmlLoaderCompat(Context context) {
super(context);
mContext = context;
}
@Override
public File loadInBackground() {
return generateHtmlFromDefaultXmlFiles();
}
@Override
protected void onDiscardResult(File f) {
}
private File generateHtmlFromDefaultXmlFiles() {
final List<File> xmlFiles = getVaildXmlFiles();
if (xmlFiles.isEmpty()) {
Log.e(TAG, "No notice file exists.");
return null;
}
File cachedHtmlFile = getCachedHtmlFile();
if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile)
|| generateHtmlFile(xmlFiles, cachedHtmlFile)) {
return cachedHtmlFile;
}
return null;
}
@VisibleForTesting
List<File> getVaildXmlFiles() {
final List<File> xmlFiles = new ArrayList();
for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) {
File file = new File(xmlPath);
if (file.exists() && file.length() != 0) {
xmlFiles.add(file);
}
}
return xmlFiles;
}
@VisibleForTesting
File getCachedHtmlFile() {
return new File(mContext.getCacheDir(), NOTICE_HTML_FILE_NAME);
}
@VisibleForTesting
boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) {
boolean outdated = true;
if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) {
outdated = false;
for (File file : xmlFiles) {
if (cachedHtmlFile.lastModified() < file.lastModified()) {
outdated = true;
break;
}
}
}
return outdated;
}
@VisibleForTesting
boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) {
return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile);
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2018 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.settingslib.net;
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.SET_FOREGROUND;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import android.content.Context;
import android.net.INetworkStatsSession;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.Bundle;
import android.os.RemoteException;
import com.android.settingslib.AppItem;
import androidx.loader.content.AsyncTaskLoader;
/**
* Loader for historical chart data for both network and UID details.
*/
public class ChartDataLoaderCompat extends AsyncTaskLoader<ChartData> {
private static final String KEY_TEMPLATE = "template";
private static final String KEY_APP = "app";
private static final String KEY_FIELDS = "fields";
private final INetworkStatsSession mSession;
private final Bundle mArgs;
public static Bundle buildArgs(NetworkTemplate template, AppItem app) {
return buildArgs(template, app, FIELD_RX_BYTES | FIELD_TX_BYTES);
}
public static Bundle buildArgs(NetworkTemplate template, AppItem app, int fields) {
final Bundle args = new Bundle();
args.putParcelable(KEY_TEMPLATE, template);
args.putParcelable(KEY_APP, app);
args.putInt(KEY_FIELDS, fields);
return args;
}
public ChartDataLoaderCompat(Context context, INetworkStatsSession session, Bundle args) {
super(context);
mSession = session;
mArgs = args;
}
@Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
}
@Override
public ChartData loadInBackground() {
final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
final AppItem app = mArgs.getParcelable(KEY_APP);
final int fields = mArgs.getInt(KEY_FIELDS);
try {
return loadInBackground(template, app, fields);
} catch (RemoteException e) {
// since we can't do much without history, and we don't want to
// leave with half-baked UI, we bail hard.
throw new RuntimeException("problem reading network stats", e);
}
}
private ChartData loadInBackground(NetworkTemplate template, AppItem app, int fields)
throws RemoteException {
final ChartData data = new ChartData();
data.network = mSession.getHistoryForNetwork(template, fields);
if (app != null) {
// load stats for current uid and template
final int size = app.uids.size();
for (int i = 0; i < size; i++) {
final int uid = app.uids.keyAt(i);
data.detailDefault = collectHistoryForUid(
template, uid, SET_DEFAULT, data.detailDefault);
data.detailForeground = collectHistoryForUid(
template, uid, SET_FOREGROUND, data.detailForeground);
}
if (size > 0) {
data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration());
data.detail.recordEntireHistory(data.detailDefault);
data.detail.recordEntireHistory(data.detailForeground);
} else {
data.detailDefault = new NetworkStatsHistory(HOUR_IN_MILLIS);
data.detailForeground = new NetworkStatsHistory(HOUR_IN_MILLIS);
data.detail = new NetworkStatsHistory(HOUR_IN_MILLIS);
}
}
return data;
}
@Override
protected void onStopLoading() {
super.onStopLoading();
cancelLoad();
}
@Override
protected void onReset() {
super.onReset();
cancelLoad();
}
/**
* Collect {@link NetworkStatsHistory} for the requested UID, combining with
* an existing {@link NetworkStatsHistory} if provided.
*/
private NetworkStatsHistory collectHistoryForUid(
NetworkTemplate template, int uid, int set, NetworkStatsHistory existing)
throws RemoteException {
final NetworkStatsHistory history = mSession.getHistoryForUid(
template, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
if (existing != null) {
existing.recordEntireHistory(history);
return existing;
} else {
return history;
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2018 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.settingslib.net;
import android.content.Context;
import android.net.INetworkStatsSession;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
import android.os.Bundle;
import android.os.RemoteException;
import androidx.loader.content.AsyncTaskLoader;
public class SummaryForAllUidLoaderCompat extends AsyncTaskLoader<NetworkStats> {
private static final String KEY_TEMPLATE = "template";
private static final String KEY_START = "start";
private static final String KEY_END = "end";
private final INetworkStatsSession mSession;
private final Bundle mArgs;
public static Bundle buildArgs(NetworkTemplate template, long start, long end) {
final Bundle args = new Bundle();
args.putParcelable(KEY_TEMPLATE, template);
args.putLong(KEY_START, start);
args.putLong(KEY_END, end);
return args;
}
public SummaryForAllUidLoaderCompat(Context context, INetworkStatsSession session,
Bundle args) {
super(context);
mSession = session;
mArgs = args;
}
@Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
}
@Override
public NetworkStats loadInBackground() {
final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
final long start = mArgs.getLong(KEY_START);
final long end = mArgs.getLong(KEY_END);
try {
return mSession.getSummaryForAllUid(template, start, end, false);
} catch (RemoteException e) {
return null;
}
}
@Override
protected void onStopLoading() {
super.onStopLoading();
cancelLoad();
}
@Override
protected void onReset() {
super.onReset();
cancelLoad();
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2018 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.settingslib.suggestions;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.service.settings.suggestions.Suggestion;
import android.util.Log;
import com.android.settingslib.core.lifecycle.Lifecycle;
import java.util.List;
import androidx.annotation.Nullable;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
/**
* Manages IPC communication to SettingsIntelligence for suggestion related services.
*/
public class SuggestionControllerMixinCompat implements
SuggestionController.ServiceConnectionListener, androidx.lifecycle.LifecycleObserver,
LoaderManager.LoaderCallbacks<List<Suggestion>> {
public interface SuggestionControllerHost {
/**
* Called when suggestion data fetching is ready.
*/
void onSuggestionReady(List<Suggestion> data);
/**
* Returns {@link LoaderManager} associated with the host. If host is not attached to
* activity then return null.
*/
@Nullable
LoaderManager getLoaderManager();
}
private static final String TAG = "SuggestionCtrlMixin";
private static final boolean DEBUG = false;
private final Context mContext;
private final SuggestionController mSuggestionController;
private final SuggestionControllerHost mHost;
private boolean mSuggestionLoaded;
public SuggestionControllerMixinCompat(Context context, SuggestionControllerHost host,
Lifecycle lifecycle, ComponentName componentName) {
mContext = context.getApplicationContext();
mHost = host;
mSuggestionController = new SuggestionController(mContext, componentName,
this /* serviceConnectionListener */);
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart() {
if (DEBUG) {
Log.d(TAG, "SuggestionController started");
}
mSuggestionController.start();
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onStop() {
if (DEBUG) {
Log.d(TAG, "SuggestionController stopped.");
}
mSuggestionController.stop();
}
@Override
public void onServiceConnected() {
final LoaderManager loaderManager = mHost.getLoaderManager();
if (loaderManager != null) {
loaderManager.restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS,
null /* args */, this /* callback */);
}
}
@Override
public void onServiceDisconnected() {
if (DEBUG) {
Log.d(TAG, "SuggestionService disconnected");
}
final LoaderManager loaderManager = mHost.getLoaderManager();
if (loaderManager != null) {
loaderManager.destroyLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS);
}
}
@Override
public Loader<List<Suggestion>> onCreateLoader(int id, Bundle args) {
if (id == SuggestionLoader.LOADER_ID_SUGGESTIONS) {
mSuggestionLoaded = false;
return new SuggestionLoaderCompat(mContext, mSuggestionController);
}
throw new IllegalArgumentException("This loader id is not supported " + id);
}
@Override
public void onLoadFinished(Loader<List<Suggestion>> loader, List<Suggestion> data) {
mSuggestionLoaded = true;
mHost.onSuggestionReady(data);
}
@Override
public void onLoaderReset(Loader<List<Suggestion>> loader) {
mSuggestionLoaded = false;
}
public boolean isSuggestionLoaded() {
return mSuggestionLoaded;
}
public void dismissSuggestion(Suggestion suggestion) {
mSuggestionController.dismissSuggestions(suggestion);
}
public void launchSuggestion(Suggestion suggestion) {
mSuggestionController.launchSuggestion(suggestion);
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.settingslib.suggestions;
import android.content.Context;
import android.service.settings.suggestions.Suggestion;
import android.util.Log;
import com.android.settingslib.utils.AsyncLoaderCompat;
import java.util.List;
public class SuggestionLoaderCompat extends AsyncLoaderCompat<List<Suggestion>> {
public static final int LOADER_ID_SUGGESTIONS = 42;
private static final String TAG = "SuggestionLoader";
private final SuggestionController mSuggestionController;
public SuggestionLoaderCompat(Context context, SuggestionController controller) {
super(context);
mSuggestionController = controller;
}
@Override
protected void onDiscardResult(List<Suggestion> result) {
}
@Override
public List<Suggestion> loadInBackground() {
final List<Suggestion> data = mSuggestionController.getSuggestions();
if (data == null) {
Log.d(TAG, "data is null");
} else {
Log.d(TAG, "data size " + data.size());
}
return data;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2018 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.settingslib.utils;
import android.content.Context;
import androidx.loader.content.AsyncTaskLoader;
/**
* This class fills in some boilerplate for AsyncTaskLoader to actually load things.
*
* Subclasses need to implement {@link AsyncLoaderCompat#loadInBackground()} to perform the actual
* background task, and {@link AsyncLoaderCompat#onDiscardResult(T)} to clean up previously loaded
* results.
*
* This loader is based on the MailAsyncTaskLoader from the AOSP EmailUnified repo.
*
* @param <T> the data type to be loaded.
*/
public abstract class AsyncLoaderCompat<T> extends AsyncTaskLoader<T> {
private T mResult;
public AsyncLoaderCompat(final Context context) {
super(context);
}
@Override
protected void onStartLoading() {
if (mResult != null) {
deliverResult(mResult);
}
if (takeContentChanged() || mResult == null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
public void deliverResult(final T data) {
if (isReset()) {
if (data != null) {
onDiscardResult(data);
}
return;
}
final T oldResult = mResult;
mResult = data;
if (isStarted()) {
super.deliverResult(data);
}
if (oldResult != null && oldResult != mResult) {
onDiscardResult(oldResult);
}
}
@Override
protected void onReset() {
super.onReset();
onStopLoading();
if (mResult != null) {
onDiscardResult(mResult);
}
mResult = null;
}
@Override
public void onCanceled(final T data) {
super.onCanceled(data);
if (data != null) {
onDiscardResult(data);
}
}
/**
* Called when discarding the load results so subclasses can take care of clean-up or
* recycling tasks. This is not called if the same result (by way of pointer equality) is
* returned again by a subsequent call to loadInBackground, or if result is null.
*
* Note that this may be called concurrently with loadInBackground(), and in some circumstances
* may be called more than once for a given object.
*
* @param result The value returned from {@link AsyncLoaderCompat#loadInBackground()} which
* is to be discarded.
*/
protected abstract void onDiscardResult(T result);
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2018 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.settingslib.widget;
import android.content.Context;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
public class FooterPreferenceMixinCompat implements LifecycleObserver, SetPreferenceScreen {
private final PreferenceFragmentCompat mFragment;
private FooterPreference mFooterPreference;
public FooterPreferenceMixinCompat(PreferenceFragmentCompat fragment, Lifecycle lifecycle) {
mFragment = fragment;
lifecycle.addObserver(this);
}
@Override
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
if (mFooterPreference != null) {
preferenceScreen.addPreference(mFooterPreference);
}
}
/**
* Creates a new {@link FooterPreference}.
*/
public FooterPreference createFooterPreference() {
final PreferenceScreen screen = mFragment.getPreferenceScreen();
if (mFooterPreference != null && screen != null) {
screen.removePreference(mFooterPreference);
}
mFooterPreference = new FooterPreference(getPrefContext());
if (screen != null) {
screen.addPreference(mFooterPreference);
}
return mFooterPreference;
}
/**
* Returns an UI context with theme properly set for new Preference objects.
*/
private Context getPrefContext() {
return mFragment.getPreferenceManager().getContext();
}
public boolean hasFooter() {
return mFooterPreference != null;
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2018 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.settingslib;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.view.View;
import android.widget.EditText;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.ReflectionHelpers;
@RunWith(SettingsLibRobolectricTestRunner.class)
public class CustomEditTextPreferenceComaptTest {
@Mock
private View mView;
private TestPreference mPreference;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mPreference = new TestPreference(RuntimeEnvironment.application);
}
@Test
public void bindDialogView_shouldRequestFocus() {
final String testText = "";
final EditText editText = spy(new EditText(RuntimeEnvironment.application));
editText.setText(testText);
when(mView.findViewById(android.R.id.edit)).thenReturn(editText);
mPreference.onBindDialogView(mView);
verify(editText).requestFocus();
}
@Test
public void getEditText_noDialog_shouldNotCrash() {
ReflectionHelpers.setField(mPreference, "mFragment",
mock(CustomEditTextPreferenceCompat.CustomPreferenceDialogFragment.class));
mPreference.getEditText();
// no crash
}
private static class TestPreference extends CustomEditTextPreferenceCompat {
public TestPreference(Context context) {
super(context);
}
}
}

View File

@@ -0,0 +1,271 @@
/*
* Copyright (C) 2018 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.settingslib.inputmethod;
import static com.google.common.truth.Truth.assertThat;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@RunWith(RobolectricTestRunner.class)
public class InputMethodAndSubtypeUtilCompatTest {
private static final HashSet<String> EMPTY_STRING_SET = new HashSet<>();
private static HashSet<String> asHashSet(String... strings) {
HashSet<String> hashSet = new HashSet<>();
for (String s : strings) {
hashSet.add(s);
}
return hashSet;
}
@Test
public void parseInputMethodsAndSubtypesString_EmptyString() {
assertThat(InputMethodAndSubtypeUtilCompat.
parseInputMethodsAndSubtypesString("")).isEmpty();
assertThat(InputMethodAndSubtypeUtilCompat.
parseInputMethodsAndSubtypesString(null)).isEmpty();
}
@Test
public void parseInputMethodsAndSubtypesString_SingleImeNoSubtype() {
HashMap<String, HashSet<String>> r =
InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString("ime0");
assertThat(r).containsExactly("ime0", EMPTY_STRING_SET);
}
@Test
public void parseInputMethodsAndSubtypesString_MultipleImesNoSubtype() {
HashMap<String, HashSet<String>> r =
InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString("ime0:ime1");
assertThat(r).containsExactly("ime0", EMPTY_STRING_SET, "ime1", EMPTY_STRING_SET);
}
@Test
public void parseInputMethodsAndSubtypesString_SingleImeSingleSubtype() {
HashMap<String, HashSet<String>> r =
InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString("ime0;subtype0");
assertThat(r).containsExactly("ime0", asHashSet("subtype0"));
}
@Test
public void parseInputMethodsAndSubtypesString_SingleImeDuplicateSameSubtypes() {
HashMap<String, HashSet<String>> r =
InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
"ime0;subtype0;subtype0");
assertThat(r).containsExactly("ime0", asHashSet("subtype0"));
}
@Test
public void parseInputMethodsAndSubtypesString_SingleImeMultipleSubtypes() {
HashMap<String, HashSet<String>> r =
InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
"ime0;subtype0;subtype1");
assertThat(r).containsExactly("ime0", asHashSet("subtype0", "subtype1"));
}
@Test
public void parseInputMethodsAndSubtypesString_MultiplePairsOfImeSubtype() {
assertThat(InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
"ime0;subtype0:ime1;subtype1"))
.containsExactly("ime0", asHashSet("subtype0"), "ime1", asHashSet("subtype1"));
assertThat(InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
"ime0;subtype0;subtype1:ime1;subtype2"))
.containsExactly("ime0", asHashSet("subtype0", "subtype1"),
"ime1", asHashSet("subtype2"));
assertThat(InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
"ime0;subtype0;subtype1:ime1;subtype1;subtype2"))
.containsExactly("ime0", asHashSet("subtype0", "subtype1"),
"ime1", asHashSet("subtype1", "subtype2"));
}
@Test
public void parseInputMethodsAndSubtypesString_MixedImeSubtypePairsAndImeNoSubtype() {
HashMap<String, HashSet<String>> r =
InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
"ime0;subtype0;subtype1:ime1;subtype1;subtype2:ime2");
assertThat(r).containsExactly("ime0", asHashSet("subtype0", "subtype1"),
"ime1", asHashSet("subtype1", "subtype2"),
"ime2", EMPTY_STRING_SET);
}
@Test
public void buildInputMethodsAndSubtypesString_EmptyInput() {
HashMap<String, HashSet<String>> map = new HashMap<>();
assertThat(map).isEmpty();
}
@Test
public void buildInputMethodsAndSubtypesString_SingleIme() {
HashMap<String, HashSet<String>> map = new HashMap<>();
map.put("ime0", new HashSet<>());
String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
assertThat(result).isEqualTo("ime0");
}
@Test
public void buildInputMethodsAndSubtypesString_SingleImeSingleSubtype() {
HashMap<String, HashSet<String>> map = new HashMap<>();
map.put("ime0", asHashSet("subtype0"));
String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
assertThat(result).isEqualTo("ime0;subtype0");
}
@Test
public void buildInputMethodsAndSubtypesString_SingleImeMultipleSubtypes() {
HashMap<String, HashSet<String>> map = new HashMap<>();
map.put("ime0", asHashSet("subtype0", "subtype1"));
String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
// We do not expect what order will be used to concatenate items in
// InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
// permutations here.
assertThat(result).matches("ime0;subtype0;subtype1|ime0;subtype1;subtype0");
}
@Test
public void buildInputMethodsAndSubtypesString_MultipleImesNoSubtypes() {
HashMap<String, HashSet<String>> map = new HashMap<>();
map.put("ime0", EMPTY_STRING_SET);
map.put("ime1", EMPTY_STRING_SET);
String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
// We do not expect what order will be used to concatenate items in
// InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
// permutations here.
assertThat(result).matches("ime0:ime1|ime1:ime0");
}
@Test
public void buildInputMethodsAndSubtypesString_MultipleImesWithAndWithoutSubtypes() {
HashMap<String, HashSet<String>> map = new HashMap<>();
map.put("ime0", asHashSet("subtype0", "subtype1"));
map.put("ime1", EMPTY_STRING_SET);
String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
// We do not expect what order will be used to concatenate items in
// InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
// permutations here.
assertThat(result).matches("ime0;subtype0;subtype1:ime1|ime0;subtype1;subtype0:ime1"
+ "|ime1:ime0;subtype0;subtype1|ime1:ime0;subtype1;subtype0");
}
@Test
public void buildInputMethodsAndSubtypesString_MultipleImesWithSubtypes() {
HashMap<String, HashSet<String>> map = new HashMap<>();
map.put("ime0", asHashSet("subtype0", "subtype1"));
map.put("ime1", asHashSet("subtype2", "subtype3"));
String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
// We do not expect what order will be used to concatenate items in
// InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
// permutations here.
assertThat(result).matches("ime0;subtype0;subtype1:ime1;subtype2;subtype3"
+ "|ime0;subtype1;subtype0:ime1;subtype2;subtype3"
+ "|ime0;subtype0;subtype1:ime1;subtype3;subtype2"
+ "|ime0;subtype1;subtype0:ime1;subtype3;subtype2"
+ "|ime1;subtype2;subtype3:ime0;subtype0;subtype1"
+ "|ime2;subtype3;subtype2:ime0;subtype0;subtype1"
+ "|ime3;subtype2;subtype3:ime0;subtype1;subtype0"
+ "|ime4;subtype3;subtype2:ime0;subtype1;subtype0");
}
@Test
public void isValidSystemNonAuxAsciiCapableIme() {
// System IME w/ no subtype
assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
createDummyIme(true, false)))
.isFalse();
// System IME w/ non-Aux and non-ASCII-capable "keyboard" subtype
assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
createDummyIme(true, false, createDummySubtype("keyboard", false, false))))
.isFalse();
// System IME w/ non-Aux and ASCII-capable "keyboard" subtype
assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
createDummyIme(true, false, createDummySubtype("keyboard", false, true))))
.isTrue();
// System IME w/ Aux and ASCII-capable "keyboard" subtype
assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
createDummyIme(true, true, createDummySubtype("keyboard", true, true))))
.isFalse();
// System IME w/ non-Aux and ASCII-capable "voice" subtype
assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
createDummyIme(true, false, createDummySubtype("voice", false, true))))
.isFalse();
// System IME w/ non-Aux and non-ASCII-capable subtype + Non-Aux and ASCII-capable subtype
assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
createDummyIme(true, false,
createDummySubtype("keyboard", false, true),
createDummySubtype("keyboard", false, false))))
.isTrue();
// Non-system IME w/ non-Aux and ASCII-capable "keyboard" subtype
assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
createDummyIme(false, false, createDummySubtype("keyboard", false, true))))
.isFalse();
}
private static InputMethodInfo createDummyIme(boolean isSystem, boolean isAuxIme,
InputMethodSubtype... subtypes) {
final ResolveInfo ri = new ResolveInfo();
final ServiceInfo si = new ServiceInfo();
final ApplicationInfo ai = new ApplicationInfo();
ai.packageName = "com.example.android.dummyime";
ai.enabled = true;
ai.flags |= (isSystem ? ApplicationInfo.FLAG_SYSTEM : 0);
si.applicationInfo = ai;
si.enabled = true;
si.packageName = "com.example.android.dummyime";
si.name = "Dummy IME";
si.exported = true;
si.nonLocalizedLabel = "Dummy IME";
ri.serviceInfo = si;
return new InputMethodInfo(ri, isAuxIme, "", Arrays.asList(subtypes), 1, false);
}
private static InputMethodSubtype createDummySubtype(
String mode, boolean isAuxiliary, boolean isAsciiCapable) {
return new InputMethodSubtypeBuilder()
.setSubtypeNameResId(0)
.setSubtypeIconResId(0)
.setSubtypeLocale("en_US")
.setLanguageTag("en-US")
.setSubtypeMode(mode)
.setIsAuxiliary(isAuxiliary)
.setIsAsciiCapable(isAsciiCapable)
.build();
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.settingslib.license;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import com.android.settingslib.SettingsLibRobolectricTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.File;
import java.util.ArrayList;
@RunWith(SettingsLibRobolectricTestRunner.class)
public class LicenseHtmlLoaderCompatTest {
@Mock
private Context mContext;
LicenseHtmlLoaderCompat newLicenseHtmlLoader(ArrayList<File> xmlFiles,
File cachedHtmlFile, boolean isCachedHtmlFileOutdated,
boolean generateHtmlFileSucceeded) {
LicenseHtmlLoaderCompat loader = spy(new LicenseHtmlLoaderCompat(mContext));
doReturn(xmlFiles).when(loader).getVaildXmlFiles();
doReturn(cachedHtmlFile).when(loader).getCachedHtmlFile();
doReturn(isCachedHtmlFileOutdated).when(loader).isCachedHtmlFileOutdated(any(), any());
doReturn(generateHtmlFileSucceeded).when(loader).generateHtmlFile(any(), any());
return loader;
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testLoadInBackground() {
ArrayList<File> xmlFiles = new ArrayList();
xmlFiles.add(new File("test.xml"));
File cachedHtmlFile = new File("test.html");
LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true);
assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile);
verify(loader).generateHtmlFile(any(), any());
}
@Test
public void testLoadInBackgroundWithNoVaildXmlFiles() {
ArrayList<File> xmlFiles = new ArrayList();
File cachedHtmlFile = new File("test.html");
LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true);
assertThat(loader.loadInBackground()).isNull();
verify(loader, never()).generateHtmlFile(any(), any());
}
@Test
public void testLoadInBackgroundWithNonOutdatedCachedHtmlFile() {
ArrayList<File> xmlFiles = new ArrayList();
xmlFiles.add(new File("test.xml"));
File cachedHtmlFile = new File("test.html");
LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, false,
true);
assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile);
verify(loader, never()).generateHtmlFile(any(), any());
}
@Test
public void testLoadInBackgroundWithGenerateHtmlFileFailed() {
ArrayList<File> xmlFiles = new ArrayList();
xmlFiles.add(new File("test.xml"));
File cachedHtmlFile = new File("test.html");
LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true,
false);
assertThat(loader.loadInBackground()).isNull();
verify(loader).generateHtmlFile(any(), any());
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2018 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.settingslib.suggestions;
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import com.android.settingslib.SettingsLibRobolectricTestRunner;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import androidx.lifecycle.LifecycleOwner;
import androidx.loader.app.LoaderManager;
@RunWith(SettingsLibRobolectricTestRunner.class)
@Config(shadows = ShadowSuggestionController.class)
public class SuggestionControllerMixinCompatTest {
@Mock
private SuggestionControllerMixinCompat.SuggestionControllerHost mHost;
private Context mContext;
private LifecycleOwner mLifecycleOwner;
private Lifecycle mLifecycle;
private SuggestionControllerMixinCompat mMixin;
private ComponentName mComponentName;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mLifecycleOwner = () -> mLifecycle;
mLifecycle = new Lifecycle(mLifecycleOwner);
mComponentName = new ComponentName(
"com.android.settings.intelligence",
"com.android.settings.intelligence.suggestions.SuggestionService");
}
@After
public void tearDown() {
ShadowSuggestionController.reset();
}
@Test
public void goThroughLifecycle_onStartStop_shouldStartStopController() {
mMixin = new SuggestionControllerMixinCompat(mContext, mHost, mLifecycle, mComponentName);
mLifecycle.handleLifecycleEvent(ON_START);
assertThat(ShadowSuggestionController.sStartCalled).isTrue();
mLifecycle.handleLifecycleEvent(ON_STOP);
assertThat(ShadowSuggestionController.sStopCalled).isTrue();
}
@Test
public void onServiceConnected_shouldGetSuggestion() {
final LoaderManager loaderManager = mock(LoaderManager.class);
when(mHost.getLoaderManager()).thenReturn(loaderManager);
mMixin = new SuggestionControllerMixinCompat(mContext, mHost, mLifecycle, mComponentName);
mMixin.onServiceConnected();
verify(loaderManager).restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS,
null /* args */, mMixin /* callback */);
}
@Test
public void onServiceConnected_hostNotAttached_shouldDoNothing() {
when(mHost.getLoaderManager()).thenReturn(null);
mMixin = new SuggestionControllerMixinCompat(mContext, mHost, mLifecycle, mComponentName);
mMixin.onServiceConnected();
verify(mHost).getLoaderManager();
}
@Test
public void onServiceDisconnected_hostNotAttached_shouldDoNothing() {
when(mHost.getLoaderManager()).thenReturn(null);
mMixin = new SuggestionControllerMixinCompat(mContext, mHost, mLifecycle, mComponentName);
mMixin.onServiceDisconnected();
verify(mHost).getLoaderManager();
}
@Test
public void doneLoadingg_shouldSetSuggestionLoaded() {
mMixin = new SuggestionControllerMixinCompat(mContext, mHost, mLifecycle, mComponentName);
mMixin.onLoadFinished(mock(SuggestionLoaderCompat.class), null);
assertThat(mMixin.isSuggestionLoaded()).isTrue();
mMixin.onLoaderReset(mock(SuggestionLoaderCompat.class));
assertThat(mMixin.isSuggestionLoaded()).isFalse();
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.widget;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.android.settingslib.SettingsLibRobolectricTestRunner;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.shadows.ShadowApplication;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
@RunWith(SettingsLibRobolectricTestRunner.class)
public class FooterPreferenceMixinCompatTest {
@Mock
private PreferenceFragmentCompat mFragment;
@Mock
private PreferenceScreen mScreen;
private LifecycleOwner mLifecycleOwner;
private Lifecycle mLifecycle;
private FooterPreferenceMixinCompat mMixin;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mLifecycleOwner = () -> mLifecycle;
mLifecycle = new Lifecycle(mLifecycleOwner);
when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
when(mFragment.getPreferenceManager().getContext())
.thenReturn(ShadowApplication.getInstance().getApplicationContext());
mMixin = new FooterPreferenceMixinCompat(mFragment, mLifecycle);
}
@Test
public void createFooter_screenNotAvailable_noCrash() {
assertThat(mMixin.createFooterPreference()).isNotNull();
}
@Test
public void createFooter_screenAvailable_canAttachToScreen() {
when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
final FooterPreference preference = mMixin.createFooterPreference();
assertThat(preference).isNotNull();
verify(mScreen).addPreference(preference);
}
@Test
public void createFooter_screenAvailableDelayed_canAttachToScreen() {
final FooterPreference preference = mMixin.createFooterPreference();
mLifecycle.setPreferenceScreen(mScreen);
assertThat(preference).isNotNull();
verify(mScreen).addPreference(preference);
}
@Test
public void createFooterTwice_screenAvailable_replaceOldFooter() {
when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
mMixin.createFooterPreference();
mMixin.createFooterPreference();
verify(mScreen).removePreference(any(FooterPreference.class));
verify(mScreen, times(2)).addPreference(any(FooterPreference.class));
}
}

View File

@@ -23,11 +23,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceFragment;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import com.android.settingslib.SettingsLibRobolectricTestRunner;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -38,6 +33,11 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.shadows.ShadowApplication;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceFragment;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
@RunWith(SettingsLibRobolectricTestRunner.class)
public class FooterPreferenceMixinTest {