1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
|
/**
* Copyright (C) 2011 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.inputmethod.dictionarypack;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.animation.AnimationUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.android.inputmethod.latin.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;
import java.util.TreeMap;
/**
* Preference screen.
*/
public final class DictionarySettingsFragment extends PreferenceFragment
implements UpdateHandler.UpdateEventListener {
private static final String TAG = DictionarySettingsFragment.class.getSimpleName();
static final private String DICT_LIST_ID = "list";
static final public String DICT_SETTINGS_FRAGMENT_CLIENT_ID_ARGUMENT = "clientId";
static final private int MENU_UPDATE_NOW = Menu.FIRST;
private View mLoadingView;
private String mClientId;
private ConnectivityManager mConnectivityManager;
private MenuItem mUpdateNowMenu;
private boolean mChangedSettings;
private DictionaryListInterfaceState mDictionaryListInterfaceState =
new DictionaryListInterfaceState();
private final BroadcastReceiver mConnectivityChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
refreshNetworkState();
}
};
/**
* Empty constructor for fragment generation.
*/
public DictionarySettingsFragment() {
}
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.loading_page, container, true);
mLoadingView = v.findViewById(R.id.loading_container);
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final Activity activity = getActivity();
mClientId = activity.getIntent().getStringExtra(DICT_SETTINGS_FRAGMENT_CLIENT_ID_ARGUMENT);
mConnectivityManager =
(ConnectivityManager)activity.getSystemService(Context.CONNECTIVITY_SERVICE);
addPreferencesFromResource(R.xml.dictionary_settings);
refreshInterface();
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
mUpdateNowMenu = menu.add(Menu.NONE, MENU_UPDATE_NOW, 0, R.string.check_for_updates_now);
mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
refreshNetworkState();
}
@Override
public void onResume() {
super.onResume();
mChangedSettings = false;
UpdateHandler.registerUpdateEventListener(this);
final Activity activity = getActivity();
if (!MetadataDbHelper.isClientKnown(activity, mClientId)) {
Log.i(TAG, "Unknown dictionary pack client: " + mClientId + ". Requesting info.");
final Intent unknownClientBroadcast =
new Intent(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT);
unknownClientBroadcast.putExtra(
DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA, mClientId);
activity.sendBroadcast(unknownClientBroadcast);
}
final IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
getActivity().registerReceiver(mConnectivityChangedReceiver, filter);
refreshNetworkState();
}
@Override
public void onPause() {
super.onPause();
final Activity activity = getActivity();
UpdateHandler.unregisterUpdateEventListener(this);
activity.unregisterReceiver(mConnectivityChangedReceiver);
if (mChangedSettings) {
final Intent newDictBroadcast =
new Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
activity.sendBroadcast(newDictBroadcast);
mChangedSettings = false;
}
}
@Override
public void downloadedMetadata(final boolean succeeded) {
stopLoadingAnimation();
if (!succeeded) return; // If the download failed nothing changed, so no need to refresh
new Thread("refreshInterface") {
@Override
public void run() {
refreshInterface();
}
}.start();
}
@Override
public void wordListDownloadFinished(final String wordListId, final boolean succeeded) {
final WordListPreference pref = findWordListPreference(wordListId);
if (null == pref) return;
// TODO: Report to the user if !succeeded
final Activity activity = getActivity();
if (null == activity) return;
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
// We have to re-read the db in case the description has changed, and to
// find out what state it ended up if the download wasn't successful
// TODO: don't redo everything, only re-read and set this word list status
refreshInterface();
}
});
}
private WordListPreference findWordListPreference(final String id) {
final PreferenceGroup prefScreen = getPreferenceScreen();
if (null == prefScreen) {
Log.e(TAG, "Could not find the preference group");
return null;
}
for (int i = prefScreen.getPreferenceCount() - 1; i >= 0; --i) {
final Preference pref = prefScreen.getPreference(i);
if (pref instanceof WordListPreference) {
final WordListPreference wlPref = (WordListPreference)pref;
if (id.equals(wlPref.mWordlistId)) {
return wlPref;
}
}
}
Log.e(TAG, "Could not find the preference for a word list id " + id);
return null;
}
@Override
public void updateCycleCompleted() {}
private void refreshNetworkState() {
NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
boolean isConnected = null == info ? false : info.isConnected();
if (null != mUpdateNowMenu) mUpdateNowMenu.setEnabled(isConnected);
}
private void refreshInterface() {
final Activity activity = getActivity();
if (null == activity) return;
final long lastUpdateDate =
MetadataDbHelper.getLastUpdateDateForClient(getActivity(), mClientId);
final PreferenceGroup prefScreen = getPreferenceScreen();
final Collection<? extends Preference> prefList =
createInstalledDictSettingsCollection(mClientId);
final String updateNowSummary = getString(R.string.last_update) + " "
+ DateUtils.formatDateTime(activity, lastUpdateDate,
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
// TODO: display this somewhere
// if (0 != lastUpdate) mUpdateNowPreference.setSummary(updateNowSummary);
refreshNetworkState();
removeAnyDictSettings(prefScreen);
for (Preference preference : prefList) {
prefScreen.addPreference(preference);
}
}
});
}
private Preference createErrorMessage(final Activity activity, final int messageResource) {
final Preference message = new Preference(activity);
message.setTitle(messageResource);
message.setEnabled(false);
return message;
}
private void removeAnyDictSettings(final PreferenceGroup prefGroup) {
for (int i = prefGroup.getPreferenceCount() - 1; i >= 0; --i) {
prefGroup.removePreference(prefGroup.getPreference(i));
}
}
/**
* Creates a WordListPreference list to be added to the screen.
*
* This method only creates the preferences but does not add them.
* Thus, it can be called on another thread.
*
* @param clientId the id of the client for which we want to display the dictionary list
* @return A collection of preferences ready to add to the interface.
*/
private Collection<? extends Preference> createInstalledDictSettingsCollection(
final String clientId) {
// This will directly contact the DictionaryProvider and request the list exactly like
// any regular client would do.
// Considering the respective value of the respective constants used here for each path,
// segment, the url generated by this is of the form (assuming "clientId" as a clientId)
// content://com.android.inputmethod.latin.dictionarypack/clientId/list?procotol=2
final Uri contentUri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(getString(R.string.authority))
.appendPath(clientId)
.appendPath(DICT_LIST_ID)
// Need to use version 2 to get this client's list
.appendQueryParameter(DictionaryProvider.QUERY_PARAMETER_PROTOCOL_VERSION, "2")
.build();
final Activity activity = getActivity();
final Cursor cursor = null == activity ? null
: activity.getContentResolver().query(contentUri, null, null, null, null);
if (null == cursor) {
final ArrayList<Preference> result = new ArrayList<Preference>();
result.add(createErrorMessage(activity, R.string.cannot_connect_to_dict_service));
return result;
} else if (!cursor.moveToFirst()) {
final ArrayList<Preference> result = new ArrayList<Preference>();
result.add(createErrorMessage(activity, R.string.no_dictionaries_available));
cursor.close();
return result;
} else {
final String systemLocaleString = Locale.getDefault().toString();
final TreeMap<String, WordListPreference> prefList =
new TreeMap<String, WordListPreference>();
final int idIndex = cursor.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN);
final int versionIndex = cursor.getColumnIndex(MetadataDbHelper.VERSION_COLUMN);
final int localeIndex = cursor.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
final int descriptionIndex = cursor.getColumnIndex(MetadataDbHelper.DESCRIPTION_COLUMN);
final int statusIndex = cursor.getColumnIndex(MetadataDbHelper.STATUS_COLUMN);
do {
final String wordlistId = cursor.getString(idIndex);
final int version = cursor.getInt(versionIndex);
final String localeString = cursor.getString(localeIndex);
final Locale locale = new Locale(localeString);
final String description = cursor.getString(descriptionIndex);
final int status = cursor.getInt(statusIndex);
final int matchLevel = LocaleUtils.getMatchLevel(systemLocaleString, localeString);
final String matchLevelString = LocaleUtils.getMatchLevelSortedString(matchLevel);
// The key is sorted in lexicographic order, according to the match level, then
// the description.
final String key = matchLevelString + "." + description + "." + wordlistId;
final WordListPreference existingPref = prefList.get(key);
if (null == existingPref || hasPriority(status, existingPref.mStatus)) {
final WordListPreference pref = new WordListPreference(activity,
mDictionaryListInterfaceState, mClientId, wordlistId, version, locale,
description, status);
prefList.put(key, pref);
}
} while (cursor.moveToNext());
cursor.close();
return prefList.values();
}
}
/**
* Finds out if a given status has priority over another for display order.
*
* @param newStatus
* @param oldStatus
* @return whether newStatus has priority over oldStatus.
*/
private static boolean hasPriority(final int newStatus, final int oldStatus) {
// Both of these should be one of MetadataDbHelper.STATUS_*
return newStatus > oldStatus;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case MENU_UPDATE_NOW:
if (View.GONE == mLoadingView.getVisibility()) {
startRefresh();
} else {
cancelRefresh();
}
return true;
}
return false;
}
private void startRefresh() {
startLoadingAnimation();
mChangedSettings = true;
UpdateHandler.registerUpdateEventListener(this);
final Activity activity = getActivity();
new Thread("updateByHand") {
@Override
public void run() {
UpdateHandler.update(activity, true);
}
}.start();
}
private void cancelRefresh() {
UpdateHandler.unregisterUpdateEventListener(this);
final Context context = getActivity();
UpdateHandler.cancelUpdate(context, mClientId);
stopLoadingAnimation();
}
private void startLoadingAnimation() {
mLoadingView.setVisibility(View.VISIBLE);
getView().setVisibility(View.GONE);
mUpdateNowMenu.setTitle(R.string.cancel);
}
private void stopLoadingAnimation() {
final View preferenceView = getView();
final Activity activity = getActivity();
if (null == activity) return;
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
mLoadingView.setVisibility(View.GONE);
preferenceView.setVisibility(View.VISIBLE);
mLoadingView.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_out));
preferenceView.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
// The menu is created by the framework asynchronously after the activity,
// which means it's possible to have the activity running but the menu not
// created yet - hence the necessity for a null check here.
if (null != mUpdateNowMenu) {
mUpdateNowMenu.setTitle(R.string.check_for_updates_now);
}
}
});
}
}
|