Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f7921b12 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Load only unique dictionary words"

parents becd4e68 6539c9a1
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import com.android.settings.Settings;
import com.android.settings.TestingSettings;
import com.android.settings.TetherSettings;
import com.android.settings.TrustedCredentialsSettings;
import com.android.settings.UserDictionarySettings;
import com.android.settings.WifiCallingSettings;
import com.android.settings.accessibility.AccessibilitySettings;
import com.android.settings.accessibility.AccessibilitySettingsForSetupWizard;
@@ -91,6 +90,7 @@ import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
import com.android.settings.inputmethod.SpellCheckersSettings;
import com.android.settings.inputmethod.UserDictionaryList;
import com.android.settings.inputmethod.UserDictionarySettings;
import com.android.settings.language.LanguageAndInputSettings;
import com.android.settings.localepicker.LocaleListEditor;
import com.android.settings.location.LocationSettings;
+0 −1
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.view.View;
import android.widget.EditText;

import com.android.settings.R;
import com.android.settings.UserDictionarySettings;
import com.android.settings.Utils;

import java.util.ArrayList;
+98 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.inputmethod;

import android.content.Context;
import android.content.CursorLoader;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.UserDictionary;
import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;

import java.util.Locale;
import java.util.Objects;
import java.util.Set;

public class UserDictionaryCursorLoader extends CursorLoader {

    @VisibleForTesting
    static final String[] QUERY_PROJECTION = {
            UserDictionary.Words._ID,
            UserDictionary.Words.WORD,
            UserDictionary.Words.SHORTCUT
    };

    // The index of the shortcut in the above array.
    static final int INDEX_SHORTCUT = 2;

    // Either the locale is empty (means the word is applicable to all locales)
    // or the word equals our current locale
    private static final String QUERY_SELECTION =
            UserDictionary.Words.LOCALE + "=?";
    private static final String QUERY_SELECTION_ALL_LOCALES =
            UserDictionary.Words.LOCALE + " is null";


    // Locale can be any of:
    // - The string representation of a locale, as returned by Locale#toString()
    // - The empty string. This means we want a cursor returning words valid for all locales.
    // - null. This means we want a cursor for the current locale, whatever this is.
    // Note that this contrasts with the data inside the database, where NULL means "all
    // locales" and there should never be an empty string. The confusion is called by the
    // historical use of null for "all locales".
    // TODO: it should be easy to make this more readable by making the special values
    // human-readable, like "all_locales" and "current_locales" strings, provided they
    // can be guaranteed not to match locales that may exist.
    private final String mLocale;

    public UserDictionaryCursorLoader(Context context, String locale) {
        super(context);
        mLocale = locale;
    }

    @Override
    public Cursor loadInBackground() {
        final MatrixCursor result = new MatrixCursor(QUERY_PROJECTION);
        final Cursor candidate;
        if ("".equals(mLocale)) {
            // Case-insensitive sort
            candidate = getContext().getContentResolver().query(
                    UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
                    QUERY_SELECTION_ALL_LOCALES, null,
                    "UPPER(" + UserDictionary.Words.WORD + ")");
        } else {
            final String queryLocale = null != mLocale ? mLocale : Locale.getDefault().toString();
            candidate = getContext().getContentResolver().query(UserDictionary.Words.CONTENT_URI,
                    QUERY_PROJECTION, QUERY_SELECTION,
                    new String[]{queryLocale}, "UPPER(" + UserDictionary.Words.WORD + ")");
        }
        final Set<Integer> hashSet = new ArraySet<>();
        for (candidate.moveToFirst(); !candidate.isAfterLast(); candidate.moveToNext()) {
            final int id = candidate.getInt(0);
            final String word = candidate.getString(1);
            final String shortcut = candidate.getString(2);
            final int hash = Objects.hash(word, shortcut);
            if (hashSet.contains(hash)) {
                continue;
            }
            hashSet.add(hash);
            result.addRow(new Object[]{id, word, shortcut});
        }
        return result;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -180,7 +180,7 @@ public class UserDictionaryList extends SettingsPreferenceFragment {
            newPref.getExtras().putString("locale", locale);
        }
        newPref.setIntent(intent);
        newPref.setFragment(com.android.settings.UserDictionarySettings.class.getName());
        newPref.setFragment(UserDictionarySettings.class.getName());
        return newPref;
    }

+76 −85
Original line number Diff line number Diff line
/**
 * Copyright (C) 2009 Google Inc.
/*
 * Copyright (C) 2009 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
 * 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.
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings;
package com.android.settings.inputmethod;

import android.annotation.Nullable;
import android.app.ActionBar;
import android.app.ListFragment;
import android.app.LoaderManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.UserDictionary;
@@ -38,28 +42,13 @@ import android.widget.SimpleCursorAdapter;
import android.widget.TextView;

import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.core.instrumentation.VisibilityLoggerMixin;
import com.android.settings.inputmethod.UserDictionaryAddWordContents;
import com.android.settings.inputmethod.UserDictionarySettingsUtils;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.core.instrumentation.Instrumentable;
import com.android.settings.core.instrumentation.VisibilityLoggerMixin;

import java.util.Locale;

public class UserDictionarySettings extends ListFragment implements Instrumentable {

    private static final String[] QUERY_PROJECTION = {
        UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT
    };

    // The index of the shortcut in the above array.
    private static final int INDEX_SHORTCUT = 2;

    // Either the locale is empty (means the word is applicable to all locales)
    // or the word equals our current locale
    private static final String QUERY_SELECTION =
            UserDictionary.Words.LOCALE + "=?";
    private static final String QUERY_SELECTION_ALL_LOCALES =
            UserDictionary.Words.LOCALE + " is null";
public class UserDictionarySettings extends ListFragment implements Instrumentable,
        LoaderManager.LoaderCallbacks<Cursor> {

    private static final String DELETE_SELECTION_WITH_SHORTCUT = UserDictionary.Words.WORD
            + "=? AND " + UserDictionary.Words.SHORTCUT + "=?";
@@ -68,12 +57,13 @@ public class UserDictionarySettings extends ListFragment implements Instrumentab
            + UserDictionary.Words.SHORTCUT + "=''";

    private static final int OPTIONS_MENU_ADD = Menu.FIRST;
    private static final int LOADER_ID = 1;

    private final VisibilityLoggerMixin mVisibilityLoggerMixin =
            new VisibilityLoggerMixin(getMetricsCategory());

    private Cursor mCursor;
    protected String mLocale;
    private String mLocale;

    @Override
    public int getMetricsCategory() {
@@ -87,16 +77,8 @@ public class UserDictionarySettings extends ListFragment implements Instrumentab
    }

    @Override
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(
                com.android.internal.R.layout.preference_list_fragment, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getActivity().getActionBar().setTitle(R.string.user_dict_settings_title);
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final Intent intent = getActivity().getIntent();
        final String localeFromIntent =
@@ -116,56 +98,49 @@ public class UserDictionarySettings extends ListFragment implements Instrumentab
        }

        mLocale = locale;
        mCursor = createCursor(locale);
        TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);

        setHasOptionsMenu(true);
        getLoaderManager().initLoader(LOADER_ID, null, this /* callback */);
    }

    @Override
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Show the language as a subtitle of the action bar
        final ActionBar actionBar = getActivity().getActionBar();
        if (actionBar != null) {
            actionBar.setTitle(R.string.user_dict_settings_title);
            actionBar.setSubtitle(
                    UserDictionarySettingsUtils.getLocaleDisplayName(getActivity(), mLocale));
        }

        return inflater.inflate(
                com.android.internal.R.layout.preference_list_fragment, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        TextView emptyView = getView().findViewById(android.R.id.empty);
        emptyView.setText(R.string.user_dict_settings_empty_text);

        final ListView listView = getListView();
        listView.setAdapter(createAdapter());
        listView.setFastScrollEnabled(true);
        listView.setEmptyView(emptyView);

        setHasOptionsMenu(true);
        // Show the language as a subtitle of the action bar
        getActivity().getActionBar().setSubtitle(
                UserDictionarySettingsUtils.getLocaleDisplayName(getActivity(), mLocale));
    }

    @Override
    public void onResume() {
        super.onResume();
        mVisibilityLoggerMixin.onResume();
    }

    private Cursor createCursor(final String locale) {
        // Locale can be any of:
        // - The string representation of a locale, as returned by Locale#toString()
        // - The empty string. This means we want a cursor returning words valid for all locales.
        // - null. This means we want a cursor for the current locale, whatever this is.
        // Note that this contrasts with the data inside the database, where NULL means "all
        // locales" and there should never be an empty string. The confusion is called by the
        // historical use of null for "all locales".
        // TODO: it should be easy to make this more readable by making the special values
        // human-readable, like "all_locales" and "current_locales" strings, provided they
        // can be guaranteed not to match locales that may exist.
        if ("".equals(locale)) {
            // Case-insensitive sort
            return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
                    QUERY_SELECTION_ALL_LOCALES, null,
                    "UPPER(" + UserDictionary.Words.WORD + ")");
        } else {
            final String queryLocale = null != locale ? locale : Locale.getDefault().toString();
            return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
                    QUERY_SELECTION, new String[] { queryLocale },
                    "UPPER(" + UserDictionary.Words.WORD + ")");
        }
        getLoaderManager().restartLoader(LOADER_ID, null, this /* callback */);
    }

    private ListAdapter createAdapter() {
        return new MyAdapter(getActivity(),
                R.layout.user_dictionary_item, mCursor,
                new String[]{UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT},
                new int[] { android.R.id.text1, android.R.id.text2 }, this);
                new int[]{android.R.id.text1, android.R.id.text2});
    }

    @Override
@@ -203,6 +178,7 @@ public class UserDictionarySettings extends ListFragment implements Instrumentab

    /**
     * Add or edit a word. If editingWord is null, it's an add; otherwise, it's an edit.
     *
     * @param editingWord     the word to edit, or null if it's an add.
     * @param editingShortcut the shortcut for this entry, or null if none.
     */
@@ -253,6 +229,22 @@ public class UserDictionarySettings extends ListFragment implements Instrumentab
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new UserDictionaryCursorLoader(getContext(), mLocale);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        mCursor = data;
        getListView().setAdapter(createAdapter());
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {

    }

    private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer {

        private AlphabetIndexer mIndexer;
@@ -261,8 +253,8 @@ public class UserDictionarySettings extends ListFragment implements Instrumentab

            @Override
            public boolean setViewValue(View v, Cursor c, int columnIndex) {
                if (columnIndex == INDEX_SHORTCUT) {
                    final String shortcut = c.getString(INDEX_SHORTCUT);
                if (columnIndex == UserDictionaryCursorLoader.INDEX_SHORTCUT) {
                    final String shortcut = c.getString(UserDictionaryCursorLoader.INDEX_SHORTCUT);
                    if (TextUtils.isEmpty(shortcut)) {
                        v.setVisibility(View.GONE);
                    } else {
@@ -277,8 +269,7 @@ public class UserDictionarySettings extends ListFragment implements Instrumentab
            }
        };

        public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to,
                UserDictionarySettings settings) {
        public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
            super(context, layout, c, from, to);

            if (null != c) {
Loading