package net.osmand.plus.download; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.AsyncTask.Status; import android.os.Build; import android.os.StatFs; import android.support.annotation.UiThread; import android.support.v7.app.AlertDialog; import android.support.v7.app.NotificationCompat; import android.support.v7.app.NotificationCompat.Builder; import android.view.View; import android.widget.Toast; import net.osmand.IndexConstants; import net.osmand.PlatformUtil; import net.osmand.map.WorldRegion; import net.osmand.map.WorldRegion.RegionParams; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandSettings; import net.osmand.plus.OsmandSettings.OsmandPreference; import net.osmand.plus.R; import net.osmand.plus.Version; import net.osmand.plus.base.BasicProgressAsyncTask; import net.osmand.plus.download.DownloadFileHelper.DownloadFileShowWarning; import net.osmand.plus.helpers.DatabaseHelper; import net.osmand.plus.resources.ResourceManager; import net.osmand.util.Algorithms; import org.apache.commons.logging.Log; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; @SuppressLint({ "NewApi", "DefaultLocale" }) public class DownloadIndexesThread { private final static Log LOG = PlatformUtil.getLog(DownloadIndexesThread.class); private static final int NOTIFICATION_ID = 45; private OsmandApplication app; private DownloadEvents uiActivity = null; private DatabaseHelper dbHelper; private DownloadFileHelper downloadFileHelper; private List<BasicProgressAsyncTask<?, ?, ?, ?>> currentRunningTask = Collections.synchronizedList(new ArrayList<BasicProgressAsyncTask<?, ?, ?, ?>>()); private ConcurrentLinkedQueue<IndexItem> indexItemDownloading = new ConcurrentLinkedQueue<IndexItem>(); private IndexItem currentDownloadingItem = null; private int currentDownloadingItemProgress = 0; private DownloadResources indexes; private Notification notification; public interface DownloadEvents { void newDownloadIndexes(); void downloadInProgress(); void downloadHasFinished(); } public DownloadIndexesThread(OsmandApplication app) { this.app = app; indexes = new DownloadResources(app); updateLoadedFiles(); downloadFileHelper = new DownloadFileHelper(app); dbHelper = new DatabaseHelper(app); } public void updateLoadedFiles() { indexes.updateLoadedFiles(); } /// UI notifications methods public void setUiActivity(DownloadEvents uiActivity) { this.uiActivity = uiActivity; } public void resetUiActivity(DownloadEvents uiActivity) { if (this.uiActivity == uiActivity) { this.uiActivity = null; } } @UiThread protected void downloadInProgress() { if (uiActivity != null) { uiActivity.downloadInProgress(); } updateNotification(); } private void updateNotification() { if(getCurrentDownloadingItem() != null) { BasicProgressAsyncTask<?, ?, ?, ?> task = getCurrentRunningTask(); final boolean isFinished = task == null || task.getStatus() == AsyncTask.Status.FINISHED; Intent contentIntent = new Intent(app, DownloadActivity.class); PendingIntent contentPendingIntent = PendingIntent.getActivity(app, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT); Builder bld = new NotificationCompat.Builder(app); String msg = Version.getAppName(app); if(!isFinished) { msg = task.getDescription(); } StringBuilder contentText = new StringBuilder(); List<IndexItem> ii = getCurrentDownloadingItems(); for(IndexItem i : ii) { if(contentText.length() > 0) { contentText.append(", "); } contentText.append(i.getVisibleName(app, app.getRegions())); } bld.setContentTitle(msg).setSmallIcon(android.R.drawable.stat_sys_download) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setContentText(contentText.toString()) .setContentIntent(contentPendingIntent).setOngoing(true); int progress = getCurrentDownloadingItemProgress(); bld.setProgress(100, Math.max(progress, 0), progress < 0); notification = bld.build(); NotificationManager mNotificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.notify(NOTIFICATION_ID, notification); } else { if(notification != null) { NotificationManager mNotificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.cancel(NOTIFICATION_ID); notification = null; } } } @UiThread protected void downloadHasFinished() { if (uiActivity != null) { uiActivity.downloadHasFinished(); } updateNotification(); } public void initSettingsFirstMap(WorldRegion reg) { if (app.getSettings().FIRST_MAP_IS_DOWNLOADED.get() || reg == null) { return; } app.getSettings().FIRST_MAP_IS_DOWNLOADED.set(true); RegionParams params = reg.getParams(); if (!app.getSettings().DRIVING_REGION_AUTOMATIC.get()) { app.setupDrivingRegion(reg); } String lang = params.getRegionLang(); if (lang != null) { String lng = lang.split(",")[0]; String setTts = null; for (String s : OsmandSettings.TTS_AVAILABLE_VOICES) { if (lng.startsWith(s)) { setTts = s + "-tts"; break; } else if (lng.contains("," + s)) { setTts = s + "-tts"; } } if (setTts != null) { app.getSettings().VOICE_PROVIDER.set(setTts); } } } @UiThread protected void newDownloadIndexes() { if (uiActivity != null) { uiActivity.newDownloadIndexes(); } } // PUBLIC API public DownloadResources getIndexes() { return indexes; } public List<IndexItem> getCurrentDownloadingItems() { List<IndexItem> res = new ArrayList<IndexItem>(); IndexItem ii = currentDownloadingItem; if(ii != null) { res.add(ii); } res.addAll(indexItemDownloading); return res; } public boolean isDownloading(IndexItem item) { if(item == currentDownloadingItem) { return true; } for(IndexItem ii : indexItemDownloading) { if (ii == item) { return true; } } return false; } public int getCountedDownloads() { int i = 0; if(currentDownloadingItem != null && DownloadActivityType.isCountedInDownloads(currentDownloadingItem)) { i++; } for(IndexItem ii : indexItemDownloading) { if (DownloadActivityType.isCountedInDownloads(ii)) { i++; } } return i; } public void runReloadIndexFilesSilent() { if (checkRunning(true)) { return; } execute(new ReloadIndexesTask()); } public void runReloadIndexFiles() { if (checkRunning(false)) { return; } execute(new ReloadIndexesTask()); } public void runDownloadFiles(IndexItem... items) { if (getCurrentRunningTask() instanceof ReloadIndexesTask) { if(checkRunning(false)) { return; } } if(uiActivity instanceof Activity) { app.logEvent((Activity) uiActivity, "download_files"); } for(IndexItem item : items) { if (!item.equals(currentDownloadingItem) && !indexItemDownloading.contains(item)) { indexItemDownloading.add(item); } } if (currentDownloadingItem == null) { execute(new DownloadIndexesAsyncTask()); } else { downloadInProgress(); } } public void cancelDownload(IndexItem item) { if(currentDownloadingItem == item) { downloadFileHelper.setInterruptDownloading(true); } else { indexItemDownloading.remove(item); downloadInProgress(); } } public IndexItem getCurrentDownloadingItem() { return currentDownloadingItem; } public int getCurrentDownloadingItemProgress() { return currentDownloadingItemProgress; } public BasicProgressAsyncTask<?, ?, ?, ?> getCurrentRunningTask() { for (int i = 0; i < currentRunningTask.size(); ) { if (currentRunningTask.get(i).getStatus() == Status.FINISHED) { currentRunningTask.remove(i); } else { i++; } } if (currentRunningTask.size() > 0) { return currentRunningTask.get(0); } return null; } @SuppressWarnings("deprecation") public double getAvailableSpace() { File dir = app.getAppPath("").getParentFile(); double asz = -1; if (dir.canRead()) { StatFs fs = new StatFs(dir.getAbsolutePath()); asz = (((long) fs.getAvailableBlocks()) * fs.getBlockSize()) / (1 << 20); } return asz; } /// PRIVATE IMPL private boolean checkRunning(boolean silent) { if (getCurrentRunningTask() != null) { if (!silent) { Toast.makeText(app, R.string.wait_current_task_finished, Toast.LENGTH_SHORT).show(); } return true; } return false; } @SuppressWarnings("unchecked") private <P> void execute(BasicProgressAsyncTask<?, P, ?, ?> task, P... indexItems) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, indexItems); } else { task.execute(indexItems); } } private class ReloadIndexesTask extends BasicProgressAsyncTask<Void, Void, Void, DownloadResources> { public ReloadIndexesTask() { super(app); } @Override protected void onPreExecute() { currentRunningTask.add(this); super.onPreExecute(); this.message = ctx.getString(R.string.downloading_list_indexes); indexes.downloadFromInternetFailed = false; } @Override protected DownloadResources doInBackground(Void... params) { DownloadResources result = null; DownloadOsmandIndexesHelper.IndexFileList indexFileList = DownloadOsmandIndexesHelper.getIndexesList(ctx); if (indexFileList != null) { try { while (app.isApplicationInitializing()) { Thread.sleep(200); } result = new DownloadResources(app); result.isDownloadedFromInternet = indexFileList.isDownloadedFromInternet(); result.mapVersionIsIncreased = indexFileList.isIncreasedMapVersion(); app.getSettings().LAST_CHECKED_UPDATES.set(System.currentTimeMillis()); result.prepareData(indexFileList.getIndexFiles()); } catch (Exception e) { } } return result == null ? new DownloadResources(app) : result; } protected void onPostExecute(DownloadResources result) { indexes = result; result.downloadFromInternetFailed = !result.isDownloadedFromInternet; if (result.mapVersionIsIncreased) { showWarnDialog(); } currentRunningTask.remove(this); newDownloadIndexes(); } private void showWarnDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(ctx); builder.setMessage(R.string.map_version_changed_info); builder.setPositiveButton(R.string.button_upgrade_osmandplus, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://search?q=pname:net.osmand.plus")); try { ctx.startActivity(intent); } catch (ActivityNotFoundException e) { } } }); builder.setNegativeButton(R.string.shared_string_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.show(); } @Override protected void updateProgress(boolean updateOnlyProgress, Void tag) { downloadInProgress(); } } private class DownloadIndexesAsyncTask extends BasicProgressAsyncTask<IndexItem, IndexItem, Object, String> implements DownloadFileShowWarning { private OsmandPreference<Integer> downloads; public DownloadIndexesAsyncTask() { super(app); downloads = app.getSettings().NUMBER_OF_FREE_DOWNLOADS; } @Override public void setInterrupted(boolean interrupted) { super.setInterrupted(interrupted); if (interrupted) { downloadFileHelper.setInterruptDownloading(true); } } @Override protected void onProgressUpdate(Object... values) { for (Object o : values) { if (o instanceof IndexItem) { IndexItem item = (IndexItem) o; String name = item.getBasename(); long count = dbHelper.getCount(name, DatabaseHelper.DOWNLOAD_ENTRY) + 1; item.setDownloaded(true); DatabaseHelper.HistoryDownloadEntry entry = new DatabaseHelper.HistoryDownloadEntry(name, count); if (count == 1) { dbHelper.add(entry, DatabaseHelper.DOWNLOAD_ENTRY); } else { dbHelper.update(entry, DatabaseHelper.DOWNLOAD_ENTRY); } } else if (o instanceof String) { String message = (String) o; // ctx.getString(R.string.shared_string_io_error) +": Interrupted"; if (!message.toLowerCase().contains("interrupted")) { if (uiActivity == null || !message.equals(app.getString(R.string.shared_string_download_successful))) { app.showToastMessage(message); } } } } downloadInProgress(); super.onProgressUpdate(values); } @Override protected void onPreExecute() { currentRunningTask.add(this); super.onPreExecute(); downloadFileHelper.setInterruptDownloading(false); if (uiActivity instanceof Activity) { View mainView = ((Activity) uiActivity).findViewById(R.id.MainLayout); if (mainView != null) { mainView.setKeepScreenOn(true); } } startTask(ctx.getString(R.string.shared_string_downloading) + ctx.getString(R.string.shared_string_ellipsis), -1); } @Override protected void onPostExecute(String result) { if (result != null && result.length() > 0) { Toast.makeText(ctx, result, Toast.LENGTH_LONG).show(); } if (uiActivity instanceof Activity) { View mainView = ((Activity) uiActivity).findViewById(R.id.MainLayout); if (mainView != null) { mainView.setKeepScreenOn(false); } } currentRunningTask.remove(this); indexes.updateFilesToUpdate(); downloadHasFinished(); } @Override protected String doInBackground(IndexItem... filesToDownload) { try { List<File> filesToReindex = new ArrayList<File>(); boolean forceWifi = downloadFileHelper.isWifiConnected(); Set<IndexItem> currentDownloads = new HashSet<IndexItem>(); String warn = ""; try { downloadCycle: while (!indexItemDownloading.isEmpty()) { IndexItem item = indexItemDownloading.poll(); currentDownloadingItem = item; currentDownloadingItemProgress = 0; if (currentDownloads.contains(item)) { continue; } currentDownloads.add(item); boolean success = false; if(!validateEnoughSpace(item)) { break downloadCycle; } if(!validateNotExceedsFreeLimit(item)) { break downloadCycle; } setTag(item); boolean result = downloadFile(item, filesToReindex, forceWifi); success = result || success; if (result) { if (DownloadActivityType.isCountedInDownloads(item)) { downloads.set(downloads.get() + 1); } if(item.getBasename().toLowerCase().equals(DownloadResources.WORLD_SEAMARKS_KEY)) { File oldFile = new File(app.getAppPath(IndexConstants.MAPS_PATH), DownloadResources.WORLD_SEAMARKS_OLD_NAME + IndexConstants.BINARY_MAP_INDEX_EXT); Algorithms.removeAllFiles(oldFile); } File bf = item.getBackupFile(app); if (bf.exists()) { Algorithms.removeAllFiles(bf); } // trackEvent(entry); publishProgress(item); String wn = reindexFiles(filesToReindex); if(!Algorithms.isEmpty(wn)) { warn += " " + wn; } filesToReindex.clear(); // slow down but let update all button work properly indexes.updateFilesToUpdate();; } } } finally { currentDownloadingItem = null; currentDownloadingItemProgress = 0; } //String warn = reindexFiles(filesToReindex); if(warn.trim().length() == 0) { return null; } return warn.trim(); } catch (InterruptedException e) { LOG.info("Download Interrupted"); // do not dismiss dialog } return null; } private boolean validateEnoughSpace(IndexItem item) { double asz = getAvailableSpace(); double cs =(item.contentSize / (1 << 20)); // validate enough space if (asz != -1 && cs > asz) { String breakDownloadMessage = app.getString(R.string.download_files_not_enough_space, cs, asz); publishProgress(breakDownloadMessage); return false; } return true; } private boolean validateNotExceedsFreeLimit(IndexItem item) { boolean exceed = Version.isFreeVersion(app) && !app.getSettings().LIVE_UPDATES_PURCHASED.get() && !app.getSettings().FULL_VERSION_PURCHASED.get() && DownloadActivityType.isCountedInDownloads(item) && downloads.get() >= DownloadValidationManager.MAXIMUM_AVAILABLE_FREE_DOWNLOADS; if(exceed) { String breakDownloadMessage = app.getString(R.string.free_version_message, DownloadValidationManager.MAXIMUM_AVAILABLE_FREE_DOWNLOADS + ""); publishProgress(breakDownloadMessage); } return !exceed; } private String reindexFiles(List<File> filesToReindex) { boolean vectorMapsToReindex = false; // reindex vector maps all at one time ResourceManager manager = app.getResourceManager(); for (File f : filesToReindex) { if (f.getName().endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) { vectorMapsToReindex = true; } } List<String> warnings = new ArrayList<String>(); manager.indexVoiceFiles(this); manager.indexFontFiles(this); if (vectorMapsToReindex) { warnings = manager.indexingMaps(this); } List<String> wns = manager.indexAdditionalMaps(this); if (wns != null) { warnings.addAll(wns); } if (!warnings.isEmpty()) { return warnings.get(0); } return null; } // private void trackEvent(DownloadEntry entry) { // String v = Version.getAppName(app); // if (Version.isProductionVersion(app)) { // v = Version.getFullVersion(app); // } else { // v += " test"; // } // new DownloadTracker().trackEvent(app, v, Version.getAppName(app), // entry.baseName, 1, app.getString(R.string.ga_api_key)); // } @Override public void showWarning(String warning) { publishProgress(warning); } public boolean downloadFile(IndexItem item, List<File> filesToReindex, boolean forceWifi) throws InterruptedException { downloadFileHelper.setInterruptDownloading(false); IndexItem.DownloadEntry de = item.createDownloadEntry(app); boolean res = false; if(de == null) { return res; } if (de.isAsset) { try { if (ctx != null) { ResourceManager.copyAssets(ctx.getAssets(), de.assetName, de.targetFile); boolean changedDate = de.targetFile.setLastModified(de.dateModified); if (!changedDate) { LOG.error("Set last timestamp is not supported"); } res = true; } } catch (IOException e) { LOG.error("Copy exception", e); } } else { res = downloadFileHelper.downloadFile(de, this, filesToReindex, this, forceWifi); } return res; } @Override protected void updateProgress(boolean updateOnlyProgress, IndexItem tag) { currentDownloadingItemProgress = getProgressPercentage(); downloadInProgress(); } } }