package app.organicmaps; import static app.organicmaps.sdk.DownloadResourcesLegacyActivity.ERR_DISK_ERROR; import static app.organicmaps.sdk.DownloadResourcesLegacyActivity.ERR_DOWNLOAD_ERROR; import static app.organicmaps.sdk.DownloadResourcesLegacyActivity.ERR_DOWNLOAD_SUCCESS; import static app.organicmaps.sdk.DownloadResourcesLegacyActivity.ERR_NOT_ENOUGH_FREE_SPACE; import static app.organicmaps.sdk.DownloadResourcesLegacyActivity.ERR_NO_MORE_FILES; import static app.organicmaps.sdk.DownloadResourcesLegacyActivity.ERR_STORAGE_DISCONNECTED; import static app.organicmaps.sdk.DownloadResourcesLegacyActivity.nativeCancelCurrentFile; import static app.organicmaps.sdk.DownloadResourcesLegacyActivity.nativeGetBytesToDownload; import static app.organicmaps.sdk.DownloadResourcesLegacyActivity.nativeStartNextFileDownload; import android.annotation.SuppressLint; import android.app.Dialog; import android.content.ComponentName; import android.content.Intent; import android.location.Location; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.view.ViewCompat; import app.organicmaps.base.BaseMwmFragmentActivity; import app.organicmaps.downloader.MapManagerHelper; import app.organicmaps.intent.Factory; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.downloader.CountryItem; import app.organicmaps.sdk.downloader.MapManager; import app.organicmaps.sdk.location.LocationListener; import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.ConnectionState; import app.organicmaps.sdk.util.StringUtils; import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener; import com.google.android.material.button.MaterialButton; import com.google.android.material.checkbox.MaterialCheckBox; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.progressindicator.LinearProgressIndicator; import com.google.android.material.textview.MaterialTextView; import java.util.List; import java.util.Objects; @SuppressLint("StringFormatMatches") public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity { private static final String TAG = DownloadResourcesLegacyActivity.class.getSimpleName(); private MaterialTextView mTvMessage; private LinearProgressIndicator mProgress; private MaterialButton mBtnDownload; private MaterialCheckBox mChbDownloadCountry; private String mCurrentCountry; @Nullable private Dialog mAlertDialog; @NonNull private ActivityResultLauncher mApiRequest; private boolean mAreResourcesDownloaded; private static final int DOWNLOAD = 0; private static final int PAUSE = 1; private static final int RESUME = 2; private static final int TRY_AGAIN = 3; private static final int PROCEED_TO_MAP = 4; private static final int BTN_COUNT = 5; private View.OnClickListener[] mBtnListeners; private String[] mBtnNames; private int mCountryDownloadListenerSlot; private final LocationListener mLocationListener = new LocationListener() { @Override public void onLocationUpdated(@NonNull Location location) { if (mCurrentCountry != null) return; final double lat = location.getLatitude(); final double lon = location.getLongitude(); mCurrentCountry = MapManager.nativeFindCountry(lat, lon); if (TextUtils.isEmpty(mCurrentCountry)) { mCurrentCountry = null; return; } CountryItem item = CountryItem.fill(mCurrentCountry); String fileSizeString = StringUtils.getFileSizeString(getApplicationContext(), item.totalSize); if (item.status != CountryItem.STATUS_DONE) { UiUtils.show(mChbDownloadCountry); String checkBoxText; if (item.status == CountryItem.STATUS_UPDATABLE) checkBoxText = String.format(getString(R.string.update_country_ask), item.name, fileSizeString); else checkBoxText = String.format(getString(R.string.download_country_ask), item.name, fileSizeString); mChbDownloadCountry.setText(checkBoxText); } MwmApplication.from(DownloadResourcesLegacyActivity.this).getLocationHelper().removeListener(this); } }; private final app.organicmaps.sdk.DownloadResourcesLegacyActivity.Listener mResourcesDownloadListener = new app.organicmaps.sdk.DownloadResourcesLegacyActivity.Listener() { @Override public void onProgress(final int bytesDownloaded) { if (!isFinishing()) mProgress.setProgressCompat(bytesDownloaded, true); } @Override public void onFinish(final int errorCode) { if (isFinishing()) return; if (errorCode == ERR_DOWNLOAD_SUCCESS) { final int res = nativeStartNextFileDownload(mResourcesDownloadListener); if (res == ERR_NO_MORE_FILES) finishFilesDownload(res); } else finishFilesDownload(errorCode); } }; private final MapManager.StorageCallback mCountryDownloadListener = new MapManager.StorageCallback() { @Override public void onStatusChanged(List data) { for (MapManager.StorageCallbackData item : data) { if (!item.isLeafNode) continue; switch (item.newStatus) { case CountryItem.STATUS_DONE: mAreResourcesDownloaded = true; showMap(); return; case CountryItem.STATUS_FAILED: MapManagerHelper.showError(DownloadResourcesLegacyActivity.this, item, null); return; } } } @Override public void onProgress(String countryId, long localSize, long remoteSize) { mProgress.setProgressCompat((int) localSize, true); } }; @CallSuper @Override protected void onSafeCreate(@Nullable Bundle savedInstanceState) { super.onSafeCreate(savedInstanceState); UiUtils.setLightStatusBar(this, true); setContentView(R.layout.activity_download_resources); final View view = getWindow().getDecorView().findViewById(android.R.id.content); ViewCompat.setOnApplyWindowInsetsListener(view, PaddingInsetsListener.allSides()); initViewsAndListeners(); mApiRequest = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { setResult(result.getResultCode(), result.getData()); finish(); }); if (prepareFilesDownload(false)) { Utils.keepScreenOn(true, getWindow()); setAction(DOWNLOAD); return; } showMap(); } @CallSuper @Override protected void onSafeDestroy() { super.onSafeDestroy(); mApiRequest.unregister(); mApiRequest = null; Utils.keepScreenOn(Config.isKeepScreenOnEnabled(), getWindow()); if (mCountryDownloadListenerSlot != 0) { MapManager.nativeUnsubscribe(mCountryDownloadListenerSlot); mCountryDownloadListenerSlot = 0; } } @CallSuper @Override protected void onResume() { super.onResume(); if (!isFinishing()) MwmApplication.from(this).getLocationHelper().addListener(mLocationListener); } @Override protected void onPause() { super.onPause(); MwmApplication.from(this).getLocationHelper().removeListener(mLocationListener); if (mAlertDialog != null && mAlertDialog.isShowing()) mAlertDialog.dismiss(); mAlertDialog = null; } private void setDownloadMessage(int bytesToDownload) { mTvMessage.setText(getString(R.string.download_resources, StringUtils.getFileSizeString(this, bytesToDownload))); } private boolean prepareFilesDownload(boolean showMap) { final int bytes = nativeGetBytesToDownload(); if (bytes == 0) { mAreResourcesDownloaded = true; if (showMap) showMap(); return false; } if (bytes > 0) { setDownloadMessage(bytes); mProgress.setMax(bytes); // Start progress at 1% according to M3 guidelines mProgress.setProgressCompat(bytes/100, true); } else finishFilesDownload(bytes); return true; } private void initViewsAndListeners() { mTvMessage = findViewById(R.id.download_message); mProgress = findViewById(R.id.progressbar); mBtnDownload = findViewById(R.id.btn_download_resources); mChbDownloadCountry = findViewById(R.id.chb_download_country); mBtnListeners = new View.OnClickListener[BTN_COUNT]; mBtnNames = new String[BTN_COUNT]; mBtnListeners[DOWNLOAD] = v -> onDownloadClicked(); mBtnNames[DOWNLOAD] = getString(R.string.download); mBtnListeners[PAUSE] = v -> onPauseClicked(); mBtnNames[PAUSE] = getString(R.string.pause); mBtnListeners[RESUME] = v -> onResumeClicked(); mBtnNames[RESUME] = getString(R.string.continue_button); mBtnListeners[TRY_AGAIN] = v -> onTryAgainClicked(); mBtnNames[TRY_AGAIN] = getString(R.string.try_again); mBtnListeners[PROCEED_TO_MAP] = v -> onProceedToMapClicked(); mBtnNames[PROCEED_TO_MAP] = getString(R.string.download_resources_continue); } private void setAction(int action) { mBtnDownload.setOnClickListener(mBtnListeners[action]); mBtnDownload.setText(mBtnNames[action]); } private void doDownload() { if (nativeStartNextFileDownload(mResourcesDownloadListener) == ERR_NO_MORE_FILES) finishFilesDownload(ERR_NO_MORE_FILES); } private void onDownloadClicked() { setAction(PAUSE); doDownload(); } private void onPauseClicked() { setAction(RESUME); nativeCancelCurrentFile(); } private void onResumeClicked() { setAction(PAUSE); doDownload(); } private void onTryAgainClicked() { if (prepareFilesDownload(true)) { setAction(PAUSE); doDownload(); } } private void onProceedToMapClicked() { mAreResourcesDownloaded = true; showMap(); } public void showMap() { if (!mAreResourcesDownloaded) return; // Re-use original intent to retain all flags and payload. // https://github.com/organicmaps/organicmaps/issues/6944 final Intent intent = Objects.requireNonNull(getIntent()); intent.setComponent(new ComponentName(this, MwmActivity.class)); // Disable animation because MwmActivity should appear exactly over this one intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_CLEAR_TOP); // See {@link SplashActivity.processNavigation()} if (Factory.isStartedForApiResult(intent)) { // Wait for the result from MwmActivity for API callers. mApiRequest.launch(intent); return; } startActivity(intent); finish(); } private void finishFilesDownload(int result) { if (result == ERR_NO_MORE_FILES) { // World and WorldCoasts has been downloaded, we should register maps again to correctly add them to the model. Framework.nativeReloadWorldMaps(); if (mCurrentCountry != null && mChbDownloadCountry.isChecked()) { UiUtils.hide(mChbDownloadCountry); CountryItem item = CountryItem.fill(mCurrentCountry); String fileSizeString = StringUtils.getFileSizeString(this, item.totalSize); mTvMessage.setText(getString(R.string.downloading_country_can_proceed, item.name, fileSizeString)); mProgress.setMax((int) item.totalSize); // Start progress at 1% according to M3 guidelines mProgress.setProgressCompat((int) (item.totalSize/100), true); mCountryDownloadListenerSlot = MapManager.nativeSubscribe(mCountryDownloadListener); MapManagerHelper.startDownload(mCurrentCountry); setAction(PROCEED_TO_MAP); } else { mAreResourcesDownloaded = true; showMap(); } } else { showErrorDialog(result); } } private void showErrorDialog(int result) { if (mAlertDialog != null && mAlertDialog.isShowing()) return; @StringRes final int titleId; @StringRes final int messageId = switch (result) { case ERR_NOT_ENOUGH_FREE_SPACE -> { titleId = R.string.routing_not_enough_space; yield R.string.not_enough_free_space_on_sdcard; } case ERR_STORAGE_DISCONNECTED -> { titleId = R.string.disconnect_usb_cable_title; yield R.string.disconnect_usb_cable; } case ERR_DOWNLOAD_ERROR -> { titleId = R.string.connection_failure; yield(ConnectionState.INSTANCE.isConnected() ? R.string.download_has_failed : R.string.common_check_internet_connection_dialog); } case ERR_DISK_ERROR -> { titleId = R.string.disk_error_title; yield R.string.disk_error; } default -> throw new AssertionError("Unexpected result code = " + result); }; mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog) .setTitle(titleId) .setMessage(messageId) .setCancelable(true) .setOnCancelListener((dialog) -> setAction(PAUSE)) .setPositiveButton(R.string.try_again, (dialog, which) -> { setAction(TRY_AGAIN); onTryAgainClicked(); }) .setOnDismissListener(dialog -> mAlertDialog = null) .show(); } }