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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 */);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user