]> gitweb.fperrin.net Git - Dictionary.git/blob - src/com/hughes/android/dictionary/DictionaryManagerActivity.java
Support runtime permissions.
[Dictionary.git] / src / com / hughes / android / dictionary / DictionaryManagerActivity.java
1 // Copyright 2011 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package com.hughes.android.dictionary;
16
17 import android.Manifest;
18 import android.app.AlertDialog;
19 import android.app.DownloadManager;
20 import android.app.DownloadManager.Request;
21 import android.content.BroadcastReceiver;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.SharedPreferences;
27 import android.content.SharedPreferences.Editor;
28 import android.content.pm.PackageManager;
29 import android.database.Cursor;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.Environment;
33 import android.os.Handler;
34 import android.preference.PreferenceManager;
35 import android.support.v4.app.ActivityCompat;
36 import android.support.v4.content.ContextCompat;
37 import android.support.v4.view.MenuItemCompat;
38 import android.support.v7.app.ActionBar;
39 import android.support.v7.app.ActionBarActivity;
40 import android.support.v7.widget.SearchView;
41 import android.support.v7.widget.SearchView.OnQueryTextListener;
42 import android.support.v7.widget.Toolbar;
43 import android.text.InputType;
44 import android.util.Log;
45 import android.util.TypedValue;
46 import android.view.ContextMenu;
47 import android.view.ContextMenu.ContextMenuInfo;
48 import android.view.LayoutInflater;
49 import android.view.Menu;
50 import android.view.MenuItem;
51 import android.view.View;
52 import android.view.View.OnClickListener;
53 import android.view.ViewGroup;
54 import android.view.inputmethod.EditorInfo;
55 import android.view.inputmethod.InputMethodManager;
56 import android.widget.AdapterView.AdapterContextMenuInfo;
57 import android.widget.BaseAdapter;
58 import android.widget.Button;
59 import android.widget.CompoundButton;
60 import android.widget.CompoundButton.OnCheckedChangeListener;
61 import android.widget.FrameLayout;
62 import android.widget.LinearLayout;
63 import android.widget.ListAdapter;
64 import android.widget.ListView;
65 import android.widget.TextView;
66 import android.widget.Toast;
67 import android.widget.ToggleButton;
68
69 import com.hughes.android.dictionary.DictionaryInfo.IndexInfo;
70 import com.hughes.android.util.IntentLauncher;
71
72 import java.io.File;
73 import java.io.FileInputStream;
74 import java.io.FileOutputStream;
75 import java.io.IOException;
76 import java.io.InputStream;
77 import java.io.OutputStream;
78 import java.net.MalformedURLException;
79 import java.net.URL;
80 import java.util.Collections;
81 import java.util.HashSet;
82 import java.util.List;
83 import java.util.Set;
84 import java.util.zip.ZipEntry;
85 import java.util.zip.ZipFile;
86 import java.util.zip.ZipInputStream;
87
88 // Right-click:
89 //  Delete, move to top.
90
91 public class DictionaryManagerActivity extends ActionBarActivity {
92
93     static final String LOG = "QuickDic";
94     static boolean blockAutoLaunch = false;
95
96     private ListView listView;
97     private ListView getListView() {
98         if (listView == null) {
99             listView = (ListView)findViewById(android.R.id.list);
100         }
101         return listView;
102     }
103     private void setListAdapter(ListAdapter adapter) {
104         getListView().setAdapter(adapter);
105     }
106     private ListAdapter getListAdapter() {
107         return getListView().getAdapter();
108     }
109
110     // For DownloadManager bug workaround
111     private Set<Long> finishedDownloadIds = new HashSet<Long>();
112
113     DictionaryApplication application;
114
115     SearchView filterSearchView;
116     ToggleButton showDownloadable;
117
118     LinearLayout dictionariesOnDeviceHeaderRow;
119     LinearLayout downloadableDictionariesHeaderRow;
120
121     Handler uiHandler;
122
123     Runnable dictionaryUpdater = new Runnable() {
124         @Override
125         public void run() {
126             if (uiHandler == null) {
127                 return;
128             }
129             uiHandler.post(new Runnable() {
130                 @Override
131                 public void run() {
132                     setMyListAdapater();
133                 }
134             });
135         }
136     };
137
138     final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
139         @Override
140         public synchronized void onReceive(Context context, Intent intent) {
141             final String action = intent.getAction();
142
143             if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
144                 final long downloadId = intent.getLongExtra(
145                         DownloadManager.EXTRA_DOWNLOAD_ID, 0);
146                 if (finishedDownloadIds.contains(downloadId)) return; // ignore double notifications
147                 final DownloadManager.Query query = new DownloadManager.Query();
148                 query.setFilterById(downloadId);
149                 final DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
150                 final Cursor cursor = downloadManager.query(query);
151
152                 if (cursor == null || !cursor.moveToFirst()) {
153                     Log.e(LOG, "Couldn't find download.");
154                     return;
155                 }
156
157                 final String dest = cursor
158                         .getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
159                 final int status = cursor
160                         .getInt(cursor
161                                 .getColumnIndex(DownloadManager.COLUMN_STATUS));
162                 if (DownloadManager.STATUS_SUCCESSFUL != status) {
163                     final int reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON));
164                     Log.w(LOG,
165                             "Download failed: status=" + status +
166                                     ", reason=" + reason);
167                     String msg = Integer.toString(reason);
168                     switch (reason) {
169                     case DownloadManager.ERROR_FILE_ALREADY_EXISTS: msg = "File exists"; break;
170                     case DownloadManager.ERROR_FILE_ERROR: msg = "File error"; break;
171                     case DownloadManager.ERROR_INSUFFICIENT_SPACE: msg = "Not enough space"; break;
172                     }
173                     new AlertDialog.Builder(context).setTitle(getString(R.string.error)).setMessage(getString(R.string.downloadFailed, msg)).setNeutralButton("Close", null).show();
174                     return;
175                 }
176
177                 Log.w(LOG, "Download finished: " + dest + " Id: " + downloadId);
178                 Toast.makeText(context, getString(R.string.unzippingDictionary, dest),
179                         Toast.LENGTH_LONG).show();
180
181
182                 final Uri zipUri = Uri.parse(dest);
183                 File localZipFile = null;
184                 InputStream zipFileStream = null;
185                 ZipInputStream zipFile = null;
186                 OutputStream zipOut = null;
187                 try {
188                     if (zipUri.getScheme().equals("content")) {
189                         zipFileStream = context.getContentResolver().openInputStream(zipUri);
190                         localZipFile = null;
191                     } else {
192                         localZipFile = new File(zipUri.getPath());
193                         zipFileStream = new FileInputStream(localZipFile);
194                     }
195                     zipFile = new ZipInputStream(zipFileStream);
196                     final ZipEntry zipEntry = zipFile.getNextEntry();
197                     Log.d(LOG, "Unzipping entry: " + zipEntry.getName());
198                     File targetFile = new File(application.getDictDir(), zipEntry.getName());
199                     if (targetFile.exists()) {
200                         targetFile.renameTo(new File(targetFile.getAbsolutePath().replace(".quickdic", ".bak.quickdic")));
201                         targetFile = new File(application.getDictDir(), zipEntry.getName());
202                     }
203                     zipOut = new FileOutputStream(targetFile);
204                     copyStream(zipFile, zipOut);
205                     application.backgroundUpdateDictionaries(dictionaryUpdater);
206                     Toast.makeText(context, getString(R.string.installationFinished, dest),
207                             Toast.LENGTH_LONG).show();
208                     finishedDownloadIds.add(downloadId);
209                     Log.w(LOG, "Unzipping finished: " + dest + " Id: " + downloadId);
210                 } catch (Exception e) {
211                     String msg = getString(R.string.unzippingFailed, dest);
212                     File dir = application.getDictDir();
213                     if (!dir.canWrite() || !application.checkFileCreate(dir)) {
214                         msg = getString(R.string.notWritable, dir.getAbsolutePath());
215                     }
216                     new AlertDialog.Builder(context).setTitle(getString(R.string.error)).setMessage(msg).setNeutralButton("Close", null).show();
217                     Log.e(LOG, "Failed to unzip.", e);
218                 } finally {
219                     try { if (zipOut != null) zipOut.close(); } catch (IOException e) {}
220                     try { if (zipFile != null) zipFile.close(); } catch (IOException e) {}
221                     try { if (zipFileStream != null) zipFileStream.close(); } catch (IOException e) {}
222                     if (localZipFile != null) localZipFile.delete();
223                 }
224             }
225         }
226     };
227
228     public static Intent getLaunchIntent(Context c) {
229         final Intent intent = new Intent(c, DictionaryManagerActivity.class);
230         intent.putExtra(C.CAN_AUTO_LAUNCH_DICT, false);
231         return intent;
232     }
233
234     public void readableCheckAndError(boolean requestPermission) {
235         final File dictDir = application.getDictDir();
236         if (dictDir.canRead() && dictDir.canExecute()) return;
237         blockAutoLaunch = true;
238         if (requestPermission &&
239             ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
240             ActivityCompat.requestPermissions(this,
241                 new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
242                              Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
243             return;
244         }
245         blockAutoLaunch = true;
246
247         AlertDialog.Builder builder = new AlertDialog.Builder(getListView().getContext());
248         builder.setTitle(getString(R.string.error));
249         builder.setMessage(getString(
250                 R.string.unableToReadDictionaryDir,
251                 dictDir.getAbsolutePath(),
252                 Environment.getExternalStorageDirectory()));
253         builder.setNeutralButton("Close", null);
254         builder.create().show();
255     }
256
257     @Override
258     public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
259         readableCheckAndError(false);
260
261         application.backgroundUpdateDictionaries(dictionaryUpdater);
262
263         setMyListAdapater();
264     }
265
266     @Override
267     public void onCreate(Bundle savedInstanceState) {
268         // This must be first, otherwise the actiona bar doesn't get
269         // styled properly.
270         setTheme(((DictionaryApplication) getApplication()).getSelectedTheme().themeId);
271
272         super.onCreate(savedInstanceState);
273         Log.d(LOG, "onCreate:" + this);
274
275         application = (DictionaryApplication) getApplication();
276
277         blockAutoLaunch = false;
278
279         // UI init.
280         setContentView(R.layout.dictionary_manager_activity);
281
282         dictionariesOnDeviceHeaderRow = (LinearLayout) LayoutInflater.from(
283                 getListView().getContext()).inflate(
284                 R.layout.dictionary_manager_header_row_on_device, getListView(), false);
285
286         downloadableDictionariesHeaderRow = (LinearLayout) LayoutInflater.from(
287                 getListView().getContext()).inflate(
288                 R.layout.dictionary_manager_header_row_downloadable, getListView(), false);
289
290         showDownloadable = (ToggleButton) downloadableDictionariesHeaderRow
291                 .findViewById(R.id.hideDownloadable);
292         showDownloadable.setOnCheckedChangeListener(new OnCheckedChangeListener() {
293             @Override
294             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
295                 onShowDownloadableChanged();
296             }
297         });
298
299         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
300         final String thanksForUpdatingLatestVersion = getString(R.string.thanksForUpdatingVersion);
301         if (!prefs.getString(C.THANKS_FOR_UPDATING_VERSION, "").equals(
302                 thanksForUpdatingLatestVersion)) {
303             blockAutoLaunch = true;
304             startActivity(HtmlDisplayActivity.getWhatsNewLaunchIntent(getApplicationContext()));
305             prefs.edit().putString(C.THANKS_FOR_UPDATING_VERSION, thanksForUpdatingLatestVersion)
306                     .commit();
307         }
308
309         registerReceiver(broadcastReceiver, new IntentFilter(
310                 DownloadManager.ACTION_DOWNLOAD_COMPLETE));
311
312         setMyListAdapater();
313         registerForContextMenu(getListView());
314
315         readableCheckAndError(true);
316
317         onCreateSetupActionBar();
318     }
319
320     private void onCreateSetupActionBar() {
321         ActionBar actionBar = getSupportActionBar();
322         actionBar.setDisplayShowTitleEnabled(false);
323         actionBar.setDisplayShowHomeEnabled(false);
324         actionBar.setDisplayHomeAsUpEnabled(false);
325
326         filterSearchView = new SearchView(getSupportActionBar().getThemedContext());
327         filterSearchView.setIconifiedByDefault(false);
328         // filterSearchView.setIconified(false); // puts the magnifying glass in
329         // the
330         // wrong place.
331         filterSearchView.setQueryHint(getString(R.string.searchText));
332         filterSearchView.setSubmitButtonEnabled(false);
333         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
334                 FrameLayout.LayoutParams.WRAP_CONTENT);
335         filterSearchView.setLayoutParams(lp);
336         filterSearchView.setInputType(InputType.TYPE_CLASS_TEXT);
337         filterSearchView.setImeOptions(
338                 EditorInfo.IME_ACTION_DONE |
339                         EditorInfo.IME_FLAG_NO_EXTRACT_UI |
340                         // EditorInfo.IME_FLAG_NO_FULLSCREEN | // Requires API
341                         // 11
342                         EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
343
344         filterSearchView.setOnQueryTextListener(new OnQueryTextListener() {
345             @Override
346             public boolean onQueryTextSubmit(String query) {
347                 filterSearchView.clearFocus();
348                 return false;
349             }
350
351             @Override
352             public boolean onQueryTextChange(String filterText) {
353                 setMyListAdapater();
354                 return true;
355             }
356         });
357         filterSearchView.setFocusable(true);
358
359         actionBar.setCustomView(filterSearchView);
360         actionBar.setDisplayShowCustomEnabled(true);
361
362         // Avoid wasting space on large left inset
363         Toolbar tb = (Toolbar)filterSearchView.getParent();
364         tb.setContentInsetsRelative(0, 0);
365     }
366
367     @Override
368     public void onDestroy() {
369         super.onDestroy();
370         unregisterReceiver(broadcastReceiver);
371     }
372
373     private static int copyStream(final InputStream in, final OutputStream out)
374             throws IOException {
375         int bytesRead;
376         final byte[] bytes = new byte[1024 * 16];
377         while ((bytesRead = in.read(bytes)) != -1) {
378             out.write(bytes, 0, bytesRead);
379         }
380         in.close();
381         out.close();
382         return bytesRead;
383     }
384
385     @Override
386     protected void onStart() {
387         super.onStart();
388         uiHandler = new Handler();
389     }
390
391     @Override
392     protected void onStop() {
393         super.onStop();
394         uiHandler = null;
395     }
396
397     @Override
398     protected void onResume() {
399         super.onResume();
400
401         if (PreferenceActivity.prefsMightHaveChanged) {
402             PreferenceActivity.prefsMightHaveChanged = false;
403             finish();
404             startActivity(getIntent());
405         }
406
407         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
408         showDownloadable.setChecked(prefs.getBoolean(C.SHOW_DOWNLOADABLE, true));
409
410         if (!blockAutoLaunch &&
411                 getIntent().getBooleanExtra(C.CAN_AUTO_LAUNCH_DICT, true) &&
412                 prefs.contains(C.DICT_FILE) &&
413                 prefs.contains(C.INDEX_SHORT_NAME)) {
414             Log.d(LOG, "Skipping DictionaryManager, going straight to dictionary.");
415             startActivity(DictionaryActivity.getLaunchIntent(getApplicationContext(),
416                     new File(prefs.getString(C.DICT_FILE, "")),
417                     prefs.getString(C.INDEX_SHORT_NAME, ""),
418                     prefs.getString(C.SEARCH_TOKEN, "")));
419             finish();
420             return;
421         }
422
423         // Remove the active dictionary from the prefs so we won't autolaunch
424         // next time.
425         final Editor editor = prefs.edit();
426         editor.remove(C.DICT_FILE);
427         editor.remove(C.INDEX_SHORT_NAME);
428         editor.remove(C.SEARCH_TOKEN);
429         editor.commit();
430
431         application.backgroundUpdateDictionaries(dictionaryUpdater);
432
433         setMyListAdapater();
434     }
435
436     @Override
437     public boolean onCreateOptionsMenu(final Menu menu) {
438         final MenuItem sort = menu.add(getString(R.string.sortDicts));
439         MenuItemCompat.setShowAsAction(sort, MenuItem.SHOW_AS_ACTION_NEVER);
440         sort.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
441             public boolean onMenuItemClick(final MenuItem menuItem) {
442                 application.sortDictionaries();
443                 setMyListAdapater();
444                 return true;
445             }
446         });
447
448         application.onCreateGlobalOptionsMenu(this, menu);
449         return true;
450     }
451
452     @Override
453     public void onCreateContextMenu(final ContextMenu menu, final View view,
454             final ContextMenuInfo menuInfo) {
455         super.onCreateContextMenu(menu, view, menuInfo);
456         Log.d(LOG, "onCreateContextMenu, " + menuInfo);
457
458         final AdapterContextMenuInfo adapterContextMenuInfo =
459                 (AdapterContextMenuInfo) menuInfo;
460         final int position = adapterContextMenuInfo.position;
461         final MyListAdapter.Row row = (MyListAdapter.Row) getListAdapter().getItem(position);
462
463         if (row.dictionaryInfo == null) {
464             return;
465         }
466
467         if (position > 0 && row.onDevice) {
468             final android.view.MenuItem moveToTopMenuItem =
469                     menu.add(R.string.moveToTop);
470             moveToTopMenuItem.setOnMenuItemClickListener(new
471                     android.view.MenuItem.OnMenuItemClickListener() {
472                         @Override
473                         public boolean onMenuItemClick(android.view.MenuItem item) {
474                             application.moveDictionaryToTop(row.dictionaryInfo);
475                             setMyListAdapater();
476                             return true;
477                         }
478                     });
479         }
480
481         if (row.onDevice) {
482             final android.view.MenuItem deleteMenuItem = menu.add(R.string.deleteDictionary);
483             deleteMenuItem
484                     .setOnMenuItemClickListener(new android.view.MenuItem.OnMenuItemClickListener() {
485                         @Override
486                         public boolean onMenuItemClick(android.view.MenuItem item) {
487                             application.deleteDictionary(row.dictionaryInfo);
488                             setMyListAdapater();
489                             return true;
490                         }
491                     });
492         }
493     }
494
495     private void onShowDownloadableChanged() {
496         setMyListAdapater();
497         Editor prefs = PreferenceManager.getDefaultSharedPreferences(this).edit();
498         prefs.putBoolean(C.SHOW_DOWNLOADABLE, showDownloadable.isChecked());
499         prefs.commit();
500     }
501
502     class MyListAdapter extends BaseAdapter {
503
504         List<DictionaryInfo> dictionariesOnDevice;
505         List<DictionaryInfo> downloadableDictionaries;
506
507         class Row {
508             DictionaryInfo dictionaryInfo;
509             boolean onDevice;
510
511             private Row(DictionaryInfo dictionaryInfo, boolean onDevice) {
512                 this.dictionaryInfo = dictionaryInfo;
513                 this.onDevice = onDevice;
514             }
515         }
516
517         private MyListAdapter(final String[] filters) {
518             dictionariesOnDevice = application.getDictionariesOnDevice(filters);
519             if (showDownloadable.isChecked()) {
520                 downloadableDictionaries = application.getDownloadableDictionaries(filters);
521             } else {
522                 downloadableDictionaries = Collections.emptyList();
523             }
524         }
525
526         @Override
527         public int getCount() {
528             return 2 + dictionariesOnDevice.size() + downloadableDictionaries.size();
529         }
530
531         @Override
532         public Row getItem(int position) {
533             if (position == 0) {
534                 return new Row(null, true);
535             }
536             position -= 1;
537
538             if (position < dictionariesOnDevice.size()) {
539                 return new Row(dictionariesOnDevice.get(position), true);
540             }
541             position -= dictionariesOnDevice.size();
542
543             if (position == 0) {
544                 return new Row(null, false);
545             }
546             position -= 1;
547
548             assert position < downloadableDictionaries.size();
549             return new Row(downloadableDictionaries.get(position), false);
550         }
551
552         @Override
553         public long getItemId(int position) {
554             return position;
555         }
556
557         @Override
558         public View getView(int position, View convertView, ViewGroup parent) {
559             if (convertView instanceof LinearLayout &&
560                     convertView != dictionariesOnDeviceHeaderRow &&
561                     convertView != downloadableDictionariesHeaderRow) {
562                 /*
563                  * This is done to try to avoid leaking memory that used to
564                  * happen on Android 4.0.3
565                  */
566                 ((LinearLayout) convertView).removeAllViews();
567             }
568
569             final Row row = getItem(position);
570
571             if (row.onDevice) {
572                 if (row.dictionaryInfo == null) {
573                     return dictionariesOnDeviceHeaderRow;
574                 }
575                 return createDictionaryRow(row.dictionaryInfo, parent, true);
576             }
577
578             if (row.dictionaryInfo == null) {
579                 return downloadableDictionariesHeaderRow;
580             }
581             return createDictionaryRow(row.dictionaryInfo, parent, false);
582         }
583
584     }
585
586     private void setMyListAdapater() {
587         final String filter = filterSearchView == null ? "" : filterSearchView.getQuery()
588                 .toString();
589         final String[] filters = filter.trim().toLowerCase().split("(\\s|-)+");
590         setListAdapter(new MyListAdapter(filters));
591     }
592
593     private View createDictionaryRow(final DictionaryInfo dictionaryInfo,
594             final ViewGroup parent, boolean canLaunch) {
595
596         View row = LayoutInflater.from(parent.getContext()).inflate(
597                 R.layout.dictionary_manager_row, parent, false);
598         final TextView name = (TextView) row.findViewById(R.id.dictionaryName);
599         final TextView details = (TextView) row.findViewById(R.id.dictionaryDetails);
600         name.setText(application.getDictionaryName(dictionaryInfo.uncompressedFilename));
601
602         final boolean updateAvailable = application.updateAvailable(dictionaryInfo);
603         final Button downloadButton = (Button) row.findViewById(R.id.downloadButton);
604         final DictionaryInfo downloadable = application.getDownloadable(dictionaryInfo.uncompressedFilename);
605         boolean broken = false;
606         if (!dictionaryInfo.isValid()) {
607             broken = true;
608             canLaunch = false;
609         }
610         if (downloadable != null && (!canLaunch || updateAvailable)) {
611             downloadButton
612                     .setText(getString(
613                             R.string.downloadButton,
614                             downloadable.zipBytes / 1024.0 / 1024.0));
615             downloadButton.setMinWidth(application.languageButtonPixels * 3 / 2);
616             downloadButton.setOnClickListener(new OnClickListener() {
617                 @Override
618                 public void onClick(View arg0) {
619                     downloadDictionary(downloadable.downloadUrl, downloadable.zipBytes, downloadButton);
620                 }
621             });
622         } else {
623             downloadButton.setVisibility(View.INVISIBLE);
624         }
625
626         LinearLayout buttons = (LinearLayout) row.findViewById(R.id.dictionaryLauncherButtons);
627         final List<IndexInfo> sortedIndexInfos = application
628                 .sortedIndexInfos(dictionaryInfo.indexInfos);
629         final StringBuilder builder = new StringBuilder();
630         if (updateAvailable) {
631             builder.append(getString(R.string.updateAvailable));
632         }
633         for (IndexInfo indexInfo : sortedIndexInfos) {
634             final View button = application.createButton(buttons.getContext(), dictionaryInfo,
635                     indexInfo);
636             buttons.addView(button);
637
638             if (canLaunch) {
639                 button.setOnClickListener(
640                         new IntentLauncher(buttons.getContext(),
641                                 DictionaryActivity.getLaunchIntent(getApplicationContext(),
642                                         application.getPath(dictionaryInfo.uncompressedFilename),
643                                         indexInfo.shortName, "")));
644
645             } else {
646                 button.setEnabled(false);
647             }
648             if (builder.length() != 0) {
649                 builder.append("; ");
650             }
651             builder.append(getString(R.string.indexInfo, indexInfo.shortName,
652                     indexInfo.mainTokenCount));
653         }
654         builder.append("; ");
655         builder.append(getString(R.string.downloadButton, dictionaryInfo.uncompressedBytes / 1024.0 / 1024.0));
656         if (broken) {
657             name.setText("Broken: " + application.getDictionaryName(dictionaryInfo.uncompressedFilename));
658             builder.append("; Cannot be used, redownload, check hardware/file system");
659             // Allow deleting, but cannot open
660             row.setLongClickable(true);
661         }
662         details.setText(builder.toString());
663
664         if (canLaunch) {
665             row.setClickable(true);
666             row.setOnClickListener(new IntentLauncher(parent.getContext(),
667                     DictionaryActivity.getLaunchIntent(getApplicationContext(),
668                             application.getPath(dictionaryInfo.uncompressedFilename),
669                             dictionaryInfo.indexInfos.get(0).shortName, "")));
670             row.setFocusable(true);
671             row.setLongClickable(true);
672         }
673         row.setBackgroundResource(android.R.drawable.menuitem_background);
674
675         return row;
676     }
677
678     private synchronized void downloadDictionary(final String downloadUrl, long bytes, Button downloadButton) {
679         String destFile;
680         try {
681             destFile = new File(new URL(downloadUrl).getPath()).getName();
682         } catch (MalformedURLException e) {
683             throw new RuntimeException("Invalid download URL!", e);
684         }
685         DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
686         final DownloadManager.Query query = new DownloadManager.Query();
687         query.setFilterByStatus(DownloadManager.STATUS_PAUSED | DownloadManager.STATUS_PENDING | DownloadManager.STATUS_RUNNING);
688         final Cursor cursor = downloadManager.query(query);
689
690         // Due to a bug, cursor is null instead of empty when
691         // the download manager is disabled.
692         if (cursor == null) {
693             new AlertDialog.Builder(DictionaryManagerActivity.this).setTitle(getString(R.string.error))
694                     .setMessage(getString(R.string.downloadFailed, R.string.downloadManagerQueryFailed))
695                     .setNeutralButton("Close", null).show();
696             return;
697         }
698
699         while (cursor.moveToNext()) {
700             if (downloadUrl.equals(cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI))))
701                 break;
702             if (destFile.equals(cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE))))
703                 break;
704         }
705         if (!cursor.isAfterLast()) {
706             downloadManager.remove(cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID)));
707             downloadButton
708                     .setText(getString(
709                             R.string.downloadButton,
710                             bytes / 1024.0 / 1024.0));
711             cursor.close();
712             return;
713         }
714         cursor.close();
715         Request request = new Request(
716                 Uri.parse(downloadUrl));
717
718         Log.d(LOG, "Downloading to: " + destFile);
719         request.setTitle(destFile);
720
721         File destFilePath = new File(application.getDictDir(), destFile);
722         destFilePath.delete();
723         try {
724             request.setDestinationUri(Uri.fromFile(destFilePath));
725         } catch (Exception e) {
726         }
727
728         try {
729             downloadManager.enqueue(request);
730         } catch (SecurityException e) {
731             request = new Request(Uri.parse(downloadUrl));
732             request.setTitle(destFile);
733             downloadManager.enqueue(request);
734         }
735         Log.w(LOG, "Download started: " + destFile);
736         downloadButton.setText("X");
737     }
738
739 }